Удалить дубликаты из массива

100

У меня есть массив объектов, который выглядит так:

var array = [
    {id:123, value:"value1", name:"Name1"},
    {id:124, value:"value2", name:"Name1"},
    {id:125, value:"value3", name:"Name2"},
    {id:126, value:"value4", name:"Name2"}
    ...
];

Как видите, некоторые имена повторяются. Я хочу получить новый массив только с именами, но если какое-то имя повторяется, я не хочу добавлять его снова. Я хочу этот массив:

var newArray = ["Name1", "Name2"];

Я пытаюсь сделать это с помощью map:

var newArray = array.map((a) => {
    return a.name;
});

Но проблема в том, что это возвращается:

newArray = ["Name1", "Name1", "Name2", "Name2"];

Как я могу установить внутри какое-то условие map, чтобы оно не возвращало уже существующий элемент? Я хочу сделать это с помощью mapкакой-либо другой функции ECMAScript 5 или ECMAScript 6.

snoopy25
источник
2
Затем удалите дубликаты из массива.
Тушар
1
возможный дубликат удаления дубликатов из массива JavaScript
Берги
Почему строка, содержащая id: 125, не заканчивается запятой?
Питер Мортенсен
Обратите внимание, что все ответы с использованием .indexOf () будут иметь низкую производительность, если массив большой, из-за их квадратичной временной сложности . Я бы рекомендовал использовать ES6 Set или объект ES5 в качестве словаря.
joeytwiddle

Ответы:

211

С ES6 вы можете использовать Setдля уникальных значений после сопоставления только имен объектов.

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

const array = [{ id: 123, value: "value1", name:"Name1" }, { id: 124, value: "value2", name: "Name1" }, { id: 125, value: "value3", name: "Name2" }, { id: 126, value: "value4", name: "Name2" }],
      names = [...new Set(array.map(a => a.name))];

console.log(names);

Нина Шольц
источник
9
Для моей информации .. что делает ...?
Rajshekar Reddy
9
@RajshekarReddy, это расширенный синтаксис... для построения массива с итерируемым объектом.
Нина Шольц
5
Я не поддерживаю сам «Set», а скорее за умное использование оператора спреда. Всегда приятно узнать что-то новое или увидеть что-то новое на практическом примере, поэтому этот пост заслуживает одобрения.
briosheje
34
Это просто мое личное мнение, но довольно распространенное . Использование Array.fromгораздо лучше передает намерение преобразования (а также его легче понять / найти новичкам), синтаксис распространения следует использовать только там, где распространяемый элемент является одним из многих. Может быть, называть это «плохой практикой» слишком резко, извините, я просто надеюсь, что это не станет распространенной идиомой.
Берги
3
@KRyan - ES6 такой новый? Он был завершен в июне 2015 года. Пора начать понимать и использовать новые функции
edc65
64

Если вы ищете решение JavaScript , что не является ES 6 (не комплект) , вы можете использовать массив в reduceметод :

var array=[
  {id:123, value:"value1", name:"Name1"},
  {id:124, value:"value2", name:"Name1"},
  {id:125, value:"value3", name:"Name2"},
  {id:126, value:"value4", name:"Name2"}
];
var names = array.reduce(function (a, b) {
  if (a.indexOf(b.name) == -1) {
    a.push(b.name)
  }
  return a;
}, []);

console.log(names);

Декель
источник
2
Этот ответ также имеет то преимущество, что не требует промежуточных массивов или наборов, особенно тех, которые содержат только сами имена. (Он переходит непосредственно от массива объектов к массиву объектов.) +1
jpmc26
@Iwrestledabearonce, разве этот второй параметр не тот же объект, что и возвращенный?
Kaiido
Да, но он не идет напрямую от одного к другому, как сортировка или фильтр, он перестраивается из пустого массива.
Однажды я боролся с медведем.
1
@Dekel - Меньшая версия :)var names = array.reduce(function(a, b) { a.indexOf(b.name) === -1 && a.push(b.name) return a; }, []);
Rayon
3
@Rayon Пожалуйста, не обращайте слишком много внимания на размер исходного кода, это обычная ошибка для многих самодельных программистов, самое главное, ваша программа должна быть эффективной и удобочитаемой.
лист
16

Лично я не понимаю, почему всем нравится ES 6. Если бы это был мой код, я бы предпочел поддерживать как можно больше браузеров.

var array=[
{id:123, value:"value1", name:"Name1"},
{id:124, value:"value2", name:"Name1"},
{id:125, value:"value3", name:"Name2"},
{id:126, value:"value4", name:"Name2"}
];

   // Create array of unique names
var a = (function(a){
  for (var i = array.length; i--;)
    if (a.indexOf(array[i].name) < 0) a.push(array[i].name);
  return a;
})([]);

console.log(a);

