Удаление повторяющихся объектов с помощью Underscore для Javascript

124

У меня такой массив:

var foo = [ { "a" : "1" }, { "b" : "2" }, { "a" : "1" } ];

Я бы хотел отфильтровать его, чтобы:

var bar = [ { "a" : "1" }, { "b" : "2" }];

Я пробовал использовать _.uniq, но думаю, поскольку { "a" : "1" }он не равен самому себе, он не работает. Есть ли способ предоставить подчеркивание uniq с переопределенной функцией равенства?

Плюс
источник
Пожалуйста, опубликуйте свой код
Четтер Хаммин
Такие вещи как { "a" : "2" }существуют? Если да, то что делает его уникальным - атрибут или значение?
Мэтт
Да, у меня есть атрибут в качестве ключа, я реализовал индекс, который кто-то показал мне по другой теме, но потом я захотел очистить свой код, используя некоторые общие библиотеки
плюс
1
Пожалуйста, измените ответ.
Vadorequest

Ответы:

232

.uniq / .unique принимает обратный вызов

var list = [{a:1,b:5},{a:1,c:5},{a:2},{a:3},{a:4},{a:3},{a:2}];

var uniqueList = _.uniq(list, function(item, key, a) { 
    return item.a;
});

// uniqueList = [Object {a=1, b=5}, Object {a=2}, Object {a=3}, Object {a=4}]

Ноты:

  1. Возвращаемое значение обратного вызова, используемое для сравнения
  2. Первый объект сравнения с уникальным возвращаемым значением, используемым как уникальное
  3. underscorejs.org демонстрирует отсутствие использования обратного вызова
  4. lodash.com показывает использование

Другой пример: использование обратного вызова для извлечения марок автомобилей, цветов из списка

Shanimal
источник
falseне требуется для _.uniq(). Также в lodash вы могли бы написать это так _.uniq(a, 'a');, поскольку он будет отбирать свойство aу объектов.
Larry Battle
Сокращение обратного вызова "_.pluck" работает только в том случае, если вы передаете значение для isSorted (например _.uniq(a, false, 'a')). Я отправил ping-запрос на github / bestiejs / lodash, и они сказали, что проблема решена на грани. Поэтому, если вы не используете функцию, убедитесь, что у вас установлена ​​последняя версия. Это может не быть проблемой для подчеркивания.
Shanimal
2
Итератор звучит не очень удачно, это хэш-функция, которая будет определять идентичность каждого объекта
Хуан Мендес
Отредактировано, чтобы использовать обратный вызов для большей согласованности с документами lodash :)
Shanimal,
1
У вас, например, в jsbin может быть обновление. (1) Создает: _ (cars) .uniq ('make'). Map ('make'). ValueOf () AND (2) Цвета: _ (cars) .uniq ('color'). Map ('color' ).значение(). Вы можете выдержать цвет и сделать закрытие. (Все, что если вы используете апгрейд de lodash)
Витор Тыбурский
38

Если вы хотите удалить дубликаты на основе идентификатора, вы можете сделать что-то вроде этого:

var res = [
  {id: 1, content: 'heeey'},
  {id: 2, content: 'woah'}, 
  {id: 1, content:'foo'},
  {id: 1, content: 'heeey'},
];
var uniques = _.map(_.groupBy(res,function(doc){
  return doc.id;
}),function(grouped){
  return grouped[0];
});

//uniques
//[{id: 1, content: 'heeey'},{id: 2, content: 'woah'}]
Petter
источник
Принятый ответ не работает, если уникальным идентификатором является Date. Однако это так.
gunwin 05
17

Реализация ответа Шиплу.

var foo = [ { "a" : "1" }, { "b" : "2" }, { "a" : "1" } ];

var x = _.uniq( _.collect( foo, function( x ){
    return JSON.stringify( x );
}));

