How to get distinct values from an array of object

2019-01-01 07:56发布

问题:

Assuming I have the following:

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

What is the best way to be able to get an array of all of the distinct ages such that I get an result array of:

[17, 35]

Is there some way I could alternatively structure the data or better method such that I would not have to iterate through each array checking the value of \"age\" and check against another array for its existence, and add it if not?

If there was some way I could just pull out the distinct ages without iterating...

Current inefficent way I would like to improve... If it means that instead of \"array\" being an array of objects, but a \"map\" of objects with some unique key (i.e. \"1,2,3\") that would be okay too. Im just looking for the most performance efficient way.

The following is how I currently do it, but for me, iteration appears to just be crummy for efficiency even though it does work...

var distinct = []
for (var i = 0; i < array.length; i++)
   if (array[i].age not in distinct)
      distinct.push(array[i].age)

回答1:

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);
}


回答2:

If you are using ES6/ES2015 or later you can do it this way:

const unique = [...new Set(array.map(item => item.age))];

Here is an example on how to do it.



回答3:

You could use a dictionary approach like this one. Basically you assign the value you want to be distinct as the key in a dictionary. If the key did not exist then you add that value as distinct.

var unique = {};
var distinct = [];
for( var i in array ){
 if( typeof(unique[array[i].age]) == \"undefined\"){
  distinct.push(array[i].age);
 }
 unique[array[i].age] = 0;
}

Here is a working demo: http://jsfiddle.net/jbUKP/1

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.

Performance

http://jsperf.com/filter-versus-dictionary When I ran this dictionary was 30% faster.



回答4:

using ES6

