【问题标题】:Removing elements with Array.map in JavaScript在 JavaScript 中使用 Array.map 删除元素
【发布时间】:2010-09-05 18:36:22
【问题描述】:

我想使用map() 函数过滤一组项目。这是一个代码sn-p:

var filteredItems = items.map(function(item)
{
    if( ...some condition... )
    {
        return item;
    }
});

问题是过滤掉的项目仍然使用数组中的空间,我想完全清除它们。

有什么想法吗?

编辑:谢谢,我忘了filter(),我想要的实际上是filter(),然后是map()

EDIT2:感谢您指出map()filter() 并未在所有浏览器中实现,尽管我的特定代码不打算在浏览器中运行。

【问题讨论】:

  • 您能否详细说明为什么 2 次迭代比 1 次最差?我的意思是,2*O(n) 对我来说相当于 O(2*n)...
  • 在一个函数中过滤和映射通常很有用,不是出于性能原因,而是为了减少重复代码。事实上,Swift 内置了它的 compactMap 函数。请参阅this answer 了解更多信息。

标签: javascript functional-programming data-manipulation client-side


【解决方案1】:

受写这个答案的启发,我后来扩展并写了一篇博客文章,详细讨论了这个问题。如果你想更深入地理解如何思考这个问题,我推荐checking that out——我尝试逐条解释,并在最后给出一个 JSperf 比较,从速度考虑。

也就是说,** tl;dr 是这样的:

要完成您的要求(在一个函数调用中进行过滤和映射),您可以使用Array.reduce()**。

然而,可读性更强 (不太重要)通常明显更快2方法是只需使用链接在一起的过滤器和地图:

[1,2,3].filter(num => num > 2).map(num => num * 2)

以下是对Array.reduce() 工作原理的描述,以及如何使用它在一次迭代中完成过滤和映射。同样,如果这太浓缩了,我强烈建议您查看上面链接的博客文章,这是一个更友好的介绍,具有清晰的示例和进展。


你给 reduce 一个参数,它是一个(通常是匿名的)函数。

那个匿名函数有两个参数——一个(比如传入map/filter/forEach的匿名函数)是要操作的迭代对象。然而,传递给 reduce 的匿名函数还有另一个参数,即那些函数不接受,这就是 将在函数调用之间传递的值,通常称为 memo

请注意,虽然 Array.filter() 只接受一个参数(一个函数),但 Array.reduce() 还接受一个重要的(尽管是可选的)第二个参数:'memo' 的初始值,它将被传递给该匿名函数作为它的第一个参数,随后可以在函数调用之间进行变异和传递。 (如果未提供,则第一个匿名函数调用中的 'memo' 默认为第一个 iteratee,而 'iteratee' 参数实际上是数组中的第二个值)

在我们的例子中,我们将传入一个空数组开始,然后根据我们的函数选择是否将迭代器注入到我们的数组中——这就是过滤过程。

最后,我们将在每个匿名函数调用中返回“正在进行的数组”,reduce 将获取该返回值并将其作为参数(称为 memo)传递给下一个函数调用。

这允许过滤器和映射在一次迭代中发生,将我们所需的迭代次数减少了一半——尽管每次迭代只做两倍的工作,所以除了函数调用之外什么都没有真正保存,而函数调用并非如此在 javascript 中很昂贵。

如需更完整的解释,请参阅MDN docs(或此答案开头引用的我的帖子)。

Reduce 调用的基本示例:

let array = [1,2,3];
const initialMemo = [];

array = array.reduce((memo, iteratee) => {
    // if condition is our filter
    if (iteratee > 1) {
        // what happens inside the filter is the map
        memo.push(iteratee * 2); 
    }

    // this return value will be passed in as the 'memo' argument
    // to the next call of this function, and this function will have
    // every element passed into it at some point.
    return memo; 
}, initialMemo)

console.log(array) // [4,6], equivalent to [(2 * 2), (3 * 2)]

更简洁的版本:

[1,2,3].reduce((memo, value) => value > 1 ? memo.concat(value * 2) : memo, [])

