フィルタ関数による配列の分割


92

各要素で呼び出された関数が返すtrueか、を返すかに基づいて2つに分割したいJavascript配列がありますfalse。基本的にこれはですが、array.filterフィルターで除外さた要素も手元に置いておきたいと思います。

現在、私の計画ではarray.forEach、各要素で述語関数を使用して呼び出すことです。これが真か偽かに応じて、現在の要素を2つの新しい配列のいずれかにプッシュします。これを行うためのよりエレガントな、またはそうでなければより良い方法はありますか?たとえばarray.filter、要素が戻る前に別の配列に要素をプッシュする場所はどこfalseですか?


サンプルコードを投稿できれば、より良い答えが得られるでしょう!
Mark Pieszak-Trilon.io 2012年

Javascriptを使用する実装に関係なく、常に次のことを行う必要があります。アイテムをループし、関数を実行し、アイテムを配列にプッシュします。それをもっと効率的にする方法ないと思います。
TheZ 2012

3
渡されたコールバックでやりたいことは何でもできますが、.filterそのような副作用を追跡して理解するのは困難です。配列を繰り返し処理して、いずれかの配列にプッシュするだけです。
Felix Kling 2012

回答:


75

ES6では、reduceを使用してspread構文を利用できます。

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

または1行で:

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

8
私にとっては、lodashパーティションまたはforEachの方が理解しやすいでしょうが、それでも、すばらしい努力です
Toni Leigh

15
これにより、元の要素ごとに2つの新しい配列が作成されます。1つの配列には2つの要素しかありませんが、もう1つの配列は配列のサイズに応じて大きくなります。したがって、これは非常に遅くなり、大量のメモリを浪費します。(プッシュで同じことを行うことができ、それはより効率的です。)
Stuart Schechter 2018

ありがとう、私の時間を節約しました!
7urkm3n

これは本当に便利です、ブラザ。他の人への注意:以下にもっと読みやすいバージョンがいくつかあります。
ラフィ

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
IMHO、最初の解決策(プッシュ)は、アレイサイズが大きくなるにつれてパフォーマンスが向上することです。
ToolmakerSteve

@ToolmakerSteve理由を詳しく説明していただけますか?私もトップアンサーのコメントを読みましたが、それでも理由が
わかりません

2
@buncis。最初のアプローチでは、各要素を1回調べ、その要素を適切な配列にプッシュするだけです。第2のアプローチは、構築物[...left, current]又は[...right, current]要素ごと- 。正確な内部はわかりませんが、要素を配列にプッシュするよりも構築に費用がかかると確信しています。また、原則として、再帰は毎回「スタックフレーム」を作成する必要があるため、反復よりもコストがかかります。
ToolmakerSteve

14

これはRubyのEnumerable#partition方法と非常によく似ています。

関数に副作用がない場合(つまり、元の配列を変更できない場合)、各要素を反復処理して要素を2つの配列のいずれかにプッシュする以外に、配列を分割する効率的な方法はありません。

そうは言っても、Arrayこの機能を実行するためのメソッドを作成する方が、間違いなく「エレガント」です。この例では、フィルター関数は元の配列のコンテキストで実行され(つまり、元の配列に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 ] ]

11
最新のリーダーの場合、グローバル標準ライブラリオブジェクトにメソッドを追加しないでください。それは危険であり、上書きされる可能性が高く、神秘的で壊れた行動につながります。適切にスコープされた単純な古い関数の方がはるかに安全であり、myFunc(array)の呼び出しはarray.myFunc()と同じくらい「エレガント」です。
エメット

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
この解決策は、賛成票が多いいくつかの解決策、IMHOよりもはるかに優れています。読みやすく、配列を1回通過し、パーティション結果配列を割り当てたり再割り当てしたりしません。また、3つの一般的なフィルター値すべてをフィルター関数(値、インデックス、および配列全体)に公開することも気に入っています。これにより、この関数ははるかに再利用可能になります。
スペックル

私はまた、そのシンプルさと優雅さのためにこれがとても好きです。そうは言っても、for@ qwertymkによる古い回答のように、単純なループよりも大幅に遅いことがわかりました。たとえば、100,000個の要素を含む配列の場合、私のシステムでは2倍遅くなりました。
tromgy

9

フィルタ関数では、falseアイテムを関数外の別の変数にプッシュできます。

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 2018

フィルタは理にかなっていると思います。元の配列からそれらを削除して、フィルタにします
Mojimi 2018

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

デモ


配列のフィルター機能が実際にはすべてのブラウザーでサポートされているわけではないことを指摘します(私は
古い

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、可変アキュムレータの構築に使用されます。大きな配列の場合、これは、たとえば、スプレッド演算子を使用して反復ごとに新しい配列をコピーするよりも効率的であることを正しく指摘します。欠点は、短いラムダ構文を使用した「純粋な」式ほどきれいではないことです。

しかし、それを回避する方法は、コンマ演算子を使用することです。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']

0

ワンライナーパーティション

const partition = (a,f)=>a.reduce((p,q)=>(p[+!f(q)].push(q),p),[[],[]]);

デモ

// to make it consistent to filter pass index and array as arguments
const partition = (a, f) =>
    a.reduce((p, q, i, ar) => (p[+!f(q, i, ar)].push(q), p), [[], []]);

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

Typescriptの場合

const partition = <T>(
  a: T[],
  f: (v: T, i?: number, ar?: T[]) => boolean
): [T[], T[]] =>
  a.reduce((p, q, i, ar) => (p[+!f(q, i, ar)].push(q), p), [[], []]);

-1

理解しやすい(そして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)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.