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

时间:2014-07-17 14:48:16

标签: javascript

如何跳过.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"]

15 个答案:

答案 0 :(得分:429)

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

答案 1 :(得分:17)

TLDR:您可以先过滤数组,然后执行地图,但这需要在数组上进行两次传递(过滤器返回一个要映射的数组)。由于该阵列很小,因此性能成本非常低。但是,如果您想要想象如何通过对阵列进行单次传递来完成此操作,则可以使用名为"传感器"受到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

答案 2 :(得分:13)

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

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

答案 3 :(得分:10)

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

android.app.Application

答案 4 :(得分:10)

TL; TR;

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

使用此方法和 ES6 语法,您可以用一行编写代码:

var sources = images.filter(img => img.src.slice(-4) != "json").map(img => img.src);

,这将返回您想要的内容

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

答案 5 :(得分:6)

为什么不只使用forEach循环?

let arr = ['a','b','c','d','e'];
let filtered = [];

arr.forEach(x => {
    if (!x.includes('b')) filtered.push(x);
});

// filtered === ['a','c','d','e'];

答案 6 :(得分:3)

这是一个实用程序方法(ES5兼容),它只映射非空值(隐藏调用以减少):

MathFullForm

答案 7 :(得分:2)

要在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);

答案 8 :(得分:1)

Array.prototype.flatMap是另一种选择。

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

From MDN

  

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

答案 9 :(得分:1)

我使用.forEach进行迭代,然后将结果推入results数组,然后使用它,通过这种解决方案,我不会在数组上循环两次

答案 10 :(得分:1)

您可以这样做

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
    }
});

答案 11 :(得分:0)

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)中的所有虚假值。

答案 12 :(得分:0)

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

答案 13 :(得分:0)

您可以使用之后的方法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中。

答案 14 :(得分:0)

如果在一行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);