请注意,第一个 iteratee 不大于 1,因此被过滤掉了。还要注意 initialMemo,命名只是为了明确它的存在并引起人们的注意。再次,它作为 'memo' 传递给第一个匿名函数调用,然后匿名函数的返回值作为 'memo' 参​​数传递给下一个函数。

memo 的另一个经典用例示例是返回数组中的最小或最大数字。示例:

[7,4,1,99,57,2,1,100].reduce((memo, val) => memo > val ? memo : val)
// ^this would return the largest number in the list.

如何编写自己的 reduce 函数的示例(我发现这通常有助于理解此类函数):

test_arr = [];

// we accept an anonymous function, and an optional 'initial memo' value.
test_arr.my_reducer = function(reduceFunc, initialMemo) {
    // if we did not pass in a second argument, then our first memo value 
    // will be whatever is in index zero. (Otherwise, it will 
    // be that second argument.)
    const initialMemoIsIndexZero = arguments.length < 2;

    // here we use that logic to set the memo value accordingly.
    let memo = initialMemoIsIndexZero ? this[0] : initialMemo;

    // here we use that same boolean to decide whether the first
    // value we pass in as iteratee is either the first or second
    // element
    const initialIteratee = initialMemoIsIndexZero ? 1 : 0;

    for (var i = initialIteratee; i < this.length; i++) {
        // memo is either the argument passed in above, or the 
        // first item in the list. initialIteratee is either the
        // first item in the list, or the second item in the list.
           memo = reduceFunc(memo, this[i]);
        // or, more technically complete, give access to base array
        // and index to the reducer as well:
        // memo = reduceFunc(memo, this[i], i, this);
    }

    // after we've compressed the array into a single value,
    // we return it.
    return memo;
}

例如,真正的实现允许访问诸如索引之类的东西,但我希望这可以帮助您对它的要点有一个简单的感觉。

【讨论】:

  • 太棒了!多年来我一直想做这样的事情。决定尝试找出一个不错的方法,哇,自然的 javascript!
  • reduce 的另一个用处是,与filter + map 不同,回调可以传递一个索引参数,该参数是原始数组的索引,而不是过滤后数组的索引.
  • @KyleBaker 指向您博客文章的链接指向未找到的页面。你能更新链接吗?谢谢!
【解决方案2】:

TLDR:使用map(在需要时返回undefined)和然后 filter


首先,我认为 map + filter 函数很有用,因为您不想在两者中重复计算。 Swift 最初将此函数称为 flatMap,但后来将其重命名为 compactMap

例如,如果我们没有compactMap 函数,我们最终可能会定义两次computation

  let array = [1, 2, 3, 4, 5, 6, 7, 8];
  let mapped = array
  .filter(x => {
    let computation = x / 2 + 1;
    let isIncluded = computation % 2 === 0;
    return isIncluded;
  })
  .map(x => {
    let computation = x / 2 + 1;
    return `${x} is included because ${computation} is even`
  })

  // Output: [2 is included because 2 is even, 6 is included because 4 is even]

因此compactMap 将有助于减少重复代码。

类似于compactMap 的一个非常简单的方法是:

  1. 映射实际值或undefined
  2. 过滤掉所有undefined 值。

这当然依赖于您永远不需要将未定义的值作为原始地图函数的一部分返回。

例子:

  let array = [1, 2, 3, 4, 5, 6, 7, 8];
  let mapped = array
  .map(x => {
    let computation = x / 2 + 1;
    let isIncluded = computation % 2 === 0;
    if (isIncluded) {
      return `${x} is included because ${computation} is even`
    } else {
      return undefined
    }
  })
  .filter(x => typeof x !== "undefined")

【讨论】:

  • 或者你可以使用reduce来防止重复计算。
【解决方案3】:

这不是地图的作用。你真的想要Array.filter。或者,如果您真的想从原始列表中删除元素,则需要使用 for 循环强制执行。

