【问题标题】:How to find duplicate values in a JavaScript array of objects, and output only unique values?如何在 JavaScript 对象数组中查找重复值,并仅输出唯一值?
【发布时间】:2017-02-14 14:27:42
【问题描述】:

我正在学习 JS。假设我有以下对象数组:

var family = [
  {
    name: "Mike",
    age: 10
  },
  {
    name: "Matt"
    age: 13
  },
  {
    name: "Nancy",
    age: 15
  },
  {
    name: "Adam",
    age: 22
  },
  {
    name: "Jenny",
    age: 85
  },
  {
    name: "Nancy",
    age: 2
  },
  {
    name: "Carl",
    age: 40
  }
];

请注意,Nancy 出现了两次(仅更改了年龄)。假设我只想输出唯一的名称。如何输出上述对象数组而不重复? ES6 的回答非常受欢迎。

相关(找不到在对象上使用的好方法):

编辑这是我尝试过的。它适用于字符串,但我不知道如何使它适用于对象:

family.reduce((a, b) => {
  if (a.indexOf(b) < 0 ) {
    a.push(b);
  }
  return a;
},[]);

【问题讨论】:

  • 你能发布你尝试过的东西吗?
  • 实际上我尝试了我提到的两个相关问题的大部分答案。
  • 有人能解释一下为什么投反对票吗?我应该复制/粘贴任何在这里不起作用的代码只是说我试过了吗?
  • 因为这实际上并不太难,但需要更多的研究,谷歌这个:get unique objects array javascript
  • @RicoLetterman 我编辑了我的答案以解决您的编辑问题,以及为什么它不起作用。

标签: javascript arrays ecmascript-6 javascript-objects


【解决方案1】:

由于大多数答案不会有很好的表现,我想我分享一下我对此的看法:

const arrayWithDuplicateData = [{ id: 5, name: 'Facebook'}, { id: 3, name: 'Twitter' }, { id: 5, name: 'Facebook' }];


const uniqueObj = {};

arrayWithDuplicateData.forEach(i => {
  uniqueObj[i.id] = i;
});

const arrayWithoutDuplicates = Object.values(uniqueObj);

我们正在利用键在对象中是唯一的这一事实。这意味着第一个数组中的最后一个重复项将胜过它的前辈。如果我们想改变它,我们可以在迭代之前翻转数组。

此外,我们不必只使用对象的一个​​属性来识别重复。

const arrayWithDuplicateData = [{ id: 5, name: 'Facebook'}, { id: 3, name: 'Twitter' }, { id: 5, name: 'Facebook' }];


const uniqueObj = {};

arrayWithDuplicateData.forEach(item => {
  uniqueObj[`${item.id}_${item.name}`] = item;
});

const arrayWithoutDuplicates = Object.values(uniqueObj);

或者我们可以简单地添加一个检查,如果 uniqueObj 已经拥有一个密钥,如果是,则不覆盖它。

总体而言,这种方式在性能方面并不是很昂贵,并且到目前为止对我的服务很好。

