【问题标题】:Create array of unique objects by property按属性创建唯一对象数组
【发布时间】:2013-09-17 09:37:56
【问题描述】:

我创建了一个对象数组,如下所示:

[
    {
        "lat": 12.123,
        "lng": 13.213,
        "city": "New York"
    },
    {
        "lat": 3.123,
        "lng": 2.213,
        "city": "New York"
    },
    {
        "lat": 1.513,
        "lng": 1.113,
        "city": "London"
    }
]

我正在尝试创建一个新数组,将places 过滤为仅包含不具有相同city 属性的对象(lat/lng 重复是可以的)。是否有内置的 JS 或 Jquery 函数来实现这一点?

【问题讨论】:

  • 是的,看看 [].forEach() 或 for 循环。
  • 看起来很定制。你如何确定应该扔掉哪一个?甲还是乙?编写一个自定义函数来引入这个逻辑。
  • 我会首先按城市名称对数组进行排序,然后在找到重复项时逐个遍历它们。如果您不先对其进行排序,则必须为数组中的每个条目迭代整个数组。
  • 我会使用城市名称作为对象的属性。
  • 一个“集合”可能会帮助你:stackoverflow.com/questions/7958292/…

标签: javascript jquery filter


【解决方案1】:

我可能会在过滤期间使用标志对象,如下所示:

var flags = {};
var newPlaces = places.filter(function(entry) {
    if (flags[entry.city]) {
        return false;
    }
    flags[entry.city] = true;
    return true;
});

它使用 ECMAScript5 (ES5) 中的 Array#filter,它是 ES5 可填充的新增功能之一(搜索“es5 shim”以获得多个选项)。

不用filter也可以,当然,就是啰嗦一点:

var flags = {};
var newPlaces = [];
var index;
for (index = 0; index < places.length; ++index) {
    if (!flags[entry.city]) {
        flags[entry.city] = true;
        newPlaces.push(entry);
    }
});

以上两个都假设应该保留具有给定城市的 first 对象,而所有其他对象都应丢弃。


注意:正如 user2736012 在下面指出的那样,我的测试 if (flags[entry.city]) 将适用于名称恰好与 Object.prototype 上存在的属性相同的城市,例如 toString。在这种情况下不太可能发生,但有四种方法可以避免这种可能性:

  • (我通常首选的解决方案)创建没有原型的对象:var flags = Object.create(null);。这是 ES5 的一个特性。请注意,对于 IE8 等过时的浏览器,这不能被填充(当参数的值为 null 时,Object.create 的单参数版本可以是 except)。

  • 使用hasOwnProperty 进行测试,例如if (flags.hasOwnProperty(entry.city))

  • 为任何Object.prototype 属性添加一个您知道不存在的前缀,例如xx

    var key = "xx" + entry.city;
    if (flags[key]) {
        // ...
    }
    flags[key] = true;
    
  • 从 ES2015 开始,您可以改用 Set

    const flags = new Set();
    const newPlaces = places.filter(entry => {
        if (flags.has(entry.city)) {
            return false;
        }
        flags.add(entry.city);
        return true;
    });
    

【讨论】:

  • 虽然不太可能存在名为“toString”的城市或任何其他Object.prototype 属性,但为此使用.hasOwnProperty() 仍然不是一个坏主意。
  • 是的,有点偏执。我会承认,当然对于这种情况。不过,它确实使它成为更安全的通用解决方案。
  • @undefined: 是的,但我认为用户的观点是,如果你概括这一点,这是可能的。例如,如果有人推广该技术并被constructor 属性所咬,我不会感到惊讶。他/她提出了一个非常有用的观点。
  • 使用 Javascript 中的新 Set 类,您可以用新的 Set() 替换标志对象,并使用 flags.has(key) 和 flags.add(key)。那么你就不用担心与对象原型属性的冲突了。
  • 最佳答案:几乎所有其他解决方案都在 O(n^2) 时间内运行或类似的时间。 @Robert Byrne 的解决方案是 O(2*n) 的下一个最佳解决方案。这几乎只是 O(n)。
【解决方案2】:

最短,但不是最佳性能(请参阅下面的更新)es6 解决方案:

function unique(array, propertyName) {
   return array.filter((e, i) => array.findIndex(a => a[propertyName] === e[propertyName]) === i);
}

性能:https://jsperf.com/compare-unique-array-by-property

