y a-t-il une fonction dans lodash pour remplacer l'élément correspondant

134

Je me demande s'il existe une méthode plus simple dans lodash pour remplacer un élément dans une collection JavaScript? (Possible duplicata mais je n'ai pas compris la réponse :)

J'ai regardé leur documentation mais je n'ai rien trouvé

Mon code est:

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

Mise à jour: l'objet que j'essaie de remplacer a de nombreuses propriétés comme

{id: 1, Prop1: ..., Prop2: ..., et ainsi de suite}

Solution:

Merci à dfsq, mais j'ai trouvé une solution appropriée dans lodash qui semble fonctionner correctement et qui est assez soignée et je la mets également dans un mixin puisque j'ai cette exigence à de nombreux endroits. 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));

Solution plus rapide Comme l'a souligné @dfsq, suivre est beaucoup plus rapide

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
la source
7
Je pense que vous pouvez également utiliser match comme deuxième paramètre de _.indexOf sur la liine 4 de votre "Faster Solution", pas besoin de recalculer cette valeur là-bas, ce qui devrait rendre les choses un peu plus rapides.
davertron le
2
Encore plus rapide: utilisez _.findIndexpour le match.
Julian K
1
Juste pour développer ce que @JulianK et @davertron ont dit, utiliser _.findIndexau lieu de _.findvous permettra de supprimer à la fois le second _.findet le _.indexOf. Vous parcourez le tableau 3 fois alors que tout ce dont vous avez besoin est de 1.
Justin Morgan

Réponses:

191

Dans votre cas, tout ce que vous avez à faire est de trouver un objet dans un tableau et d'utiliser la Array.prototype.splice()méthode, lisez plus de détails ici :

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
la source
1
Et bien votre solution va coûter plus cher en termes de performances, puisqu'elle indexOfva être très rapide (elle utilisera les navigateurs natifs Array.prototype.indexOf). Mais quoi qu'il en soit, heureux que vous trouviez une solution qui fonctionne pour vous.
dfsq
14
Pourquoi ne pas l'utiliser _.findIndex? Ensuite, vous n'avez pas besoin d'utiliser _.indexOf.
AJ Richardson
35

On dirait que la solution la plus simple consisterait à utiliser ES6 .mapou 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;
});

Cela a pour effet d'éviter de muter le tableau d'origine.

Spencer
la source
8
Mais vous créez un nouveau tableau à chaque fois ... À noter.
kboom
3
La seule alternative à ne pas créer de nouveau tableau est la mutation de l'existant. De plus, la création d'une nouvelle baie n'aura probablement aucun impact en termes de performances. Un vote positif de ma part.
Nobita
24

[ES6] Ce code fonctionne pour moi.

let result = array.map(item => item.id === updatedItem.id ? updatedItem : item)
shebik
la source
1. vous créez une nouvelle instance de tableau, il n'est donc pas vrai de "remplacer" un élément. 2. vous perdrez votre updatedItemtableau if n'inclut pas un élément avec le même id.
Evilive
c'est une solution pour 'update' et non 'upsert' (la question était "y a-t-il une fonction dans lodash pour remplacer l'élément MATCHED") et oui, cela crée une copie du tableau, donc ne l'utilisez pas si vous devez travailler avec même tableau (je n'ai pas)
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);
}

Testons maintenant les performances de toutes les méthodes:

méchant
la source
6
Évalué parce qu'être négatif avec les gens («est-ce une sorte de blague») les empêche d'apprendre. Imaginez si je finissais cela par «être une personne intelligente, je m'attendrais à ce que vous ne soyez pas paresseux à propos de vos émotions et que vous y pensiez».
Aditya MP
5
Je ne voulais faire de mal à personne, mais je me demande comment la solution qui est pire que l'approche originale du créateur du sujet a obtenu autant de votes. Quelles règles les gens qui ont voté pour cela? Et j'ai déçu que les gens fassent aveuglément confiance à la réponse la plus votée et n'aient pas de pensée critique.
evilive
1
@evilive Points valides, mais je ne vois pas comment ceux-ci vous obligent à croire que tous ceux qui ont précédemment fourni des réponses / votes étaient des idiots. Les parties factuelles de cette réponse sont excellentes, le reste a l'air d'un complexe de supériorité à peine contenu. Cela n'aide personne. Vous pouvez facilement faire valoir vos points sans la réponse émotionnelle excessive.
Thor84no
1
Il convient de noter cependant que votre solution et la solution de TC ne filtrent que par ID. C'est la première raison pour laquelle ces deux sont plus rapides. Les deux autres vous permettent de passer n'importe quelle partie de l'objet dont vous avez besoin pour le filtrage, ce qui pourrait être plus préférable en tant que fonction upsert.
Aram
10

Vous pouvez également utiliser findIndex et pick pour obtenir le même résultat:

  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
la source
6

Au fil du temps, vous devez adopter une approche plus fonctionnelle dans laquelle vous devez éviter les mutations de données et écrire de petites fonctions de responsabilité unique. Avec la norme ECMAScript 6, vous pouvez profiter de paradigme de programmation fonctionnelle en JavaScript avec les fournis map, filteret les reduceméthodes. Vous n'avez pas besoin d'un autre lodash, d'un trait de soulignement ou de quoi d'autre pour faire les choses les plus élémentaires.

Ci-dessous, j'ai inclus quelques solutions proposées à ce problème afin de montrer comment ce problème peut être résolu en utilisant différentes fonctionnalités de langage:

Utilisation de la carte 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)


Version récursive - équivalent du mappage:

Nécessite une déstructuration et une répartition du tableau .

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)


Lorsque l'ordre final du tableau n'est pas important, vous pouvez utiliser un objectcomme structure de données HashMap . Très pratique si vous avez déjà une collection de clés en tant que object- sinon vous devez d'abord changer votre représentation.

Nécessite une répartition du reste des objets , des noms de propriétés calculés et des entrées 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
la source
5

Si vous essayez juste de remplacer une propriété, lodash _.findet _.setdevrait suffire:

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

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

Si le point d'insertion du nouvel objet n'a pas besoin de correspondre à l'index de l'objet précédent, le moyen le plus simple de le faire avec lodash consiste à utiliser _.rejectpuis à pousser de nouvelles valeurs dans le tableau:

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 vous souhaitez remplacer plusieurs valeurs en un seul passage, vous pouvez effectuer les opérations suivantes (écrites au format 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" }]
richt
la source
cette méthode modifie le tri des tableaux
sospedra
1

En utilisant la fonction lodash unionWith, vous pouvez effectuer un simple upsert vers un objet. La documentation indique qu'en cas de correspondance, elle utilisera le premier tableau. Enveloppez votre objet mis à jour dans [] (tableau) et placez-le comme premier tableau de la fonction union. Spécifiez simplement votre logique de correspondance et si elle est trouvée, elle la remplacera et sinon elle l'ajoutera

Exemple:

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
la source
1

Pas mal de variante aussi)

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
la source
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
la source
0

Si vous cherchez un moyen de changer immuablement la collection (comme je l'étais lorsque j'ai trouvé votre question), vous pouvez jeter un œil à immutability-helper , une bibliothèque dérivée de l'utilitaire React original. Dans votre cas, vous accomplirez ce que vous avez mentionné via ce qui suit:

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
la source
0

Vous pouvez le faire sans utiliser de 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") 
Ensoleillé
la source
0

Immuable , convient pour ReactJS:

Présumer:

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

L'élément mis à jour est le deuxième et le nom est changé en Special Person:

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

Astuce : le lodash a des outils utiles mais maintenant nous en avons certains sur Ecmascript6 +, donc j'utilise juste unemapfonction qui existe à la fois surlodashetecmascript6+:

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

Je suis tombé sur cela aussi et je l'ai fait simplement de cette façon.

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 on veut on peut le généraliser

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

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