Lodash'ta eşleşen öğeyi değiştirmek için bir işlev var mı


134

Bir JavaScript koleksiyonundaki bir öğeyi değiştirmek için Lodash'ta daha basit bir yöntem olup olmadığını merak ediyorum. (Olası kopya ama oradaki cevabı anlamadım :)

Belgelerine baktım ama hiçbir şey bulamadım

Benim kodum:

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

Güncelleme: Değiştirmeye çalıştığım nesnenin birçok özelliği var.

{id: 1, Özellik1: ..., Özellik2: ... ve benzeri}

Çözüm:

Dfsq sayesinde, ancak lodash içinde gayet iyi çalışan ve oldukça düzgün görünen uygun bir çözüm buldum ve birçok yerde bu gereksinime sahip olduğum için onu da bir mixin'e koydum. 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));

Daha Hızlı Çözüm @dfsq tarafından belirtildiği gibi, takip etmek çok daha hızlı

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
Bence "Daha Hızlı Çözümünüzün" liine 4'ünde _.indexOf'un ikinci parametresi olarak eşleşmeyi kullanabilirsiniz, orada bu değeri yeniden hesaplamanıza gerek yok, bu da işleri biraz daha hızlandırmalı.
davertron

2
Daha da hızlı: _.findIndexeşleştirme için kullanın .
Julian K

1
Sadece @JulianK ve @davertron'un söylediklerini genişletmek için, _.findIndexyerine kullanmak _.findhem saniyeyi _.findhem de _.indexOf. Tek ihtiyacınız olan 1 iken diziyi 3 kez
Justin Morgan

Yanıtlar:


191

Sizin durumunuzda yapmanız gereken tek şey, dizideki nesneyi bulmak ve Array.prototype.splice()yöntemi kullanmaktır , buradan daha fazla ayrıntı okuyun :

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
Çözümünüz performans açısından daha pahalıya mal olacak, çünkü indexOfçok hızlı olacak (yerel tarayıcılar Array.prototype.indexOf kullanacak). Ama yine de, işinize yarayan bir çözüm bulduğunuza sevindim.
dfsq

14
Neden kullanmıyorsun _.findIndex? O zaman kullanmana gerek yok _.indexOf.
AJ Richardson

35

Görünüşe göre en basit çözüm ES6'ları .mapveya lodash'ları kullanmak olacaktır _.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;
});

Bu, orijinal diziyi değiştirmekten kaçınmak gibi güzel bir etkiye sahiptir.


8
Ama her seferinde yeni bir dizi oluşturuyorsunuz ... Dikkat etmeye değer.
kboom

3
Yeni bir dizi oluşturmamanın tek alternatifi var olanı değiştirmektir. Ayrıca, yeni bir dizi oluşturmanın performans açısından büyük olasılıkla bir etkisi olmayacaktır. Benden oy verin.
Nobita

24

[ES6] Bu kod benim için çalışıyor.

let result = array.map(item => item.id === updatedItem.id ? updatedItem : item)

1. Yeni bir dizi örneği oluşturuyorsunuz, bu nedenle bir öğeyi "değiştirmek" doğru değildir. 2. updatedItemif dizisi aynı olan bir öğe içermiyorsa kaybedersiniz id.
evilive

bu, 'yükseltmek' değil 'güncellemek' için bir çözümdür (soru, "ZAMAN içinde EŞLEŞTİRİLEN öğeyi değiştirmek için bir işlev var mı") ve evet, dizinin bir kopyasını oluşturur, bu nedenle birlikte çalışmanız gerekiyorsa kullanmayın aynı dizi (ben yapmadım)
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);
}

Şimdi tüm yöntemler için performansı test edelim:


6
Olumsuz oy verildi çünkü insanlara karşı olumsuz olmak ("bu bir tür şaka mı?") Onları öğrenmekten alıkoyar. Bunu "zeki bir insan olarak, duygularınız hakkında tembel olmamanızı ve bunun hakkında düşünmenizi bekleyeceğim" ile bitirdiğimi hayal edin.
Aditya MP

5
Kimseyi incitmek istemedim, ancak orijinal konuyu oluşturucunun yaklaşımından daha kötü olan çözümün nasıl bu kadar çok oy aldığını merak ediyorum. Buna oy verenlerin hükmü nedir? Ve insanların en çok oylanan yanıta körü körüne güvendikleri ve eleştirel düşünceye sahip olmadıkları konusunda hayal kırıklığına uğradım.
şeytani

