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


417

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

18
您不能,但是之后可以过滤掉所有空值。
Felix Kling 2014年

1
为什么不?我知道使用continue不起作用,但是最好知道为什么(也可以避免双重循环)-编辑-对于您的情况,您不能只反转if条件,仅img.src当split pop的结果返回时才返回! = json?
GrayedFox

@GrayedFox然后undefined将隐式而不是放入数组null。没那么好...
FZ在

Answers:


637

.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()回调的一部分。仅在过滤操作可以保留结果的情况下,才将图像源推入结果数组。


21
这不是要求您遍历整个数组两次吗?有什么办法可以避免这种情况?
亚历克斯·麦克米伦

7
您可以使用@AlexMcMillan .reduce()并一次性完成所有操作,尽管从性能角度来看,我怀疑它是否会产生重大影响。
尖尖的

9
对于所有这些负的,“空”样式的值(null,等),如果我们可以利用a内部的一个值作为指示符,表明该对象没有映射并且应该被跳过undefinedNaN那将是很好的map()。我经常遇到我想映射98%的数组(例如:String.split()在结尾处留一个空字符串,我不在乎)。感谢您的回答:)
Alex McMillan 2015年

6
@AlexMcMillan很好地归类.reduce()于基线“随心所欲”功能,因为您可以完全控制返回值。您可能会对Rich Hickey在Clojure中有关换能器概念的出色工作感兴趣。
尖尖的2015年

3
@vsync,您不能使用跳过元素.map()。但是.reduce(),您可以改用,所以我将其添加。
尖尖的

25

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


1
那正是.filter()出于此目的
avalanche1

2
这比一次forEach完成而不是两次完成更好吗?
wuliwong

1
如你所愿@wuliwong。但请注意,这仍然O(n)是复杂性的衡量标准,请至少也看以下两篇文章:frontendcollisionblog.com/javascript/2015/08/15/…coderwall.com/p/kvzbpa/don-t-最好使用数组代替 每个!
simhumileco,

1
谢谢@simhumileco!正因为如此,我在这里(可能还有很多其他人)。问题可能是如何通过仅迭代一次来组合.filter和.map。
杰克·布莱克

21

从2019年开始,Array.prototype.flatMap是一个不错的选择。

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

从MDN

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



1
这是真正的答案,简单而强大。我们了解到,这比过滤和减少效果更好。
捍卫逆戟鲸

19

TLDR:您可以先过滤数组,然后执行地图,但这需要对数组进行两次通过(过滤器将数组返回到地图)。由于此阵列很小,因此性能成本非常低。您也可以做一个简单的减少。但是,如果您想重新想象一下如何通过一次遍历数组(或任何数据类型)来完成此操作,则可以使用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!!!']

资源:Rich Hickey传感器发布


16

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

/**
 * 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运算符一起使用

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

1
这种方法不必使用单独救了我mapfilterconcat电话。
LogicalBranch

11

回答无用的边缘情况:

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

10

为什么不只使用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'];

1
最好的方法是简单的for循环,它过滤并创建一个新数组,但是对于使用上下文,map让它保持现在状态。(是4年前,当我对编码一无所知时,我问了这个问题)
Ismail

公平地说,考虑到map并没有直接的实现方法,并且所有解决方案都使用一种替代方法,我认为我会以我想到的最简单的方法进行操作。
Alex

8
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)将在给定阵列中,而你的情况是中滤除任何falsey值null


3

这是一个实用程序方法(与ES5兼容),它仅映射非null值(隐藏了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"]


1

我使用.forEach遍历遍历,并将结果推入results数组然后使用它,通过这种解决方案,我不会遍历数组两次


1

要推断Felix Kling的评论,可以这样使用.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);

0

这是@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);

0

您可以使用after after method 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)

然后,只有现有项目在新array中sourceFiltered

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.