如何跳过 .map() 中的元素?

IT技术 javascript
2021-01-21 04:09:46

如何跳过 中的数组元素.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"]
6个回答

只是.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(). 您通常可以表示.map().reduce

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()回调的一部分仅在过滤操作将保留它的情况下,图像源才会被推送到结果数组上。

更新——这个问题引起了很多关注,我想添加以下澄清说明。.map()作为一个概念,的目的是完全符合“映射”的含义:根据某些规则将值列表转换为另一个值列表。就像如果几个城市完全缺失,某个国家的纸质地图看起来很奇怪,从一个列表到另一个列表的映射只有在有一对一的结果值集时才真正有意义。

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

@vsync 你不能跳过带有.map(). 但是.reduce()您可以改用,因此我将添加它。
2021-03-31 04:09:46
@AlexMcMillan 您可以一次使用.reduce()并完成所有操作,尽管在性能方面我怀疑它会产生重大影响。
2021-04-03 04:09:46
所有这些负面的,“空”式的值(nullundefinedNaN等),这将是很好,如果我们可以利用一个内部map()的,这个对象映射到什么和应跳过的一个指标。我经常遇到我想映射 98% 的数组(例如:String.split()在最后留下一个空字符串,我不在乎)。感谢您的回答 :)
2021-04-10 04:09:46
@AlexMcMillan.reduce()是一种“随心所欲”的基线功能,因为您可以完全控制返回值。您可能对 Rich Hickey 在 Clojure 中关于转换器概念的出色工作感兴趣
2021-04-10 04:09:46
这不是需要您将整个数组循环两次吗?有什么办法可以避免吗?
2021-04-14 04:09:46

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

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

来自 MDN

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

首先,支持 MDN 提供这种评论。文档具有这种实际用例示例并不常见。其次,我确实希望它对稍微更有效的部分更具体map后面的效率高多少flat
2021-03-19 04:09:46
这是真正的答案,简单而强大。我们了解到这比过滤和减少更好。
2021-03-26 04:09:46
你为我发现了多么可爱的功能。谢谢!
2021-04-01 04:09:46
这应该是公认的答案!像魅力一样工作!
2021-04-05 04:09:46
2021-04-06 04:09:46

我认为从数组中跳过某些元素的最简单方法是使用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);

谢谢@simhumileco!正因为如此,我才在这里(可能还有很多其他人)。问题可能是如何通过只迭代一次来组合 .filter 和 .map 。
2021-03-16 04:09:46
这正是.filter()
2021-03-27 04:09:46
这比一次forEach完成而不是两次更好吗?
2021-04-03 04:09:46
如你所愿@wuliwong。但请考虑到这仍然O(n)是复杂的度量,请至少也看看这两篇文章:frontendcollisionblog.com/javascript/2015/08/15/...coderwall.com/p/kvzbpa/don-t- use-array-foreach-use-for-instead 一切顺利!
2021-04-12 04:09:46

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

回答:

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

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

归约函数是传入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!!!']

资源:丰富的吻痕传感器帖子

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

/**
 * 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;
    }, []);
}

绑定运算符一起使用

[1,2,-1,3]::fmap(x => x > 0 ? x * 2 : undefined); // [2,4,6]
这种方法使我不必使用单独的map,filterconcat调用。
2021-04-07 04:09:46