如何从JavaScript中的对象数组中获取不同的值?


383

假设我有以下内容:

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

能够获得所有不同年龄的数组的最佳方法是什么,这样我得到以下结果数组:

[17, 35]

有什么办法可以替代地构造数据或更好的方法,这样我就不必遍历每个数组检查“ age”的值并检查另一个数组是否存在,如果没有,可以添加它?

如果有某种方法,我可以不重复就退出不同的年龄...

我想改善当前无效的方法...如果这意味着不是“数组”是对象数组,而是带有一些唯一键(即“ 1,2,3”)的对象“映射”也可以 我只是在寻找最高效的方式。

以下是我目前的操作方式,但是对我来说,即使迭代确实有效,迭代似乎也很糟糕。

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

34
迭代并不是“效率低下”,并且您不能“不迭代”地对每个元素做任何事情。您可以使用各种具有功能外观的方法,但最终,必须在某种程度上对这些项目进行迭代。
伊夫2013年

// 100%正在运行的代码const listOfTags = [{{id:1,标签:“ Hello”,颜色:“ red”,排序:0},{id:2,标签:“ World”,颜色:“ green”,排序:1},{id:3,标签:“ Hello”,颜色:“ blue”,排序:4},{id:4,标签:“ Sunshine”,颜色:“ yellow”,排序:5},{id :5,5,标签:“ Hello”,颜色:“红色”,排序:6}],键= ['标签','颜色'],过滤= listOfTags.filter((s => o =>(k => !s.has(k)&& s.add(k))(keys.map(k => o [k])。join('|')))(新集合)); console.log(已过滤);
Sandeep Mishra,

1
赏金很大,但是此处给出了带有给定数据和答案的问题:stackoverflow.com/questions/53542882/…。赏金的目的是什么?我应该用两个或更多个键来回答这个特定问题吗?
Nina Scholz

Setobject和maps是浪费的。这项工作只是一个简单的.reduce()阶段。
Redu

Answers:


126

如果这是PHP,我会用键构建一个数组并array_keys在末尾使用,但是JS却没有这样的奢侈。相反,请尝试以下操作:

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

15
不,因为array_unique会比较整个项目,而不仅仅是此处要求的年龄。
Niet the Dark Absol

6
我认为flags = {}flags = []
zhuguowei

3
@zhuguowei也许,尽管稀疏数组没有真正的问题-我还假定这age是一个相对较小的整数(肯定小于120)
Niet the Dark Absol

我们如何还能打印出具有相同年龄的总人数?
user1788736

605

如果您使用的是ES6 / ES2015或更高版本,则可以通过以下方式进行操作:

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

是有关如何执行操作的示例。


11
我收到一个错误:TypeError: (intermediate value).slice is not a function
Github上的AngJobs,2013年

7
@Thomas ...表示传播算子。在这种情况下,这意味着创建新集并将其散布在列表中。:你可以找到更多关于它这里developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...
弗拉德Bezden

10
对于打字稿,您必须使用包装在Array.from()中的新Set ...我将在下面提供该答案。@AngJobs
Christian Matthew

4
是否可以根据对象中的多个键找出唯一的对象?
Jefree Sujit's

13
@Russell Vea 几乎在几个月前就给出了该解决方案。您检查现有答案了吗?但是话又说回来,266票以上的投票结果表明,没有人检查过。
Dan Dascalescu

164

使用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]

2
如果我想获取带有索引的值,那么我该如何获取示例:我想获取{“ name”:“ Bob”,“ age”:“ 17”},{“ name”:“ Bob”,“ age “:” 17“}
Abdullah Al Mamun

10
这就是为什么您必须不断滚动
Alejandro Bastidas

5
@AbdullahAlMamun,您可以在.filter中使用map而不是先调用.map,例如:array.filter((value, index, self) => self.map(x => x.age).indexOf(value.age) == index)
Leo

1
ps:.filter中的.map可能会变慢,因此请注意大型数组
Leo

