web-dev-qa-db-fra.com

Différence et intersection de deux tableaux contenant des objets

J'ai deux tableaux list1 et list2 qui ont des objets avec quelques propriétés; userId est l'ID ou la propriété unique:

list1 = [
    { userId: 1234, userName: 'XYZ'  }, 
    { userId: 1235, userName: 'ABC'  }, 
    { userId: 1236, userName: 'IJKL' },
    { userId: 1237, userName: 'WXYZ' }, 
    { userId: 1238, userName: 'LMNO' }
]

list2 = [
    { userId: 1235, userName: 'ABC'  },  
    { userId: 1236, userName: 'IJKL' },
    { userId: 1252, userName: 'AAAA' }
]

Je recherche un moyen simple d'exécuter les trois opérations suivantes:

  1. list1 operation list2 devrait renvoyer l'intersection des éléments:

    [
        { userId: 1235, userName: 'ABC'  },
        { userId: 1236, userName: 'IJKL' }
    ]
    
  2. list1 operation list2 devrait renvoyer la liste de tous les éléments de list1 qui ne se produisent pas dans list2:

    [
        { userId: 1234, userName: 'XYZ'  },
        { userId: 1237, userName: 'WXYZ' }, 
        { userId: 1238, userName: 'LMNO' }
    ]
    
  3. list2 operation list1 devrait renvoyer la liste des éléments de list2 qui ne se produisent pas dans list1:

    [
        { userId: 1252, userName: 'AAAA' }
    ]
    
21
Shashi

C'est la solution qui a fonctionné pour moi.

 var intersect = function (arr1, arr2) {
            var intersect = [];
            _.each(arr1, function (a) {
                _.each(arr2, function (b) {
                    if (compare(a, b))
                        intersect.Push(a);
                });
            });

            return intersect;
        };

 var unintersect = function (arr1, arr2) {
            var unintersect = [];
            _.each(arr1, function (a) {
                var found = false;
                _.each(arr2, function (b) {
                    if (compare(a, b)) {
                        found = true;    
                    }
                });

                if (!found) {
                    unintersect.Push(a);
                }
            });

            return unintersect;
        };

        function compare(a, b) {
            if (a.userId === b.userId)
                return true;
            else return false;
        }
1
Shashi

Vous pouvez définir trois fonctions inBoth, inFirstOnly et inSecondOnly qui prennent toutes deux listes comme arguments et renvoient une liste telle que l’on peut comprendre à partir du nom de la fonction. La logique principale pourrait être placée dans une fonction commune operation sur laquelle les trois s'appuient.

Voici quelques implémentations parmi lesquelles choisir operation, pour lesquelles vous pouvez trouver un extrait plus bas:

  • Anciennes boucles JavaScript simples for
  • Fonctions fléchées utilisant les méthodes de tableau filter et some
  • Recherche optimisée avec un Set

Boucles anciennes simples for

// Generic helper function that can be used for the three operations:        
function operation(list1, list2, isUnion) {
    var result = [];
    
    for (var i = 0; i < list1.length; i++) {
        var item1 = list1[i],
            found = false;
        for (var j = 0; j < list2.length && !found; j++) {
            found = item1.userId === list2[j].userId;
        }
        if (found === !!isUnion) { // isUnion is coerced to boolean
            result.Push(item1);
        }
    }
    return result;
}

// Following functions are to be used:
function inBoth(list1, list2) {
    return operation(list1, list2, true);
}

function inFirstOnly(list1, list2) {
    return operation(list1, list2);
}

function inSecondOnly(list1, list2) {
    return inFirstOnly(list2, list1);
}

// Sample data
var list1 = [
    { userId: 1234, userName: 'XYZ'  }, 
    { userId: 1235, userName: 'ABC'  }, 
    { userId: 1236, userName: 'IJKL' },
    { userId: 1237, userName: 'WXYZ' }, 
    { userId: 1238, userName: 'LMNO' }
];
var list2 = [
    { userId: 1235, userName: 'ABC'  },  
    { userId: 1236, userName: 'IJKL' },
    { userId: 1252, userName: 'AAAA' }
];
  
console.log('inBoth:', inBoth(list1, list2)); 
console.log('inFirstOnly:', inFirstOnly(list1, list2)); 
console.log('inSecondOnly:', inSecondOnly(list1, list2)); 

Fonctions fléchées utilisant les méthodes de tableau filter et some

Cela utilise certaines fonctionnalités ES5 et ES6:

// Generic helper function that can be used for the three operations:        
const operation = (list1, list2, isUnion = false) =>
    list1.filter( a => isUnion === list2.some( b => a.userId === b.userId ) );

// Following functions are to be used:
const inBoth = (list1, list2) => operation(list1, list2, true),
      inFirstOnly = operation,
      inSecondOnly = (list1, list2) => inFirstOnly(list2, list1);

// Sample data
const list1 = [
    { userId: 1234, userName: 'XYZ'  }, 
    { userId: 1235, userName: 'ABC'  }, 
    { userId: 1236, userName: 'IJKL' },
    { userId: 1237, userName: 'WXYZ' }, 
    { userId: 1238, userName: 'LMNO' }
];
const list2 = [
    { userId: 1235, userName: 'ABC'  },  
    { userId: 1236, userName: 'IJKL' },
    { userId: 1252, userName: 'AAAA' }
];
  
