How to get distinct values from an array of objects in JavaScript?

前端 未结 30 2604
执笔经年
执笔经年 2020-11-22 05:29

Assuming I have the following:

var array = 
    [
        {\"name\":\"Joe\", \"age\":17}, 
        {\"name\":\"Bob\", \"age\":17}, 
        {\"name\":\"Carl\         


        
相关标签:
30条回答
  • 2020-11-22 05:54

    You could use a dictionary approach like this one. Basically you assign the value you want to be distinct as a key in the "dictionary" (here we use an array as an object to avoid dictionary-mode). If the key did not exist then you add that value as distinct.

    Here is a working demo:

    var array = [{"name":"Joe", "age":17}, {"name":"Bob", "age":17}, {"name":"Carl", "age": 35}];
    var unique = [];
    var distinct = [];
    for( let i = 0; i < array.length; i++ ){
      if( !unique[array[i].age]){
        distinct.push(array[i].age);
        unique[array[i].age] = 1;
      }
    }
    var d = document.getElementById("d");
    d.innerHTML = "" + distinct;
    <div id="d"></div>

    This will be O(n) where n is the number of objects in array and m is the number of unique values. There is no faster way than O(n) because you must inspect each value at least once.

    The previous version of this used an object, and for in. These were minor in nature, and have since been minorly updated above. However, the reason for a seeming advance in performance between the two versions in the original jsperf was due to the data sample size being so small. Thus, the main comparison in the previous version was looking at the difference between the internal map and filter use versus the dictionary mode lookups.

    I have updated the code above, as noted, however, I have also updated the jsperf to look through 1000 objects instead of 3. 3 overlooked many of the performance pitfalls involved (obsolete jsperf).

    Performance

    https://jsperf.com/filter-vs-dictionary-more-data When I ran this dictionary was 96% faster.

    0 讨论(0)
  • 2020-11-22 05:55

    Here's a versatile solution that uses reduce, allows for mapping, and maintains insertion order.

    items: An array

    mapper: A unary function that maps the item to the criteria, or empty to map the item itself.

    function distinct(items, mapper) {
        if (!mapper) mapper = (item)=>item;
        return items.map(mapper).reduce((acc, item) => {
            if (acc.indexOf(item) === -1) acc.push(item);
            return acc;
        }, []);
    }
    

    Usage

    const distinctLastNames = distinct(items, (item)=>item.lastName);
    const distinctItems = distinct(items);
    

    You can add this to your Array prototype and leave out the items parameter if that's your style...

    const distinctLastNames = items.distinct( (item)=>item.lastName) ) ;
    const distinctItems = items.distinct() ;
    

    You can also use a Set instead of an Array to speed up the matching.

    function distinct(items, mapper) {
        if (!mapper) mapper = (item)=>item;
        return items.map(mapper).reduce((acc, item) => {
            acc.add(item);
            return acc;
        }, new Set());
    }
    
    0 讨论(0)
  • 2020-11-22 05:56

    If like me you prefer a more "functional" without compromising speed, this example uses fast dictionary lookup wrapped inside reduce closure.

    var array = 
    [
        {"name":"Joe", "age":17}, 
        {"name":"Bob", "age":17}, 
        {"name":"Carl", "age": 35}
    ]
    var uniqueAges = array.reduce((p,c,i,a) => {
        if(!p[0][c.age]) {
            p[1].push(p[0][c.age] = c.age);
        }
        if(i<a.length-1) {
            return p
        } else {
            return p[1]
        }
    }, [{},[]])
    

    According to this test my solution is twice as fast as the proposed answer

    0 讨论(0)
  • 2020-11-22 05:57

    If this were PHP I'd build an array with the keys and take array_keys at the end, but JS has no such luxury. Instead, try this:

    var flags = [], output = [], l = array.length, i;
    for( i=0; i<l; i++) {
        if( flags[array[i].age]) continue;
        flags[array[i].age] = true;
        output.push(array[i].age);
    }
    
    0 讨论(0)
  • 2020-11-22 05:57

    Using ES6 features, you could do something like:

    const uniqueAges = [...new Set( array.map(obj => obj.age)) ];
    
    0 讨论(0)
  • 2020-11-22 05:57
    var unique = array
        .map(p => p.age)
        .filter((age, index, arr) => arr.indexOf(age) == index)
        .sort(); // sorting is optional
    
    // or in ES6
    
    var unique = [...new Set(array.map(p => p.age))];
    
    // or with lodash
    
    var unique = _.uniq(_.map(array, 'age'));
    

    ES6 example

    const data = [
      { name: "Joe", age: 17}, 
      { name: "Bob", age: 17}, 
      { name: "Carl", age: 35}
    ];
    
    const arr = data.map(p => p.age); // [17, 17, 35]
    const s = new Set(arr); // {17, 35} a set removes duplications, but it's still a set
    const unique = [...s]; // [17, 35] Use the spread operator to transform a set into an Array
    // or use Array.from to transform a set into an array
    const unique2 = Array.from(s); // [17, 35]
    
    0 讨论(0)
提交回复
热议问题