用过滤器函数划分数组


91

我有一个Javascript数组,我想分成基于是否呼吁各要素回报函数的两个truefalse。从本质上讲,这是一个array.filter,但我想也有手头上筛选出的元素出来

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


如果您可以发布一些示例代码,则将为您提供更好的答案!
Mark Pieszak-Trilon.io,2012年

不管您使用Javascript的哪种实现,都将始终必须:遍历项目,运行函数,将项目推入数组。我不认为有一个方法,使效率更高。
TheZ 2012年

3
您可以在传递给的回调中做任何您想做的事情,.filter但是这样的副作用很难跟踪和理解。只需遍历数组并推送到一个或另一个数组即可。
菲利克斯·克林

Answers:


75

借助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]]), [[], []]);

8
对我来说,lodash分区或forfor会更容易理解,但即使这样,也要付出很大的努力
Toni Leigh

15
这将为原始元素中的每个元素创建两个新数组。虽然一个数组只有两个元素,但另一个随着数组的大小而增长。因此,这将非常慢,并浪费大量内存。(您可以通过推动执行相同的操作,这样会更有效率。)
Stuart Schechter,

谢谢,节省了我的时间!
7urkm3n

braza,这真的很有用。其他注意事项:下面有一些更易读的版本。
拉菲

38

您可以使用lodash.partition

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.partition

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

16

您可以使用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;
      }, [[],[]]
    );
 };

更新。使用ES6语法,您还可以使用递归来实现:

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

3
恕我直言,第一个解决方案(推动)是随着阵列大小的增长而提供的更好的性能。
制造商史蒂夫

@ToolmakerSteve您能详细说明为什么吗?我也阅读了关于最佳答案的评论,但仍然感到困惑
顺便说一句

2
@buncis。第一种方法是一次检查每个元素,只需将该元素推入适当的数组即可。第二种方法为每个元素构造[...left, current][...right, current]-。我不知道确切的内部原理,但是我敢肯定,构造比将元素简单地推到数组上要贵得多。同样,作为一般规则,递归迭代要昂贵,因为递归每次都要创建一个“堆栈框架”。
ToolmakerSteve

14

这听起来与Ruby的Enumerable#partition方法非常相似。

如果函数没有副作用(即它不能更改原始数组),则没有比迭代每个元素并将元素推入两个数组之一更有效的分区方法了。

话虽这么说,创建Array执行此功能的方法可以说是“更优雅” 。在此示例中,filter函数是在原始数组的上下文中执行的(this即将是原始数组),它接收元素和元素的索引作为参数(类似于jQuery的eachmethod):

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

11
对于现代读者,请不要向全局标准库对象添加方法。这很危险,很可能会被覆盖,从而导致神秘和破坏行为。一个普通的旧函数(适当范围内)要安全得多,并且调用myFunc(array)与array.myFunc()一样“优雅”。
Emmett R.

14

我想出了这个小家伙。它用于您描述的所有内容,但在我看来,它看起来干净简洁。

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


1
该解决方案远胜于某些投票更多的解决方案,恕我直言。易于阅读,可一次遍历数组,并且不会分配和重新分配分区结果数组。我还喜欢将所有三个常见的过滤器值暴露给过滤器功能(值,索引和整个数组)。这将使此功能更加可重用。
speckledcarp

我也非常喜欢它的简洁和优雅。话虽如此,我发现它比for@qwertymk的旧答案中的简单循环慢得多。例如,对于包含100,000个元素的数组,其速度是我的系统的两倍。
tromgy

9

在过滤器功能中,您可以将错误的项目放入功能之外的另一个变量中:

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来提高代码的可读性。


同意以上海报...在过滤器函数中添加这种逻辑感觉有点肿并且难以管理。
theUtherSide

我认为过滤器很有意义,您想将它们从原始数组中删除,使其成为过滤器
Mojimi

5

易于阅读。

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)

6
不利的一面是,您要进行2次循环,而不是仅进行一次循环
洛朗

支持可读性,对于小型阵列,这显然是更未来的证明
allan.simon

4

试试这个:

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

演示


指出实际上并不是所有浏览器都支持数组的过滤器功能(我在寻找旧版本的IE)
TheZ 2012年

4

那这个呢?

[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:[]} )


2

这里有很多答案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']

-1

我最终这样做是因为它很容易理解(并使用打字稿完全键入)。

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