чи є функція в 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, яке, здається, працює чудово і досить акуратно, і я вкладаю його в mixin, так як я маю цю вимогу в багатьох місцях. 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
Я думаю, що ви можете використовувати матч як другий параметр для _.indexOf на рядку 4 вашого "Швидшого рішення", не потрібно перераховувати це значення там, що повинно зробити трохи швидше.
давертрон

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>


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)

1. ви створюєте новий екземпляр масиву, тому невірна "заміна" елемента. 2. Ви втратите свою програму, updatedItemякщо до масиву не входить елемент з тим самим id.
evilive

це рішення для 'update', а не 'upsert' (питання було "чи існує функція в lodash замінити елемент MATCHED"), і так, він створює копію масиву, тому не використовуйте його, якщо вам потрібно працювати з той же масив (я не робив)
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);
}

Тепер перевіримо продуктивність для всіх методів:


6
Неприхильне тому, що негатив з людьми ("це якась жарт") відключає їх від навчання. Уявіть собі, якби я закінчив це з "розумною людиною, я очікував би, що ти не лінуєшся у своїх емоціях і думаєш про це".
Депутат

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

1
@evilive Неправильні бали, але я не бачу, як вони вимагають, щоб ти натрапив на те, як ніби всі, хто раніше надав відповіді / голоси, - ідіоти. Фактичні частини цієї відповіді чудові, в іншому є повітря ледь утримуваного комплексу переваги. Це нікому не допомагає. Ви можете легко зарахувати свої бали без зайвої емоційної реакції.
Thor84no

1
Варто зауважити, хоча ваше рішення та рішення ТС фільтрують лише ідентифікатори. Це перша причина, що ці двоє бігають швидше. Інші два дозволяють вам передавати будь-яку частину об'єкта, яка вам потрібна для фільтрації, що може бути більш кращим як функція введення.
Арам

10

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

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

6

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

Внизу я включив кілька запропонованих рішень цієї проблеми, щоб показати, як можна вирішити цю проблему за допомогою різних мовних функцій:

Використання карти 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

Якщо ви просто намагаєтесь замінити одне майно, лодаш _.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" }]

цей метод змінює сортування масиву
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';


0

Якщо ви шукаєте спосіб незмінно змінити колекцію (як я, коли я знайшов ваше запитання), ви можете поглянути на помічника , що допомагає непорушності , бібліотеку, роздвоєну з оригінальної утиліти 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"}]

0

Ви можете це зробити, не використовуючи лодаш.

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

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