获取两个对象键交集的最佳方法?
Best way to get intersection of keys of two objects?
我有两个像这样的对象文字:
var firstObject =
{
x: 0,
y: 1,
z: 2,
a: 10,
b: 20,
e: 30
}
var secondObject =
{
x: 0,
y: 1,
z: 2,
a: 10,
c: 20,
d: 30
}
我想得到这两个对象文字的键的交集,如下所示:
var intersectionKeys = ['x', 'y', 'z', 'a']
我显然可以做一个循环,看看另一个对象中是否存在同名的键,但我想知道这是否适合某些函数式编程和 map/filter/reduce 用法?我自己并没有做过那么多的函数式编程,但我有一种感觉,可能存在一个干净而聪明的解决方案来解决这个问题。
我建议的程序是:
- 为其中一个对象使用
Object.keys()
获取键的 array
。
- 使用
.filter
查找数组的交集,并检查第二个对象是否包含与第一个数组匹配的键。
var firstObject = {
x: 0,
y: 1,
z: 2,
a: 10,
b: 20,
e: 30
}
var secondObject = {
x: 0,
y: 1,
z: 2,
a: 10,
c: 20,
d: 30
}
function getIntKeys(obj1, obj2){
var k1 = Object.keys(obj1);
return k1.filter(function(x){
return obj2[x] !== undefined;
});
}
alert(getIntKeys(firstObject, secondObject));
没有indexOf
的解决方案。
var firstObject = { x: 0, y: 1, z: 2, a: 10, b: 20, e: 30 },
secondObject = { x: 0, y: 1, z: 2, a: 10, c: 20, d: 30 };
function intersection(o1, o2) {
return Object.keys(o1).concat(Object.keys(o2)).sort().reduce(function (r, a, i, aa) {
if (i && aa[i - 1] === a) {
r.push(a);
}
return r;
}, []);
}
document.write('<pre>' + JSON.stringify(intersection(firstObject, secondObject), 0, 4) + '</pre>');
第二次尝试,时间复杂度为 O(n)。
var firstObject = { x: 0, y: 1, z: 2, a: 10, b: 20, e: 30 },
secondObject = { x: 0, y: 1, z: 2, a: 10, c: 20, d: 30 };
function intersection(o1, o2) {
return Object.keys(o1).filter({}.hasOwnProperty.bind(o2));
}
document.write('<pre>' + JSON.stringify(intersection(firstObject, secondObject), 0, 4) + '</pre>');
给出的答案很好而且令人惊讶,但 void's 中可能存在问题,那就是:
“如果有意将 属性 值之一设置为 undefined
。 会怎样?”
Nina's 很好(真的很棒)但是因为我们处在欢乐的时代JavaScript 我觉得我的不会太差:
var a = { x: undefined, y: 1, z: 2, a: 10, b: 20, e: 30 }
var b = { x: 0, y: 1, z: 2, a: 10, c: 20, d: 30 }
function intersect(o1, o2){
return Object.keys(o1).filter(k => k in o2)
}
document.write('<pre>' + JSON.stringify(intersect(a, b)) + '</pre>');
更新
onalbi 在评论中提到了一些合理的性能问题,因此下面的代码似乎是处理该问题的更好方法:
var a = { x: undefined, y: 1, z: 2, a: 10, b: 20, e: 30};
var b = { x: 0, y: 1, z: 2, a: 10, c: 20, d: 30};
function intersect(o1, o2) {
const [k1, k2] = [Object.keys(o1), Object.keys(o2)];
const [first, next] = k1.length > k2.length ? [k2, o1] : [k1, o2];
return first.filter(k => k in next);
}
document.write('<pre>' + JSON.stringify(intersect(a, b)) + '</pre>');
递归函数
这是其他解决方案,也许对您有帮助。我使用递归函数来拦截两个对象。此解决方案的优点是您无需担心同时是对象的属性。
在这种情况下,函数拦截存在于两个对象中的属性并分配 'objSource' 的值,就像拦截属性的最终值一样。
{
function interceptObjects(objSource, objInterface) {
let newObj = {};
for (const key in objSource) {
if (objInterface.hasOwnProperty(key)) {
// in javascript an array is a object too.
if (objSource[key] instanceof Object && !Array.isArray(objSource[key]) && objInterface[key] instanceof Object && !Array.isArray(objInterface[key])) {
newObj[key] = {};
newObj[key] = interceptObjects(objSource[key], objInterface[key])
} else {
newObj[key] = objSource[key];
}
}
}
return newObj;
}
// FOR TESTING
let objSource = {
attr1: '',
attr2: 2,
attr3: [],
attr4: {
attr41: 'lol',
attr42: 12,
attr43: 15,
attr45: [1, 4],
},
attr5: [2, 3, 4],
};
let objInterface = {
attr1: null,
attr4: {
attr41: null,
attr42: 12,
attr45: [1],
},
attr5: [],
attr6: null,
};
console.log(this.interceptObjects(objSource, objInterface));
}
这是一个简单的条目,非常实用,可以处理任意数量的对象,并且 returns 来自传递的第一个对象的匹配键的值。
此行为类似于 PHP 中 array_intersect_key() 的行为,以防有人搜索。
function intersectKeys(first, ...rest) {
const restKeys = rest.map(o => Object.keys(o));
return Object.fromEntries(Object.entries(first).filter(entry => restKeys.every(rk => rk.includes(entry[0]))));
}
为了更好的解释和评论,在此处展开
function intersectKeys(first, ...rest) {
// extract the keys of the other objects first so that won't be done again for each check
const restKeys = rest.map(o => Object.keys(o));
// In my version I am returning the first objects values under the intersect keys
return Object.fromEntries(
// extract [key, value] sets for each key and filter them, Object.fromEntries() reverses this back into an object of the remaining fields after the filter
Object.entries(first).filter(
// make sure each of the other object key sets includes the current key, or filter it out
entry => restKeys.every(
rk => rk.includes(entry[0])
)
)
);
// to get JUST the keys as OP requested the second line would simplify down to this
return Object.keys(first).filter(key => restKeys.every(rk => rk.includes(key)));
}
请务必注意,此解决方案仅适用于字符串键,符号键将被忽略,最终对象将不包含任何键。尽管也可以编写类似的函数来比较符号相交。
我知道这是一个旧的 post,但是,我想分享我今天写的一个解决方案,我认为它是高效和干净的。
function intersectingKeys(...objects) {
return objects
.map((object) => Object.keys(object))
.sort((a, b) => a.length - b.length)
.reduce((a, b) => a.filter((key) => b.includes(key)));
}
这个函数可以接受n个对象,并找到相交键。
这是它的工作原理。
- 映射对象,创建键数组数组。
- 按长度对数组进行排序,这会将最小的键数组放在最前面。
- 最后,通过根据下一个列表过滤每个键列表来减少我们的键数组。
我认为这个算法的巧妙之处在于键数组的预排序。通过从最小的键列表开始,我们可以减少比较键的工作量。
这是用法:
var firstObject = {
x: 0,
y: 1,
z: 2,
a: 10,
b: 20,
e: 30,
};
var secondObject = {
x: 0,
y: 1,
z: 2,
a: 10,
c: 20,
d: 30,
};
intersectingKeys(firstObject, secondObject);
// [ 'x', 'y', 'z', 'a' ]
我有两个像这样的对象文字:
var firstObject =
{
x: 0,
y: 1,
z: 2,
a: 10,
b: 20,
e: 30
}
var secondObject =
{
x: 0,
y: 1,
z: 2,
a: 10,
c: 20,
d: 30
}
我想得到这两个对象文字的键的交集,如下所示:
var intersectionKeys = ['x', 'y', 'z', 'a']
我显然可以做一个循环,看看另一个对象中是否存在同名的键,但我想知道这是否适合某些函数式编程和 map/filter/reduce 用法?我自己并没有做过那么多的函数式编程,但我有一种感觉,可能存在一个干净而聪明的解决方案来解决这个问题。
我建议的程序是:
- 为其中一个对象使用
Object.keys()
获取键的array
。 - 使用
.filter
查找数组的交集,并检查第二个对象是否包含与第一个数组匹配的键。
var firstObject = {
x: 0,
y: 1,
z: 2,
a: 10,
b: 20,
e: 30
}
var secondObject = {
x: 0,
y: 1,
z: 2,
a: 10,
c: 20,
d: 30
}
function getIntKeys(obj1, obj2){
var k1 = Object.keys(obj1);
return k1.filter(function(x){
return obj2[x] !== undefined;
});
}
alert(getIntKeys(firstObject, secondObject));
没有indexOf
的解决方案。
var firstObject = { x: 0, y: 1, z: 2, a: 10, b: 20, e: 30 },
secondObject = { x: 0, y: 1, z: 2, a: 10, c: 20, d: 30 };
function intersection(o1, o2) {
return Object.keys(o1).concat(Object.keys(o2)).sort().reduce(function (r, a, i, aa) {
if (i && aa[i - 1] === a) {
r.push(a);
}
return r;
}, []);
}
document.write('<pre>' + JSON.stringify(intersection(firstObject, secondObject), 0, 4) + '</pre>');
第二次尝试,时间复杂度为 O(n)。
var firstObject = { x: 0, y: 1, z: 2, a: 10, b: 20, e: 30 },
secondObject = { x: 0, y: 1, z: 2, a: 10, c: 20, d: 30 };
function intersection(o1, o2) {
return Object.keys(o1).filter({}.hasOwnProperty.bind(o2));
}
document.write('<pre>' + JSON.stringify(intersection(firstObject, secondObject), 0, 4) + '</pre>');
给出的答案很好而且令人惊讶,但 void's undefined
。 会怎样?”
Nina's
var a = { x: undefined, y: 1, z: 2, a: 10, b: 20, e: 30 }
var b = { x: 0, y: 1, z: 2, a: 10, c: 20, d: 30 }
function intersect(o1, o2){
return Object.keys(o1).filter(k => k in o2)
}
document.write('<pre>' + JSON.stringify(intersect(a, b)) + '</pre>');
更新
onalbi 在评论中提到了一些合理的性能问题,因此下面的代码似乎是处理该问题的更好方法:
var a = { x: undefined, y: 1, z: 2, a: 10, b: 20, e: 30};
var b = { x: 0, y: 1, z: 2, a: 10, c: 20, d: 30};
function intersect(o1, o2) {
const [k1, k2] = [Object.keys(o1), Object.keys(o2)];
const [first, next] = k1.length > k2.length ? [k2, o1] : [k1, o2];
return first.filter(k => k in next);
}
document.write('<pre>' + JSON.stringify(intersect(a, b)) + '</pre>');
递归函数
这是其他解决方案,也许对您有帮助。我使用递归函数来拦截两个对象。此解决方案的优点是您无需担心同时是对象的属性。
在这种情况下,函数拦截存在于两个对象中的属性并分配 'objSource' 的值,就像拦截属性的最终值一样。
{
function interceptObjects(objSource, objInterface) {
let newObj = {};
for (const key in objSource) {
if (objInterface.hasOwnProperty(key)) {
// in javascript an array is a object too.
if (objSource[key] instanceof Object && !Array.isArray(objSource[key]) && objInterface[key] instanceof Object && !Array.isArray(objInterface[key])) {
newObj[key] = {};
newObj[key] = interceptObjects(objSource[key], objInterface[key])
} else {
newObj[key] = objSource[key];
}
}
}
return newObj;
}
// FOR TESTING
let objSource = {
attr1: '',
attr2: 2,
attr3: [],
attr4: {
attr41: 'lol',
attr42: 12,
attr43: 15,
attr45: [1, 4],
},
attr5: [2, 3, 4],
};
let objInterface = {
attr1: null,
attr4: {
attr41: null,
attr42: 12,
attr45: [1],
},
attr5: [],
attr6: null,
};
console.log(this.interceptObjects(objSource, objInterface));
}
这是一个简单的条目,非常实用,可以处理任意数量的对象,并且 returns 来自传递的第一个对象的匹配键的值。
此行为类似于 PHP 中 array_intersect_key() 的行为,以防有人搜索。
function intersectKeys(first, ...rest) {
const restKeys = rest.map(o => Object.keys(o));
return Object.fromEntries(Object.entries(first).filter(entry => restKeys.every(rk => rk.includes(entry[0]))));
}
为了更好的解释和评论,在此处展开
function intersectKeys(first, ...rest) {
// extract the keys of the other objects first so that won't be done again for each check
const restKeys = rest.map(o => Object.keys(o));
// In my version I am returning the first objects values under the intersect keys
return Object.fromEntries(
// extract [key, value] sets for each key and filter them, Object.fromEntries() reverses this back into an object of the remaining fields after the filter
Object.entries(first).filter(
// make sure each of the other object key sets includes the current key, or filter it out
entry => restKeys.every(
rk => rk.includes(entry[0])
)
)
);
// to get JUST the keys as OP requested the second line would simplify down to this
return Object.keys(first).filter(key => restKeys.every(rk => rk.includes(key)));
}
请务必注意,此解决方案仅适用于字符串键,符号键将被忽略,最终对象将不包含任何键。尽管也可以编写类似的函数来比较符号相交。
我知道这是一个旧的 post,但是,我想分享我今天写的一个解决方案,我认为它是高效和干净的。
function intersectingKeys(...objects) {
return objects
.map((object) => Object.keys(object))
.sort((a, b) => a.length - b.length)
.reduce((a, b) => a.filter((key) => b.includes(key)));
}
这个函数可以接受n个对象,并找到相交键。
这是它的工作原理。
- 映射对象,创建键数组数组。
- 按长度对数组进行排序,这会将最小的键数组放在最前面。
- 最后,通过根据下一个列表过滤每个键列表来减少我们的键数组。
我认为这个算法的巧妙之处在于键数组的预排序。通过从最小的键列表开始,我们可以减少比较键的工作量。
这是用法:
var firstObject = {
x: 0,
y: 1,
z: 2,
a: 10,
b: 20,
e: 30,
};
var secondObject = {
x: 0,
y: 1,
z: 2,
a: 10,
c: 20,
d: 30,
};
intersectingKeys(firstObject, secondObject);
// [ 'x', 'y', 'z', 'a' ]