【讨论】:

  • 19.08.2018 更新:在 Chrome 67.0.3396 / Mac OS X 10.13.6 中获得最佳性能这里建议的 2 个其他变体的得分.使用前检查目标浏览器的性能。
  • 你真是个天才!
  • @M.A.Naseer 抱歉,现在知道为什么链接被删除,无法恢复。
  • 此解决方案还有一个额外的好处,即它可以与具有某种非平凡equals 函数的对象一起使用。在这种情况下,findIndex 调用的内部变为a =&gt; a.equals(e)
【解决方案3】:

https://lodash.com/docs#uniqBy

https://github.com/lodash/lodash/blob/4.13.1/lodash.js#L7711

/**
 * This method is like `_.uniq` except that it accepts `iteratee` which is
 * invoked for each element in `array` to generate the criterion by which
 * uniqueness is computed. The iteratee is invoked with one argument: (value).
 *
 * @static
 * @memberOf _
 * @since 4.0.0
 * @category Array
 * @param {Array} array The array to inspect.
 * @param {Array|Function|Object|string} [iteratee=_.identity]
 *  The iteratee invoked per element.
 * @returns {Array} Returns the new duplicate free array.
 * @example
 *
 * _.uniqBy([2.1, 1.2, 2.3], Math.floor);
 * // => [2.1, 1.2]
 *
 * // The `_.property` iteratee shorthand.
 * _.uniqBy([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x');
 * // => [{ 'x': 1 }, { 'x': 2 }]
 */