Однажды я боролся с медведем.
источник
2
Не будет ли более производительным использовать объект в качестве словаря вместо того, чтобы постоянно использовать indexOf-ing? Напр. Делай added[name] = trueи if(added[name])вместо if(a.indexOf(name)).
Божидар Маринов
7
«лично я не понимаю, почему все так увлекаются es6», почему вы ухаживаете за a goto fail, пропускаете свою индексную переменную в охватывающую область и т. д. и т. д. ES6 был кодифицирован в стандарте по уважительным причинам, и для большинства часть тривиально легко полифилить / преобразовывать для старых браузеров.
Джаред Смит
5
Более читабельный? Это чертовски цикл for, который помещает элементы в массив. Нет ничего более читабельного, чем это ...
Я однажды боролся с медведем.
4
Честно говоря, кажется, что ты слишком стараешься найти, на что жаловаться.
Однажды я боролся с медведем.
2
«Я имею в виду знаменитую ошибку Apple SSL» - это не так уж и популярно, что вы можете просто обратиться к ней в случайных комментариях вне контекста и ожидать, что люди ее узнают.
Hejazzman
14

Вы также можете просто комбинировать mapсfilter

var array = [
  {id:123, value:"value1", name:"Name1"},
  {id:124, value:"value2", name:"Name1"},
  {id:125, value:"value3", name:"Name2"},
  {id:126, value:"value4", name:"Name2"}
];

var unique = array
  .map( item => item.name )
  .filter( ( item, idx, arr ) => arr.indexOf( item ) == idx ) 

console.log(unique)

DavidDomain
источник
11

Вы можете использовать Object.keys (), чтобы получить массив имен перечислимых свойств данного объекта из результата перебора arrayпеременной с помощью Array.prototype.reduce (), где ключи - это разрушенные имена.

Код:

const array = [{id:123, value:"value1", name:"Name1"}, {id:124, value:"value2", name:"Name1"}, {id:125, value:"value3", name:"Name2"}, {id:126, value:"value4", name:"Name2"}],
      names = Object.keys(array.reduce((a, { name }) => (a[name] = 1, a), {}))

console.log(names)

Йосвел Кинтеро Аргуэльес
источник
7

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

В JavaScript массивы относятся к объектному типу, поэтому их можно одновременно использовать как хэш. Используя эту функциональность, мы можем значительно упростить работу, которую нужно выполнить за одну операцию сокращения с временной сложностью O (n).

Если вас не устраивает, что ваш массив содержит некоторые свойства, отличные от ключей массива, вы также можете рассмотреть возможность сохранения отдельного хеш-объекта.

var array = [{id:123, value:"value1", name:"Name1"},
             {id:124, value:"value2", name:"Name1"},
             {id:125, value:"value3", name:"Name2"},
             {id:126, value:"value4", name:"Name2"}
            ],
result = array.reduce((p,c) => p[c.name] ? p : (p[c.name] = true, p.push(c.name), p), []);
console.log(result);

Уменьшить
источник
Это решение ES5 имеет хорошую производительность, но использование массива для двух целей сбивает с толку и опасно, если одно из имен представляет собой просто число. Я бы порекомендовал сделать это p[c.name]на отдельном объекте, oа потом отказаться от него.
joeytwiddle
7

Я согласен с тем, что если вам нужны только nameценности, лучше всего использовать a Set.

тем не мение , если вы хотите получить массив уникальных объектов на основе nameсвойства, я бы предложил использовать файл Map. Быстрый способ создать карту - использовать массив [key, value]массивов:

const array = [{ id: 123, value: "value1", name:"Name1" }, { id: 124, value: "value2", name: "Name1" }, { id: 125, value: "value3", name: "Name2" }, { id: 126, value: "value4", name: "Name2" }],
      unique = new Map(array.map(obj => [obj.name, obj]));

// To get the unique objects
const uniques = Array.from(unique.values());

// Get the names like you already did:
console.log("Names:", uniques.map(obj => obj.name));

// If you ever need the complete array of unique objects, you got a ref:
console.log(JSON.stringify(uniques));
.as-console-wrapper { min-height: 100%; }

Дополнительным преимуществом Mapявляется то, что вы получаете обе filterфункции, исключающие неуникальные объекты, без потери связи с исходными объектами. Конечно, это необходимо только в том случае, если вам нужно несколько раз ссылаться на уникальный набор объектов.

user3297291
источник
3

Если вы ограничены ES5, я бы использовал Lodash's_.uniq

var newArray = _.uniq(array.map(function(a) {
  return a.name;
}));
Эндрю Хендерсон
источник
2

С ES6 это должно работать.

var array=[
    {id:123, value:"value1", name:"Name1"},
    {id:124, value:"value2", name:"Name1"},
    {id:125, value:"value3", name:"Name2"},
    {id:126, value:"value4", name:"Name2"}
];

var set = new Set();

array.forEach((a)=>{
    set.add(a.name);
}); 

console.log(Array.from(set));

Константин Крефт
источник
11
mapпредполагается использовать с чистыми функциями, чтобы не иметь побочных эффектов. Это была бы работа для forEachили reduce.
Винсент ван дер Виле
1

