【问题标题】:How to skip over an element in .map()?如何跳过 .map() 中的元素?
【发布时间】:2014-09-08 12:22:25
【问题描述】:

如何跳过.map 中的数组元素?

我的代码:

var sources = images.map(function (img) {
    if(img.src.split('.').pop() === "json"){ // if extension is .json
        return null; // skip
    }
    else{
        return img.src;
    }
});

这将返回:

["img.png", null, "img.png"]

【问题讨论】:

  • 不能,但之后可以过滤掉所有空值。
  • 为什么不呢?我知道使用continue 不起作用,但最好知道为什么(也可以避免双重循环)-编辑-对于您的情况,您不能只反转 if 条件并且仅返回 img.src 如果结果为分裂流行!== json?
  • @GrayedFox 然后隐式undefined 将被放入数组中,而不是null。不是更好......

标签: javascript


【解决方案1】:

这是一个有趣的解决方案:

/**
 * Filter-map. Like map, but skips undefined values.
 *
 * @param callback
 */
function fmap(callback) {
    return this.reduce((accum, ...args) => {
        let x = callback(...args);
        if(x !== undefined) {
            accum.push(x);
        }
        return accum;
    }, []);
}

bind operator一起使用:

[1,2,-1,3]::fmap(x => x > 0 ? x * 2 : undefined); // [2,4,6]

【讨论】:

  • 这种方法使我不必使用单独的mapfilterconcat 调用。
【解决方案2】:

最有效的方法是在一次迭代中同时filter + map 您的输入数组。为此,您需要将您的数组视为iterable(它在内部就是这样)。

下面的答案使用iter-ops 库来处理你的图像:

import {pipe, filter, map} from 'iter-ops';

const result = pipe(
    images,
    filter(img => img.src.split('.').pop() !== 'json'),
    map(img => img.src)
);

console.log('result:', [...result]);

附:我是iter-ops的电子作者。

