javascript - Diferencia e intersección de dos matrices que contienen objetos
arrays set-intersection (7)
Tengo dos matrices
list1
y
list2
que tienen objetos con algunas propiedades;
userId
es el Id o propiedad única:
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'' }
]
Estoy buscando una manera fácil de ejecutar las siguientes tres operaciones:
-
list1 operation list2
debería devolver la intersección de elementos:[ { userId: 1235, userName: ''ABC'' }, { userId: 1236, userName: ''IJKL'' } ]
-
list1 operation list2
debería devolver la lista de todos los elementos delist1
que no se producen enlist2
:[ { userId: 1234, userName: ''XYZ'' }, { userId: 1237, userName: ''WXYZ'' }, { userId: 1238, userName: ''LMNO'' } ]
-
list2 operation list1
debería devolver la lista de elementos delist2
que no aparecen enlist1
:[ { userId: 1252, userName: ''AAAA'' } ]
Aquí hay una solución de programación funcional con guión bajo / lodash para responder a su primera pregunta (intersección).
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
}, []);
Te dejaré mejorar esto para responder las otras preguntas ;-)
Esta es la solución que funcionó para mí.
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;
}
Puede definir tres funciones
inBoth
,
inFirstOnly
e
inSecondOnly
que toman dos listas como argumentos y devuelven una lista como se puede entender del nombre de la función.
La lógica principal se podría poner en una
operation
función común
operation
que confían los tres.
Aquí hay algunas implementaciones para elegir esa
operation
, para lo cual puede encontrar un fragmento más abajo:
-
JavaScript simple
for
bucles -
Funciones de flecha usando
filter
ysome
métodos de matriz -
Búsqueda optimizada con un
Set
Llano viejo
for
bucles
// 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));
Funciones de flecha usando
filter
y
some
métodos de matriz
Utiliza algunas características de ES5 y 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));
Optimizando la búsqueda
Las soluciones anteriores tienen una complejidad de tiempo
O (n²)
debido al bucle anidado;
some
representan un bucle.
Entonces, para matrices grandes, será mejor que crees un hash (temporal) en la identificación de usuario.
Esto se puede hacer
sobre la marcha
proporcionando un
Set
(ES6) como argumento para una función que generará la función de devolución de llamada del filtro.
Esa función puede realizar la búsqueda en tiempo constante con
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));
Simplemente use el
filter
y
some
métodos de matriz de JS y puede hacerlo.
let arr1 = list1.filter(e => {
return !list2.some(item => item.userId === e.userId);
});
Esto devolverá los elementos que están presentes en
list1
pero no en
list2
.
Si está buscando los elementos comunes en ambas listas.
Solo haz esto.
let arr1 = list1.filter(e => {
return list2.some(item => item.userId === e.userId); // take the ! out and you''re done
});
Use
lodash''s
método
lodash''s
_.isEqual
.
Específicamente:
list1.reduce(function(prev, curr){
!list2.some(function(obj){
return _.isEqual(obj, curr)
}) ? prev.push(curr): false;
return prev
}, []);
Arriba le da el equivalente de
A given !B
(en términos de SQL,
A LEFT OUTER JOIN B
).
¡Puede mover el código alrededor del código para obtener lo que desea!
respuesta corta:
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));
respuesta más larga:
El código anterior verificará los objetos por valor de
userId
de
userId
,
Si necesita reglas de comparación complejas, puede definir un comparador personalizado:
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)));
También hay una manera de comparar objetos por referencias
¡ADVERTENCIA!
dos objetos con los mismos valores se considerarán diferentes:
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
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]); })
}