есть ли в lodash функция для замены совпадающего элемента

134

Интересно, есть ли в lodash более простой метод для замены элемента в коллекции JavaScript? (Возможен дубликат, но я там не понял ответа :)

Я просмотрел их документацию, но ничего не нашел

Мой код:

var arr = [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];
// Can following code be reduced to something like _.XX(arr, {id:1}, {id:1, name: "New Name"});
_.each(arr, function(a, idx){
  if(a.id === 1){
    arr[idx] = {id:1, name: "Person New Name"};
    return false;
  }
});

_.each(arr, function(a){
  document.write(a.name);
});

Обновление: объект, который я пытаюсь заменить, имеет много свойств, например

{id: 1, Prop1: ..., Prop2: ... и так далее}

Решение:

Благодаря dfsq, но я нашел подходящее решение в lodash, которое, кажется, работает нормально и довольно аккуратно, и я также поместил его в миксин, поскольку это требование есть во многих местах. JSBin

var update = function(arr, key, newval) {
  var match = _.find(arr, key);
  if(match)
    _.merge(match, newval);
  else
    arr.push(newval);    
};

_.mixin({ '$update': update });

var arr = [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];

_.$update(arr, {id:1}, {id:1, name: "New Val"});


document.write(JSON.stringify(arr));

Более быстрое решение Как указано в @dfsq, следующее намного быстрее

var upsert = function (arr, key, newval) {
    var match = _.find(arr, key);
    if(match){
        var index = _.indexOf(arr, _.find(arr, key));
        arr.splice(index, 1, newval);
    } else {
        arr.push(newval);
    }
};
Вишал Сет
источник
7
Я думаю, вы также можете использовать match в качестве второго параметра для _.indexOf в строке 4 вашего «Более быстрого решения», нет необходимости пересчитывать это значение там, что должно немного ускорить работу.
davertron
2
Еще быстрее: используйте _.findIndexна матч.
Julian K
1
Просто чтобы расширить то, что сказали @JulianK и @davertron, использование _.findIndexвместо _.findпозволит вам отбросить и второй, _.findи _.indexOf. Вы повторяете массив 3 раза, когда все, что вам нужно, это 1.
Джастин Морган

Ответы:

191

В вашем случае все, что вам нужно сделать, это найти объект в массиве и использовать Array.prototype.splice()метод, подробнее читайте здесь :

var arr = [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];

// Find item index using _.findIndex (thanks @AJ Richardson for comment)
var index = _.findIndex(arr, {id: 1});

// Replace item at index using native splice
arr.splice(index, 1, {id: 100, name: 'New object.'});

// "console.log" result
document.write(JSON.stringify( arr ));
<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash.min.js"></script>

dfsq
источник
1
Что ж, ваше решение будет стоить дороже с точки зрения производительности, так как indexOfоно будет очень быстрым (оно будет использовать собственные браузеры Array.prototype.indexOf). Но в любом случае рад, что вы нашли решение, которое работает для вас.
dfsq
14
Почему бы не использовать _.findIndex? Тогда вам не нужно пользоваться _.indexOf.
AJ Richardson
35

Похоже, самым простым решением было бы использовать ES6 .mapили lodash _.map:

var arr = [{id: 1, name: "Person 1"}, {id: 2, name: "Person 2"}];

// lodash
var newArr = _.map(arr, function(a) {
  return a.id === 1 ? {id: 1, name: "Person New Name"} : a;
});

// ES6
var newArr = arr.map(function(a) {
  return a.id === 1 ? {id: 1, name: "Person New Name"} : a;
});

Это дает хороший эффект, позволяющий избежать изменения исходного массива.

спенсер
источник
8
Но каждый раз вы создаете новый массив ... Стоит отметить.
kboom
3
Однако единственная альтернатива не создавать новый массив - это изменить существующий. Кроме того, создание нового массива, скорее всего, не повлияет на производительность. Возврат от меня.
Нобита
24

[ES6] Этот код работает у меня.

let result = array.map(item => item.id === updatedItem.id ? updatedItem : item)
shebik
источник
1. вы создаете новый экземпляр массива, поэтому «замена» элемента неверна. 2. вы потеряете свой updatedItemмассив if, который не включает элемент с таким же id.
Evilive 07
это решение для 'update', а не 'upsert' (вопрос был в том, «есть ли в lodash функция для замены MATCHED item»), и да, он создает копию массива, поэтому не используйте ее, если вам нужно работать с тот же массив (я не сделал)
shebik
21
function findAndReplace(arr, find, replace) {
  let i;
  for(i=0; i < arr.length && arr[i].id != find.id; i++) {}
  i < arr.length ? arr[i] = replace : arr.push(replace);
}

Теперь проверим производительность всех методов:

