Javascript lodash 中是否有替换匹配项的函数

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/27641731/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 00:27:19  来源:igfitidea点击:

is there a function in lodash to replace matched item

javascriptlodash

提问by Vishal Seth

I wonder if there is a simpler method in lodash to replace an item in a JavaScript collection? (Possible duplicatebut I did not understand the answer there:)

我想知道 lodash 中是否有更简单的方法来替换 JavaScript 集合中的项目?(可能重复,但我不明白那里的答案:)

I looked at their documentation but could not find anything

我查看了他们的文档,但找不到任何内容

My code is:

我的代码是:

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

Update:The object I'm trying to replace with has many properties like

更新:我试图替换的对象有很多属性,比如

{id: 1, Prop1: ..., Prop2:..., and so on}

{id: 1, Prop1: ..., Prop2:..., 等等}

Solution:

解决方案:

Thanks to dfsqbut I found a proper solution within lodash that seems to work fine and is pretty neat and I put it in a mixin as well since I've this requirement at many places. JSBin

感谢dfsq但我在 lodash 中找到了一个合适的解决方案,它似乎工作正常并且非常整洁,我也把它放在了一个 mixin 中,因为我在很多地方都有这个要求。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));

Faster SolutionAs pointed out by @dfsq, following is way faster

更快的解决方案正如@dfsq 所指出的,以下方法更快

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

回答by dfsq

In your case all you need to do is to find object in array and use Array.prototype.splice()method, read more details here:

在您的情况下,您需要做的就是在数组中查找对象并使用Array.prototype.splice()方法,请在此处阅读更多详细信息:

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>

回答by Spencer

Seems like the simplest solution would to use ES6's .mapor lodash's _.map:

似乎最简单的解决方案是使用 ES6.map或 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;
});

This has the nice effect of avoiding mutating the original array.

这具有避免改变原始数组的良好效果。

回答by shebik

[ES6]This code works for me.

[ES6]这段代码对我有用

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

回答by evilive

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

Now let's test performance for all methods:

现在让我们测试所有方法的性能:

// TC's first approach
function first(arr, a, b) {
  _.each(arr, function (x, idx) {
    if (x.id === a.id) {
      arr[idx] = b;
      return false;
    }
  });
}

// solution with merge
function second(arr, a, b) {
  const match = _.find(arr, a);
  if (match) {
    _.merge(match, b);
  } else {
    arr.push(b);
  }
}

// most voted solution
function third(arr, a, b) {
  const match = _.find(arr, a);
  if (match) {
    var index = _.indexOf(arr, _.find(arr, a));
    arr.splice(index, 1, b);
  } else {
    arr.push(b);
  }
}

// my approach
function fourth(arr, a, b){
  let l;
  for(l=0; l < arr.length && arr[l].id != a.id; l++) {}
  l < arr.length ? arr[l] = b : arr.push(b);
}

function test(fn, times, el) {
  const arr = [], size = 250;
  for (let i = 0; i < size; i++) {
    arr[i] = {id: i, name: `name_${i}`, test: "test"};
  }

  let start = Date.now();
  _.times(times, () => {
    const id = Math.round(Math.random() * size);
    const a = {id};
    const b = {id, name: `${id}_name`};
    fn(arr, a, b);
  });
  el.innerHTML = Date.now() - start;
}

test(first, 1e5, document.getElementById("first"));
test(second, 1e5, document.getElementById("second"));
test(third, 1e5, document.getElementById("third"));
test(fourth, 1e5, document.getElementById("fourth"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.1/lodash.min.js"></script>
<div>
  <ol>
    <li><b id="first"></b> ms [TC's first approach]</li>
    <li><b id="second"></b> ms [solution with merge]</li>
    <li><b id="third"></b> ms [most voted solution]</li>
    <li><b id="fourth"></b> ms [my approach]</li>
  </ol>
<div>

回答by JVitela

You can also use findIndex and pick to achieve the same result:

您还可以使用 findIndex 和 pick 来实现相同的结果:

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

回答by Przemys?aw Zalewski

As the time passes you should embrace a more functional approach in which you should avoid data mutations and write small, single responsibility functions. With the ECMAScript 6 standard, you can enjoy functional programming paradigm in JavaScript with the provided map, filterand reducemethods. You don't need another lodash, underscore or what else to do most basic things.

随着时间的推移,您应该采用更实用的方法,在这种方法中,您应该避免数据突变并编写小的、单一职责的函数。使用 ECMAScript 6 标准,您可以使用 JavaScript 提供的map,filterreduce方法享受函数式编程范式。你不需要另一个 lodash、下划线或其他什么来做最基本的事情。

Down below I have included some proposed solutions to this problem in order to show how this problem can be solved using different language features:

下面我列出了一些针对此问题的建议解决方案,以展示如何使用不同的语言功能解决此问题:

Using ES6 map:

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



Recursive version - equivalent of mapping:

递归版本 - 相当于映射:

Requires destructuringand array spread.

需要解构数组展开

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)



When the final array's order is not important you can use an objectas a HashMapdata structure. Very handy if you already have keyed collection as an object- otherwise you have to change your representation first.

当最终数组的顺序不重要时,您可以使用 anobject作为HashMap数据结构。如果您已经将键控集合作为一个object,则非常方便- 否则您必须先更改您的表示。

Requires object rest spread, computed property namesand Object.entries.

需要对象其余扩展计算属性名称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)

回答by Andrei Gavrilov

If you're just trying to replace one property, lodash _.findand _.setshould be enough:

如果你只是想更换一个属性,lodash_.find_.set应该足够:

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

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

回答by richt

If the insertion point of the new object does not need to match the previous object's index then the simplest way to do this with lodash is by using _.rejectand then pushing new values in to the array:

如果新对象的插入点不需要匹配前一个对象的索引,那么使用 lodash 执行此操作的最简单方法是使用_.reject然后将新值推送到数组中:

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

If you have multiple values that you want to replace in one pass, you can do the following (written in non-ES6 format):

如果您想一次性替换多个值,您可以执行以下操作(以非 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" }]

回答by Jeffrey

Using lodash unionWith function, you can accomplish a simple upsert to an object. The documentation states that if there is a match, it will use the first array. Wrap your updated object in [ ] (array) and put it as the first array of the union function. Simply specify your matching logic and if found it will replace it and if not it will add it

使用 lodash unionWith 函数,您可以完成对对象的简单 upsert。文档指出,如果有匹配项,它将使用第一个数组。将更新后的对象包装在 [ ](数组)中,并将其作为 union 函数的第一个数组。只需指定您的匹配逻辑,如果找到它将替换它,如果没有它会添加它

Example:

例子:

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)

回答by Ivan Pirus

Not bad variant too)

变种也不错)

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';