1
@evilive Geçerli puanlar, ancak bunların daha önce cevap / oy veren herkes aptalmış gibi karşılaşmanızı nasıl gerektirdiğini anlamıyorum. Bu cevabın gerçek kısımları harika, geri kalanı ise zar zor sınırlandırılmış bir üstünlük kompleksi havasına sahip. Bu kimseye yardımcı olmuyor. Puanlarınızı aşırı duygusal tepki olmadan kolayca yapabilirsiniz.
Thor84no

1
Çözümünüzün ve TC'nin çözümünün yalnızca kimliklere göre filtrelendiğini belirtmekte fayda var. Bu ikisinin daha hızlı koşmasının ilk nedeni budur. Diğer ikisi, filtreleme için ihtiyacınız olan nesnenin herhangi bir parçasını geçmenize izin veriyor; bu, bir yükseltme işlevi olarak daha tercih edilebilir olabilir.
Aram

10

Aynı sonucu elde etmek için findIndex ve pick'i de kullanabilirsiniz:

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

7

Zaman geçtikçe, veri mutasyonlarından kaçınmanız ve küçük, tek sorumluluk fonksiyonları yazmanız gereken daha işlevsel bir yaklaşımı benimsemelisiniz. ECMAScript 6 standardı ile, sağlanan ile JavaScript fonksiyonel programlama paradigmasını keyfini map, filterve reduceyöntemlerle. Başka bir lodash, alt çizgi ya da en temel şeyleri başka ne yapmak zorunda değilsiniz.

Aşağıda, bu sorunun farklı dil özellikleri kullanılarak nasıl çözülebileceğini göstermek için bu soruna önerilen bazı çözümleri ekledim:

ES6 haritasını kullanma:

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)


Yinelemeli sürüm - eşlemeye eşdeğer:

Yıkım ve dizi yayma gerektirir .

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)


Son dizinin sırası önemli olmadığında object, bir HashMap veri yapısı olarak kullanabilirsiniz . Zaten anahtarlı koleksiyonunuz varsa çok kullanışlıdır object- aksi takdirde önce temsilinizi değiştirmeniz gerekir.

Gerektirir nesne dinlenme yayılmasını , bilgisayarlı özellik adlarını ve 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

Sadece bir mülkü değiştirmeye çalışıyorsanız, lodash _.findve _.setyeterli olmalıdır:

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

_.set(_.find(arr, {id: 1}), 'name', 'New Person');

1

Yeni nesnenin ekleme noktasının önceki nesnenin diziniyle eşleşmesi gerekmiyorsa, bunu lodash ile yapmanın en basit yolu kullanarak _.rejectve sonra diziye yeni değerler göndermektir:

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" }]

Tek geçişte değiştirmek istediğiniz birden fazla değeriniz varsa, aşağıdakileri yapabilirsiniz (ES6 olmayan formatta yazılmıştır):

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" }]

bu yöntem dizi sıralamasını değiştirir
sospedra

1

Lodash birleştirme işlevini kullanarak, bir nesneye basit bir yükseltme gerçekleştirebilirsiniz. Dokümantasyon, bir eşleşme varsa, ilk diziyi kullanacağını belirtir. Güncellenen nesnenizi [] (dizi) içine sarın ve onu birleşim işlevinin ilk dizisi olarak koyun. Sadece eşleşen mantığınızı belirtin ve bulunursa onun yerini alacak ve değilse ekleyecektir.

Misal:

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

Kötü bir varyant da değil)

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

Koleksiyonu değişmez bir şekilde değiştirmenin bir yolunu arıyorsanız (sorunuzu bulduğumda olduğu gibi), orijinal React kullanımından çatallanmış bir kütüphane olan değişmezlik yardımcısına bir göz atabilirsiniz . Sizin durumunuzda, bahsettiğiniz şeyi aşağıdakiler yoluyla başarırsınız:

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

Lodash kullanmadan yapabilirsiniz.

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

Değişmez , aşağıdakiler için uygundur ReactJS:

varsayalım:

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

Güncellenen öğe ikinci öğedir ve adı şu şekilde değiştirilir Special Person:

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

İpucu : lodash'ın kullanışlı araçları var ama şimdi bunlardan bazıları Ecmascript6 + 'da var, bu yüzden sadecemapher ikisinde de var olan fonksiyonukullanıyorumlodashveecmascript6+:

const newArr = arr.map(item => item.id === 2 ? updatedItem : item);

0

Buna da rastladı ve basitçe bu şekilde yaptı.

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

İsterseniz genelleştirebiliriz

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

replaceWhere(persons, person => person.id === updatedPerson.id, updatedPerson)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.