【讨论】:

    【解决方案2】:

    这会很好用。

    const result = [1, 2, 2, 3, 3, 3, 3].reduce((x, y) => x.includes(y) ? x : [...x, y], []);
    
    console.log(result);

    【讨论】:

    • 你能告诉我X[spreadd x : y ] 是什么吗?最后一个空数组也是[ ] ?
    【解决方案3】:

    我只是为 Lodash 用户想到了 2 个简单的方法

    给定这个数组:

    let family = [
      {
        name: "Mike",
        age: 10
      },
      {
        name: "Matt",
        age: 13
      },
      {
        name: "Nancy",
        age: 15
      },
      {
        name: "Adam",
        age: 22
      },
      {
        name: "Jenny",
        age: 85
      },
      {
        name: "Nancy",
        age: 2
      },
      {
        name: "Carl",
        age: 40
      }
    ]
    

    1.查找重复项:

    let duplicatesArr = _.difference(family, _.uniqBy(family, 'name'), 'name')
    
    // duplicatesArr:
    // [{ 
    //   name: "Nancy", 
    //   age: 2 
    // }]
    

    2 查找是否有重复项,用于验证目的:

    let uniqArr = _.uniqBy(family, 'name')
    
    if (uniqArr.length === family.length) {
        // No duplicates
    }
    
    if (uniqArr.length !== family.length) {
        // Has duplicates
    }
    

    【讨论】:

      【解决方案4】:

      解决方案

      将出现的name 存储在对象中的循环外部,如果之前出现过,则进行过滤。

      https://jsfiddle.net/nputptbb/2/

      var occurrences = {}
      
      var filteredFamily = family.filter(function(x) {
        if (occurrences[x.name]) {
          return false;
        }
        occurrences[x.name] = true;
        return true;
      })
      

      您也可以将此解决方案推广到函数

      function filterByProperty(array, propertyName) {
        var occurrences = {}
      
        return array.filter(function(x) {
          var property = x[propertyName]
          if (occurrences[property]) {
            return false;
          }
          occurrences[property]] = true;
          return true;
        })
      }
      

      并像使用它

      var filteredFamily = filterByProperty(family, 'name')
      

      说明

      不要使用indexOf 比较对象,它只在对象之间使用=== 运算符。您当前的答案不起作用的原因是因为 JS 中的 === 不会深入比较对象,而是比较引用。我的意思是你可以在下面的代码中看到:

      var a = { x: 1 }
      var b = { x: 1 }
      
      console.log(a === b) // false
      console.log(a === a) // true
      

      如果您找到了完全相同对象,Equality 会告诉您,但如果您找到内容相同的对象,则不会。

      在这种情况下,您可以在 name 上比较您的对象,因为它应该是唯一键。所以obj.name === obj.name 而不是obj === obj。此外,影响其运行时而非其功能的代码的另一个问题是您在reduce 内部使用了indexOfindexOfO(n),这使得你的算法复杂度 O(n^2)。因此,最好使用具有O(1) 查找的对象。

      【讨论】:

      • 这可能是因为您的小提琴写入控制台...正在运行小提琴,查看 o/p,并想知道是否有任何工作...并没有真正费心先检查代码(并查看它的写入控制台)
      • 我非常感谢这个答案——它是跨浏览器、高性能且易于阅读的。
      • 因此,如果名称重复,但您希望年龄全部存储在年龄数组中。所以名称 nacey 和 age 2 已经消失了,但是如果你可以将 age 2 添加到剩下的对象 nacey 上会怎样。而且你可以拥有漂亮的对象年龄 [15, 2]
      【解决方案5】:

      您可以在一行中将SetArray#mapspread operator ... 结合使用。

      Map 返回一个包含所有名称的数组,这些名称将进入集合初始值设定项,然后集合中的所有值都返回到一个数组中。

      var family = [{ name: "Mike", age: 10 }, { name: "Matt", age: 13 }, { name: "Nancy", age: 15 }, { name: "Adam", age: 22 }, { name: "Jenny", age: 85 }, { name: "Nancy", age: 2 }, { name: "Carl", age: 40 }],
          unique = [...new Set(family.map(a => a.name))];
      
      console.log(unique);

      要过滤并仅返回唯一名称,您可以使用 Array#filterSet

      var family = [{ name: "Mike", age: 10 }, { name: "Matt", age: 13 }, { name: "Nancy", age: 15 }, { name: "Adam", age: 22 }, { name: "Jenny", age: 85 }, { name: "Nancy", age: 2 }, { name: "Carl", age: 40 }],
          unique = family.filter((set => f => !set.has(f.name) && set.add(f.name))(new Set));
      
      console.log(unique);

      【讨论】:

      • 有没有办法输出我的对象数组而不是这个平面数组?
      • 您的问题如何我只想输出唯一的名称。?如果不是,您希望获得第一个对象还是最后一个对象?
      • 做得很好。我喜欢你详细解释的两个选项。
      • 嗨@Nina Scholz 它工作正常但不工作我IE11 提供任何其他解决方案
      • @jose,这个问题标记为ES6,IE11不支持。您可以使用哈希表代替对象并在那里收集所有名称。
      【解决方案6】:

      有了你提到的代码,你可以试试:

      family.filter((item, index, array) => {
        return array.map((mapItem) => mapItem['name']).indexOf(item['name']) === index
      })
      

      或者你可以有一个通用函数来使它也适用于其他对象数组:

      function printUniqueResults (arrayOfObj, key) {
        return arrayOfObj.filter((item, index, array) => {
          return array.map((mapItem) => mapItem[key]).indexOf(item[key]) === index
        })
      }
      

      然后只需使用printUniqueResults(family, 'name')

      (FIDDLE)

      【讨论】:

        【解决方案7】:

        我可能会设置某种对象。既然您已经说过 ECMAScript 6,您可以访问 Set,但是由于您想比较对象上的值,因此需要做更多的工作。

        一个例子可能看起来像这样(为了清楚起见,删除了命名空间模式):

        var setOfValues = new Set();
        var items = [];
        
        function add(item, valueGetter) {
           var value = valueGetter(item);
           if (setOfValues.has(value))
              return;
        
           setOfValues.add(value);
           items.push(item);
        }
        
        function addMany(items, valueGetter) {
           items.forEach(item => add(item, valueGetter));
        }
        

        像这样使用它:

        var family = [
        ...
        ];
        
        addMany(family, item => item.name);
        
        // items will now contain the unique items
        

        说明:您需要在添加每个对象时从每个对象中提取一个值,并根据您获得的值确定它是否已经添加。它需要一个 value getter,这是一个给定项目的函数,返回一个值 (item =&gt; item.name)。然后,您只添加尚未看到其值的项目。


        类实现:

        // Prevents duplicate objects from being added
        
        class ObjectSet {
          constructor(key) {
            this.key = key;
            this.items = [];
            this.set = new Set();
          }
        
          add(item) {
            if (this.set.has(item[this.key])) return;
            this.set.add(item[this.key]);
            this.items.push(item);
          }
        
          addMany(items) {
            items.forEach(item => this.add(item));
          }
        }
        
        var mySet = new ObjectSet('name');
        mySet.addMany(family);
        console.log(mySet.items);
        

        【讨论】:

        • 这太不必要了,太复杂了
        • @AR7 复杂?我大概5分钟就写好了。它看起来很丑,因为这就是封装在 javascript 中的工作方式。
        • @AR7 我已经删除了不必要的命名空间模式
        • 如果您要包含 ES6 集,为什么不包含 ES6 类,因为它会像您的命名空间模式一样封装您的代码。现在它看起来很奇怪哈哈。
        • @AR7 我正在学习 ES6。还不是 ES6 类的粉丝,我不能只编写一个代码。 (其实我已经改用打字稿了。)
        猜你喜欢
        • 1970-01-01
        • 2021-11-14
        • 2022-11-15
        • 1970-01-01
        • 2014-12-27
        • 2016-10-15
        • 1970-01-01
        相关资源
        最近更新 更多