czy w lodash jest funkcja zastępująca dopasowany element

139

Zastanawiam się, czy w lodash istnieje prostsza metoda zastąpienia elementu w kolekcji JavaScript? (Możliwy duplikat, ale nie rozumiem tam odpowiedzi :)

Przejrzałem ich dokumentację, ale nic nie znalazłem

Mój kod to:

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

Aktualizacja: obiekt, który próbuję zastąpić, ma wiele właściwości, takich jak

{id: 1, Prop1: ..., Prop2: ... i tak dalej}

Rozwiązanie:

Dzięki dfsq, ale znalazłem odpowiednie rozwiązanie w lodash, które wydaje się działać dobrze i jest całkiem schludne i również umieściłem je w miksie, ponieważ mam ten wymóg w wielu miejscach. 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));

Szybsze rozwiązanie Jak wskazał @dfsq, śledzenie jest znacznie szybsze

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);
    }
};
Vishal Seth
źródło
7
Myślę, że możesz użyć match jako drugiego parametru do _.indexOf w liine 4 swojego „Szybszego rozwiązania”, nie ma potrzeby ponownego obliczania tam tej wartości, co powinno nieco przyspieszyć działanie.
davertron,
2
Jeszcze szybciej: użyj _.findIndexdo dopasowania.
Julian K
1
Aby rozwinąć to, co powiedzieli @JulianK i @davertron, użycie _.findIndexzamiast _.findpozwoli ci upuścić zarówno drugi, jak _.findi _.indexOf. Przechodzisz przez tablicę 3 razy, kiedy wszystko, czego potrzebujesz, to 1.
Justin Morgan,

Odpowiedzi:

197

W twoim przypadku wszystko co musisz zrobić to znaleźć obiekt w tablicy i użyć Array.prototype.splice()metody, przeczytaj więcej szczegółów tutaj :

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
źródło
1
Cóż, twoje rozwiązanie będzie kosztować więcej pod względem wydajności, ponieważ indexOfbędzie bardzo szybkie (będzie używać natywnych przeglądarek Array.prototype.indexOf). Mimo wszystko cieszę się, że znalazłeś rozwiązanie, które będzie dla Ciebie odpowiednie.
dfsq
14
Dlaczego nie używać _.findIndex? Wtedy nie musisz używać _.indexOf.
AJ Richardson
39

Wydaje się, że najprostszym rozwiązaniem byłoby użycie ES6 .maplub 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;
});

Ma to fajny efekt w postaci uniknięcia mutacji oryginalnej tablicy.

Spencer
źródło
8
Ale za każdym razem tworzysz nową tablicę ... Warto zauważyć.
kboom
5
Jedyną alternatywą, aby nie tworzyć nowej tablicy, jest jednak mutacja istniejącej. Ponadto utworzenie nowej macierzy prawdopodobnie nie będzie miało wpływu na wydajność. Głosuj ode mnie.
Aurelio,
25

[ES6] Ten kod działa dla mnie.

let result = array.map(item => item.id === updatedItem.id ? updatedItem : item)
szebik
źródło
1. tworzysz nową instancję tablicy, więc nie jest prawdą „zamiana” elementu. 2. stracisz updatedItemjeśli tablica nie zawiera elementu z tym samym id.
zły
to jest rozwiązanie dla 'update', a nie 'upsert' (pytanie brzmiało: "czy w lodash jest funkcja zastępująca dopasowany element") i tak, tworzy kopię tablicy, więc nie używaj jej, jeśli musisz pracować ta sama tablica (nie zrobiłem)
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);
}

Teraz przetestujmy wydajność dla wszystkich metod:

zły
źródło
6
Negocjowane, ponieważ negatywne nastawienie do ludzi („czy to jakiś żart”) uniemożliwia im naukę. Wyobraź sobie, że gdybym zakończył to na „byciu mądrą osobą, spodziewałbym się, że nie będziesz leniwy wobec swoich emocji i nie będziesz o tym myślał”.
Aditya MP
5
Nie chciałem nikogo skrzywdzić, ale zastanawiam się, w jaki sposób rozwiązanie, które jest najgorsze niż podejście twórcy tematu, zdobyło tyle głosów. Jakie zasady rządzą ludźmi, którzy oddali na to głos? I zawiodłem się, że ludzie ślepo ufają większości głosowanej odpowiedzi i nie mają krytycznego myślenia.
złowrogi
1
@evilive Ważne punkty, ale nie widzę, jak wymagają one od ciebie spojrzenia, jakby wszyscy, którzy wcześniej udzielili odpowiedzi / głosów, byli idiotami. Faktyczne części tej odpowiedzi są świetne, reszta ma charakter ledwie skrywanego kompleksu wyższości. To nikomu nie pomaga. Możesz łatwo przedstawić swoje punkty bez nadmiernej reakcji emocjonalnej.
Thor84no
1
Warto jednak zauważyć, że Twoje rozwiązanie i rozwiązanie TC filtrują tylko według identyfikatorów. To pierwszy powód, dla którego ci dwaj działają szybciej. Pozostałe dwa pozwalają na przekazanie dowolnej części obiektu potrzebnej do filtrowania, co może być bardziej preferowane jako funkcja upsert.
Aram
12

