Як отримати відмінні значення з масиву об’єктів у 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
ітерація не є «крихкою для ефективності», і ви не можете нічого зробити з кожним елементом «без ітерації». Ви можете використовувати різні функціонально виглядаючі методи, але в кінцевому підсумку щось на певному рівні має перебирати елементи.
Eevee

// 100% запущений код const listOfTags = [{id: 1, мітка: "Привіт", колір: "червоний", сортування: 0}, {id: 2, мітка: "Світ", колір: "зелений", сортування : 1}, {id: 3, мітка: "Привіт", колір: "синій", сортування: 4}, {id: 4, мітка: "Сонячне світло", колір: "жовтий", сортування: 5}, {id : 5, мітка: "Привіт", колір: "червоний", сортування: 6}], клавіші = ['мітка', 'колір'], відфільтрований = 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об’єкт і mapс є марнотратними. Ця робота просто займає простий .reduce()етап.
Redu

Відповіді:


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

як ми могли також роздрукувати загальну кількість людей, які мають один і той же вік?
користувач1788736

605

Якщо ви використовуєте ES6 / ES2015 або новішу версію, ви можете це зробити так:

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

Ось приклад того, як це зробити.


11
Я отримую помилку:TypeError: (intermediate value).slice is not a function
AngJobs в Github

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

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

4
чи можна дізнатись унікальні об’єкти на основі декількох клавіш в об’єкті?
Джефрі Суджіт

13
Це рішення було дано майже дослівно на кілька місяців раніше @Russell Vea. Ви перевіряли наявні відповіді? Але знову ж таки, 266+ голосів показують, що ніхто ще не перевіряв.
Дан Даскалеску

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 "}
Абдулла Аль Мамун

10
Ось чому вам потрібно продовжувати прокручування
Алехандро Бастідас

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

1
ps: .карта всередині .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), оскільки ви повинні перевірити кожне значення хоча б один раз.

У попередній версії цього використовувались об'єкти, і для дюйма. Вони мали незначний характер і з тих пір були незначно оновлені вище. Однак причина, здавалося б, прогресу в роботі між двома версіями в оригінальному jsperf була обумовлена ​​тим, що розмір вибірки даних був настільки малим. Таким чином, основним порівнянням у попередній версії було вивчення різниці між внутрішньою картою та використанням фільтра проти пошуку в режимі словника.

Я оновив код вище, як зазначалося, однак я також оновив jsperf, щоб переглянути 1000 об'єктів замість 3. 3 пропустив безліч підводних підказок продуктивності ( застарілий jsperf ).

Продуктивність

https://jsperf.com/filter-vs-dictionary-more-data Коли я запустив цей словник, на 96% швидше.

фільтр проти словника


4
швидко та без додаткових плагінів. Дійсно круто
mihai

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), оскільки він повинен шукати кожне значення, щоб перевірити наявність. В кінці процесу набір ключів (бункерів) в об'єкті являє собою набір унікальних подій.
Travis J

91

Ось як би ви вирішили це за допомогою нового набору через ES6 для Typescript станом на 25 серпня 2017 року

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 - "найбільш ефективний спосіб" - Цей підхід повільний .
Travis J

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
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
Саме те, що я шукав. Дякую тонну за обмін!
Девнер

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

І загальне рішення, яке виконує функцію картографування (tad повільніше, ніж пряма карта, але це очікується):

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
У загальному випадку змініть "distct.push (ключ)" на "distct.push (x)", щоб повернути список фактичних елементів, які я вважаю дуже корисними!
Silas Hansen

15

Я почав закреслювати підкреслення у всіх нових проектах за замовчуванням, так що мені ніколи не доводиться думати про ці маленькі проблеми з обробкою даних.

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

Я поняття не маю, наскільки швидко це рішення порівнюється з іншими, але мені подобається більш чистий вигляд. ;-)


EDIT: Гаразд, вищезазначене, здається, є найповільнішим рішенням з усіх тут.

Я створив тест на ефективність тут: http://jsperf.com/distinct-values-from-array

Замість тестування для віку (Цілі особи) я вибрав для порівняння імена (Strings).

Спосіб 1 (розчин ТС) дуже швидкий. Цікаво, що метод 7 перевершує всі інші рішення, тут я просто позбувся.

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

використовуючи лодаш

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 (масив) .map ('age'). унікальний (). value (); працював на мене.
Yash Vekaria

версії 4.0 були деякі важливі зміни , см - stackoverflow.com/a/31740263/4050261
Adarsh Madrecha

7

Використання Лодаша

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

Ось універсальне рішення, яке використовує зменшення, дозволяє проводити картування та підтримує порядок вставки.

items : масив

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

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

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

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

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>



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, для загального випадку, як, наприклад, у Котліна 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

Простий однолінійний з чудовими характеристиками. На 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). Я використовую ключове слово функції так рідко зараз, що мені доводиться читати речі двічі, коли я бачу це 😊
Дренай
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.