Удалить дубликаты из массива объектов в JavaScript

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

things = new Object();

things.thing = new Array();

things.thing.push({place:"here",name:"stuff"});
things.thing.push({place:"there",name:"morestuff"});
things.thing.push({place:"there",name:"morestuff"});

Мне интересно, что является лучшим методом для удаления дубликатов объектов из массива. Так, например, вещи. Все станет...

{place:"here",name:"stuff"},
{place:"there",name:"morestuff"}

79 ответов

Решение

Давайте посмотрим... примитивным будет:

var obj = {};

for ( var i=0, len=things.thing.length; i < len; i++ )
    obj[things.thing[i]['place']] = things.thing[i];

things.thing = new Array();
for ( var key in obj )
    things.thing.push(obj[key]);

Хорошо, я думаю, что это должно сработать. Проверьте это, Трэвис.

РЕДАКТИРОВАТЬ
Отредактировал код, чтобы правильно ссылаться на place (бывший id) имущество.

Как насчет некоторых es6 магия?

things.thing = things.thing.filter((thing, index, self) =>
  index === self.findIndex((t) => (
    t.place === thing.place && t.name === thing.name
  ))
)

Ссылочный URL

Для интерфейсов это может быть немного рано для реализации, так как многие используемые браузеры все еще не поддерживают функции es6

Кратчайший лайнер

Найти уникальные идентификаторы в массиве.

arr.filter((v,i,a)=>a.findIndex((t)=>(t.id === v.id))===i)

Уникальный по нескольким свойствам (идентификатор и имя)

arr.filter((v,i,a)=>a.findIndex((t)=>(t.id === v.id && t.name===v.name))===i)

Используя ES6 в одной строке, вы можете получить уникальный список объектов по ключу:

const unique = [...new Map(arr.map(item => [item[key], item])).values()]

Его можно поместить в функцию и использовать как в следующем примере:

const arr = [
  {place: "here", name: "stuff"},
  {place: "there", name: "morestuff"},
  {place: "a", name: "morestuff"},
  {place: "b", name: "morestuff"},
  {place: "c", name: "morestuff"},
  {place: "here", name: "lol"},
  {place: "there", name: "test"}
]

function getUniqueListBy(arr, key) {
    return [...new Map(arr.map(item => [item[key], item])).values()]
}

const arr1 = getUniqueListBy(arr, 'place')

console.log(arr1)


const arr2 = getUniqueListBy(arr, 'name')

console.log(arr2)

Этот ответ, вероятно, никто не найдет, но вот короткий способ ES6 с лучшим временем выполнения, чем уже существующие 50+ ответов:

       let ids = array.map(o => o.id)
let filtered = array.filter(({id}, index) => !ids.includes(id, index+1))

Пример:

         let arr = [{id: 1, name: 'one'}, {id: 2, name: 'two'}, {id: 1, name: 'one'}]

let ids = arr.map(o => o.id)
let filtered = arr.filter(({id}, index) => !ids.includes(id, index + 1))

console.log(filtered)

Как это устроено:

Array.filter() удаляет все повторяющиеся объекты, проверяя, включает ли ранее сопоставленный массив идентификаторов текущий идентификатор ({id} разрушает объект только до его идентификатора).

Используя Array.includes() второй параметр fromIndex каждая итерация filter Метод обратного вызова будет искать только в массиве, начинающемся с текущего индекса + 1. Это значительно сокращает время выполнения, поскольку проверяются только объекты, не отфильтрованные ранее.

Это, очевидно, также работает для любого другого ключа, который не называется id или даже несколько или все ключи.

Если вы можете использовать библиотеки Javascript, такие как подчеркивание или lodash, я рекомендую взглянуть на _.uniq функционировать в своих библиотеках. От lodash:

_.uniq(array, [isSorted=false], [callback=_.identity], [thisArg])

По сути, вы передаете массив, который здесь является литералом объекта, и передаете атрибут, с которым вы хотите удалить дубликаты в исходном массиве данных, например:

var data = [{'name': 'Amir', 'surname': 'Rahnama'}, {'name': 'Amir', 'surname': 'Stevens'}];
var non_duplidated_data = _.uniq(data, 'name'); 