Możesz również użyć funkcji findIndex i pick, aby osiągnąć ten sam wynik:

  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
źródło
7

W miarę upływu czasu powinieneś przyjąć bardziej funkcjonalne podejście, w którym powinieneś unikać mutacji danych i pisać małe funkcje o pojedynczej odpowiedzialności. Ze standardem ECMAScript 6, można korzystać funkcjonalny paradygmat programowania w JavaScript z dostarczonych map, filteri reducemetod. Nie potrzebujesz kolejnego lodash, podkreślenia ani tego, co jeszcze zrobić, aby zrobić najbardziej podstawowe rzeczy.

Poniżej zamieściłem kilka propozycji rozwiązań tego problemu, aby pokazać, jak można go rozwiązać za pomocą różnych funkcji językowych:

Korzystanie z mapy 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)


Wersja rekurencyjna - odpowiednik mapowania:

Wymaga destrukturyzacji i rozproszenia tablic .

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)


Gdy ostateczna kolejność tablicy nie jest ważna, możesz użyć objectjako HashMap struktury danych struktury danych. Bardzo przydatne, jeśli masz już kolekcję kluczy jako object- w przeciwnym razie musisz najpierw zmienić swoją reprezentację.

Wymaga rozproszenia reszty obiektu , obliczonych nazw właściwości i 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)

Przemysław Zalewski
źródło
6

Jeśli próbujesz tylko zamienić jedną nieruchomość, lodash _.findi _.setpowinno wystarczyć:

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

_.set(_.find(arr, {id: 1}), 'name', 'New Person');
Andrei Gavrilov
źródło
2

Jeśli punkt wstawiania nowego obiektu nie musi pasować do indeksu poprzedniego obiektu, najprostszym sposobem na zrobienie tego z lodash jest użycie, _.rejecta następnie wpychanie nowych wartości do tablicy:

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

Jeśli masz wiele wartości, które chcesz zastąpić w jednym przebiegu, możesz wykonać następujące czynności (zapisane w formacie innym niż 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
źródło
1
ta metoda zmienia sortowanie tablic
sospedra
2
var arr= [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];
var index = _.findIndex(arr, {id: 1});
arr[index] = {id: 100, name: 'xyz'}
Amit Chhatbar
źródło
1

Używając funkcji lodash unionWith, możesz wykonać prosty skok do obiektu. Dokumentacja stwierdza, że ​​jeśli istnieje dopasowanie, użyje pierwszej tablicy. Zawiń zaktualizowany obiekt w [] (tablica) i umieść go jako pierwszą tablicę funkcji unii. Po prostu określ swoją pasującą logikę, a jeśli zostanie znaleziona, zastąpi ją, a jeśli nie, doda ją

Przykład:

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)
Jeffrey
źródło
1

Niezły wariant)

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';
Ivan Pirus
źródło
1

Przeszedłem też przez to i zrobiłem to po prostu w ten sposób.

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

Jeśli chcemy, możemy to uogólnić

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

replaceWhere(persons, person => person.id === updatedPerson.id, updatedPerson)
rmmjohann
źródło
0

Jeśli szukasz sposobu na niezmienną zmianę kolekcji (tak jak ja, kiedy znalazłem twoje pytanie), możesz rzucić okiem na niezmienność-pomocnika , bibliotekę rozwidloną z oryginalnego narzędzia React. W twoim przypadku osiągnąłbyś to, o czym wspomniałeś w następujący sposób:

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
źródło
0

Możesz to zrobić bez użycia 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") 
Słoneczny
źródło
0

Niezmienny , odpowiedni dla ReactJS:

Założyć:

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

Zaktualizowana pozycja jest drugą, a nazwa zmienia się na Special Person:

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

Podpowiedź : lodash ma przydatne narzędzia, ale teraz mamy niektóre z nich w Ecmascript6 +, więc po prostu używammapfunkcji, która istnieje na obulodashiecmascript6+:

const newArr = arr.map(item => item.id === 2 ? updatedItem : item);
AmerllicA
źródło