Teilen eines Arrays durch Filterfunktion

89

Ich habe ein Javascript-Array, das ich in zwei Teile aufteilen möchte, je nachdem, ob eine für jedes Element aufgerufene Funktion trueoder zurückgibt false. Im Wesentlichen ist dies ein array.filter, aber ich möchte auch die Elemente zur Hand haben, die herausgefiltert wurden .

Derzeit ist mein Plan, array.forEachdie Prädikatfunktion für jedes Element zu verwenden und aufzurufen. Je nachdem, ob dies wahr oder falsch ist, werde ich das aktuelle Element auf eines der beiden neuen Arrays verschieben. Gibt es einen eleganteren oder anderweitig besseren Weg, dies zu tun? Und array.filterwo wird das Element zum Beispiel auf ein anderes Array verschoben, bevor es zurückkehrt false?

Mike Chen
quelle
Wenn Sie einen Beispielcode veröffentlichen können, erhalten Sie eine bessere Antwort!
Mark Pieszak - Trilon.io
Unabhängig davon, welche Implementierung Sie verwenden, muss Javascript immer: Elemente durchlaufen, die Funktion ausführen, das Element in ein Array verschieben. Ich glaube nicht , es ist eine Art und Weise , dass effizienter zu gestalten.
TheZ
3
Sie können in dem an den Rückruf übergebenen Rückruf alles tun, was Sie wollen, .filteraber solche Nebenwirkungen sind schwer zu verfolgen und zu verstehen. Durchlaufen Sie einfach das Array und wechseln Sie zu dem einen oder anderen Array.
Felix Kling

Antworten:

70

Mit ES6 können Sie die Spread-Syntax mit redu verwenden:

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

Oder in einer einzigen Zeile:

const [pass, fail] = a.reduce(([p, f], e) => (e > 5 ? [[...p, e], f] : [p, [...f, e]]), [[], []]);
Braza
quelle
7
Für mich wäre eine Lodash-Partition oder nur für jede leichter zu verstehen, aber trotzdem eine nette Anstrengung
Toni Leigh
15
Dadurch werden zwei neue Arrays für jedes Element im Original erstellt. Während ein Array nur zwei Elemente enthält, wächst das andere mit der Größe des Arrays. Dies ist also sehr langsam und verschwendet viel Speicher. (Sie könnten das gleiche mit einem Stoß tun und es wäre effizienter.)
Stuart Schechter
Danke, hat mir Zeit gespart!
7urkm3n
Das ist wirklich nützlich, Braza. Hinweis für andere: Es gibt unten einige besser lesbare Versionen.
Raffi vor
36

Sie können lodash.partition verwenden

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

oder 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' }  ]
Zoltan Kochan
quelle
16

Sie können dafür reduzieren:

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

Aktualisieren. Mit der ES6-Syntax können Sie dies auch mithilfe der Rekursion tun:

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]]);
}
Yaremenko Andrii
quelle
3
IMHO ist die erste Lösung (Push) eine bessere Leistung, wenn die Array-Größe zunimmt.
ToolmakerSteve
@ToolmakerSteve könnten Sie näher erläutern, warum? Ich habe auch einen Kommentar zur Top-Antwort gelesen, bin aber immer noch verwirrt, warum
buncis
2
@buncis. Der erste Ansatz untersucht jedes Element einmal und schiebt es einfach in das entsprechende Array. Der zweite Ansatz konstruiert [...left, current]oder [...right, current]- für jedes Element. Ich kenne die genauen Interna nicht, aber ich bin sicher, dass die Konstruktion teurer ist, als einfach ein Element auf ein Array zu übertragen. Außerdem ist die Rekursion in der Regel teurer als die Iteration , da jedes Mal ein "Stapelrahmen" erstellt wird.
ToolmakerSteve
14

Das klingt RubysEnumerable#partition Methode sehr ähnlich .

Wenn die Funktion keine Nebenwirkungen haben kann (dh das ursprüngliche Array kann nicht geändert werden), gibt es keine effizientere Möglichkeit, das Array zu partitionieren, als über jedes Element zu iterieren und das Element in eines Ihrer beiden Arrays zu verschieben.

ArrayDavon abgesehen ist es wohl "eleganter", eine Methode zur Ausführung dieser Funktion zu erstellen . In diesem Beispiel wird die Filterfunktion im Kontext des ursprünglichen Arrays ausgeführt (dh ist thisdas ursprüngliche Array) und empfängt das Element und den Index des Elements als Argumente (ähnlich der eachMethode von jQuery ):

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 ] ]
Brandan
quelle
11
Für den modernen Leser fügen Sie BITTE keine Methoden zu globalen Standardbibliotheksobjekten hinzu. Es ist gefährlich und wird wahrscheinlich überschrieben, was zu mysteriösem und gebrochenem Verhalten führt. Eine einfache alte Funktion mit ordnungsgemäßem Gültigkeitsbereich ist weitaus sicherer, und das Aufrufen von myFunc (Array) ist nicht weniger "elegant" als array.myFunc ().
Emmett R.
14

Ich habe mir diesen kleinen Kerl ausgedacht. Es verwendet für alles, was Sie beschrieben haben, aber es sieht meiner Meinung nach sauber und prägnant aus.

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

UDrake
quelle
1
Diese Lösung ist meiner Meinung nach weitaus besser als einige mit mehr positiven Stimmen. Einfach zu lesen, führt einen einzelnen Durchgang durch das Array durch und ordnet die Partitionsergebnis-Arrays nicht zu und ordnet sie nicht neu zu. Mir gefällt auch, dass alle drei allgemeinen Filterwerte der Filterfunktion (Wert, Index und das gesamte Array) zugänglich gemacht werden. Dadurch wird diese Funktion viel wiederverwendbarer.
Speckledcarp
Ich mag dieses auch sehr wegen seiner Einfachheit und Eleganz. forTrotzdem fand ich es deutlich langsamer als eine einfache Schleife, wie in einer alten Antwort von @qwertymk. Zum Beispiel war ein Array mit 100.000 Elementen auf meinem System doppelt so langsam.
Tromgy
9

In der Filterfunktion können Sie Ihre falschen Elemente in eine andere Variable außerhalb der Funktion verschieben:

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

Natürlich value === falsemuss ein echter Vergleich sein;)

Aber es macht fast die gleiche Operation wie forEach. Ich denke, Sie sollten forEachfür eine bessere Lesbarkeit des Codes verwenden.

Code Name-
quelle
Einverstanden mit dem obigen Poster ... das Einfügen dieser Art von Logik in eine Filterfunktion fühlt sich etwas aufgebläht und schwer zu handhaben an.
theUtherSide
Ich denke, Filter macht Sinn, Sie möchten sie aus dem ursprünglichen Array entfernen, damit es ein Filter ist
Mojimi
4

Versuche dies:

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

qwertymk
quelle
Ich weise darauf hin, dass die Filterfunktion von Arrays nicht in allen Browsern unterstützt wird (ich sehe dich nach älteren IEs)
TheZ
4

Was ist damit?

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

Wahrscheinlich ist dies effizienter als der Spread-Operator

Oder etwas kürzer, aber hässlicher

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

Einfach zu lesen.

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)
Matsumoto Kazuya
quelle
6
Der Nachteil ist, dass Sie 2 Schleifen statt nur einer machen
Laurent
Upvote für Lesbarkeit, für kleine Arrays ist dieses deutlich zukunftssicherer
allan.simon
-1

Am Ende habe ich das gemacht, weil es leicht zu verstehen ist (und vollständig mit Typoskript getippt ist).

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)
Andreas Gassmann
quelle