evilive
источник
6
Проголосовали против, потому что негативное отношение к людям ("это какая-то шутка") отвлекает их от обучения. Представьте, если бы я закончил это словами «будучи умным человеком, я ожидаю, что вы не будете лениться в своих эмоциях и будете думать об этом».
Aditya MP
5
Я не хотел никого обидеть, но мне интересно, как решение, худшее, чем подход автора оригинальной темы, получило столько голосов. Какие правила люди, отдавшие за это свой голос? И я разочарован тем, что люди слепо доверяют самому популярному ответу и не обладают критическим мышлением.
Evilive
1
@evilive Правильные баллы, но я не понимаю, как они требуют, чтобы вы производили впечатление, будто все, кто ранее давал ответы / голоса, - идиоты. Фактические части этого ответа великолепны, остальное имеет вид едва сдерживаемого комплекса превосходства. Это никому не помогает. Вы можете легко доказать свою точку зрения без чрезмерной эмоциональной реакции.
Thor84no
1
Однако стоит отметить, что ваше решение и решение TC фильтруются только по идентификаторам. Это первая причина того, что эти двое работают быстрее. Два других позволяют передавать любую часть объекта, которая вам нужна для фильтрации, что может быть более предпочтительным в качестве функции upsert.
Арам
10

Вы также можете использовать findIndex и pick для достижения того же результата:

  var arr  = [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];
  var data = {id: 2, name: 'Person 2 (updated)'};
  var index = _.findIndex(arr, _.pick(data, 'id'));
  if( index !== -1) {
    arr.splice(index, 1, data);
  } else {
    arr.push(data);
  }
JVitela
источник
6

По прошествии времени вы должны принять более функциональный подход, в котором вы должны избегать мутаций данных и писать небольшие функции с единственной ответственностью. С стандартом ECMAScript 6, вы можете наслаждаться функциональной парадигмой программирования в JavaScript прилагаемой map, filterи reduceметоде. Вам не нужен еще один lodash, подчеркивание или что-то еще, чтобы делать самые простые вещи.

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

Используя карту ES6:

const replace = predicate => replacement => element =>
  predicate(element) ? replacement : element
 
const arr = [ { id: 1, name: "Person 1" }, { id:2, name:"Person 2" } ];
const predicate = element => element.id === 1
const replacement = { id: 100, name: 'New object.' }

const result = arr.map(replace (predicate) (replacement))
console.log(result)


Рекурсивная версия - эквивалент отображения:

Требуется деструктуризация и распространение массива .

const replace = predicate => replacement =>
{
  const traverse = ([head, ...tail]) =>
    head
    ? [predicate(head) ? replacement : head, ...tail]
    : []
  return traverse
}
 
const arr = [ { id: 1, name: "Person 1" }, { id:2, name:"Person 2" } ];
const predicate = element => element.id === 1
const replacement = { id: 100, name: 'New object.' }

const result = replace (predicate) (replacement) (arr)
console.log(result)


Когда последний порядок массива не важен, вы можете использовать его objectкак структуру данных HashMap . Очень удобно, если у вас уже есть коллекция с ключами object- в противном случае вам сначала нужно изменить свое представление.

Требуется разброс остатков объекта , вычисленные имена свойств и объекты Object.entries .

const replace = key => ({id, ...values}) => hashMap =>
({
  ...hashMap,       //original HashMap
  [key]: undefined, //delete the replaced value
  [id]: values      //assign replacement
})

// HashMap <-> array conversion
const toHashMapById = array =>
  array.reduce(
    (acc, { id, ...values }) => 
    ({ ...acc, [id]: values })
  , {})
  
const toArrayById = hashMap =>
  Object.entries(hashMap)
  .filter( // filter out undefined values
    ([_, value]) => value 
  ) 
  .map(
    ([id, values]) => ({ id, ...values })
  )

const arr = [ { id: 1, name: "Person 1" }, { id:2, name:"Person 2" } ];
const replaceKey = 1
const replacement = { id: 100, name: 'New object.' }

// Create a HashMap from the array, treating id properties as keys
const hashMap = toHashMapById(arr)
console.log(hashMap)

// Result of replacement - notice an undefined value for replaced key
const resultHashMap = replace (replaceKey) (replacement) (hashMap)
console.log(resultHashMap)

// Final result of conversion from the HashMap to an array
const result = toArrayById (resultHashMap)
console.log(result)

Пшемыслав Залевский
источник
5

Если вы просто пытаетесь заменить одно свойство, достаточно lodash _.findи _.set:

var arr = [{id: 1, name: "Person 1"}, {id: 2, name: "Person 2"}];

_.set(_.find(arr, {id: 1}), 'name', 'New Person');
Андрей Гаврилов
источник
1

