在lodash中有一个函数来替换匹配的项目

时间:2014-12-24 20:06:31

标签: javascript lodash

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

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

我的代码是:

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

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

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

解决方案:

感谢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));

更快的解决方案 正如@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);
    }
};

16 个答案:

答案 0 :(得分:143)

在您的情况下,您需要做的就是在数组中查找对象并使用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>
&#13;
&#13;
&#13;

答案 1 :(得分:19)

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

这样可以避免改变原始数组。

答案 2 :(得分:18)

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

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

&#13;
&#13;
// 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"));
&#13;
<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>
&#13;
&#13;
&#13;

答案 3 :(得分:15)

[ES6] 此代码适合我。

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

答案 4 :(得分:9)

您也可以使用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);
  }

答案 5 :(得分:5)

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

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

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

递归版 - 相当于映射:

需要destructuringarray 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)

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

需要object rest spreadcomputed property namesObject.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)

答案 6 :(得分:2)

如果您只是尝试替换一个属性,则lodash _.find_.set就足够了:

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

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

答案 7 :(得分:1)

如果新对象的插入点不需要与前一个对象的索引匹配,那么使用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" }]

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

答案 8 :(得分:1)

您可以不使用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") 

答案 9 :(得分:1)

同样也可以做到这一点。

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

如果需要,我们可以将其概括化

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

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

答案 10 :(得分:1)

如果你想创建一个函数并保持“lodash-ey”,你可以创建一个与回调一起工作的包装函数。它使功能更通用。

写这个试试像

function findAllAndReplace(array, replacement, callback){
    return array.map( element => callback(element) ? replacement : element )
}

要按键查找和替换,只需使您的回调非常简单。 (itemInArray) => itemInArray.keyOnItem

但是如果您想要更高级的功能,您几乎可以毫不费力地将其合并。以下是一些示例。

  1. (简单)找到 id 为 2 的项目,将其替换为 id:7
const items = [{id: 1}, {id: 2}, {id: 3}, {id: 4}, {id: 5}]

findAllAndReplace( items, {id: 7}, item => item.id === 2 )
  1. (稍微复杂一点)找到 28 岁的 John,并用 28 岁的 Jon 代替他

const people = [
    {
        name: "John",
        age: 20
    },
    {
        name: "John",
        age: 28
    },
    {
        name: "Jim",
        age: 28
    },
]


findAllAndReplace(
    people, // all the people
    { name: "Jon", age: 28 }, // Replacement value
    (person) => person.name === "jon" && person.age === 21 // callback function
)

此外,上述方法会找到所有匹配并替换它们的实例,但如果您只想为一个实例执行此操作,您可以执行以下操作。

function findOneAndReplace(array, replacement, callback){
    const splitIndex = array.findIndex(callback)
    
    // This if statement can be ommitted, but might 
    // be handy depending on your use case
    if(splitIndex < 0){
        throw new Error("Swap Element not found")
    }

    const leadingTerms = array.slice(0, splitIndex)
    const trailingTerms = array.slice(splitIndex + 1, array.length)
    return [...leadingTerms, replacement, ...trailingTerms]
)

注意:如果没有找到匹配的元素,让你的函数中断可能会很有用,但如果你不想要那个特性,你可以删掉那些代码行。

答案 11 :(得分:0)

如果您正在寻找一种不可改变的方式来更改集合(就像我在找到您的问题时那样),您可以查看immutability-helper,一个从原始React util派生的库。在您的情况下,您将通过以下方式完成您提到的内容:

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

答案 12 :(得分:0)

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

示例:

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)

答案 13 :(得分:0)

变体也不错)

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

答案 14 :(得分:0)

var arr= [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];
var index = _.findIndex(arr, {id: 1});
arr[index] = {id: 100, name: 'xyz'}

答案 15 :(得分:0)

不可修改,适用于ReactJS

假设:

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

更新后的项目是第二个,名称更改为Special Person

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

提示 lodash有有用的工具,但是现在我们在Ecmascript6 +上有其中一些工具,所以我只使用map函数lodashecmascript6+上都存在:

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