【问题标题】:is there a function in lodash to replace matched itemlodash中是否有替换匹配项的功能
【发布时间】:2015-02-22 20:33:56
【问题描述】:

我想知道 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);
    }
};

【问题讨论】:

  • 我认为您也可以在“更快的解决方案”的第 4 行使用 match 作为 _.indexOf 的第二个参数,无需在那里重新计算该值,这应该会使事情变得更快。
  • 更快:使用_.findIndex 进行匹配。
  • 只是为了扩展@JulianK 和@davertron 所说的内容,使用_.findIndex 而不是_.find 将让您放弃第二个_.find 和_.indexOf。当您只需要 1 次时,您将遍历数组 3 次。

标签: javascript lodash


【解决方案1】:

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

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>

【讨论】:

  • 嗯,你的解决方案在性能方面会花费更多,因为indexOf 会非常快(它将使用原生浏览器 Array.prototype.indexOf)。但无论如何,很高兴您找到适合您的解决方案。
  • 为什么不使用_.findIndex?那你就不用_.indexOf了。
  • 实际上使用find 而不是findIndex 更有意义。在大多数情况下,当您想要更新一个对象时,您只想更新一个或两个属性。当只需要更新一两个属性时,动态重新创建对象是没有意义的。也就是说,我并不是说你应该使用indexOf。我想我想说的是另一个答案更好。
【解决方案2】:

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

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

【讨论】:

  • 但是您每次都在创建一个新数组...值得注意。
  • 不创建新数组的唯一选择是改变现有数组。此外,创建新阵列可能不会对性能产生影响。给我点赞。
  • @kboom 有时您不想改变现有数组,而是创建一个新数组。
【解决方案3】:

[ES6] 这段代码适合我。

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

【讨论】:

  • 1.您正在创建一个新的数组实例,因此它不是真正的“替换”一个项目。 2. 如果数组不包含具有相同id 的项目,您将丢失updatedItem。
  • 这是 'update' 而不是 'upsert' 的解决方案(问题是“lodash 中是否有替换 MATCHED 项的函数”),是的,它创建了数组的副本,所以不要如果您需要使用相同的数组,请使用它(我没有)
【解决方案4】:
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);
}

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

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

【讨论】:

  • 投反对票是因为对人持消极态度(“这是某种玩笑”)使他们无法学习。想象一下,如果我以“作为一个聪明的人,我希望你不要对自己的情绪感到懒惰并考虑这一点”来完成这件事。
  • 我不想伤害任何人,但我想知道比原始主题创建者的方法更糟糕的解决方案如何获得如此多的选票。投票支持的人是什么规则?我对人们盲目相信投票最多的答案而没有批判性思维感到失望。
  • @evilive 有效点,但我不明白这些要求您如何看待,好像以前提供答案/投票的每个人都是白痴。这个答案的事实部分很棒,其余部分则具有勉强容纳的优越感。这对任何人都没有帮助。您可以轻松地表达自己的观点,而不会引起过度的情绪反应。
  • 值得注意的是,您的解决方案和 TC 的解决方案仅按 id 过滤。这是这两个运行得更快的第一个原因。另外两个允许您传递过滤所需的对象的任何部分,这可能更适合作为 upsert 函数。
【解决方案5】:

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

【讨论】:

    【解决方案6】:

    如果你只是想替换一个属性,lodash _.find 和 _.set 应该足够了:

    var arr = [{id: 1, name: "Person 1"}, {id: 2, name: "Person 2"}];
    
    _.set(_.find(arr, {id: 1}), 'name', 'New Person');
    

    【讨论】:

    • 这是最好的,因为我们中的一些人只想更新一个键值。这是这样做的。谢谢
    【解决方案7】:

    随着时间的推移,您应该采用一种功能更强大的方法,在这种方法中您应该避免数据突变并编写小型、单一职责的函数。借助 ECMAScript 6 标准,您可以使用提供的 map、filter 和 reduce 方法享受 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)

    递归版本 - 相当于映射:

    需要destructuring 和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)

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

    需要object rest spread、computed property names 和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)

    【讨论】:

      【解决方案8】:

      也遇到过这个问题,就是这么干的。

      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)
      

      【讨论】:

        【解决方案9】:
        var arr= [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];
        var index = _.findIndex(arr, {id: 1});
        arr[index] = {id: 100, name: 'xyz'}
        

        【讨论】:

          【解决方案10】:

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

          【讨论】:

          • 这个方法改变了数组排序
          【解决方案11】:

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

          【讨论】:

            【解决方案12】:

            如果您想创建一个函数并将其保留为“lodash-ey”,您可以创建一个与回调一起使用的包装函数。它使函数更通用。

            要写这个尝试类似

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

            要按键查找和替换,只需使您的回调非常简单。 (itemInArray) =&gt; 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. (稍微复杂一点)找到名叫 John 的 28 岁的人,并用一个名叫 Jon 的 28 岁的人替换他
            
            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]
            )
            

            注意:如果找不到匹配的元素,让函数中断可能会很有用,但如果您不想要该功能,则可以删除这些代码行。

            【讨论】:

              【解决方案13】:

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

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

              【讨论】:

                【解决方案14】:

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

                【讨论】:

                  【解决方案15】:

                  变种也不错)

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

                  【讨论】:

                    【解决方案16】:

                    不可变,适用于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 函数lodash 和 ecmascript6+:

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

                    【讨论】:

                      猜你喜欢
                      • 2016-05-31
                      • 1970-01-01
                      • 2013-05-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2023-04-04
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多