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

如何在.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"]
663359 次浏览

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

这个问题得到了很多关注,我想补充以下几点来澄清问题。作为一个概念,.map()的目的是准确地完成“map”的含义:根据一定的规则将值列表转换为另一个值列表。就像一个国家的纸质地图如果完全缺少几个城市会显得很奇怪一样,只有当有1到1组结果值时,从一个列表到另一个列表的映射才真正有意义。

我并不是说从旧列表中创建一个排除了一些值的新列表没有意义。我只是想弄清楚.map()有一个简单的意图,那就是创建一个与旧数组长度相同的新数组,只是具有由旧值转换形成的值。

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!!!']

资源:富吻痕传感器柱

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

/**
* Filter-map. Like map, but skips undefined values.
*
* @param callback
*/
function fmap(callback) {
return this.reduce((accum, ...args) => {
const 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]

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

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"]

无多余边缘情况的答案:

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

为什么不直接使用for循环呢?

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'];

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

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

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

从MDN

flatMap可以用作添加和删除项目的方法(修改 项目的数量)。换句话说,它允许您映射 多个项目到多个项目(通过单独处理每个输入项目), 而不总是一对一。从这个意义上说,它的工作原理就像 与过滤器相反。只需返回一个1元素数组来保留项目, 用于添加项目的多元素数组,或用于删除的0元素数组 项目。

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

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

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

您可以使用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中。

要推断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()返回的数组中删除false sey值

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

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

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

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

你能做到的

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

我使用foreach()

var sources = [];


images.forEach(function (img) {
if(img.src.split('.').pop() !== "json"){ // if extension is .json
sources.push(img);
}
});


注:我否定了你的逻辑。

你可以像这样使用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;
}})?.filter(x => x !== null);

const arr = [0, 1, '', undefined, false, 2, undefined, null, , 3, NaN];
const filtered = arr.filter(Boolean);
console.log(filtered);


/*
Output: [ 1, 2, 3 ]
*/