用过滤器函数划分数组

我有一个 Javascript 数组,根据对每个元素调用的函数返回 true还是 false,我想把它分成两个。本质上,这是一个 array.filter,但我也想在手头上的元素,被过滤的 出去

目前,我的计划是使用 array.forEach并对每个元素调用谓词函数。根据这个值为 true 还是 false,我将把当前元素推送到两个新数组中的一个上。有没有更优雅或更好的方法来做到这一点?例如,在返回 false之前将元素推送到另一个数组的 array.filter

76401 次浏览

试试这个:

function filter(a, fun) {
var ret = { good: [], bad: [] };
for (var i = 0; i < a.length; i++)
if (fun(a[i])
ret.good.push(a[i]);
else
ret.bad.push(a[i]);
return ret;
}

演示

在过滤器函数中,你可以将虚假项推入另一个外部函数变量:

var bad = [], good = [1,2,3,4,5];
good = good.filter(function (value) { if (value === false) { bad.push(value) } else { return true});

当然,value === false需要真正的比较;)

但是它的操作几乎和 forEach一样。我认为你应该使用 forEach来提高代码的可读性。

这听起来非常类似于 Ruby 的 Enumerable#partition方法。

如果函数没有副作用(例如,它不能改变原始数组) ,那么没有比迭代每个元素并将元素推送到两个数组中的一个更有效的方法来划分数组。

也就是说,在 Array上创建一个方法来执行这个函数可能更“优雅”。在这个例子中,filter 函数在原始数组的上下文中执行(也就是说,this将是原始数组) ,它接收元素和元素的索引作为参数(类似于 JQuery 的 each方法) :

Array.prototype.partition = function (f){
var matched = [],
unmatched = [],
i = 0,
j = this.length;


for (; i < j; i++){
(f.call(this, this[i], i) ? matched : unmatched).push(this[i]);
}


return [matched, unmatched];
};


console.log([1, 2, 3, 4, 5].partition(function (n, i){
return n % 2 == 0;
}));


//=> [ [ 2, 4 ], [ 1, 3, 5 ] ]

你可以使用 Lodash 分区

var users = [
{ 'user': 'barney',  'age': 36, 'active': false },
{ 'user': 'fred',    'age': 40, 'active': true },
{ 'user': 'pebbles', 'age': 1,  'active': false }
];


_.partition(users, function(o) { return o.active; });
// → objects for [['fred'], ['barney', 'pebbles']]


// The `_.matches` iteratee shorthand.
_.partition(users, { 'age': 1, 'active': false });
// → objects for [['pebbles'], ['barney', 'fred']]


// The `_.matchesProperty` iteratee shorthand.
_.partition(users, ['active', false]);
// → objects for [['barney', 'pebbles'], ['fred']]


// The `_.property` iteratee shorthand.
_.partition(users, 'active');
// → objects for [['fred'], ['barney', 'pebbles']]

Ramda 分区

R.partition(R.contains('s'), ['sss', 'ttt', 'foo', 'bars']);
// => [ [ 'sss', 'bars' ],  [ 'ttt', 'foo' ] ]


R.partition(R.contains('s'), { a: 'sss', b: 'ttt', foo: 'bars' });
// => [ { a: 'sss', foo: 'bars' }, { b: 'ttt' }  ]

你可以使用 reduce:

function partition(array, callback){
return array.reduce(function(result, element, i) {
callback(element, i, array)
? result[0].push(element)
: result[1].push(element);
    

return result;
}, [[],[]]
);
};

或者如果使用 Typecript:

const partition = <T,>(
array: T[],
callback: (element: T, index: number, array: T[]) => boolean
) => {
return array.reduce(function(result, element, i) {
callback(element, i, array)
? result[0].push(element)
: result[1].push(element);


return result;
}, [[],[]]);
};

例如:

const groceries = [
{ type: "apple" },
{ type: "pear" },
{ type: "banana" }
]


const [apples, others] = partition(
groceries,
(item) => item.type === "apple",
);


// => apples: [{ type: "apple" }]
// => others: [{ type: "pear" }, { type: "banana" }]

使用 ES6语法,您也可以使用递归(更新以避免在每次迭代中创建新数组)来实现这一点:

function partition([current, ...tail], f, left = [], right = []) {
if(current === undefined) {
return [left, right];
}
if(f(current)) {
left.push(current);
return partition(tail, f, left, right);
}
right.push(current);
return partition(tail, f, left, right);
}

使用 ES6,您可以使用 reduce 语法:

function partition(array, isValid) {
return array.reduce(([pass, fail], elem) => {
return isValid(elem) ? [[...pass, elem], fail] : [pass, [...fail, elem]];
}, [[], []]);
}


const [pass, fail] = partition(myArray, (e) => e > 5);

或者一行字:

const [pass, fail] = a.reduce(([p, f], e) => (e > 5 ? [[...p, e], f] : [p, [...f, e]]), [[], []]);

我想到了这个小家伙。它适用于你所描述的每一样东西,但在我看来,它看起来干净而简洁。

//Partition function
function partition(array, filter) {
let pass = [], fail = [];
array.forEach((e, idx, arr) => (filter(e, idx, arr) ? pass : fail).push(e));
return [pass, fail];
}


//Run it with some dummy data and filter
const [lessThan5, greaterThanEqual5] = partition([0,1,4,3,5,7,9,2,4,6,8,9,0,1,2,4,6], e => e < 5);


//Output
console.log(lessThan5);
console.log(greaterThanEqual5);

很容易读懂。

const partition = (arr, condition) => {
const trues = arr.filter(el => condition(el));
const falses = arr.filter(el => !condition(el));
return [trues, falses];
};


// sample usage
const nums = [1,2,3,4,5,6,7]
const [evens, odds] = partition(nums, (el) => el%2 == 0)

我最终这样做是因为这很容易理解:

const partition = (array, isValid) => {
const pass = []
const fail = []
array.forEach(element => {
if (isValid(element)) {
pass.push(element)
} else {
fail.push(element)
}
})
return [pass, fail]
}


// usage
const [pass, fail] = partition([1, 2, 3, 4, 5], (element) => element > 3)

同样的方法包括打字的类型:

const partition = <T>(array: T[], isValid: (element: T) => boolean): [T[], T[]] => {
const pass: T[] = []
const fail: T[] = []
array.forEach(element => {
if (isValid(element)) {
pass.push(element)
} else {
fail.push(element)
}
})
return [pass, fail]
}


// usage
const [pass, fail] = partition([1, 2, 3, 4, 5], (element: number) => element > 3)

这个怎么样?

[1,4,3,5,3,2].reduce( (s, x) => { s[ x > 3 ].push(x); return s;} , {true: [], false:[]} )

也许这比扩展运算符更有效

或者更矮一点,但更丑一点

[1,4,3,5,3,2].reduce( (s, x) => s[ x > 3 ].push(x)?s:s , {true: [], false:[]} )


这里的许多答案使用 Array.prototype.reduce来构建一个可变的累加器,并且正确地指出,对于大型数组,这比使用扩展运算符在每次迭代时复制一个新数组更有效。缺点是它不像使用短 lambda 语法的“纯”表达式那样漂亮。

但是绕过这个问题的一个方法是使用逗号运算符。在类 C 语言中,逗号是一个总是返回右手操作数的运算符。您可以使用它来创建调用 void 函数并返回值的表达式。

function partition(array, predicate) {
return array.reduce((acc, item) => predicate(item)
? (acc[0].push(item), acc)
: (acc[1].push(item), acc), [[], []]);
}

如果你利用布尔表达式隐式转换为0和1这一事实,你可以使它更加简洁,尽管我不认为它是可读的:

function partition(array, predicate) {
return array.reduce((acc, item) => (acc[+!predicate(item)].push(item), acc), [[], []]);
}

用法:

const [trues, falses] = partition(['aardvark', 'cat', 'apple'], i => i.startsWith('a'));
console.log(trues); // ['aardvark', 'apple']
console.log(falses); // ['cat']

我知道已经有多种解决方案,但我冒昧地把上面的答案中最好的部分放在一起,并使用了 Typecript 上的扩展方法。复制粘贴就可以了:

declare global {


interface Array<T> {
partition(this: T[], predicate: (e: T) => boolean): T[][];
}


}


if(!Array.prototype.partition){


Array.prototype.partition = function<T>(this: T[], predicate: (e: T) => boolean): T[][] {


return this.reduce<T[][]>(([pass, fail], elem) => {
(predicate(elem) ? pass : fail).push(elem);
return [pass, fail];
}, [[], []]);


}
}


用法:


const numbers = [1, 2, 3, 4, 5, 6];
const [even, odd] = numbers.partition(n => n % 2 === 0);


Lodash 分区 可选,与@Yaremenko Andrii 的 第一个解决方案相同,但语法更短

function partition(arr, callback) {
return arr.reduce(
(acc, val, i, arr) => {
acc[callback(val, i, arr) ? 0 : 1].push(val)
return acc
},
[[], []]
)
}

一行分区

const partitionBy = (arr, predicate) =>
arr.reduce((acc, item) => (acc[+!predicate(item)].push(item), acc), [[], []]);

演示

// to make it consistent to filter pass index and array as arguments
const partitionBy = (arr, predicate) =>
arr.reduce(
(acc, item, index, array) => (
acc[+!predicate(item, index, array)].push(item), acc
),
[[], []]
);


console.log(partitionBy([1, 2, 3, 4, 5], x => x % 2 === 0));
console.log(partitionBy([..."ABCD"], (x, i) => i % 2 === 0));

打字稿(v4.5)

const partitionBy = <T>(
arr: T[],
predicate: (v: T, i: number, ar: T[]) => boolean
) =>
arr.reduce(
(acc, item, index, array) => {
acc[+!predicate(item, index, array)].push(item);
return acc;
},
[[], []] as [T[], T[]]
);