2
@IvanNosov您的片段是虽然解释如何与指针的作品映射和过滤器的文档将使其完善并清除初学者几行非常好
azakgaim

128

您可以使用像这样的字典方法。基本上,您将要与众不同的值分配为“字典”中的键(此处,我们使用数组作为对象以避免字典模式)。如果键不存在,则将该值添加为distinct。

这是一个工作示例:

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>

这将是O(n),其中n是数组中对象的数量,m是唯一值的数量。没有比O(n)更快的方法,因为您必须检查每个值至少一次。

此对象的先前版本使用了一个对象,并且用于in。这些对象本质上是次要的,因此已在上面进行了次要更新。但是,原始jsperf中两个版本之间的性能似乎有所提高的原因是由于数据样本大小很小。因此,先前版本中的主要比较是查看内部映射和过滤器使用与字典模式查找之间的差异。

如前所述,我已经更新了上面的代码,但是,我还更新了jsperf,以查看1000个对象而不是3个对象。3个忽略了许多性能缺陷(过时的jsperf)。

性能

https://jsperf.com/filter-vs-dictionary-more-data当我运行该字典时,速度提高了96%。

过滤器与字典


4
快速,并且不需要任何其他插件。真的很酷
mihai 2015年

2
怎么样if( typeof(unique[array[i].age]) == "undefined"){ distinct.push(array[i].age); unique[array[i].age] = 0; }
永恒的

7
哇,性能确实发生了变化,转而使用了map选项。单击该jsperf链接!
AT

1
@ 98percentmonkey-该对象用于简化“词典方法”。在对象内部,将跟踪的每个事件都添加为键(或仓)。这样,当我们遇到一个事件时,我们可以检查键(或bin)是否存在。如果它确实存在,我们知道发生是不是唯一的-如果不存在的话,我们知道发生是独一无二的,它添加。
特拉维斯Ĵ

1
@ 98percentmonkey-使用对象的原因是查找为O(1),因为它只检查一次属性名称,而数组为O(n),因为它必须查看每个值以检查其存在。在过程结束时,对象中的一组键(仓)代表一组唯一的事件。
特拉维斯J

90

截至2017年8月25日,这就是您如何通过ES6使用适用于Typescript的新Set来解决此问题的方法

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

3
这有所帮助,感谢之前。类型“集合”不是数组类型
罗伯特·金

1
这是一个很好的答案。直到我向下滚动到这一行,我才写一个答案:Object.keys(values.reduce <any>(((x,y)=> {x [y] = null; return x;},{} )); 但是,此答案更为简洁,适用于所有数据类型,而不仅仅是字符串。
jgosar

1
这个答案是惊人的!
lukeocodes

78

使用ES6功能,您可以执行以下操作:

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

6
这种方法仅适用于原始类型(在这种情况下,年龄是数字数组),但对于对象将失败。
k0pernikus

任何想法如何使其具有两个键的对象工作?
cegprakash

1
const uniqueObjects = [ ...new Set( array.map( obj => obj.age) ) ].map( age=> { return array.find(obj => obj.age === age) } )
Harley B,

62

我只需要映射并删除公仔:

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]

编辑:好的!就性能而言,这不是最有效的方法,而是最简单,最易读的IMO。如果您真的在乎微优化,或者您有大量数据,那么定期for循环将更加“高效”。


3
@elclanrs-“最高效的方法”-这种方法是
特拉维斯J

1
@伊夫:我明白了。您的答案中的下划线版本也不是很快,我的意思是,最终您选择更方便的方法,我怀疑1-30%的数字或多或少会反映出通用测试中的“巨大改进”以及当OP / s达到数千时。
Elclanrs

4
好吧,当然。只有三个元素,最快的事情是制作三个变量并使用一些ifs。三百万,您将获得一些截然不同的结果。
伊夫

1
速度不快,但就我而言,它确实成功了,因为我不处理大型数据集,谢谢。
Felipe Alarcon

37
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示例

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]

6
尽管此代码可以回答问题,但提供有关如何以及为什么解决问题的其他上下文将提高答案的长期价值。
亚历山大