ОБНОВЛЕНИЕ: Lodash теперь представил .uniqBy также.

У меня было точно такое же требование, чтобы удалить дубликаты объектов в массиве, основываясь на дубликатах в одном поле. Я нашел код здесь: Javascript: удаление дубликатов из массива объектов

Так что в моем примере я удаляю любой объект из массива, который имеет повторяющееся строковое значение licenseNum.

var arrayWithDuplicates = [
    {"type":"LICENSE", "licenseNum": "12345", state:"NV"},
    {"type":"LICENSE", "licenseNum": "A7846", state:"CA"},
    {"type":"LICENSE", "licenseNum": "12345", state:"OR"},
    {"type":"LICENSE", "licenseNum": "10849", state:"CA"},
    {"type":"LICENSE", "licenseNum": "B7037", state:"WA"},
    {"type":"LICENSE", "licenseNum": "12345", state:"NM"}
];

function removeDuplicates(originalArray, prop) {
     var newArray = [];
     var lookupObject  = {};

     for(var i in originalArray) {
        lookupObject[originalArray[i][prop]] = originalArray[i];
     }

     for(i in lookupObject) {
         newArray.push(lookupObject[i]);
     }
      return newArray;
 }

var uniqueArray = removeDuplicates(arrayWithDuplicates, "licenseNum");
console.log("uniqueArray is: " + JSON.stringify(uniqueArray));

Результаты, достижения:

uniqueArray - это:

[{"type":"LICENSE","licenseNum":"10849","state":"CA"},
{"type":"LICENSE","licenseNum":"12345","state":"NM"},
{"type":"LICENSE","licenseNum":"A7846","state":"CA"},
{"type":"LICENSE","licenseNum":"B7037","state":"WA"}]

Один лайнер, используя Set

var things = new Object();

things.thing = new Array();

things.thing.push({place:"here",name:"stuff"});
things.thing.push({place:"there",name:"morestuff"});
things.thing.push({place:"there",name:"morestuff"});

// assign things.thing to myData for brevity
var myData = things.thing;

things.thing = Array.from(new Set(myData.map(JSON.stringify))).map(JSON.parse);

console.log(things.thing)

Объяснение:

  1. new Set(myData.map(JSON.stringify)) создает объект Set, используя строковые элементы myData.
  2. Установка объекта гарантирует, что каждый элемент уникален.
  3. Затем я создаю массив на основе элементов созданного набора, используя Array.from.
  4. Наконец, я использую JSON.parse, чтобы преобразовать строковый элемент обратно в объект.

Один лайнер здесь

let arr = [
  {id:1,name:"sravan ganji"},
  {id:2,name:"anu"},
  {id:4,name:"mammu"},
  {id:3,name:"sanju"},
  {id:3,name:"ram"},
];

console.log(Object.values(arr.reduce((acc,cur)=>Object.assign(acc,{[cur.id]:cur}),{})))

var uniq = {}
var arr  = [{"id":"1"},{"id":"1"},{"id":"2"}]
var arrFiltered = arr.filter(obj => !uniq[obj.id] && (uniq[obj.id] = true));
console.log('arrFiltered', arrFiltered)

Один вкладыш с картой (высокая производительность, не сохраняет порядок)

Найдите уникальное idнаходится в массиве.

      const arrUniq = [...new Map(arr.map(v => [v.id, v])).values()]

Если порядок важен, ознакомьтесь с решением с фильтром: Решение с фильтром


Уникальный по нескольким свойствам ( placeа также name) в массиве

      const arrUniq = [...new Map(arr.map(v => [JSON.stringify([v.place,v.name]), v])).values()]

Уникальный по всем свойствам в массиве

      const arrUniq = [...new Map(arr.map(v => [JSON.stringify(v), v])).values()]

Сохранить первое вхождение в массиве arr

      const arrUniq = [...new Map(arr.slice().reverse().map(v => [v.id, v])).values()].reverse()

Вот еще один вариант сделать это с помощью итерационных методов Array, если вам нужно сравнение только по одному полю объекта:

    function uniq(a, param){
        return a.filter(function(item, pos, array){
            return array.map(function(mapItem){ return mapItem[param]; }).indexOf(item[param]) === pos;
        })
    }

    uniq(things.thing, 'place');

