¿hay una función en lodash para reemplazar el elemento coincidente

134

Me pregunto si hay un método más simple en lodash para reemplazar un elemento en una colección de JavaScript. (Posible duplicado pero no entendí la respuesta allí :)

Miré su documentación pero no pude encontrar nada

Mi código es:

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

Actualización: el objeto con el que intento reemplazar tiene muchas propiedades como

{id: 1, Prop1: ..., Prop2: ..., y así sucesivamente}

Solución:

Gracias a dfsq, pero encontré una solución adecuada dentro de lodash que parece funcionar bien y es bastante ordenada, y también la puse en un mixin ya que tengo este requisito en muchos lugares. 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));

Solución más rápida Como señaló @dfsq, seguir es mucho más rápido

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
fuente
77
Creo que también puede usar match como el segundo parámetro para _.indexOf en la línea 4 de su "Solución más rápida", no es necesario volver a calcular ese valor allí, lo que debería hacer las cosas un poco más rápido.
davertron
2
Aún más rápido: usar _.findIndexpara el partido.
Julian K
1
Solo para expandir lo que dijeron @JulianK y @davertron, usar en _.findIndexlugar de _.findte permitirá soltar tanto el segundo _.findcomo el _.indexOf. Estás iterando a través de la matriz 3 veces cuando todo lo que necesitas es 1.
Justin Morgan

Respuestas:

191

En su caso, todo lo que necesita hacer es encontrar el objeto en la matriz y usar el Array.prototype.splice()método, lea más detalles aquí :

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
fuente
1
Bueno, su solución costará más en términos de rendimiento, ya indexOfque será muy rápida (utilizará los navegadores nativos Array.prototype.indexOf). Pero de todos modos, me alegra que encuentre una solución que funcione para usted.
dfsq
14
¿Por qué no usar _.findIndex? Entonces no necesitas usar _.indexOf.
AJ Richardson
35

Parece que la solución más simple sería usar 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;
});

Esto tiene el buen efecto de evitar la mutación de la matriz original.

Spencer
fuente
8
Pero estás creando una nueva matriz cada vez ... Vale la pena señalar.
kboom
3
Sin embargo, la única alternativa para no crear una nueva matriz es mutar la existente. Además, la creación de una nueva matriz probablemente no tendrá ningún impacto en términos de rendimiento. Voto de mi parte.
Nobita
24

[ES6] Este código funciona para mí.

let result = array.map(item => item.id === updatedItem.id ? updatedItem : item)
shebik
fuente
1. está creando una nueva instancia de matriz, por lo que no es cierto "reemplazar" un elemento. 2. perderá su updatedItemmatriz si no incluye un elemento con el mismo id.
malvado
esta es una solución para 'actualizar' no 'actualizar' (la pregunta era "¿hay una función en lodash para reemplazar el elemento COINCIDIDO") y sí, crea una copia de la matriz, así que no la use si necesita trabajar con misma matriz (no lo hice)
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);
}

Ahora probemos el rendimiento para todos los métodos:

maligno
fuente
66
Votados negativamente porque ser negativo con las personas ("es una especie de broma") les impide aprender. Imagina que si terminara esto con "ser una persona inteligente, esperaría que no seas flojo con tus emociones y pienses en eso".
Aditya MP
55
No quería lastimar a nadie, pero me pregunto cómo la solución que peor que el enfoque original del creador del tema obtuvo tantos votos. ¿Qué reglas gobiernan las personas que votaron por eso? Y decepcioné que la gente confía ciegamente en la respuesta más votada y no tiene un pensamiento crítico.
malvado
1
@evilive Puntos válidos, pero no veo cómo esos requieren que te encuentres como si todos los que previamente proporcionaron respuestas / votos fueran idiotas. Las partes objetivas de esta respuesta son geniales, el resto tiene el aire de un complejo de superioridad apenas contenido. Eso no ayuda a nadie. Puede expresar fácilmente sus puntos sin la respuesta emocional excesiva.
Thor84no
1
Sin embargo, vale la pena señalar que su solución y la solución de TC solo se filtran por ID. Esa es la primera razón por la que esos dos están corriendo más rápido. Los otros dos le permiten pasar cualquier parte del objeto que necesita para filtrar, lo que podría ser más preferible como función upsert.
Aram
10

También puede usar findIndex y pick para lograr el mismo resultado:

  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
fuente
6

A medida que pasa el tiempo, debe adoptar un enfoque más funcional en el que debe evitar las mutaciones de datos y escribir pequeñas funciones de responsabilidad única. Con el estándar ECMAScript 6, se puede disfrutar paradigma de programación funcional en JavaScript con los previstos map, filtery reducemétodos. No necesita otro lodash, guión bajo o qué más hacer para hacer las cosas más básicas.

A continuación, he incluido algunas soluciones propuestas para este problema con el fin de mostrar cómo se puede resolver este problema utilizando diferentes características del lenguaje:

Usando el mapa 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)


Versión recursiva - equivalente de mapeo:

Requiere desestructuración y dispersión de la matriz .

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)


Cuando la orden de la matriz final no es importante que se puede utilizar una objectcomo un HashMap estructura de datos. Muy útil si ya tiene una colección con clave como object, de lo contrario, primero debe cambiar su representación.

Requiere propagación de reposo de objetos , nombres de propiedades calculados y entradas de objeto .

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
fuente
5

Si solo está tratando de reemplazar una propiedad, lodash _.findy _.setdebería ser suficiente:

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

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

Si el punto de inserción del nuevo objeto no necesita coincidir con el índice del objeto anterior, entonces la forma más simple de hacer esto con lodash es usar _.rejecty luego introducir nuevos valores en la matriz:

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

Si tiene varios valores que desea reemplazar en una sola pasada, puede hacer lo siguiente (escrito en formato que no sea 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
fuente
este método cambia la clasificación de la matriz
sospedra
1

Usando la función lodash unionWith, puede realizar una inserción simple a un objeto. La documentación indica que si hay una coincidencia, usará la primera matriz. Envuelva su objeto actualizado en [] (matriz) y colóquelo como la primera matriz de la función de unión. Simplemente especifique su lógica de coincidencia y, si la encuentra, la reemplazará y, si no, la agregará

Ejemplo:

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
fuente
1

No está mal la variante también)

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
fuente
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'}
Amit Chhatbar
fuente
0

Si está buscando una manera de cambiar de manera inmutable la colección (como estaba cuando encontré su pregunta), puede echar un vistazo a inmutability-helper , una biblioteca bifurcada de la utilidad React original. En su caso, lograría lo que mencionó a través de lo siguiente:

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
fuente
0

Puedes hacerlo sin usar 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") 
Soleado
fuente
0

Inmutable , adecuado para ReactJS:

Asumir:

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

El elemento actualizado es el segundo y el nombre se cambia a Special Person:

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

Sugerencia : el lodash tiene herramientas útiles, pero ahora tenemos algunas de ellas en Ecmascript6 +, así que solo uso lamapfunción que existe en amboslodashyecmascript6+:

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

Encontré esto también y lo hizo simplemente de esa manera.

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

Si lo desea podemos generalizarlo

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

replaceWhere(persons, person => person.id === updatedPerson.id, updatedPerson)
rmmjohann
fuente