【讨论】:

    【解决方案4】:

    首先你可以使用 map 和链接你可以使用 filter

    state.map(item => {
                if(item.id === action.item.id){   
                        return {
                            id : action.item.id,
                            name : item.name,
                            price: item.price,
                            quantity : item.quantity-1
                        }
    
                }else{
                    return item;
                }
            }).filter(item => {
                if(item.quantity <= 0){
                    return false;
                }else{
                    return true;
                }
            });
    

    【讨论】:

      【解决方案5】:

      我刚刚写了正确处理重复的数组交集

      https://gist.github.com/gkucmierz/8ee04544fa842411f7553ef66ac2fcf0

      // array intersection that correctly handles also duplicates
      
      const intersection = (a1, a2) => {
        const cnt = new Map();
        a2.map(el => cnt[el] = el in cnt ? cnt[el] + 1 : 1);
        return a1.filter(el => el in cnt && 0 < cnt[el]--);
      };
      
      const l = console.log;
      l(intersection('1234'.split``, '3456'.split``)); // [ '3', '4' ]
      l(intersection('12344'.split``, '3456'.split``)); // [ '3', '4' ]
      l(intersection('1234'.split``, '33456'.split``)); // [ '3', '4' ]
      l(intersection('12334'.split``, '33456'.split``)); // [ '3', '3', '4' ]

      【讨论】:

        【解决方案6】:

        以下语句使用 map 函数清理对象。

        var arraytoclean = [{v:65, toberemoved:"gronf"}, {v:12, toberemoved:null}, {v:4}];
        arraytoclean.map((x,i)=>x.toberemoved=undefined);
        console.dir(arraytoclean);
        

        【讨论】:

          【解决方案7】:

          数组Filter method

          var arr = [1, 2, 3]
          
          // ES5 syntax
          arr = arr.filter(function(item){ return item != 3 })
          
          // ES2015 syntax
          arr = arr.filter(item => item != 3)
          
          console.log( arr )

          【讨论】:

          • 你也可以var arr = [1,2,"xxx", "yyy"]; arr = arr.filter(function(e){ return e!="xxx" }) console.log(arr)
          • 你 4 年后回来添加大文本?减一
          • @user633183 你指的是谁?什么“大文本”?你的评论不清楚。你确定你在正确的地方发表评论...?
          【解决方案8】:

          您必须注意,并非所有浏览器都支持Array.filter,因此您必须进行原型设计:

          //This prototype is provided by the Mozilla foundation and
          //is distributed under the MIT license.
          //http://www.ibiblio.org/pub/Linux/LICENSES/mit.license
          
          if (!Array.prototype.filter)
          {
              Array.prototype.filter = function(fun /*, thisp*/)
              {
                  var len = this.length;
          
                  if (typeof fun != "function")
                      throw new TypeError();
          
                  var res = new Array();
                  var thisp = arguments[1];
          
                  for (var i = 0; i < len; i++)
                  {
                      if (i in this)
                      {
                          var val = this[i]; // in case fun mutates this
          
                          if (fun.call(thisp, val, i, this))
                             res.push(val);
                      }
                  }
          
                  return res;
              };
          }
          

          这样做,您可以为您可能需要的任何方法制作原型。

          【讨论】:

          • 如果你真的打算使用 polyfill 这个方法,请使用合适的 polyfill,或者更好的是像 Modernizr 这样的库。否则,您很可能会在一些晦涩难懂的浏览器中遇到令人困惑的错误,直到它们投入生产太长时间后您才会意识到。
          【解决方案9】:

          您应该使用filter 方法而不是映射,除非您想改变数组中的项目,除了过滤。

          例如。

          var filteredItems = items.filter(function(item)
          {
              return ...some condition...;
          });
          

          [编辑:当然你总是可以对过滤和变异执行sourceArray.filter(...).map(...)]

          【讨论】:

          • map 不会变异
          • 但是你可以在map中变异。
          • 小心这一点:当你用 map 改变某些东西时,JS 会传递引用,它会改变对象,但按照 MDN 的立场,maps 返回变异的数组。
          • 问题没有问如何过滤,问题是问如何在地图上删除
          • @alexOtano 不,map 不会发生变异,也不会返回变异数组。它返回一个新数组。例如,x=[1,2,3];y = x.map(z =&gt; z*2);console.log(x,y);
          猜你喜欢
          • 1970-01-01
          • 2020-03-22
          • 2019-06-27
          • 2012-04-15
          • 1970-01-01
          • 2011-08-21
          • 1970-01-01
          • 2013-03-05
          相关资源
          最近更新 更多