ОБНОВЛЕНО

Я сейчас правильно прочитал вопрос. Это общий способ сделать это: вы передаете функцию, которая проверяет, считаются ли два элемента массива равными. В этом случае он сравнивает значения name а также place свойства двух сравниваемых объектов.

function arrayContains(arr, val, equals) {
    var i = arr.length;
    while (i--) {
        if ( equals(arr[i], val) ) {
            return true;
        }
    }
    return false;
}

function removeDuplicates(arr, equals) {
    var originalArr = arr.slice(0);
    var i, len, j, val;
    arr.length = 0;

    for (i = 0, len = originalArr.length; i < len; ++i) {
        val = originalArr[i];
        if (!arrayContains(arr, val, equals)) {
            arr.push(val);
        }
    }
}

function thingsEqual(thing1, thing2) {
    return thing1.place === thing2.place
        && thing1.name === thing2.name;
}

removeDuplicates(things.thing, thingsEqual);

Если вы можете ждать устранения дубликатов до тех пор, пока не завершатся все добавления, типичным подходом будет сначала отсортировать массив, а затем удалить дубликаты. Сортировка избегает подхода N * N сканирования массива для каждого элемента при его прохождении.

Функция "устранить дубликаты" обычно называется уникальной или уникальной. Некоторые существующие реализации могут объединять два этапа, например, uniq прототипа

В этом посте есть несколько идей, которые можно попробовать (и некоторые, которых следует избегать:-)), если в вашей библиотеке их еще нет! Лично я нахожу это самым прямым:

    function unique(a){
        a.sort();
        for(var i = 1; i < a.length; ){
            if(a[i-1] == a[i]){
                a.splice(i, 1);
            } else {
                i++;
            }
        }
        return a;
    }  

    // Provide your own comparison
    function unique(a, compareFunc){
        a.sort( compareFunc );
        for(var i = 1; i < a.length; ){
            if( compareFunc(a[i-1], a[i]) === 0){
                a.splice(i, 1);
            } else {
                i++;
            }
        }
        return a;
    }

Я думаю, что лучший подход - использовать объект reduce и Map . Это однострочное решение.

Принимая во внимание lodash.uniqWith

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

_.uniqWith(objects, _.isEqual);
// => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]

Добавить еще один в список. Использование ES6 и Array.reduce с Array.find,
В этом примере фильтрация объектов на основе guid имущество.

let filtered = array.reduce((accumulator, current) => {
  if (! accumulator.find(({guid}) => guid === current.guid)) {
    accumulator.push(current);
  }
  return accumulator;
}, []);

let myData = [{place:"here",name:"stuff"}, 
 {place:"there",name:"morestuff"},
 {place:"there",name:"morestuff"}];


let q = [...new Map(myData.map(obj => [JSON.stringify(obj), obj])).values()];

console.log(q)

Однострочник с использованием ES6 и new Map(),

// assign things.thing to myData
let myData = things.thing;

[...new Map(myData.map(obj => [JSON.stringify(obj), obj])).values()];

Подробности:-

  1. дела .map() в списке данных и преобразования каждого отдельного объекта в [key, value] массив пар (длина =2), первый элемент (ключ) будет stringified версия объекта и второй (значение) будет object сам.
  2. Добавление выше созданного списка массивов в new Map() будет иметь ключ как stringified объект и любое добавление того же ключа приведет к переопределению уже существующего ключа.
  3. С помощью .values() даст MapIterator со всеми значениями в карте (obj в нашем случае)
  4. В заключение, spread ... оператор, чтобы дать новый массив со значениями из вышеприведенного шага.

Черт, дети, давайте раздавим эту штуку, почему бы и нет?

let uniqIds = {}, source = [{id:'a'},{id:'b'},{id:'c'},{id:'b'},{id:'a'},{id:'d'}];
let filtered = source.filter(obj => !uniqIds[obj.id] && (uniqIds[obj.id] = true));
console.log(filtered);
// EXPECTED: [{id:'a'},{id:'b'},{id:'c'},{id:'d'}];

