从数组中删除基于多个键的重复对象

45
假设给定以下对象数组:

Assuming an array of objects as follows:

const listOfTags = [
    {id: 1, label: "Hello", color: "red", sorting: 0},
    {id: 2, label: "World", color: "green", sorting: 1},
    {id: 3, label: "Hello", color: "blue", sorting: 4},
    {id: 4, label: "Sunshine", color: "yellow", sorting: 5},
    {id: 5, label: "Hello", color: "red", sorting: 6},
]

如果标签和颜色相同,就会出现重复条目。在这种情况下,id = 1和id = 5的对象是重复的。

我该如何筛选这个数组并删除重复项?

我知道一些解决方案,比如使用以下方法对一个键进行过滤:

const unique = [... new Set(listOfTags.map(tag => tag.label)]

但是多个键怎么办呢?

根据评论的要求,这里是所需的结果:

[
    {id: 1, label: "Hello", color: "red", sorting: 0},
    {id: 2, label: "World", color: "green", sorting: 1},
    {id: 3, label: "Hello", color: "blue", sorting: 4},
    {id: 4, label: "Sunshine", color: "yellow", sorting: 5},
]
14个回答

37
晚了一步,但我不知道为什么没有人建议更简单的方法:
listOfTags.filter((tag, index, array) => array.findIndex(t => t.color == tag.color && t.label == tag.label) == index);

35

您可以在闭包中使用Set进行过滤。

const
    listOfTags = [{ id: 1, label: "Hello", color: "red", sorting: 0 }, { id: 2, label: "World", color: "green", sorting: 1 }, { id: 3, label: "Hello", color: "blue", sorting: 4 }, { id: 4, label: "Sunshine", color: "yellow", sorting: 5 }, { id: 5, label: "Hello", color: "red", sorting: 6 }],
    keys = ['label', 'color'],
    filtered = listOfTags.filter(
        (s => o => 
            (k => !s.has(k) && s.add(k))
            (keys.map(k => o[k]).join('|'))
        )
        (new Set)
    );

console.log(filtered);
.as-console-wrapper { max-height: 100% !important; top: 0; }


19
对于一些不太了解闭包的开发者来说,对代码进行注释会很有帮助。这是一个很好的示例,展示了闭包的应用场景。对于感兴趣的人: listOfTags.filter 内部的第一个函数是一个工厂函数,它会立即使用一个新的空集s被调用。s将在过滤完成之前一直可用。第二个函数是实际的过滤函数,它会接收每个对象o并返回一个布尔值。(在这种情况下,另一个闭包函数使用对象o的连接字段作为参数进行实际的过滤测试。) - Dave Gööck
1
@Alfrex92,s 是对 new Set 的闭包,而 o 则是数组中的每个对象。 - Nina Scholz
1
@Alfrex92,k是另一个闭包,它在一些属性的联合键的下一行keys.map(k => o[k]).join('|')上。 - Nina Scholz
1
真是救命稻草!我会在完成项目后回来理解这个。 - JCm
1
应该使用更清晰的代码编写,仅使用单个字符会非常混乱。 - Redbeard
显示剩余2条评论

11

const listOfTags = [
    {id: 1, label: "Hello", color: "red", sorting: 0},
    {id: 2, label: "World", color: "green", sorting: 1},
    {id: 3, label: "Hello", color: "blue", sorting: 4},
    {id: 4, label: "Sunshine", color: "yellow", sorting: 5},
    {id: 5, label: "Hello", color: "red", sorting: 6},
]

const unique = [];

listOfTags.map(x => unique.filter(a => a.label == x.label && a.color == x.color).length > 0 ? null : unique.push(x));

console.log(unique);


7

一种方法是创建一个对象(或Map),将两个值的组合用作键,当前对象用作值,然后从该对象中获取值。

const listOfTags = [
    {id: 1, label: "Hello", color: "red", sorting: 0},
    {id: 2, label: "World", color: "green", sorting: 1},
    {id: 3, label: "Hello", color: "blue", sorting: 4},
    {id: 4, label: "Sunshine", color: "yellow", sorting: 5},
    {id: 5, label: "Hello", color: "red", sorting: 6},
]

const uniques = Object.values(
  listOfTags.reduce((a, c) => {
    a[c.label + '|' + c.color] = c;
    return a
  }, {}))

console.log(uniques)


4
你可以在这里使用reduce来获取筛选后的对象。
listOfTags.reduce((newListOfTags, current) => {
    if (!newListOfTags.some(x => x.label == current.label && x.color == current.color)) {
        newListOfTags.push(current);
    }
    return newListOfTags;
}, []);

4
我会将其放入临时Map中,并使用基于您感兴趣的属性的复合键。例如:
const foo = new Map();
for(const tag of listOfTags) {
  foo.set(tag.id + '-' tag.color, tag);
}

那也是我的第一个想法,但我觉得字符串拼接不太优雅。 - Andy
4
@Andy,这并不奇怪。这基本上是哈希表的工作原理,而哈希表是此类事物的适当数据结构。 - Evert

4

基于将值转换为字符串的假设,您可以调用

distinct(listOfTags, ["label", "color"])

其中distinct是:

/**
 * @param {array} arr The array you want to filter for dublicates
 * @param {array<string>} indexedKeys The keys that form the compound key
 *     which is used to filter dublicates
 * @param {boolean} isPrioritizeFormer Set this to true, if you want to remove
 *     dublicates that occur later, false, if you want those to be removed
 *     that occur later.
 */
const distinct = (arr, indexedKeys, isPrioritizeFormer = true) => {
    const lookup = new Map();
    const makeIndex = el => indexedKeys.reduce(
        (index, key) => `${index};;${el[key]}`, ''
    );
    arr.forEach(el => {
        const index = makeIndex(el);
        if (lookup.has(index) && isPrioritizeFormer) {
            return;
        }
        lookup.set(index, el);
    });

    return Array.from(lookup.values());
};

顺带一提:如果您使用 distinct(listOfTags, ["label", "color"], false),它将返回:

[
    {id: 1, label: "Hello", color: "red", sorting: 6},
    {id: 2, label: "World", color: "green", sorting: 1},
    {id: 3, label: "Hello", color: "blue", sorting: 4},
    {id: 4, label: "Sunshine", color: "yellow", sorting: 5},
]

3
const keys = ['label', 'color'],
const mySet = new Set();
const duplicateSet = new Set();
const result = objList.filter((item) => {
  let newItem = keys.map((k) => item[k]).join("-");
  mySet.has(newItem) && duplicateSet.add(newItem);
  return !mySet.has(newItem) && mySet.add(newItem);
});

console.log(duplicateSet, result);

这可以用来过滤重复和非重复的内容


2
我们可以通过以下脚本找到唯一的值,我们可以使用forEach循环扩展数组,并使用some()方法检查该值是否存在于新数组中,之后使用push()方法创建新数组。

const arr = [{ id: 1 }, { id: 2 }, { id: 4 }, { id: 1 }, { id: 4 }];
        var newArr =[];
        arr.forEach((item)=>{ 
            if(newArr.some(el => el.id === item.id)===false){
                newArr.push(item);
            }  
        }
        ); 
        console.log(newArr);
       //[{id: 1}, {id: 2}, {id: 4}];


1

const listOfTags = [
    {id: 1, label: "Hello", color: "red", sorting: 0},
    {id: 2, label: "World", color: "green", sorting: 1},
    {id: 3, label: "Hello", color: "blue", sorting: 4},
    {id: 4, label: "Sunshine", color: "yellow", sorting: 5},
    {id: 5, label: "Hello", color: "red", sorting: 6},
];

 const objRes=listOfTags.filter((v,i,s)=>s.findIndex(v2=>['label','color'].every(k=>v2[k]===v[k]))===i);
    console.log(objRes);


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接