Как получить отдельные значения из массива объектов в JavaScript?


386

Предполагая, что у меня есть следующее:

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
итерация не является «отвратительной для эффективности», и вы не можете ничего сделать с каждым элементом «без итерации». Вы можете использовать различные функционально выглядящие методы, но в конечном итоге что-то на некотором уровне должно перебирать элементы.
Eevee

// 100% работающий код const listOfTags = [{id: 1, метка: «Hello», цвет: «красный», сортировка: 0}, {id: 2, метка: «World», цвет: «зеленый», сортировка : 1}, {id: 3, метка: "Hello", цвет: "синий", сортировка: 4}, {id: 4, метка: "Sunshine", цвет: "yellow", сортировка: 5}, {id : 5, label: "Hello", color: "red", sort: 6}], keys = ['label', 'color'], Filter = listOfTags.filter ((s => o => (k => ! s.has (k) && s.add (k)) (keys.map (k => o [k]). join ('|'))) (новый набор)); console.log (фильтруют);
Сандип Мишра

1
щедрость велика, но на вопрос с данными данными и ответом здесь уже дан ответ: stackoverflow.com/questions/53542882/… . Какова цель награды? я должен ответить на эту конкретную проблему с двумя или более ключами?
Нина Шольц

Setобъект и maps расточительны. Эта работа просто проходит простой .reduce()этап.
Redu

Ответы:


128

Если бы это был 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

609

Если вы используете ES6 / ES2015 или новее, вы можете сделать это следующим образом:

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

Вот пример того, как это сделать.


11
Я получаю сообщение об ошибке:TypeError: (intermediate value).slice is not a function
AngJobs на Github

7
@Tommas ... означает оператор распространения. В этом случае это означает создание нового набора и распространение его в списке. Вы можете найти больше об этом здесь: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Влад Безден

10
для машинописного текста вы должны использовать новый Set, заключенный в Array.from () ... я предоставлю этот ответ ниже. @AngJobs
Кристиан Мэтью

4
Можно ли найти уникальные объекты на основе нескольких ключей в объекте?
Джефри Суджит

14
Это решение было почти дословно несколькими месяцами ранее @Russell Vea. Вы проверили на существующие ответы? Но опять же, более 266 голосов показывают, что никто больше не проверял.
Дан Даскалеску

166

используя 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 "}
Абдулла Аль Мамун,

10
Вот почему вы должны продолжать прокручивать
Алехандро Бастидас

5
@AbdullahAlMamun вы можете использовать карту в .filter вместо вызова .map, например, так:array.filter((value, index, self) => self.map(x => x.age).indexOf(value.age) == index)
Лев

1
ps: .map внутри .filter может быть медленным, поэтому будьте осторожны с большими массивами
Лев

2
@IvanNosov Ваш фрагмент очень хорош, хотя несколько строк объяснения того, как он работает с указателями для отображения на карте и фильтрации, сделают его идеальным и понятным для начинающих
azakgaim

128

Вы можете использовать словарный подход, как этот. По сути, вы присваиваете значение, которое вы хотите различить, как ключ в «словаре» (здесь мы используем массив как объект, чтобы избежать словарного режима). Если ключ не существует, вы добавляете это значение как отдельное.

Вот рабочая демонстрация:

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
быстро и без каких-либо дополнительных плагинов. Действительно круто
Михай

2
как насчетif( typeof(unique[array[i].age]) == "undefined"){ distinct.push(array[i].age); unique[array[i].age] = 0; }
Вневременное

7
Вау, производительность действительно изменилась в пользу опции карты. Нажмите на эту ссылку jsperf!
AT

1
@ 98percentmonkey - объект был использован для облегчения «словарного подхода». Внутри объекта каждое отслеживаемое вхождение добавляется как ключ (или корзина). Таким образом, когда мы сталкиваемся со случаем, мы можем проверить, существует ли ключ (или корзина); если оно существует, мы знаем, что вхождение не является уникальным - если оно не существует, мы знаем, что вхождение является уникальным, и добавляем его.
Travis J

1
@ 98percentmonkey - причина использования объекта заключается в том, что поиск - это O (1), поскольку он проверяет имя свойства один раз, тогда как массив - это O (n), так как он должен проверять каждое значение для проверки существования. В конце процесса набор ключей (корзин) в объекте представляет собой набор уникальных вхождений.
Трэвис Дж

92

Вот как вы могли бы решить эту проблему с помощью нового набора через ES6 для Typescript от 25 августа 2017 года

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