Вы также можете использовать Map:

const dedupThings = Array.from(things.thing.reduce((m, t) => m.set(t.place, t), new Map()).values());

Полный образец:

const things = new Object();

things.thing = new Array();

things.thing.push({place:"here",name:"stuff"});
things.thing.push({place:"there",name:"morestuff"});
things.thing.push({place:"there",name:"morestuff"});

const dedupThings = Array.from(things.thing.reduce((m, t) => m.set(t.place, t), new Map()).values());

console.log(JSON.stringify(dedupThings, null, 4));

Результат:

[
    {
        "place": "here",
        "name": "stuff"
    },
    {
        "place": "there",
        "name": "morestuff"
    }
]

Решение TypeScript

Это удалит дубликаты объектов, а также сохранит типы объектов.

function removeDuplicateObjects(array: any[]) {
  return [...new Set(array.map(s => JSON.stringify(s)))]
    .map(s => JSON.parse(s));
}

          const things = [
  {place:"here",name:"stuff"},
  {place:"there",name:"morestuff"},
  {place:"there",name:"morestuff"}
];
const filteredArr = things.reduce((thing, current) => {
  const x = thing.find(item => item.place === current.place);
  if (!x) {
    return thing.concat([current]);
  } else {
    return thing;
  }
}, []);
console.log(filteredArr)

Метод removeDuplicates() принимает массив объектов и возвращает новый массив без дубликатов объектов (на основе свойства id).

const allTests = [
  {name: 'Test1', id: '1'}, 
  {name: 'Test3', id: '3'},
  {name: 'Test2', id: '2'},
  {name: 'Test2', id: '2'},
  {name: 'Test3', id: '3'}
];

function removeDuplicates(array) {
  let uniq = {};
  return array.filter(obj => !uniq[obj.id] && (uniq[obj.id] = true))
}

removeDuplicates(allTests);

Ожидаемый результат:

[
  {name: 'Test1', id: '1'}, 
  {name: 'Test3', id: '3'},
  {name: 'Test2', id: '2'}
];

Сначала мы устанавливаем значение переменной uniq для пустого объекта.

Далее мы фильтруем массив объектов. Фильтр создает новый массив со всеми элементами, которые проходят тест, реализованный предоставленной функцией.

return array.filter(obj => !uniq[obj.id] && (uniq[obj.id] = true));

Выше мы используем функцию короткого замыкания &&. Если левая часть && имеет значение true, то она возвращает значение справа от &&. Если левая сторона ложна, она возвращает то, что находится слева от &&.

Для каждого объекта (obj) мы проверяем uniq на свойство, называемое значением obj.id (в этом случае на первой итерации оно проверяет свойство '1'.) Мы хотим получить противоположное тому, что возвращает (либо true или ложь) именно поэтому мы используем! в!uniq[obj.id]. Если uniq уже имеет свойство id, он возвращает true, что означает ложь (!), Указывающую функции фильтра НЕ добавлять этот объект. Однако, если он не находит свойство obj.id, он возвращает значение false, которое затем оценивается как true (!) И возвращает все справа от && или (uniq[obj.id] = true). Это истинное значение, говорящее методу фильтра добавить obj к возвращаемому массиву, а также добавляет свойство {1: true} в uniq. Это гарантирует, что любой другой экземпляр obj с тем же идентификатором не будет добавлен снова.

Быстрый (меньше времени выполнения) и типобезопасный ответ для ленивых разработчиков Typescript :

      export const uniqueBy = <T>( uniqueKey: string,objects: T[]): T[] => {
  const ids = objects.map(object => object[uniqueKey]);
  return objects.filter((object, index) => !ids.includes(object[uniqueKey], index + 1));
} 

Если массив содержит объекты, вы можете использовать это для удаления дубликатов

      const persons= [
      { id: 1, name: 'John',phone:'23' },
      { id: 2, name: 'Jane',phone:'23'},
      { id: 1, name: 'Johnny',phone:'56' },
      { id: 4, name: 'Alice',phone:'67' },
    ];
const unique = [...new Map(persons.map((m) => [m.id, m])).values()];

