c'è una funzione in lodash per sostituire l'oggetto abbinato


134

Mi chiedo se esiste un metodo più semplice in lodash per sostituire un elemento in una raccolta JavaScript? (Possibile duplicato ma non ho capito la risposta lì :)

Ho guardato la loro documentazione ma non sono riuscito a trovare nulla

Il mio codice è:

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

Aggiornamento: l'oggetto che sto cercando di sostituire ha molte proprietà simili

{id: 1, Prop1: ..., Prop2: ... e così via}

Soluzione:

Grazie a dfsq ma ho trovato una soluzione adeguata all'interno di lodash che sembra funzionare bene ed è piuttosto ordinata e l'ho messa in un mixin poiché ho questo requisito in molti posti. 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));

Soluzione più veloce Come sottolineato da @dfsq, seguire è molto più veloce

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
Penso che puoi usare match come secondo parametro per _.indexOf anche su liine 4 della tua "Soluzione più veloce", non c'è bisogno di ricalcolare quel valore lì, il che dovrebbe rendere le cose un po 'più veloci.
Davertron,

2
Ancora più veloce: usa _.findIndexper la partita.
Julian K,

1
Solo per espandere ciò che hanno detto @JulianK e @davertron, usare _.findIndexinvece di _.findti lascerà cadere sia il secondo _.findche il _.indexOf. Stai ripetendo l'array 3 volte quando tutto ciò di cui hai bisogno è 1.
Justin Morgan,

Risposte:


191

Nel tuo caso tutto ciò che devi fare è trovare l'oggetto nella matrice e usare il Array.prototype.splice()metodo, leggi maggiori dettagli qui :

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
Bene, la tua soluzione avrà un costo maggiore in termini di prestazioni, poiché indexOfsarà molto veloce (utilizzerà i browser nativi Array.prototype.indexOf). Ma comunque, felice di trovare una soluzione che funzioni per te.
dfsq,

14
Perché non usare _.findIndex? Quindi non è necessario utilizzare _.indexOf.
AJ Richardson,

35

Sembra che la soluzione più semplice sarebbe usare ES6 .mapo 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;
});

Questo ha il piacevole effetto di evitare di mutare l'array originale.


8
Ma stai creando un nuovo array ogni volta ... Vale la pena notare.
kboom,

3
Tuttavia, l'unica alternativa alla non creazione di un nuovo array è la mutazione di quello esistente. Inoltre, la creazione di un nuovo array non avrà probabilmente alcun impatto in termini di prestazioni. Voto da parte mia.
Nobita,

24

[ES6] Questo codice funziona per me.

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

1. stai creando una nuova istanza di array, quindi non è vero "sostituire" un elemento. 2. si perderà updatedItemse l'array non include un articolo con lo stesso id.
malvagio

questa è la soluzione per "update" non "upsert" (la domanda era "c'è una funzione in lodash per sostituire l'elemento MATCHED") e sì, crea una copia dell'array, quindi non usarla se devi lavorare con stesso array (non l'ho fatto)
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);
}

Ora testiamo le prestazioni per tutti i metodi:


6
Sottovalutato perché essere negativo con le persone ("è una specie di scherzo") le disattiva dall'apprendimento. Immagina che se avessi finito con "essere una persona intelligente, mi aspetterei che tu non sia pigro delle tue emozioni e ci pensi".
Aditya MP,

5
Non volevo ferire nessuno, ma mi chiedo come la soluzione peggiore dell'approccio del creatore dell'argomento originale abbia ottenuto così tanti voti. Quali regole governano le persone che hanno votato per questo? E ho deluso il fatto che le persone si fidino ciecamente della risposta più votata e non abbiano un pensiero critico.
malvagio

1
@evilive Punti validi, ma non vedo come quelli richiedano che ti imbatterai come se tutti quelli che in precedenza fornivano risposte / voti fossero idioti. Le parti fattuali di questa risposta sono grandi, il resto ha l'aria di un complesso di superiorità a malapena contenuto. Questo non aiuta nessuno. Puoi facilmente esprimere i tuoi punti senza l'eccessiva risposta emotiva.
Thor84no,

1
Vale la pena notare, tuttavia, che la tua soluzione e la soluzione TC stanno filtrando solo per ID. Questa è la prima ragione per cui quei due corrono più veloci. Gli altri due ti consentono di passare qualsiasi parte dell'oggetto necessario per il filtro che potrebbe essere preferibile come funzione di inversione.
Aram,

10

Puoi anche usare findIndex e scegliere per ottenere lo stesso risultato:

  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

Con il passare del tempo dovresti adottare un approccio più funzionale in cui dovresti evitare mutazioni di dati e scrivere piccole funzioni a responsabilità singola. Con lo standard ECMAScript 6, si può godere di paradigma di programmazione funzionale in JavaScript con i previsti map, filtere reducemetodi. Non hai bisogno di un altro lodash, trattino basso o cos'altro per fare la maggior parte delle cose di base.

Di seguito ho incluso alcune soluzioni proposte a questo problema al fine di mostrare come questo problema può essere risolto utilizzando diverse funzionalità linguistiche:

Utilizzando la mappa 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)


Versione ricorsiva - equivalente della mappatura:

Richiede la destrutturazione e la diffusione dell'array .

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)


Quando l'ordine dell'array finale non è importante, è possibile utilizzare una struttura di dati objectcome HashMap . Molto utile se hai già una raccolta con chiave come an object- altrimenti devi prima cambiare la tua rappresentazione.

Richiede la distribuzione del resto dell'oggetto , i nomi delle proprietà calcolate e 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

Se stai solo cercando di sostituire una proprietà, alloggia _.finde _.setdovrebbe essere sufficiente:

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

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

1

Se il punto di inserimento del nuovo oggetto non deve necessariamente corrispondere all'indice dell'oggetto precedente, il modo più semplice per farlo con lodash è utilizzare _.rejecte quindi inserire nuovi valori nell'array:

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

Se hai più valori che vuoi sostituire in un unico passaggio, puoi fare quanto segue (scritto in formato non 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" }]

questo metodo modifica l'ordinamento dell'array
sospedra

1

Usando lodash union Con la funzione, puoi realizzare un semplice upsert su un oggetto. La documentazione afferma che se esiste una corrispondenza, utilizzerà il primo array. Avvolgi l'oggetto aggiornato in [] (array) e inseriscilo come primo array della funzione union. Basta specificare la logica di corrispondenza e, se trovata, la sostituirà e, in caso contrario, la aggiungerà

Esempio:

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

Anche la variante non è male)

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

Se stai cercando un modo per cambiare immutabilmente la collezione (come lo ero quando ho trovato la tua domanda), potresti dare un'occhiata a immutability-helper , una libreria biforcuta dall'utilità originale di React. Nel tuo caso, realizzerai ciò che hai menzionato tramite quanto segue:

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

Puoi farlo senza usare 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

Immutabile , adatto a ReactJS:

Assumere:

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

L'elemento aggiornato è il secondo e il nome viene cambiato in Special Person:

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

Suggerimento : il lodash ha strumenti utili ma ora ne abbiamo alcuni su Ecmascript6 +, quindi uso solo lamapfunzione che esiste su entrambilodasheecmascript6+:

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

0

Anche questo è successo e lo ha fatto semplicemente in quel modo.

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

Se lo vogliamo, possiamo generalizzarlo

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

replaceWhere(persons, person => person.id === updatedPerson.id, updatedPerson)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.