3
это помогло, получалось раньше спасибо. Тип 'Set' не является типом массива
Роберт

1
Это отличный ответ. Пока я не прокрутил до этого, я собирался написать ответ, говорящий: Object.keys (values.reduce <any> ((x, y) => {x [y] = null; вернуть x;}, {} )); Но этот ответ является более кратким и применим ко всем типам данных, а не только к строкам.
jgosar

1
этот ответ впечатляющий!
Люкокоды

80

Используя функции 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]

Редактировать: Aight! Не самый эффективный способ с точки зрения производительности, но самый простой и читаемый IMO. Если вы действительно заботитесь о микрооптимизации или у вас огромные объемы данных, то регулярный forцикл будет более «эффективным».


3
@elclanrs - «самый эффективный способ» - этот подход медленный .
Трэвис Дж

1
@ Eevee: Понятно. Подчеркивающая версия в вашем ответе тоже не очень быстрая , я имею в виду, что в итоге вы выбираете то, что удобнее, я сомневаюсь, что 1-30% более или менее отражает "огромное улучшение" в общих тестах, а когда количество операций / с в тысячах ,
elclanrs

4
хорошо обязательно. только с тремя элементами самая быстрая вещь состоит в том, чтобы сделать три переменные и использовать некоторые ifs. с тремя миллионами вы получите совсем другие результаты.
Eevee

1
не быстро, но в моем случае это сработало, поскольку я не имею дело с большим набором данных, спасибо.
Фелипе Аларкон

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 против индекса, просто великолепен.
увы

Обратите внимание, что это работает только для примитивных значений. Если у вас есть массив дат, то вам нужны более настраиваемые методы. Подробнее читайте здесь: codeburst.io/javascript-array-distinct-5edc93501dc4
Молкс

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
Именно то, что я искал. Спасибо огромное за обмен!
Девнер

1
Я думаю, что это должен быть ТОП ответ, если честно!
Ярослав Бенц

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

На 34% быстрее в Chrome v29.0.1547: 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
Мне больше нравится универсальное решение, так как маловероятно, что возраст - единственная особая ценность, необходимая в сценарии реального мира.
Тофт

5
В общем случае измените «Different.push (key)» на «diver.push (x)», чтобы получить список фактических элементов, которые я считаю очень полезными!
Сайлас Хансен

15

Я начал использовать Underscore во всех новых проектах по умолчанию, чтобы мне никогда не приходилось думать об этих небольших проблемах с обработкой данных.

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 лучше всего справляется с оптимизацией.

Я не совсем уверен, почему вышеупомянутые фрагменты так быстры по сравнению с другими, может быть, кто-то мудрее меня имеет ответ. ;-)


10

используя 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.
Яш Векария

1
_.chain (массив) .map ( 'возраст') уникальный () значение ()..; работал на меня.
Яш Векария

В версии 4.0 произошли некоторые
критические

7

Использование Lodash

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

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

Возвращает [17,35]


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

Вот универсальное решение, которое использует сокращение, позволяет отображать и поддерживает порядок вставки.

элементы : массив

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

4

я думаю, что вы ищете функцию groupBy (используя Lodash)

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

дает результат:

17,35

Демонстрация jsFiddle: http://jsfiddle.net/4J2SX/201/


3

Если, как и я, вы предпочитаете более «функциональный» режим без ущерба для скорости, в этом примере используется быстрый поиск по словарю, заключенный в уменьшить замыкание.

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.include или вы хотите его заполнить , это работает:

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's 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: разум, объясняющий, почему downvote?
Цегпракаш

1

Отвечать на этот старый вопрос довольно бессмысленно, но есть простой ответ, который говорит о природе Javascript. Объекты в Javascript по своей сути являются хеш-таблицами. Мы можем использовать это, чтобы получить хеш уникальных ключей:

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

Затем мы можем уменьшить хеш до массива уникальных значений:

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

Это все, что вам нужно. Массив a2 содержит только уникальные возрасты.


1

Простой однострочник с отличными характеристиками. На 6% быстрее, чем решения ES6 в моих тестах .

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

@ Jeb50 Хотите добавить многострочный текст, который легко читать? Глядя на других здесь, я действительно не чувствую, что их легко читать или понимать. Я думаю, что лучше всего поместить это в функцию, которая описывает, что он делает.
Крэйг,

2
С функциями со стрелками: array.map( o => o.age).filter( (v,i,a) => a.indexOf(v)===i). Теперь я использую ключевое слово function так редко, что мне приходится дважды что-то читать, когда я его вижу 😊
Дренаи
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.