Если точка вставки нового объекта не обязательно должна совпадать с индексом предыдущего объекта, тогда самый простой способ сделать это с помощью lodash - использовать, _.rejectа затем вставить новые значения в массив:

var arr = [
  { id: 1, name: "Person 1" }, 
  { id: 2, name: "Person 2" }
];

arr = _.reject(arr, { id: 1 });
arr.push({ id: 1, name: "New Val" });

// result will be: [{ id: 2, name: "Person 2" }, { id: 1, name: "New Val" }]

Если у вас есть несколько значений, которые вы хотите заменить за один проход, вы можете сделать следующее (написано в формате, отличном от ES6):

var arr = [
  { id: 1, name: "Person 1" }, 
  { id: 2, name: "Person 2" }, 
  { id: 3, name: "Person 3" }
];

idsToReplace = [2, 3];
arr = _.reject(arr, function(o) { return idsToReplace.indexOf(o.id) > -1; });
arr.push({ id: 3, name: "New Person 3" });
arr.push({ id: 2, name: "New Person 2" });


// result will be: [{ id: 1, name: "Person 1" }, { id: 3, name: "New Person 3" }, { id: 2, name: "New Person 2" }]
Richt
источник
этот метод меняет сортировку массива
sospedra
1

Используя функцию lodash unionWith, вы можете выполнить простое обновление до объекта. В документации указано, что если есть совпадение, он будет использовать первый массив. Оберните обновленный объект в [] (массив) и поместите его как первый массив функции объединения. Просто укажите логику сопоставления, и если она будет найдена, она заменит ее, а если нет, то добавит.

Пример:

let contacts = [
     {type: 'email', desc: 'work', primary: true, value: 'email prim'}, 
     {type: 'phone', desc: 'cell', primary: true, value:'phone prim'},
     {type: 'phone', desc: 'cell', primary: false,value:'phone secondary'},
     {type: 'email', desc: 'cell', primary: false,value:'email secondary'}
]

// Update contacts because found a match
_.unionWith([{type: 'email', desc: 'work', primary: true, value: 'email updated'}], contacts, (l, r) => l.type == r.type && l.primary == r.primary)

// Add to contacts - no match found
_.unionWith([{type: 'fax', desc: 'work', primary: true, value: 'fax added'}], contacts, (l, r) => l.type == r.type && l.primary == r.primary)
Джеффри
источник
1

Тоже неплохой вариант)

var arr = [{id: 1, name: "Person 1"}, {id: 2, name: "Person 2"}];

var id = 1; //id to find

arr[_.find(arr, {id: id})].name = 'New Person';
Иван Пирус
источник
1
var arr= [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];
var index = _.findIndex(arr, {id: 1});
arr[index] = {id: 100, name: 'xyz'}
Амит Чхатбар
источник
0

Если вы ищете способ неизменяемого изменения коллекции (как и я, когда нашел ваш вопрос), вы можете взглянуть на immutability-helper , библиотеку, созданную на основе оригинальной утилиты React. В вашем случае вы бы выполнили то, что упомянули, следующим образом:

var update = require('immutability-helper')
var arr = [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}]
var newArray = update(arr, { 0: { name: { $set: 'New Name' } } })
//=> [{id: 1, name: "New Name"}, {id:2, name:"Person 2"}]
Aaron_H
источник
0

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

let arr = [{id: 1, name: "Person 1"}, {id: 2, name: "Person 2"}];
let newObj = {id: 1, name: "new Person"}

/*Add new prototype function on Array class*/
Array.prototype._replaceObj = function(newObj, key) {
  return this.map(obj => (obj[key] === newObj[key] ? newObj : obj));
};

/*return [{id: 1, name: "new Person"}, {id: 2, name: "Person 2"}]*/
arr._replaceObj(newObj, "id") 
Солнечно
источник
0

Неизменяемый , подходит для ReactJS:

Предполагать:

cosnt arr = [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];

Обновленный элемент является вторым, и его имя изменено на Special Person:

const updatedItem = {id:2, name:"Special Person"};

Подсказка : в lodash есть полезные инструменты, но теперь у нас есть некоторые из них на Ecmascript6 +, поэтому я просто используюmapфункцию, которая существует на обоихlodashиecmascript6+:

const newArr = arr.map(item => item.id === 2 ? updatedItem : item);
AmerllicA
источник
0

С этим тоже наткнулся и сделал это просто так.

const persons = [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];
const updatedPerson = {id: 1, name: "new Person Name"}
const updatedPersons = persons.map(person => (
  person.id === updated.id
    ? updatedPerson
    : person
))

При желании мы можем обобщить это

const replaceWhere = (list, predicate, replacement) => {
  return list.map(item => predicate(item) ? replacement : item)
}

replaceWhere(persons, person => person.id === updatedPerson.id, updatedPerson)
rmmjohann
источник