使用[. .new Set()]只获取基于内部Array<object.id>

Use [...new Set()] to get only uniques based off inner Array<object.id>?

本文关键字:Array 于内部 object id 获取 Set new 使用      更新时间:2023-09-26

是否可以使用[...new Set()]返回基于内部id值的唯一对象数组?如果这是不可能的,有没有其他聪明的ES6方法来实现这个输出?

参考:数组中的唯一值

var arr = [
  {email: 'matthew@gmail.com', id: 10}
]
var arr2 = [
  {email: 'matthew@gmail.com', id: 10},
  {email: 'matthew@gmail.com', id: 13}
]
mergedArray = arr.concat(arr2);
console.log(
  [...new Set(mergedArray)]
);
// output would be:
//  [
//    {email:'matthew@gmail.com', id: 10},
//    {email:'matthew@gmail.com', id: 13}
//  ]

要获得基于ID的唯一对象,您可以创建一个Map而不是Set,将它传递一个2元素数组作为迭代器,它将具有唯一键,然后获取它的值

var arr = [
  {email: 'matthew@gmail.com', id: 10}
]
var arr2 = [
  {email: 'matthew@gmail.com', id: 10},
  {email: 'matthew@gmail.com', id: 13}
]
var mergedArray = arr.concat(arr2);
var map         = new Map(mergedArray.map(o => [o.id,o]));
var unique      = [...map.values()];
console.log(unique);

注意:这是目前为止最快的解决方案,请参阅jsperf.com上的测试用例。

我认为最好的解决方案是创建一个映射对象与id s作为键,数组元素作为值。由于在一个对象中不可能有两个具有相同键的不同元素,因此重复的元素将被自动删除。然后,您可以使用Object.values()函数将映射对象转换回数组(注意,这是ES 2017的一部分,而不是ES 6)。

const arr = [
  { email: 'matthew@gmail.com', id: 10 },
];
const arr2 = [
  { email: 'matthew@gmail.com', id: 10 },
  { email: 'matthew@gmail.com', id: 13 },
];
const mergedArray = [...arr, ...arr2];
const map = {};
for (const element of mergedArray) {
  map[element.id] = element;
}
const newArray = Object.values(map);
console.log(newArray);

另外,您可以使用扩展操作符:[...arr, ...arr2]来代替arr.concat(arr2)

由于这被标记为函数式编程,因此我将提供一种使用泛型过程的更函数化的方法

我的unionBy实现使用了一个底层Set,但是这个实现细节没有泄露给使用它的函数。相反,通过将过滤谓词控制作为高阶函数传递给"联合"过程,可以反转过滤谓词控制。这一点很重要,因为函数的用户不应该关心是否使用了Set。也许集合是最理想的,但在其他情况下可能不是。无论哪种方式,最好是由unionBy过程的用户声明分组值是什么,而不是其他任何东西。

为了理解我的意思,让我们首先看一下unionById——它接受两个Object类型的数组,并返回一个Object类型的数组。

// unionById :: [Object] -> [Object] -> [Object]
const unionById = unionBy (p=> x=> p (x.id));

这里,unionBy的第一个参数是一个用户定义的过程,但是过程的第一个参数本身将期望另一个过程,在本例中是p。您的过程的第二个参数(在本例中是x)将是要检查的单个元素。您只需将"分组"值返回给p。因为我们要按xid字段分组,所以我们只需调用p(x.id)


这是一个可运行的代码片段

// id :: a -> a
const id = x=> x
// unionBy :: ((a -> b) -> a -> b) -> [c] -> [c] -> [c] 
const unionBy = p=> xs=> ys=> {
  let s = new Set (xs.map (p (id)));
  let zs = ys.filter (p (z => s.has (z) ? false : s.add (z)));
  return xs.concat (zs);
};
// unionById :: [Object] -> [Object] -> [Object]
const unionById = unionBy (p=> x=> p (x.id));
// your data
var arr = [
  {email: 'matthew@gmail.com', id: 10}
]
var arr2 = [
  {email: 'matthew@gmail.com', id: 10},
  {email: 'matthew@gmail.com', id: 13}
]
// check it out
console.log(unionById(arr)(arr2))


如果你看看非常通用的union

,这可能是最清楚的。
// apply :: (a -> b) -> a -> b
const apply = f=> x=> f(x)
// union :: [a] -> [a] -> [a]
const union = unionBy (apply);
union ([1,2,3]) ([2,3,4]);
// => [ 1, 2, 3, 4 ]

unionBy是强大的,因为它没有假设输入将是什么。在我们将它用于对象数组之前-这里你将看到它在字符串数组上工作。

let xs = ['A', 'B', 'C'];
let ys = ['a', 'b', 'x', 'y'];
unionBy (p=> x=> p(x.toLowerCase())) (xs) (ys);
// => [ 'A', 'B', 'C', 'x', 'y']

你可能想要unionByAll接受2个或更多的输入数组

// uncurry :: (a -> b -> c) -> (a,b) -> c  
const uncurry = f=> (x,y)=> f (x) (y);
// unionByAll :: ((a -> b) -> a -> b) -> [[c]] -> [c]
const unionByAll = p=> (x,...xs)=> {
  return xs.reduce (uncurry (unionBy (p)), x);
};
unionByAll (apply) ([1,2,3], [2,3,4], [3,4,5]);
// => [ 1, 2, 3, 4, 5 ]

Care: @adeneo指出unionBy对初始输入没有任何假设。根据设计,unionBy不检查初始输入中的重复-它是而不是一个交换过程。

// "duplicates" in xs will not be removed
unionBy (f) (xs) (ys)
// "duplicates" in ys will not be removed
unionBy (f) (ys) (xs)

如果您在一个或多个输入中有潜在的重复,使用unionByAll将帮助您

// "duplicates" in both xs and ys will now be removed
unionByAll (f) ([], xs, ys)

这将是我的方法,不使用Set,但Map:

const uniques = mergedArray
  .reduce((map, item) => map.set(item.id, item), new Map())
  .values();

这给了你一个可迭代对象,它可能满足你的需要,也可能不满足你的需要。