let array = [
  { \"name\": \"Joe\", \"age\": 17 },
  { \"name\": \"Bob\", \"age\": 17 },
  { \"name\": \"Carl\", \"age\": 35 }
];
array.map(item => item.age)
  .filter((value, index, self) => self.indexOf(value) === index)

> [17, 35]


回答5:

I\'d just map and remove dups:

var ages = array.map(function(obj) { return obj.age; });
ages = ages.filter(function(v,i) { return ages.indexOf(v) == i; });

console.log(ages); //=> [17, 35]

Edit: Aight! Not the most efficient way in terms of performance, but the simplest most readable IMO. If you really care about micro-optimization or you have huge amounts of data then a regular for loop is going to be more \"efficient\".



回答6:

this is how you would solve this using new Set via ES6 for Typescript as of August 25th, 2017

Array.from(new Set(yourArray.map((item: any) => item.id)))


回答7:

Using ES6 features, you could do something like:

const uniqueAges = [...new Set( array.map(obj => obj.age)) ];


回答8:

The forEach version of @travis-j\'s answer (helpful on modern browsers and Node JS world):

var unique = {};
var distinct = [];
array.forEach(function (x) {
  if (!unique[x.age]) {
    distinct.push(x.age);
    unique[x.age] = true;
  }
});

34% faster on Chrome v29.0.1547: http://jsperf.com/filter-versus-dictionary/3

And a generic solution that takes a mapper function (tad slower than direct map, but that\'s expected):

function uniqueBy(arr, fn) {
  var unique = {};
  var distinct = [];
  arr.forEach(function (x) {
    var key = fn(x);
    if (!unique[key]) {
      distinct.push(key);
      unique[key] = true;
    }
  });
  return distinct;
}

// usage
uniqueBy(array, function(x){return x.age;}); // outputs [17, 35]


回答9:

I\'ve started sticking Underscore in all new projects by default just so I never have to think about these little data-munging problems.

var array = [{\"name\":\"Joe\", \"age\":17}, {\"name\":\"Bob\", \"age\":17}, {\"name\":\"Carl\", \"age\": 35}];
console.log(_.chain(array).map(function(item) { return item.age }).uniq().value());

Produces [17, 35].



回答10:

using lodash

var array = [
    { \"name\": \"Joe\", \"age\": 17 },
    { \"name\": \"Bob\", \"age\": 17 },
    { \"name\": \"Carl\", \"age\": 35 }
];
_.chain(array).pluck(\'age\').unique().value();
> [17, 35]


回答11:

Here\'s another way to solve this:

var result = {};
for(var i in array) {
    result[array[i].age] = null;
}
result = Object.keys(result);

I have no idea how fast this solution is compared to the others, but I like the cleaner look. ;-)


EDIT: Okay, the above seems to be the slowest solution of all here.

I\'ve created a performance test case here: http://jsperf.com/distinct-values-from-array

Instead of testing for the ages (Integers), I chose to compare the names (Strings).

Method 1 (TS\'s solution) is very fast. Interestingly enough, Method 7 outperforms all other solutions, here I just got rid of .indexOf() and used a \"manual\" implementation of it, avoiding looped function calling:

var result = [];
loop1: for (var i = 0; i < array.length; i++) {
    var name = array[i].name;
    for (var i2 = 0; i2 < result.length; i2++) {
        if (result[i2] == name) {
            continue loop1;
        }
    }
    result.push(name);
}

The difference in performance using Safari & Firefox is amazing, and it seems like Chrome does the best job on optimization.

I\'m not exactly sure why the above snippets is so fast compared to the others, maybe someone wiser than me has an answer. ;-)



回答12:

underscore.js _.uniq(_.pluck(array,\"age\"))



回答13:

Using Lodash

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

_.chain(array).map(\'age\').unique().value();

Returns [17,35]



回答14:

function get_unique_values_from_array_object(array,property){
    var unique = {};
    var distinct = [];
    for( var i in array ){
       if( typeof(unique[array[i][property]]) == \"undefined\"){
          distinct.push(array[i]);
       }
       unique[array[i][property]] = 0;
    }
    return distinct;
}


回答15:

i think you are looking for groupBy function (using Lodash)

_personsList = [{\"name\":\"Joe\", \"age\":17}, 
                {\"name\":\"Bob\", \"age\":17}, 
                {\"name\":\"Carl\", \"age\": 35}];
_uniqAgeList = _.groupBy(_personsList,\"age\");
_uniqAges = Object.keys(_uniqAgeList);

produces result:

17,35

jsFiddle demo:http://jsfiddle.net/4J2SX/201/



回答16:

There are many valid answers already, but I wanted to add one that uses only the reduce() method because it is clean and simple.

function uniqueBy(arr, prop){
  return arr.reduce((a, d) => {
    if (!a.includes(d[prop])) { a.push(d[prop]); }
    return a;
  }, []);
}

Use it like this:

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

var ages = uniqueBy(array, \"age\");
console.log(ages); // [17, 35]


回答17:

Just found this and I thought it\'s useful

_.map(_.indexBy(records, \'_id\'), function(obj){return obj})

Again using underscore, so if you have an object like this

var records = [{_id:1,name:\'one\', _id:2,name:\'two\', _id:1,name:\'one\'}]

it will give you the unique objects only.

What happens here is that indexBy returns a map like this

{ 1:{_id:1,name:\'one\'}, 2:{_id:2,name:\'two\'} }

and just because it\'s a map, all keys are unique.

Then I\'m just mapping this list back to array.

In case you need only the distinct values

_.map(_.indexBy(records, \'_id\'), function(obj,key){return key})

Keep in mind that the key is returned as a string so, if you need integers instead, you should do

_.map(_.indexBy(records, \'_id\'), function(obj,key){return parseInt(key)})


回答18:

If you have Array.prototype.includes or are willing to polyfill it, this works:

var ages = []; array.forEach(function(x) { if (!ages.includes(x.age)) ages.push(x.age); });


回答19:

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



回答20:

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());
}


回答21:

unique(obj, prop) {
    let result = [];
    let seen = new Set();

    Object.keys(obj)
        .forEach((key) => {
            let value = obj[key];

            let test = !prop
                ? value
                : value[prop];

            !seen.has(test)
                && seen.add(test)
                && result.push(value);
        });

    return result;
}


回答22:

Using new Ecma features are great but not all users have those available yet.

Following code will attach a new function named distinct to the Global Array object. If you are trying get distinct values of an array of objects, you can pass the name of the value to get the distinct values of that type.

Array.prototype.distinct = function(item){   var results = [];
for (var i = 0, l = this.length; i < l; i++)
    if (!item){
        if (results.indexOf(this[i]) === -1)
            results.push(this[i]);
        } else {
        if (results.indexOf(this[i][item]) === -1)
            results.push(this[i][item]);
    }
return results;};

Check out my post in CodePen for a demo.



回答23:

Just try

var x = [] ;
for (var i = 0 ; i < array.length ; i++)
{
 if(x.indexOf(array[i][\'age\']) == -1)
  {
    x.push(array[i][\'age\']);
  }
}
console.log(x);


回答24:

var unique = array
    .map(p => p.age)
    .filter((age, index, arr) => arr.indexOf(age) == index)
    .sort();

// or

var unique = [...new Set(array.map(p => p.age))];


回答25:

my two cents on this function:

var result = [];
for (var len = array.length, i = 0; i < len; ++i) {
  var age = array[i].age;
  if (result.indexOf(age) > -1) continue;
  result.push(age);
}

You can see the result here (Method 8) http://jsperf.com/distinct-values-from-array/3



回答26:

this function can unique array and object

function oaunic(x,n=0){
    if(n==0) n = \"elem\";
    else n = \"elem.\"+n;
    var uval = [];
    var unic = x.filter(function(elem, index, self){
        if(uval.indexOf(eval(n)) < 0){
            uval.push(eval(n));
            return index == self.indexOf(elem);
        }
    })
    return unic;
}

use that like this

tags_obj = [{name:\"milad\"},{name:\"maziar\"},{name:\"maziar\"}]
tags_arr = [\"milad\",\"maziar\",\"maziar\"]
console.log(oaunic(tags_obj,\"name\")) //for object
console.log(oaunic(tags_arr)) //for array


回答27:

using d3.js v3:

  ages = d3.set(
    array.map(function (d) { return d.age; })
  ).values();