console.log('inBoth:', inBoth(list1, list2)); 
console.log('inFirstOnly:', inFirstOnly(list1, list2)); 
console.log('inSecondOnly:', inSecondOnly(list1, list2));

Optimisation de la recherche

Les solutions ci-dessus ont une complexité temporelle O (n²) en raison de la boucle imbriquée - some représente également une boucle. Donc, pour les grands tableaux, vous feriez mieux de créer un hachage (temporaire) sur l'ID utilisateur. Cela peut être fait à la volée en fournissant un argument Set (ES6) à une fonction qui générera la fonction de rappel du filtre. Cette fonction peut alors effectuer la recherche en temps constant avec has:

// Generic helper function that can be used for the three operations:        
const operation = (list1, list2, isUnion = false) =>
    list1.filter(
        (set => a => isUnion === set.has(a.userId))(new Set(list2.map(b => b.userId)))
    );

// Following functions are to be used:
const inBoth = (list1, list2) => operation(list1, list2, true),
      inFirstOnly = operation,
      inSecondOnly = (list1, list2) => inFirstOnly(list2, list1);

// Sample data
const list1 = [
    { userId: 1234, userName: 'XYZ'  }, 
    { userId: 1235, userName: 'ABC'  }, 
    { userId: 1236, userName: 'IJKL' },
    { userId: 1237, userName: 'WXYZ' }, 
    { userId: 1238, userName: 'LMNO' }
];
const list2 = [
    { userId: 1235, userName: 'ABC'  },  
    { userId: 1236, userName: 'IJKL' },
    { userId: 1252, userName: 'AAAA' }
];
  
console.log('inBoth:', inBoth(list1, list2)); 
console.log('inFirstOnly:', inFirstOnly(list1, list2)); 
console.log('inSecondOnly:', inSecondOnly(list1, list2));
32
trincot

réponse courte:

list1.filter(a => list2.some(b => a.userId === b.userId));  
list1.filter(a => !list2.some(b => a.userId === b.userId));  
list2.filter(a => !list1.some(b => a.userId === b.userId));  

réponse plus longue:
Le code ci-dessus vérifiera les objets par userId valeur,
si vous avez besoin de règles de comparaison complexes, vous pouvez définir un comparateur personnalisé:

comparator = function (a, b) {
    return a.userId === b.userId && a.userName === b.userName
};  
list1.filter(a => list2.some(b => comparator(a, b)));
list1.filter(a => !list2.some(b => comparator(a, b)));
list2.filter(a => !list1.some(b => comparator(a, b)));

Il existe également un moyen de comparer les objets par des références
ATTENTION! deux objets avec les mêmes valeurs seront considérés comme différents:

o1 = {"userId":1};
o2 = {"userId":2};
o1_copy = {"userId":1};
o1_ref = o1;
[o1].filter(a => [o2].includes(a)).length; // 0
[o1].filter(a => [o1_copy].includes(a)).length; // 0
[o1].filter(a => [o1_ref].includes(a)).length; // 1
5
Trizalio

Utilisez lodash's_.isEqual méthode. Plus précisément:

list1.reduce(function(prev, curr){
  !list2.some(function(obj){
    return _.isEqual(obj, curr)
  }) ? prev.Push(curr): false;
  return prev
}, []);

Ci-dessus vous donne l'équivalent de A given !B (en termes SQL, A LEFT OUTER JOIN B). Vous pouvez déplacer le code autour du code pour obtenir ce que vous voulez!

4
Bwaxxlo
function intersect(first, second) {
    return intersectInternal(first, second, function(e){ return e });
}

function unintersect(first, second){
    return intersectInternal(first, second, function(e){ return !e });  
}

function intersectInternal(first, second, filter) {
    var map = {};

    first.forEach(function(user) { map[user.userId] = user; });

    return second.filter(function(user){ return filter(map[user.userId]); })
}
1
Sagi

Voici une solution programmation fonctionnelle avec soulignement/lodash pour répondre à votre première question (intersection).

list1 = [ {userId:1234,userName:'XYZ'}, 
          {userId:1235,userName:'ABC'}, 
          {userId:1236,userName:'IJKL'},
          {userId:1237,userName:'WXYZ'}, 
          {userId:1238,userName:'LMNO'}
        ];

list2 = [ {userId:1235,userName:'ABC'},  
          {userId:1236,userName:'IJKL'},
          {userId:1252,userName:'AAAA'}
        ];

_.reduce(list1, function (memo, item) {
        var same = _.findWhere(list2, item);
        if (same && _.keys(same).length === _.keys(item).length) {
            memo.Push(item);
        }
        return memo
    }, []);

Je vous laisse améliorer cela pour répondre aux autres questions ;-)

0
laruiss

Utilisez simplement les méthodes de tableau filter et some de JS et vous pouvez le faire.

let arr1 = list1.filter(e => {
   return !list2.some(item => item.userId === e.userId);
});

Cela retournera les éléments présents dans list1 mais pas dans list2. Si vous recherchez les éléments communs dans les deux listes. Faites ça.

let arr1 = list1.filter(e => {
   return list2.some(item => item.userId === e.userId); // take the ! out and you're done
});
0
Koushik Das