同时映射和过滤一个数组

我有一个对象数组,我想对其进行迭代以生成一个新的过滤数组。同时,我还需要根据参数从新数组中过滤出一些对象。我试着这样做:

function renderOptions(options) {
return options.map(function (option) {
if (!option.assigned) {
return (someNewObject);
}
});
}

这是个好方法吗?有没有更好的方法?我愿意使用任何库,如lodash。

295964 次浏览

使用Array.prototype.filter:

function renderOptions(options) {
return options.filter(function(option){
return !option.assigned;
}).map(function (option) {
return (someNewObject);
});
}

你应该使用Array.reduce

var options = [
{ name: 'One', assigned: true },
{ name: 'Two', assigned: false },
{ name: 'Three', assigned: true },
];


var reduced = options.reduce(function(filtered, option) {
if (option.assigned) {
var someNewValue = { name: option.name, newProperty: 'Foo' }
filtered.push(someNewValue);
}
return filtered;
}, []);


document.getElementById('output').innerHTML = JSON.stringify(reduced);
<h1>Only assigned options</h1>
<pre id="output"> </pre>


或者,减速器可以是一个纯函数,就像这样

var reduced = options.reduce(function(result, option) {
if (option.assigned) {
return result.concat({
name: option.name,
newProperty: 'Foo'
});
}
return result;
}, []);

用减法,卢克!

function renderOptions(options) {
return options.reduce(function (res, option) {
if (!option.assigned) {
res.push(someNewObject);
}
return res;
}, []);
}

使用reduce,你可以在一个数组中做到这一点。函数原型。这将从数组中获取所有偶数。

var arr = [1,2,3,4,5,6,7,8];


var brr = arr.reduce((c, n) => {
if (n % 2 !== 0) {
return c;
}
c.push(n);
return c;
}, []);


document.getElementById('mypre').innerHTML = brr.toString();
<h1>Get all even numbers</h1>
<pre id="mypre"> </pre>

您可以使用相同的方法并将其泛化到您的对象,如下所示。

var arr = options.reduce(function(c,n){
if(somecondition) {return c;}
c.push(n);
return c;
}, []);

arr现在将包含筛选过的对象。

在ES6中,你可以做的很短:

options.filter(opt => !opt.assigned).map(opt => someNewObject)

在某些情况下,使用forEach不是更容易(或同样容易)吗

var options = [
{ name: 'One', assigned: true },
{ name: 'Two', assigned: false },
{ name: 'Three', assigned: true },
];


var reduced = []
options.forEach(function(option) {
if (option.assigned) {
var someNewValue = { name: option.name, newProperty: 'Foo' }
reduced.push(someNewValue);
}
});


document.getElementById('output').innerHTML = JSON.stringify(reduced);
<h1>Only assigned options</h1>
<pre id="output"> </pre>

然而,如果有一个malter()fap()函数组合mapfilter函数就更好了。它将像过滤器一样工作,除了返回true或false之外,它将返回任何对象或null/undefined。

我用以下几点优化了答案:

  1. if (cond) { stmt; }重写为cond && stmt;
  2. 使用ES6 箭头功能

我将给出两个解决方案,一个使用forEach,另一个使用减少:

解决方案1:使用forEach

解决方案通过使用forEach遍历每个元素来实现。然后,在forEach循环体中,我们有条件变量作为过滤器,它决定是否要向结果数组追加内容。

const options = [
{ name: 'One', assigned: true },
{ name: 'Two', assigned: false },
{ name: 'Three', assigned: true },
];
const reduced = [ ];
options.forEach(o => {
o.assigned && reduced.push( { name: o.name, newProperty: 'Foo' } );
} );
console.log(reduced);

解决方案2:使用reduce

这个解决方案使用Array.prototype.reduce而不是forEach来遍历数组。它认识到reduce内置了一个初始化式和一个循环机制。除此之外,这个解决方案或多或少与forEach解决方案相同,因此,差异归结为修饰语法糖。

const options = [
{ name: 'One', assigned: true },
{ name: 'Two', assigned: false },
{ name: 'Three', assigned: true },
];
const reduced = options.reduce((a, o) => {
o.assigned && a.push( { name: o.name, newProperty: 'Foo' } );
return a;
}, [ ] );
console.log(reduced);

我让你来决定采用哪种解决方案。

一行reduce与ES6花哨的传播的语法在这里!

var options = [
{ name: 'One', assigned: true },
{ name: 'Two', assigned: false },
{ name: 'Three', assigned: true },
];


const filtered = options
.reduce((result, {name, assigned}) => [...result, ...assigned ? [name] : []], []);


console.log(filtered);

我想发表评论,但我没有必要的声誉。对马克西姆·库兹敏(Maxim Kuzmin)的答案进行了一个小改进,使其更有效:

const options = [
{ name: 'One', assigned: true },
{ name: 'Two', assigned: false },
{ name: 'Three', assigned: true },
];


const filtered = options
.reduce((result, { name, assigned }) => assigned ? result.push(name) && result : result, []);


console.log(filtered);

解释

我们不是在每次迭代时将整个结果一遍又一遍地展开,而是仅在实际有值要插入时才将结果追加到数组。

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

options.flatMap(o => o.assigned ? [o.name] : []);

从MDN页面上方链接:

flatMap可以作为一种添加和删除项的方式(修改 项目数目)在一个地图。换句话说,它允许您进行映射 多项对多项(通过分别处理每个输入项), 而不是总是一对一。在这个意义上,它就像 filter的反义词。只需返回一个1元素的数组来保存该项, 用于添加项的多元素数组,或用于删除项的0元素数组 项。< / p >