если удалить дубликаты на основе телефона, просто замените m.id на m.phone

      const unique = [...new Map(persons.map((m) => [m.phone, m])).values()];

Мне этот способ подходит.

function arrayUnique(arr, uniqueKey) {
  const flagList = new Set()
  return arr.filter(function(item) {
    if (!flagList.has(item[uniqueKey])) {
      flagList.add(item[uniqueKey])
      return true
    }
  })
}
const data = [
  {
    name: 'Kyle',
    occupation: 'Fashion Designer'
  },
  {
    name: 'Kyle',
    occupation: 'Fashion Designer'
  },
  {
    name: 'Emily',
    occupation: 'Web Designer'
  },
  {
    name: 'Melissa',
    occupation: 'Fashion Designer'
  },
  {
    name: 'Tom',
    occupation: 'Web Developer'
  },
  {
    name: 'Tom',
    occupation: 'Web Developer'
  }
]
console.table(arrayUnique(data, 'name'))// work well

распечатать

┌─────────┬───────────┬────────────────────┐
│ (index) │   name    │     occupation     │
├─────────┼───────────┼────────────────────┤
│    0    │  'Kyle'   │ 'Fashion Designer' │
│    1    │  'Emily'  │   'Web Designer'   │
│    2    │ 'Melissa' │ 'Fashion Designer' │
│    3    │   'Tom'   │  'Web Developer'   │
└─────────┴───────────┴────────────────────┘

ES5:

function arrayUnique(arr, uniqueKey) {
  const flagList = []
  return arr.filter(function(item) {
    if (flagList.indexOf(item[uniqueKey]) === -1) {
      flagList.push(item[uniqueKey])
      return true
    }
  })
}

Эти два способа проще и понятнее.

Вот решение для es6, где вы хотите сохранить только последний элемент. Это решение является функциональным и соответствует стилю Airbnb.

const things = {
  thing: [
    { place: 'here', name: 'stuff' },
    { place: 'there', name: 'morestuff1' },
    { place: 'there', name: 'morestuff2' }, 
  ],
};

const removeDuplicates = (array, key) => {
  return array.reduce((arr, item) => {
    const removed = arr.filter(i => i[key] !== item[key]);
    return [...removed, item];
  }, []);
};

console.log(removeDuplicates(things.thing, 'place'));
// > [{ place: 'here', name: 'stuff' }, { place: 'there', name: 'morestuff2' }]

Другой вариант - создать пользовательскую функцию indexOf, которая сравнивает значения выбранного вами свойства для каждого объекта и заключает его в функцию сокращения.

var uniq = redundant_array.reduce(function(a,b){
      function indexOfProperty (a, b){
          for (var i=0;i<a.length;i++){
              if(a[i].property == b.property){
                   return i;
               }
          }
         return -1;
      }

      if (indexOfProperty(a,b) < 0 ) a.push(b);
        return a;
    },[]);

Я знаю, что на этот вопрос уже есть масса ответов, но потерпите меня...

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

Рассмотрим массив ниже. Скажем, вы хотите найти уникальные объекты в этом массиве, учитывая только propOne и propTwoи игнорируйте любые другие свойства, которые могут там присутствовать.

Ожидаемый результат должен включать только первый и последний объекты. Итак, вот код:

         const array = [{
    propOne: 'a',
    propTwo: 'b',
    propThree: 'I have no part in this...'
},
{
    propOne: 'a',
    propTwo: 'b',
    someOtherProperty: 'no one cares about this...'
},
{
    propOne: 'x',
    propTwo: 'y',
    yetAnotherJunk: 'I am valueless really',
    noOneHasThis: 'I have something no one has'
}];

const uniques = [...new Set(
    array.map(x => JSON.stringify((({ propOne, propTwo }) => ({ propOne, propTwo }))(x))))
].map(JSON.parse);

console.log(uniques);

ES6 магия в одну строку... читается при этом!

// returns the union of two arrays where duplicate objects with the same 'prop' are removed
const removeDuplicatesWith = (a, b, prop) => a.filter(x => !b.find(y => x[prop] === y[prop]);
Другие вопросы по тегам