第一个使用indexOf vs索引的方法非常出色。
las

请注意,这仅适用于原始值。如果您有一系列日期,则需要更多自定义方法。在此处了解更多信息:codeburst.io/javascript-array-distinct-5edc93501dc4
Molx

36

对于那些想要通过键返回具有唯一属性的对象的人

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

const key = 'age';

const arrayUniqueByKey = [...new Map(array.map(item =>
  [item[key], item])).values()];

console.log(arrayUniqueByKey);

   /*OUTPUT
       [
        { "name": "Bob", "age": 17 },
        { "name": "Carl", "age": 35 }
       ]
   */

 // Note: this will pick the last duplicated item in the list.


2
注意:这将选择列表中的最后一个重复项。
尤金·库拉布霍夫

1
将此添加到答案中!
阿伦·库马尔·塞尼

1
正是我想要的。多谢分享!
Devner

1
我认为这应该是最重要的答案!
Jaroslav Benc

26

已经有许多有效的答案,但是我想添加一个仅使用该reduce()方法的答案, 因为它既干净又简单。

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

像这样使用它:

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

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

20

forEach@ travis-j的答案的版本(在现代浏览器和Node JS世界中很有帮助):

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

在Chrome v29.0.1547上速度提高了34%:http://jsperf.com/filter-versus-dictionary/3

还有一个采用映射器功能的通用解决方案(比直接映射要慢一些,但这是可以预期的):

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]

1
我最喜欢通用解决方案,因为在现实情况中年龄不可能是唯一需要的唯一值。
2013年

5
在泛型中,将“ distinct.push(key)”更改为“ distinct.push(x)”以返回实际元素的列表,我发现这些元素非常有用!
西拉斯·汉森

15

我开始坚持下划线默认情况下,所有新项目,这样我从来没有去想这些小数据改写(munging)的问题。

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

产生[17, 35]


13

这是解决此问题的另一种方法:

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

我不知道该解决方案与其他解决方案相比有多快,但是我喜欢更干净的外观。;-)


编辑:好的,以上似乎是这里所有方法中最慢的解决方案。

我在这里创建了一个性能测试用例: http //jsperf.com/distinct-values-from-array

我没有比较年龄(整数),而是选择比较名称(字符串)。

方法1(TS的解决方案)非常快。有趣的是,方法7优于所有其他解决方案,在这里我只是摆脱了.indexOf()并使用了它的“手动”实现,避免了循环调用函数:

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

使用Safari和Firefox的性能差异令人惊讶,而且Chrome似乎在优化方面做得最好。

我不确定上述片段为什么比其他片段这么快,也许比我聪明的人有答案。;-)


9

使用lodash

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

2
您能否按照问题中的说明解释如何使用纯JavaScript?
罗斯金

这是一个下划线功能_.chain
vini

2
但是pluck()不在lodash中。
Yash Vekaria

1
_.chain(array).map('age')。unique()。value(); 为我工作。
Yash Vekaria

4.0版有一些重大更改是指- stackoverflow.com/a/31740263/4050261
阿达什讷Madrecha


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


5

这是一个通用的解决方案,它使用reduce,允许映射并保持插入顺序。

项目:数组

mapper:将项目映射到条件的一元函数,或者为空以映射项目本身。

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;
    }, []);
}

用法

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

您可以将其添加到Array原型中,如果这是您的样式,则可以省略items参数。

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

您也可以使用Set而不是Array来加快匹配速度。

function distinct(items, mapper) {
    if (!mapper) mapper = (item)=>item;
    return items.map(mapper).reduce((acc, item) => {
        acc.add(item);
        return acc;
    }, new Set());
}

5

const x = [
  {"id":"93","name":"CVAM_NGP_KW"},
  {"id":"94","name":"CVAM_NGP_PB"},
  {"id":"93","name":"CVAM_NGP_KW"},
  {"id":"94","name":"CVAM_NGP_PB"}
].reduce(
  (accumulator, current) => accumulator.some(x => x.id === current.id)? accumulator: [...accumulator, current ], []
)