【讨论】:

    【解决方案4】:

    我对@IgorL 解决方案进行了一些扩展,但扩展了原型并为其提供了选择器函数而不是属性,以使其更加灵活:

    Array.prototype.unique = function(selector) {
       return this.filter((e, i) => this.findIndex((a) => {
          if (selector) {
            return selector(a) === selector(e);
          }
          return a === e;
        }) === i);
    };
    

    用法:

    // with no param it uses strict equals (===) against the object
    let primArr = ['one','one','two','three','one']
    primArr.unique() // ['one','two','three']
    
    let a = {foo:123}
    let b = {foo:123}
    let fooArr = [a,a,b]
    fooArr.unique() //[a,b]
    
    // alternatively, you can pass a selector function
    fooArr.unique(item=>item.foo) //[{foo:123}] (first "unique" item returned)
    

    绝对不是最高效的方法,但只要选择器简单且数组不是很大,它应该可以正常工作。

    在打字稿中

    Array.prototype.unique = function<T>(this: T[], selector?: (item: T) => object): T[] {
       return this.filter((e, i) => this.findIndex((a) => {
          if (selector) {
            return selector(a) === selector(e);
          }
          return a === e;
        }) === i);
    };
    

    【讨论】:

    • 谢谢。在我的用例中完美运行!
    • 这是最好的解决方案!如果有人可以提供最高效的方式,那就太棒了。但是在这些情况下,对原型进行扩展是正确的方法,使用选择器也是正确的方法(我正在使用 typescript)
    • @Worthy7 我也在使用打字稿。我在下面添加了打字版本
    【解决方案5】:

    您可以通过仅包含具有尚未添加到 Set 的属性值的元素(之后应将其添加到 Set)来使用 Set filter。这可以使用逻辑与运算符 (&amp;&amp;) 在一行中完成。

    下面是一个通用函数,用于根据对象数组 (arr) 的特定属性 (prop) 获取唯一的对象数组。请注意,在重复的情况下,只会保留第一个具有属性值的对象。

    const getUniqueBy = (arr, prop) => {
      const set = new Set;
      return arr.filter(o => !set.has(o[prop]) && set.add(o[prop]));
    };
    

    演示:

    var places = [{
      lat: 12.123,
      lng: 13.213,
      city: 'New York'
    }, {
      lat: 3.123,
      lng: 2.213,
      city: 'New York'
    }, {
      lat: 3.123,
      lng: 4.123,
      city: 'Some City'
    }];
    const getUniqueBy = (arr, prop) => {
      const set = new Set;
      return arr.filter(o => !set.has(o[prop]) && set.add(o[prop]));
    };
    console.log(getUniqueBy(places, 'city'));

    【讨论】:

      【解决方案6】:

      我的建议:

      Array.prototype.uniqueCity = function() {
          var processed = [];
          for (var i=this.length-1; i>=0; i--){
              if (processed.indexOf(this[i].city)<0) {
                  processed.push(this[i].city);
              } else {
                  this.splice(i, 1);
              }
          }
      }
      

      使用中:

      places.uniqueCity();
      

      Array.prototype.uniqueObjectArray = function(field) {
          var processed = [];
          for (var i=this.length-1; i>=0; i--) {
              if (this[i].hasOwnProperty(field)) {
                  if (processed.indexOf(this[i][field])<0) {
                      processed.push(this[i][field]);
                  } else {
                      this.splice(i, 1);
                  }
              }
          }
      }
      
      places.uniqueObjectArray('city');
      

      使用上述方法,您可以按对象中的任何字段对数组进行排序,即使某些对象不存在这些字段

      function uniqueCity(array) {
          var processed = [];
          for (var i=array.length-1; i>=0; i--){
              if (processed.indexOf(array[i].city)<0) {
                  processed.push(array[i].city);
              } else {
                  array.splice(i, 1);
              }
          }
          return array;
      }
      
      places = uniqueCity(places);
      

      【讨论】:

      • 一个人不只是为了添加这样的特定功能而简单地玩prototype
      • 我必须同意@JakubMichálek。并不是说它不好,但对于 .prototype 方法来说似乎有点太具体了。不是技术问题,更多的是主观的概念问题。
      • 如果你把它变成通用的,比如prototype.unique,并添加一些支持说指定密钥,比如function(key) { ... },那么我认为没问题,但这对我来说似乎有点傻。跨度>
      • 当@theblueone 完全了解该特定项目中的房屋自己的代码时,扩展原型并不重要。如果您制作了一些旨在广泛使用或在任何地方使用的开源库,那将不是一个好主意。 那么你应该三思而后行。
      • 这个的另一个问题是它在 O(n^2) 时间内运行。 .indexOf() 还将遍历整个处理过的数组,并且您使用 .indexOf() 的处理过的数组可能会在您完成时增长到与原始数组大致相同的长度。这就是在最坏的情况下(即没有重复的情况下)您最终会花费几乎 O(n^2) 时间的方式。在最好的情况下(所有重复),您将获得 O(n) 时间。接受的答案使用查找表,总是接近 O(n) 时间。
      【解决方案7】:

      您可以使用地图,因此具有相同键属性(在您的情况下为“城市”)的条目只出现一次

      module.exports = (array, prop) => {
         const keyValueArray = array.map(entry => [entry[prop], entry]);
         const map = new Map(keyValueArray);
         return Array.from(map.values());
      };
      

      更多关于 Map 和数组对象的信息here

      Basic example on Codepen

      【讨论】:

        【解决方案8】:

        另一种选择:

        const uniqueBy = prop => list => {
            const uniques = {}
            return list.reduce(
                (result, item) => {
                    if (uniques[item[prop]]) return result
                    uniques[item[prop]] = item
                    return [...result, item]
                },
                [],
            )
        }
        
        const uniqueById = uniqueBy('id')
        
        uniqueById([
            { id: 1, name: 'one' },
            { id: 2, name: 'two' },
            { id: 1, name: 'one' },
            { id: 3, name: 'three' }
        ])
        

        您可以将其粘贴到您的控制台上以查看它是否正常工作。 它应该适用于所展示的场景和其他一些场景。

        【讨论】:

          【解决方案9】:

          正如 cmets 中所指出的,您可以将对象用作映射,这样可以避免重复,然后您可以枚举对象的属性。

          工作小提琴:http://jsfiddle.net/gPRPQ/1/

          var places = [];
          var a = {};
          a.lat = 12.123;
          a.lng = 13.213;
          a.city = "New York";
          
          places.push(a);
          
          var b = {};
          b.lat = 3.123;
          b.lng = 2.213;
          b.city = "New York";
          
          places.push(b);
          
          var unique = {}
          
          for (var i = 0; i < places.length; i++) {
              var place = places[i];
              unique[place.city] = place;
          }
          
          for (var name in unique) {
              var place = unique[name];
              console.log(place);
          }
          

          【讨论】:

            【解决方案10】:
            var places = [];
            var a = {};
            a.lat = 12.123;
            a.lng = 13.213;
            a.city = "New York";
            
            places.push(a);
            
            var b = {};
            b.lat = 3.123;
            b.lng = 2.213;
            b.city = "New York";
            
            places.push(b);
            
            getUniqAR(places,'city'); //Return Uniq Array by property
            
            function getUniqAR(Data,filter){
            var uniar =[];
            Data.forEach(function(item,ind,arr){
                var dupi=false;
                if(!uniar.length) uniar.push(item) //push first obj into uniq array 
                uniar.forEach(function(item2, ind2,arr){
                if(item2[filter] == item[filter]){  //check each obj prop of uniq array 
                  dupi=true; //if values are same put duplicate is true
                    }     
                })
            if(!dupi){  uniar.push(item)} //if no duplicate insert to uniq
            
            })
            console.log(uniar)
            return uniar;
            }
            

            【讨论】:

              【解决方案11】:

              我们可以使用 JavaScript Map 通过任何属性创建唯一对象列表。

              例如:

              var places = [{ 'lat': 12.123, 'lng': 13.213, 'city': "New York"},
                              { 'lat': 3.123, 'lng': 2.213, 'city': "New York"},
                              { 'lat': 43.123, 'lng': 12.213, 'city': "London"}];
                              
              var cityMap = new Map();
              places.forEach(p=> cityMap.set(p.city, p));
              
              console.log([...cityMap.values()]);

              执行代码sn-p查看结果。

              【讨论】:

                【解决方案12】:

                在简单的Javascript 代码中从places 数组列表中删除重复的城市是

                var places = [{ 'lat': 12.123, 'lng': 13.213, 'city': "New York"},
                                { 'lat': 3.123, 'lng': 2.213, 'city': "New York"},
                                { 'lat': 43.123, 'lng': 12.213, 'city': "London"}];
                var unique = [];
                var tempArr = [];
                places.forEach((value, index) => {
                    if (unique.indexOf(value.city) === -1) {
                        unique.push(value.city);
                    } else {
                        tempArr.push(index);    
                    }
                });
                tempArr.reverse();
                tempArr.forEach(ele => {
                    places.splice(ele, 1);
                });
                console.log(places);
                

                【讨论】:

                  【解决方案13】:

                  基于上述https://stackoverflow.com/a/18773857/49564 的通用 Typescript 答案:

                  export function isDistinct<T>(mapper: (value: T) => string): (value: T) => boolean {
                    const keys: { [index: string]: boolean } = {};
                  
                    return (entry: T) => {
                      const key = mapper(entry);
                  
                      if (keys[key] !== undefined) {
                        return false;
                      }
                  
                      return keys[key] = true;
                    };
                  }
                  
                  // Usage example:
                  const items = [ { id: 1 }, { id: 2 }, { id: 3 }, { id: 1 } ];
                  const unique = items.filter(isDistinct(i => i.id));
                  

                  【讨论】:

                    【解决方案14】:

                    我想你想要这个,

                    注意: 不需要库。

                    let array = [{ id: 1}, {id: 2}, {id: 3}];
                    
                    function addUniqeObj(data) {
                      let index = -1;
                    
                      for(let i = 0, i < array.length; i++) {
                        if(array[i].id === data.id) {
                          index = i;
                        }
                      }
                    
                      if(index > -1) {
                        array[index] = data;
                      } else {
                        array.push(data)
                      }
                    
                    }
                    

                    【讨论】:

                      【解决方案15】:

                      rafaelbiten 方法的另一种变体:

                      const dedupExample = [
                          {id: 1, c: 'whatever'},
                          {id: 1, c: '1whatever'},
                          {id: 2, c: '2whatever'},
                          {id: 2, c: '2whatever'},
                          {id: 3, c: '2whatever'},
                      ]
                      
                      const getUniqueBy = (prop, list) => {
                          const objUniq = list.reduce((res, item) => ({ ...res, [item[prop]]: item }), {})
                          return Object.keys(objUniq).map(item => objUniq[item])
                      }
                      
                      const uniq = getUniqueBy('id', dedupExample)
                      
                      console.info('info', { uniq })
                         /* [
                          {id: 1, c: 'whatever'},
                          {id: 2, c: '2whatever'},
                          {id: 3, c: '2whatever'},
                        ] */

                      【讨论】:

                        【解决方案16】:

                        这个帖子可能很旧,但我认为我应该分享它。它基于纯 JavaScript,并根据指定的属性删除重复对象。

                        function removeDuplicates(originalArray, properties) {
                          var newArray = [];
                          var index = 0;
                          var lookupObject = {};
                          var totalProperties = properties.length;
                        
                          for (var i = 0; i < originalArray.length; i++) {
                            var exists = false;
                        
                            for (var a = 0; a < newArray.length; a++) {
                              var propsFound = 0;
                              for (var b = 0; b < totalProperties; b++) {
                                if (originalArray[i][properties[b]] == newArray[a][properties[b]]) {
                                  propsFound++;
                                }
                              }
                        
                              //If there is a match then break the for loop
                              if (propsFound == totalProperties) {
                                exists = true;
                                break;
                              }
                            } //End of New Array
                        
                            if (!exists) {
                              newArray[index] = originalArray[i];
                              index++;
                            }
                          } //End of originalArray
                        
                          return newArray;
                        }
                        

                        你可以查看小提琴here

                        【讨论】:

                          猜你喜欢
                          • 2019-08-08
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 2020-09-15
                          • 1970-01-01
                          • 2016-02-16
                          • 1970-01-01
                          相关资源
                          最近更新 更多