【讨论】:

    【解决方案3】:

    你可以这样做

    var sources = [];
    images.map(function (img) {
        if(img.src.split('.').pop() !== "json"){ // if extension is not .json
            sources.push(img.src); // just push valid value
        }
    });

    【讨论】:

    • 这不适用于map。相反,您可以在所选答案中使用 forEachreduce
    • Array.map() 的重点是返回结果。所以上面的建议可读性不是很强。
    • 在 Perl 中可以做一些简单的事情: [ "first", "a" eq "ab" ? "second" : () ] 我(到目前为止)找不到像 javascript 中这么简单的东西。
    【解决方案4】:

    .filter()吧:

    var sources = images.filter(function(img) {
      if (img.src.split('.').pop() === "json") {
        return false; // skip
      }
      return true;
    }).map(function(img) { return img.src; });
    

    如果您不想这样做,这不是不合理的,因为它有一些成本,您可以使用更通用的.reduce()。你一般可以用.reduce来表达.map()

    someArray.map(function(element) {
      return transform(element);
    });
    

    可以写成

    someArray.reduce(function(result, element) {
      result.push(transform(element));
      return result;
    }, []);
    

    因此,如果您需要跳过元素,您可以使用 .reduce() 轻松完成:

    var sources = images.reduce(function(result, img) {
      if (img.src.split('.').pop() !== "json") {
        result.push(img.src);
      }
      return result;
    }, []);
    

    在该版本中,第一个示例中的.filter() 中的代码是.reduce() 回调的一部分。图像源仅在过滤器操作保留它的情况下被推送到结果数组。

    update — 这个问题引起了很多关注,我想添加以下澄清说明。 .map() 作为一个概念,其目的正是为了做到“映射”的含义:按照一定的规则将一个值列表转换为另一个值列表。就像某个国家的纸质地图如果完全丢失了几个城市会显得很奇怪一样,从一个列表到另一个列表的映射只有在有 1 对 1 组结果值时才真正有意义。

    我并不是说从排除某些值的旧列表创建新列表没有意义。我只是想说明.map() 有一个简单的意图,即创建一个与旧数组长度相同的新数组,仅使用旧值转换形成的值。

    【讨论】:

    • 这不需要你循环整个数组两次吗?有什么办法可以避免吗?
    • @AlexMcMillan 您可以使用.reduce() 一次性完成所有操作,但在性能方面我怀疑它会产生显着差异。
    • 对于所有这些负面的“空”式值(nullundefinedNaN 等),如果我们可以在 map() 中使用一个作为指标就好了这个对象什么都没有映射,应该被跳过。我经常遇到我想要映射 98% 的数组(例如:String.split() 在末尾留下一个空字符串,我不在乎)。谢谢你的回答:)
    • @AlexMcMillan .reduce() 是基线“随心所欲”功能,因为您可以完全控制返回值。您可能会对 Rich Hickey 在 Clojure 中关于 transducers 概念的出色工作感兴趣。
    • @vsync 你不能跳过带有.map() 的元素。但是,您可以改用.reduce(),所以我会添加它。
    【解决方案5】:

    如果它在一行 ES5/ES6 中为 null 或未定义

    //will return array of src 
    images.filter(p=>!p.src).map(p=>p.src);//p = property
    
    
    //in your condition
    images.filter(p=>p.src.split('.').pop() !== "json").map(p=>p.src);
    

    【讨论】:

      【解决方案6】:

      要推断Felix Kling's comment,您可以像这样使用.filter()

      var sources = images.map(function (img) {
        if(img.src.split('.').pop() === "json") { // if extension is .json
          return null; // skip
        } else {
          return img.src;
        }
      }).filter(Boolean);
      

      这将从.map()返回的数组中删除错误值

      你可以像这样进一步简化它:

      var sources = images.map(function (img) {
        if(img.src.split('.').pop() !== "json") { // if extension is .json
          return img.src;
        }
      }).filter(Boolean);
      

      或者甚至作为使用箭头函数、对象解构和&& 运算符的单线:

      var sources = images.map(({ src }) => src.split('.').pop() !== "json" && src).filter(Boolean);
      

      【讨论】:

      • 谢谢,.filter(Boolean) 是一个天才的解决方案!
      【解决方案7】:

      您可以使用 after of you 方法 map()。方法filter() 例如在你的情况下:

      var sources = images.map(function (img) {
        if(img.src.split('.').pop() === "json"){ // if extension is .json
          return null; // skip
        }
        else {
          return img.src;
        }
      });
      

      方法过滤器:

      const sourceFiltered = sources.filter(item => item)
      

      那么,新数组sourceFiltered中只有现有项。

      【讨论】:

        【解决方案8】:

        我认为从数组中跳过某些元素的最简单方法是使用filter() 方法。

        通过使用这种方法 (ES5) 和 ES6 语法,您可以在 一行 中编写代码,这将返回 你想要什么

        let images = [{src: 'img.png'}, {src: 'j1.json'}, {src: 'img.png'}, {src: 'j2.json'}];
        
        let sources = images.filter(img => img.src.slice(-4) != 'json').map(img => img.src);
        
        console.log(sources);

        【讨论】:

        【解决方案9】:

        自 2019 年以来,Array.prototype.flatMap 是一个不错的选择。

        images.flatMap(({src}) => src.endsWith('.json') ? [] : src);
        

        From MDN:

        flatMap 可以用作添加和删除项目的一种方式(修改 地图期间的项目数)。换句话说,它允许您映射 许多项目对许多项目(通过分别处理每个输入项目), 而不是总是一对一的。从这个意义上说,它的工作原理类似于 过滤器的对面。只需返回一个 1 元素数组来保留该项目, 用于添加项目的多元素数组,或用于删除的 0 元素数组 项目。

        【讨论】:

        • 最佳答案!更多信息在这里:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
        • 这是真正的答案,简单而强大。我们知道这比过滤和减少更好。
        • 这应该是公认的答案!像魅力一样工作!
        • 首先,感谢 MDN 提供此类评论。文档中包含这种实际用例示例并不常见。其次,我希望它更具体地说明 稍微更有效 部分。比map 后跟flat 效率高多少?
        • 你为我发现了多么可爱的功能。谢谢!
        【解决方案10】:

        TLDR:您可以先过滤数组,然后执行映射,但这需要对数组进行两次遍历(过滤器将数组返回到映射)。由于这个阵列很小,因此它的性能成本非常小。你也可以做一个简单的reduce。但是,如果您想重新设想如何通过单次遍历数组(或任何数据类型)来完成此操作,您可以使用 Rich Hickey 流行的一种称为“转换器”的想法。

        答案:

        我们不应该要求增加点链接并对数组[].map(fn1).filter(f2)... 进行操作,因为这种方法会在每个reducing 函数的内存中创建中间数组。

        最好的方法是在实际归约函数上运行,因此只有一次数据传递,没有额外的数组。

        reduce 函数是传递给reduce 的函数,它从源中获取一个累加器和输入,并返回看起来像累加器的东西

        // 1. create a concat reducing function that can be passed into `reduce`
        const concat = (acc, input) => acc.concat([input])
        
        // note that [1,2,3].reduce(concat, []) would return [1,2,3]
        
        // transforming your reducing function by mapping
        // 2. create a generic mapping function that can take a reducing function and return another reducing function
        const mapping = (changeInput) => (reducing) => (acc, input) => reducing(acc, changeInput(input))
        
        // 3. create your map function that operates on an input
        const getSrc = (x) => x.src
        const mappingSrc = mapping(getSrc)
        
        // 4. now we can use our `mapSrc` function to transform our original function `concat` to get another reducing function
        const inputSources = [{src:'one.html'}, {src:'two.txt'}, {src:'three.json'}]
        inputSources.reduce(mappingSrc(concat), [])
        // -> ['one.html', 'two.txt', 'three.json']
        
        // remember this is really essentially just
        // inputSources.reduce((acc, x) => acc.concat([x.src]), [])
        
        
        // transforming your reducing function by filtering
        // 5. create a generic filtering function that can take a reducing function and return another reducing function
        const filtering = (predicate) => (reducing) => (acc, input) => (predicate(input) ? reducing(acc, input): acc)
        
        // 6. create your filter function that operate on an input
        const filterJsonAndLoad = (img) => {
          console.log(img)
          if(img.src.split('.').pop() === 'json') {
            // game.loadSprite(...);
            return false;
          } else {
            return true;
          }
        }
        const filteringJson = filtering(filterJsonAndLoad)
        
        // 7. notice the type of input and output of these functions
        // concat is a reducing function,
        // mapSrc transforms and returns a reducing function
        // filterJsonAndLoad transforms and returns a reducing function
        // these functions that transform reducing functions are "transducers", termed by Rich Hickey
        // source: http://clojure.com/blog/2012/05/15/anatomy-of-reducer.html
        // we can pass this all into reduce! and without any intermediate arrays
        
        const sources = inputSources.reduce(filteringJson(mappingSrc(concat)), []);
        // [ 'one.html', 'two.txt' ]
        
        // ==================================
        // 8. BONUS: compose all the functions
        // You can decide to create a composing function which takes an infinite number of transducers to
        // operate on your reducing function to compose a computed accumulator without ever creating that
        // intermediate array
        const composeAll = (...args) => (x) => {
          const fns = args
          var i = fns.length
          while (i--) {
            x = fns[i].call(this, x);
          }
          return x
        }
        
        const doABunchOfStuff = composeAll(
            filtering((x) => x.src.split('.').pop() !== 'json'),
            mapping((x) => x.src),
            mapping((x) => x.toUpperCase()),
            mapping((x) => x + '!!!')
        )
        
        const sources2 = inputSources.reduce(doABunchOfStuff(concat), [])
        // ['ONE.HTML!!!', 'TWO.TXT!!!']
        

        资源:rich hickey transducers post

        【讨论】:

          【解决方案11】:

          这是code provided by @theprtk 的更新版本。在举例的同时展示通用版本是一种清理。

          注意:我会将此作为评论添加到他的帖子中,但我还没有足够的声誉

          /**
           * @see http://clojure.com/blog/2012/05/15/anatomy-of-reducer.html
           * @description functions that transform reducing functions
           */
          const transduce = {
            /** a generic map() that can take a reducing() & return another reducing() */
            map: changeInput => reducing => (acc, input) =>
              reducing(acc, changeInput(input)),
            /** a generic filter() that can take a reducing() & return */
            filter: predicate => reducing => (acc, input) =>
              predicate(input) ? reducing(acc, input) : acc,
            /**
             * a composing() that can take an infinite # transducers to operate on
             *  reducing functions to compose a computed accumulator without ever creating
             *  that intermediate array
             */
            compose: (...args) => x => {
              const fns = args;
              var i = fns.length;
              while (i--) x = fns[i].call(this, x);
              return x;
            },
          };
          
          const example = {
            data: [{ src: 'file.html' }, { src: 'file.txt' }, { src: 'file.json' }],
            /** note: `[1,2,3].reduce(concat, [])` -> `[1,2,3]` */
            concat: (acc, input) => acc.concat([input]),
            getSrc: x => x.src,
            filterJson: x => x.src.split('.').pop() !== 'json',
          };
          
          /** step 1: create a reducing() that can be passed into `reduce` */
          const reduceFn = example.concat;
          /** step 2: transforming your reducing function by mapping */
          const mapFn = transduce.map(example.getSrc);
          /** step 3: create your filter() that operates on an input */
          const filterFn = transduce.filter(example.filterJson);
          /** step 4: aggregate your transformations */
          const composeFn = transduce.compose(
            filterFn,
            mapFn,
            transduce.map(x => x.toUpperCase() + '!'), // new mapping()
          );
          
          /**
           * Expected example output
           *  Note: each is wrapped in `example.data.reduce(x, [])`
           *  1: ['file.html', 'file.txt', 'file.json']
           *  2:  ['file.html', 'file.txt']
           *  3: ['FILE.HTML!', 'FILE.TXT!']
           */
          const exampleFns = {
            transducers: [
              mapFn(reduceFn),
              filterFn(mapFn(reduceFn)),
              composeFn(reduceFn),
            ],
            raw: [
              (acc, x) => acc.concat([x.src]),
              (acc, x) => acc.concat(x.src.split('.').pop() !== 'json' ? [x.src] : []),
              (acc, x) => acc.concat(x.src.split('.').pop() !== 'json' ? [x.src.toUpperCase() + '!'] : []),
            ],
          };
          const execExample = (currentValue, index) =>
            console.log('Example ' + index, example.data.reduce(currentValue, []));
          
          exampleFns.raw.forEach(execExample);
          exampleFns.transducers.forEach(execExample);
          

          【讨论】:

            【解决方案12】:
            var sources = images.map(function (img) {
                if(img.src.split('.').pop() === "json"){ // if extension is .json
                    return null; // skip
                }
                else{
                    return img.src;
                }
            }).filter(Boolean);
            

            .filter(Boolean) 将过滤掉给定数组中的任何虚假值,在您的情况下是 null

            【讨论】:

              【解决方案13】:

              为什么不直接使用 forEach 循环?

              let arr = ['a', 'b', 'c', 'd', 'e'];
              let filtered = [];
              
              arr.forEach(x => {
                if (!x.includes('b')) filtered.push(x);
              });
              
              console.log(filtered)   // filtered === ['a','c','d','e'];

              甚至更简单的使用过滤器:

              const arr = ['a', 'b', 'c', 'd', 'e'];
              const filtered = arr.filter(x => !x.includes('b')); // ['a','c','d','e'];
              

              【讨论】:

              • 最好是一个简单的 for 循环来过滤和创建一个新数组,但是对于使用 map 的上下文,让我们保持它现在的样子。 (4年前我问过这个问题,当时我对编码一无所知)
              • 很公平,因为地图没有直接的方法,所有的解决方案都使用了另一种方法,我认为我会以我能想到的最简单的方式来做同样的事情。
              【解决方案14】:

              我使用.forEach 进行迭代,并将结果推送到results 数组然后使用它,使用此解决方案我不会循环数组两次

              【讨论】:

                【解决方案15】:

                这是一个实用方法(与 ES5 兼容),它只映射非空值(隐藏对 reduce 的调用):

                function mapNonNull(arr, cb) {
                    return arr.reduce(function (accumulator, value, index, arr) {
                        var result = cb.call(null, value, index, arr);
                        if (result != null) {
                            accumulator.push(result);
                        }
                
                        return accumulator;
                    }, []);
                }
                
                var result = mapNonNull(["a", "b", "c"], function (value) {
                    return value === "b" ? null : value; // exclude "b"
                });
                
                console.log(result); // ["a", "c"]

                【讨论】:

                  【解决方案16】:

                  回答没有多余的边缘情况:

                  const thingsWithoutNulls = things.reduce((acc, thing) => {
                    if (thing !== null) {
                      acc.push(thing);
                    }
                    return acc;
                  }, [])
                  

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 2022-07-14
                    • 1970-01-01
                    • 2012-03-12
                    • 1970-01-01
                    • 1970-01-01
                    • 2014-01-15
                    • 2017-12-09
                    相关资源
                    最近更新 更多