Используя UnderscoreJS,

array = [{id:123, value:"value1", name:"Name1"}, {id:124, value:"value2", name:"Name1"}, {id:125, value:"value3", name:"Name2"}, {id:126, value:"value4", name:"Name2"}];
get_names =  _.pluck(_.uniq(array, 'name'), 'name')
console.log(get_names)
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>

`

Мохидин бин Мохаммед
источник
0

В ES5 используйте объект в качестве словаря для O ( n ) производительности.

Это будет работать, только если все ключи являются строками.

var array = [
    {id: 123, value: "value1", name: "Name1"},
    {id: 124, value: "value2", name: "Name1"},
    {id: 125, value: "value3", name: "Name2"},
    {id: 126, value: "value4", name: "Name2"}
];

var allNames = array.map(item => item.name);

var map = {};
allNames.forEach(name => {
  map[name] = true;
});
var uniqueNames = Object.keys(map);

console.log(uniqueNames);

Вы можете сделать то же самое в одном выражении, если хотите:

var uniqueNames = Object.keys(allNames.reduce((m, n) => (m[n] = true, m), {}));

но повелительную форму мне легче читать.

Joeytwiddle
источник
Для ясности я использовал стрелочные функции ES6. Если вы действительно нацеливаетесь на ES5 без транспилятора, тогда вам нужно будет вместо этого использовать полную форму:function (item) { return item.name; }
joeytwiddle
В качестве альтернативы Object.keys(), этот ответ использований filter()сохранить только те имена , которые еще не были сохранены на карте, которая довольно элегантно.
joeytwiddle
0

Попробуй это:

nArr = [];
array.forEach((a) => {
    if (nArr.indexOf(a.name) < 0) { 
        nArr.push(a.name); 
    }
}); 
Рафаэль Гомес Франсиско
источник
0

Использование array#forEach()и array#indexOf()методы , как это , если вы хотите максимальную совместимость еще, лаконичный синтаксис:

const array = [{ id: 123, value: "value1", name:"Name1" }, { id: 124, value: "value2", name: "Name1" }, { id: 125, value: "value3", name: "Name2" }, { id: 126, value: "value4", name: "Name2" }]

// initialize an empty array named names
let names = [];

// iterate through every element of `array` & check if it's 'name' key's value already in names array if not ADD it 
array.forEach(function(element) { if (names.indexOf(element.name) === -1) names.push(element.name) });
// or use tilde like this:
//array.forEach(function(element) { if (~names.indexOf(element.name)) names.push(element.name) });

console.log(names);

Однако, если совместимость не является проблемой использования ECMAScript 6 «s Setобъекта, array#mapи такие Array.from()методы , как это:

const array = [{ id: 123, value: "value1", name:"Name1" }, { id: 124, value: "value2", name: "Name1" }, { id: 125, value: "value3", name: "Name2" }, { id: 126, value: "value4", name: "Name2" }];

// iterate through every element from array using map and store it in Set(a Set won't have duplicates) then convert the Set back to Array(using Array.from)
let names = Array.from(new Set(array.map(element => element.name)));

console.log(names);

Черная борода
источник
0

Вот как я это сделал, используя отдельный пустой массив.

var array = [
   {id:123, value:"value1", name:"Name1"},
   {id:124, value:"value2", name:"Name1"},
   {id:125, value:"value3", name:"Name2"},
   {id:126, value:"value4", name:"Name2"}	    
];

var array2 = []		
		
for (i=0; i<array.length;i++){						
   if (array2.indexOf(array[i].name) == -1){				
     array2.push(array[i].name);
    }
}			

console.log(array2)	

Абхишек Гурджар
источник
-1

Для тех, кто ищет 1 лайнер

const names = array.reduce((acc, {name}) => acc.includes(name) ? acc : [name, ...acc], []);

или без использования методов в прототипе массива

const { reduce, includes } = Array;
const names = reduce(array, (acc, {name}) => includes(acc, name) ? acc : [name, ...acc], []);

может быть полезно для написания некоторых чистых функций для работы с этим

const get_uniq_values = (key, arr) => reduce(arr, (a, o) => includes(a, o[key]) ? a : [o[key], ...a], []);
Tyrell
источник
-1
var __array=[{id:123, value:"value1", name:"Name1"},{id:124, value:"value2", name:"Name1"},{id:125, value:"value3", name:"Name2"},{id:126, value:"value4", name:"Name2"}];

function __checkArray(__obj){
    var flag = true;
    for(let i=0; i < __array.length; i++){
        if(__obj.id == __array.id){
            flag = false;
            break;
        }
    }

    return flag;
}

var __valToPush = {id: 127, value: "value5", name: "Name3"};
if(__checkArray(__valToPush)){
    __array.push(__valToPush)
}
АХИЛ
источник
11
у вас есть особая причина использовать так много подчеркиваний?
Нина Шольц