【问题标题】:Dividing an array by filter function按过滤函数除以数组
【发布时间】:2012-07-30 23:22:05
【问题描述】:

我有一个 Javascript 数组,我想根据每个元素上调用的函数是返回 true 还是 false 来将其拆分为两个。本质上,这是一个array.filter,但我还希望手头上有被过滤的元素out

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

【问题讨论】:

  • 如果您可以发布一些示例代码,将有助于给您更好的答案!
  • 无论您使用什么实现,Javascript 都必须:循环遍历项目,运行函数,将项目推送到数组中。我认为没有可以提高效率的方法。
  • 你可以在传递给.filter的回调中做任何你想做的事情,但是这样的副作用很难跟踪和理解。只需遍历数组并推送到一个或另一个数组。

标签: javascript


【解决方案1】:

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

【讨论】:

  • 对我来说,lodash 分区或只是 forEach 会更容易理解,但即便如此,还是很努力
  • 这将为原始元素中的每个元素创建两个新数组。虽然一个数组只有两个元素,但另一个会随着数组的大小而增长。因此,这将非常缓慢并浪费大量内存。 (你也可以用 push 做同样的事情,这样会更有效率。)
  • 谢谢,节省了我的时间!
  • 这真的很有用,布拉扎。其他人注意:下面有一些更易读的版本。
  • 这是对 O(n) 问题的 O(n^2) 解决方案。由于扩展运算符(或任何其他复制(部分)数组的方法)必须为数组的每个成员工作,并且此解决方案对原始数组中的每个元素多次应用此类操作。
【解决方案2】:

您可以使用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' }  ]

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

    • 这个解决方案比一些有更多支持的解决方案要好得多,恕我直言。易于阅读,通过数组进行单次传递,并且不分配和重新分配分区结果数组。我也喜欢它将所有三个常见的过滤器值暴露给过滤器函数(值、索引和整个数组)。这将使这个函数更加可重用。
    • 我也非常喜欢这一款,因为它简洁优雅。话虽如此,我发现它比@qwertymk 的旧答案中的简单for 循环慢得多。例如。对于包含 100,000 个元素的数组,它在我的系统上的速度是原来的两倍。
    【解决方案4】:

    你可以使用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)) {
            left.push(current);
            return partition(tail, f, left, right);
        }
        right.push(current);
        return partition(tail, f, left, right);
    }
    

    【讨论】:

    • 恕我直言,第一个解决方案(推送)随着数组大小的增长而具有更好的性能。
    • @ToolmakerSteve 你能详细说明为什么吗?我还阅读了关于最佳答案的评论,但仍然很困惑为什么
    • @buncis。第一种方法检查每个元素一次,只需将该元素推送到适当的数组。第二种方法为每个元素构造 [...left, current][...right, current] -。我不知道确切的内部结构,但我确信构造比简单地将元素推入数组更昂贵。此外,作为一般规则,recursioniterating 更昂贵,因为它每次都涉及创建一个“堆栈框架”。
    • @buncis 不仅必须为源数组中的每个元素构造一个数组(在 JS 中构造东西是高度优化的,这还不是最糟糕的),它还每次执行时都会复制源数组那。这是一个 O(n) 操作(工作量与数组大小成正比)。它对源数组中的每个成员重复此操作,使总工作量与数组中的元素数成平方。如果这项工作可以通过与元素数量成比例增长的许多操作来完成,这显然不是很好。
    【解决方案5】:

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

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

    话虽如此,可以说在Array 上创建一个方法来执行此功能更“优雅”。在这个例子中,过滤函数在原始数组的上下文中执行(即this将是原始数组),它接收元素和元素的索引作为参数(类似于jQuery's each method):

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

    【讨论】:

    • 对于现代读者,请不要向全局标准库对象添加方法。它很危险,很可能被覆盖,导致神秘和破坏行为。一个普通的旧函数,适当的范围,更安全,调用 myFunc(array) 并不比 array.myFunc() 更“优雅”。
    【解决方案6】:

    在过滤器函数中,你可以将你的错误项目推入函数外部的另一个变量中:

    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 以获得更好的代码可读性。

    【讨论】:

    • 同意上面的海报...把这种逻辑放在过滤函数中感觉有点臃肿且难以管理。
    • 我认为过滤器是有道理的,你想将它们从原始数组中删除,使其成为过滤器
    【解决方案7】:

    这个怎么样?

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

    【讨论】:

    • 非常简洁明了的答案。只想补充一点,如果你想重命名输出,你可以这样做:let { true: hello, false: world } = [1,4,3,5,3,2].reduce( (s, x) =&gt; s[ x &gt; 3 ].push(x)?s:s , {true: [], false:[]} )
    【解决方案8】:

    简单易读。

    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)
    

    【讨论】:

    • 缺点是你做了2个循环而不是一个
    • 支持可读性,对于小型阵列,这显然更具未来性
    【解决方案9】:

    这里的很多答案都使用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']
    

    【讨论】:

      【解决方案10】:

      试试这个:

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

      DEMO

      【讨论】:

      • 指出并非所有浏览器实际上都支持数组的过滤功能(我正在看你的旧IE)
      【解决方案11】:

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

      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)
      

      同样的方法包括 typescript 的类型:

      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)
      

      【讨论】:

        【解决方案12】:

        我知道已经有多种解决方案,但我冒昧地将上述答案的最佳部分放在一起,并在 Typescript 上使用了扩展方法。复制粘贴就行了:

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

        【讨论】:

          【解决方案13】:

          Lodash partition 替代,与@Yaremenko Andrii 的first solution 相同,但语法更短

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

          【讨论】:

            【解决方案14】:

            单线分区

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2023-03-20
              • 1970-01-01
              • 1970-01-01
              • 2016-04-20
              • 2021-08-27
              • 1970-01-01
              相关资源
              最近更新 更多