console.log( x ); // returns [ { "a" : "1" }, { "b" : "2" } ]
Ларри Батл
источник
кстати, как ты получил 4 голоса за? Чтобы получить свойства вашего результата, вам нужно будет вернуть каждое значение массива обратно в объект. Что-то вроде того, JSON.parse(x[0]).aчто x - это не массив объектов, а массив строк. Кроме того, если вы добавите значения b к уникальным объектам и измените порядок значений a / b, они больше не будут считаться уникальными для вашей функции. (например, `" {\ "a \": \ "1 \", \ "b \": 2} "! =" {\ "b \": 2, \ "a \": \ "1 \"} ") Может, я чего-то упускаю, но разве результат не должен быть хоть сколько-нибудь полезен?" Вот jsbin для иллюстрации jsbin.com/utoruz/2/edit
Shanimal
1
Вы правы, условие наличия одинаковых ключей, но в другом порядке нарушает реализацию. Но я не уверен, почему вы проверяете только ключ aдля каждого объекта, когда могут быть повторяющиеся объекты, не содержащие ключа a. Однако это имело бы смысл, если бы aбыл уникальный идентификатор.
Ларри Бэттл
Когда я отвечал на вопрос, мне казалось, что основной задачей вопроса было переопределение (a ==(=) b when a = b = {a:1}). Смысл моего ответа - итератор. Я пытался ответить, не беспокоясь о мотиве, который может быть любым, верно? (например, возможно, они хотели извлечь список марок и цветов из списка автомобилей на выставке. jsbin.com/evodub/2/edit ) Ура!
Shanimal
Также я думаю, что это помогает нам давать краткие ответы, когда кто-то задает вопрос, который указывает мотив. Это гонка, поэтому я предпочитаю быть первым и при необходимости уточнять. С Днем Святого Патрика.
Shanimal
Ну, я просто дал еще один голос, потому что это ответил на мой вопрос о сравнении вложенных массивов. iterator
Искал
15

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

var x = [{i:2}, {i:2, x:42}, {i:4}, {i:3}];
_.chain(x).indexBy("i").values().value();
// > [{i:2, x:42}, {i:4}, {i:3}]
tuxbear
источник
12

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

var uniqueEntities = _.uniq(entities, function (item, key, a) {
                                    return item._id.toString();
                                });
Акиб Мумтаз
источник
10

Вот простое решение, которое использует глубокое сравнение объектов для проверки дубликатов (не прибегая к преобразованию в JSON, что неэффективно и взломано)

var newArr = _.filter(oldArr, function (element, index) {
    // tests if the element has a duplicate in the rest of the array
    for(index += 1; index < oldArr.length; index += 1) {
        if (_.isEqual(element, oldArr[index])) {
            return false;
        }
    }
    return true;
});

Он отфильтровывает все элементы, если они имеют дубликат позже в массиве - таким образом, сохраняется последний повторяющийся элемент.

Тестирование на дублирование использования, _.isEqualкоторое выполняет оптимизированное глубокое сравнение между двумя объектами, см. В документации isEqual с подчеркиванием для получения дополнительной информации.

изменить: обновлено для использования, _.filterчто является более чистым подходом

Джошуа Бамбрик
источник
Не зависит от наличия заранее определенного уникального свойства? Мне это нравится.
Дон МакКарди
1
Хорошее решение для небольшого массива объектов, но цикл внутри цикла стоит дорого по сравнению с предоставлением uniq id.
Penner
7

Попробуйте функцию итератора

Например, вы можете вернуть первый элемент

x = [['a',1],['b',2],['a',1]]

_.uniq(x,false,function(i){  

   return i[0]   //'a','b'

})

=> [['a', 1], ['b', 2]]

IvanM
источник
аргумент секунды на самом деле необязателен, вы также можете это сделать_.uniq(x,function(i){ return i[0]; });
jakecraige
3

вот мое решение (coffeescript):

_.mixin
  deepUniq: (coll) ->
    result = []
    remove_first_el_duplicates = (coll2) ->

      rest = _.rest(coll2)
      first = _.first(coll2)
      result.push first
      equalsFirst = (el) -> _.isEqual(el,first)

      newColl = _.reject rest, equalsFirst

      unless _.isEmpty newColl
        remove_first_el_duplicates newColl

    remove_first_el_duplicates(coll)
    result

пример:

_.deepUniq([ {a:1,b:12}, [ 2, 1, 2, 1 ], [ 1, 2, 1, 2 ],[ 2, 1, 2, 1 ], {a:1,b:12} ]) 
//=> [ { a: 1, b: 12 }, [ 2, 1, 2, 1 ], [ 1, 2, 1, 2 ] ]
Шимановский
источник
3

с подчеркиванием мне пришлось использовать String () в функции итератора

function isUniq(item) {
    return String(item.user);
}
var myUniqArray = _.uniq(myArray, isUniq);
dam1
источник
0

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

function uniq(ArrayObjects){
  var out = []
  ArrayObjects.map(obj => {
    if(_.every(out, outobj => !_.isEqual(obj, outobj))) out.push(obj)
  })
  return out
}
Дзюндзи Шимагаки
источник
0
var foo = [ { "a" : "1" }, { "b" : "2" }, { "a" : "1" } ];
var bar = _.map(_.groupBy(foo, function (f) { 
        return JSON.stringify(f); 
    }), function (gr) { 
        return gr[0]; 
    }
);

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

var grouped = _.groupBy(foo, function (f) { 
    return JSON.stringify(f); 
});

grouped выглядит как:

{
    '{ "a" : "1" }' = [ { "a" : "1" } { "a" : "1" } ],
    '{ "b" : "2" }' = [ { "b" : "2" } ]
}

Затем возьмем первый элемент из каждой группы

var bar = _.map(grouped, function(gr)
    return gr[0]; 
});

bar выглядит как: [ { "a" : "1" }, { "b" : "2" } ]

Поставить все это вместе:

var foo = [ { "a" : "1" }, { "b" : "2" }, { "a" : "1" } ];
var bar = _.map(_.groupBy(foo, function (f) { 
        return JSON.stringify(f); 
    }), function (gr) { 
        return gr[0]; 
    }
);
Келли Бигли
источник
3
Добро пожаловать в stackoverflow. В дополнение к предоставленному вами коду, попробуйте объяснить, почему и как это решает проблему.
jtate
хороший звонок. Спасибо. Обновлено с описанием того, как это работает.
Келли Бигли
-5

Вы можете сделать это в сокращенном виде:

_.uniq(foo, 'a')

nnattawat
источник
ваше решение не работает для массивов объектов, а только для массивов
Toucouleur 01