console.log(x)

/* output 
[ 
  { id: '93', name: 'CVAM_NGP_KW' },
  { id: '94', name: 'CVAM_NGP_PB' } 
]
*/


2
这看起来像O(n ^ 2)
cegprakash

4

刚发现这个,我认为它很有用

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

再次使用下划线,因此如果您有这样的对象

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

它只会给您唯一的对象。

这里发生的是indexBy返回一个这样的地图

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

只是因为它是一张地图,所以所有键都是唯一的。

然后,我只是将此列表映射回数组。

如果只需要不同的值

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

请记住,key是以字符串形式返回的,因此,如果需要整数,则应该这样做

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


3

如果像我一样,您希望在不影响速度的情况下更喜欢“功能性”,那么本示例将使用包裹在reduce闭包中的快速字典查找。

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]
    }
}, [{},[]])

根据该测试,我的解决方案是建议答案的两倍



3

我知道我的代码长度短,时间复杂度低,但是可以理解,所以我尝试了这种方式。

我试图在此处开发基于原型的功能,并且代码也会更改。

在这里,Distinct是我自己的原型函数。

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

  Array.prototype.Distinct = () => {
    var output = [];
    for (let i = 0; i < array.length; i++) {
      let flag = true;
      for (let j = 0; j < output.length; j++) {
        if (array[i].age == output[j]) {
          flag = false;
          break;
        }
      }
      if (flag)
        output.push(array[i].age);
    }
    return output;
  }
  //Distinct is my own function
  console.log(array.Distinct());
</script>


2

如果您具有Array.prototype.includes或愿意对其进行填充,则可以这样做:

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

1

我下面的代码将显示唯一的年龄数组,以及没有重复年龄的新数组

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

var unique = [];
var tempArr = [];
data.forEach((value, index) => {
    if (unique.indexOf(value.age) === -1) {
        unique.push(value.age);
    } else {
        tempArr.push(index);    
    }
});
tempArr.reverse();
tempArr.forEach(ele => {
    data.splice(ele, 1);
});
console.log('Unique Ages', unique);
console.log('Unique Array', data);```

1

我用TypeScript编写了自己的应用,例如Kotlin的通用案例Array.distinctBy {}

function distinctBy<T, U extends string | number>(array: T[], mapFn: (el: T) => U) {
  const uniqueKeys = new Set(array.map(mapFn));
  return array.filter((el) => uniqueKeys.has(mapFn(el)));
}

U当然,哪里是可哈希的。对于对象,您可能需要https://www.npmjs.com/package/es6-json-stable-stringify


1

如果您需要整个对象的唯一性

const _ = require('lodash');

var objects = [
  { 'x': 1, 'y': 2 },
  { 'y': 1, 'x': 2 },
  { 'x': 2, 'y': 1 },
  { 'x': 1, 'y': 2 }
];

_.uniqWith(objects, _.isEqual);

[对象{x:1,y:2},对象{x:2,y:1}]


downvoter:解释为什么要投票?
cegprakash

1

回答这个老问题是毫无意义的,但是有一个简单的答案可以说明Javascript的本质。Javascript中的对象本质上是哈希表。我们可以使用它来获取唯一键的哈希:

var o = {}; array.map(function(v){ o[v.age] = 1; });

然后,我们可以将哈希减少为唯一值的数组:

var a2 = []; for (k in o){ a2.push(k); }

这就是您所需要的。数组a2仅包含唯一的年龄。


1

简单的单缸,性能卓越。在我的测试中比ES6解决方案快6%。

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

@ Jeb50关心添加易于阅读的多行吗?看着这里的其他人,我真的觉得它们不容易阅读或理解。我认为最好将其放在描述其功能的函数中。
Craig

2
带有箭头功能:array.map( o => o.age).filter( (v,i,a) => a.indexOf(v)===i)。我现在很少使用function关键字,以至于我一看就必须读两次书
😊– Drenai
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.