直接使用.reduce可能很难阅读,所以我建议创建一个函数来为你生成减速器:

function mapfilter(mapper) {
return (acc, val) => {
const mapped = mapper(val);
if (mapped !== false)
acc.push(mapped);
return acc;
};
}

像这样使用它:

const words = "Map and filter an array #javascript #arrays";
const tags = words.split(' ')
.reduce(mapfilter(word => word.startsWith('#') && word.slice(1)), []);
console.log(tags);  // ['javascript', 'arrays'];

可以使用带有箭头的Array.reduce函数是一行代码

const options = [
{ name: 'One', assigned: true },
{ name: 'Two', assigned: false },
{ name: 'Three', assigned: true },
];


const reduced = options.reduce((result, option) => option.assigned ? result.concat({ name: option.name, newProperty: 'Foo' }) : result, []);


document.getElementById('output').innerHTML = JSON.stringify(reduced);
<h1>Only assigned options</h1>
<pre id="output"> </pre>

我把这些伟大的答案隐藏在效用函数中,我想分享它们:

例子:只过滤奇数并增加它

  • 例如:[1, 2, 3, 4, 5] -filter->[1, 3, 5]地图→[2, 4, 6]

通常你会用filtermap这样做

const inputArray = [1, 2, 3, 4, 5];
const filterOddPlusOne = inputArray.filter((item) => item % 2).map((item) => item + 1); // [ 2, 4, 6 ]

使用reduce

const filterMap = <TSource, TTarget>(
items: TSource[],
filterFn: (item: TSource) => boolean,
mapFn: (item: TSource) => TTarget
) =>
items.reduce((acc, cur): TTarget[] => {
if (filterFn(cur)) return [...acc, mapFn(cur)];
return acc;
}, [] as TTarget[]);

使用flatMap

const filterMap = <TSource, TTarget>(
items: TSource[],
filterFn: (item: TSource) => boolean,
mapFn: (item: TSource) => TTarget
) => items.flatMap((item) => (filterFn(item) ? [mapFn(item)] : []));

用法(与reduceflatMap解决方案相同):

const inputArray = [1, 2, 3, 4, 5];
const filterOddPlusOne = filterMap(
inputArray,
(item) => item % 2, // Filter only odd numbers
(item) => item + 1 // Increment each number
); // [ 2, 4, 6 ]

JavaScript版本

上面的代码是TypeScript的,但是问题问的是JavaScript。所以,我已经为你删除了所有的泛型和类型:

const filterMap = (items, filterFn, mapFn) =>
items.reduce((acc, cur) => {
if (filterFn(cur)) return [...acc, mapFn(cur)];
return acc;
}, []);
const filterMap = (items, filterFn, mapFn) =>
items.flatMap((item) => (filterFn(item) ? [mapFn(item)] : []));

一次性执行filter + map的最有效方法是将数据作为泛型可迭代对象处理,并同时执行这两件事。在这种情况下,您最多只需要浏览一次数据。

下面的例子使用了iter-ops库,并做到了这一点:

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


const i = pipe(
inputArray,
filter(value => value === 123), // filter on whatever key you want
map(value => /* any mapping here*/) // remap data as you like
);


// i = iterable that can be processed further;


console.log([...i]); //=> list of new objects

上面,我说的是at most,因为如果你对可迭代结果应用进一步的逻辑,比如限制映射项的数量,例如,你最终将迭代对象列表甚至少于一次:

const i = pipe(
inputArray,
filter(value => value === 123), // filter on whatever key you want
map(value => /* any mapping here*/), // remap as you like
take(10) // take up to 10 items only
);

在上面,我们进一步限制迭代,一旦生成了10个结果项就停止,因此我们对数据的迭代少于一次。这是最有效的了。

更新

我被要求补充为什么这个解决方案比reduce更有效,所以它在这里…

数组的reduce是一个有限操作,它遍历完整的数据集,以产生结果。因此,当您需要对输出数据进行进一步处理时,您将最终生成一个新的迭代序列,等等。

当您有一个复杂的业务逻辑要应用到一个序列/可迭代对象时,将该逻辑链接起来总是更有效的,而只遍历序列一次。在许多情况下,您最终会对一个序列进行复杂的处理,甚至一次都不遍历完整的数据集。这就是可迭代数据处理的效率。

附注:我是上述图书馆的作者。

嘿,我刚刚在这个项目上工作,想在MDN文档中分享我基于Array.prototype.flatMap()的解决方案:

const places=[{latitude:40,longitude:1},{latitude:41,longitude:2},{latitude:44,longitude:2},{latitude:NaN,longitude:NaN},{latitude:45,longitude:4},{latitude:48,longitude:3},{latitude:44,longitude:5},{latitude:39,longitude:13},{latitude:40,longitude:8},{latitude:38,longitude:4}];


let items = places?.map((place) => [{
latitude: (place.latitude),


longitude: (place.longitude),
}, ]);


console.log("Items: ", items);


//Remove elements with NaN latitude and longitude


let newItems = places?.flatMap((o) =>
Number(o.longitude, o.latitude) ?
{
lng: Number(o.longitude),
lat: Number(o.latitude)
} :
[]
);


console.log("Coordinates after NaN values removed: ", newItems);

与顶部答案相同的方法,使用Array.prototype.reduce (),但更新了ES6语法和TypeScript类型,作为通用实用函数:

function filterThenMap<T>(l: T[], predicate: (el: T) => boolean, transform: (el: T) => T) {
return l.reduce((res: T[], el) => {
if (predicate(el)) {
res.push(transform(el));
}
return res;
}, []);
}