Dzielenie tablicy przez funkcję filtrującą


92

Mam tablicę Javascript, którą chciałbym podzielić na dwie w zależności od tego, czy funkcja wywołana na każdym elemencie zwraca, trueczy false. Zasadniczo, jest to array.filter, ale chciałbym też mieć pod ręką elementy, które zostały odfiltrowane na zewnątrz .

Obecnie planuję użyć array.forEachi wywołać funkcję predykatu na każdym elemencie. W zależności od tego, czy to prawda, czy fałsz, wypchnę bieżący element na jedną z dwóch nowych tablic. Czy jest na to bardziej elegancki lub lepszy sposób? array.filterGdzie popchnie element na innej tablicy zanim powróci falsena przykład?


Jeśli możesz opublikować przykładowy kod, pomoże ci udzielić lepszej odpowiedzi!
Mark Pieszak - Trilon.io

Bez względu na to, jakiej implementacji używasz, Javascript zawsze będzie musiał: zapętlić elementy, uruchomić funkcję, wrzucić element do tablicy. Nie sądzę, aby można było to uczynić bardziej wydajnymi.
TheZ

3
Możesz robić, co chcesz w przekazanym wywołaniu zwrotnym, .filterale takie skutki uboczne są trudne do śledzenia i zrozumienia. Po prostu wykonaj iterację po tablicy i wypchnij do jednej lub drugiej tablicy.
Felix Kling

Odpowiedzi:


75

W ES6 możesz skorzystać ze składni spreadu, używając funkcji redukuj:

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

Lub w jednym wierszu:

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

8
Dla mnie lodash partycji lub po prostu forEach byłoby łatwiejsze do zrozumienia, ale i tak miło wysiłek
Toni Leigh

15
Spowoduje to utworzenie dwóch nowych tablic dla każdego elementu oryginału. Podczas gdy jedna tablica będzie miała tylko dwa elementy, druga rośnie wraz z rozmiarem tablicy. Będzie to zatem bardzo wolne i marnuje dużo pamięci. (Możesz zrobić to samo z pchnięciem i byłoby bardziej wydajne.)
Stuart Schechter

Dzięki, zaoszczędziłem czas!
7urkm3n

To jest naprawdę przydatne, braza. Uwaga dla innych: poniżej znajdują się bardziej czytelne wersje.
Raffi

38

Możesz użyć 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']]

lub 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

Możesz użyć do tego zredukować:

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

Aktualizacja. Używając składni ES6, możesz to również zrobić używając rekursji:

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, pierwsze rozwiązanie (push) to lepsza wydajność wraz ze wzrostem rozmiaru tablicy.
ToolmakerSteve

@ToolmakerSteve czy mógłbyś wyjaśnić więcej, dlaczego? Przeczytałem również komentarz do najlepszej odpowiedzi, ale nadal nie
rozumiem

2
@buncis. Pierwsze podejście bada każdy element jeden raz, po prostu wypychając ten element do odpowiedniej tablicy. Drugie podejście konstruuje [...left, current]lub [...right, current]- dla każdego elementu. Nie znam dokładnych elementów wewnętrznych, ale jestem pewien, że konstrukcja jest droższa niż zwykłe wpychanie elementu do tablicy. Ponadto, zgodnie z ogólną zasadą, rekurencja jest droższa niż iteracja , ponieważ wymaga za każdym razem tworzenia „ramki stosu”.
ToolmakerSteve

14

Brzmi to bardzo podobnie do metody RubiegoEnumerable#partition .

Jeśli funkcja nie może mieć skutków ubocznych (tj. Nie może zmienić oryginalnej tablicy), nie ma bardziej wydajnego sposobu na podzielenie tablicy niż iterowanie po każdym elemencie i wypychanie elementu do jednej z dwóch tablic.

Biorąc to pod uwagę, prawdopodobnie bardziej „eleganckie” jest stworzenie metody Arraydo wykonywania tej funkcji. W tym przykładzie funkcja filtrująca jest wykonywana w kontekście oryginalnej tablicy (tj. thisBędzie oryginalną tablicą) i odbiera element i indeks elementu jako argumenty (podobnie do metody jQueryeach ):

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
Dla współczesnego czytelnika PROSZĘ nie dodawać metod do globalnych standardowych obiektów bibliotecznych. Jest niebezpieczny i prawdopodobnie zostanie nadpisany, co prowadzi do tajemniczego i zepsutego zachowania. Zwykła, stara funkcja z odpowiednim zakresem jest dużo bezpieczniejsza, a wywołanie funkcji myFunc (tablica) jest nie mniej „eleganckie” niż array.myFunc ().
Emmett R.

14

Wymyśliłem tego małego faceta. Używa do wszystkiego, co opisałeś, ale moim zdaniem wygląda czysto i zwięźle.

//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
To rozwiązanie jest o wiele lepsze niż niektóre z większą liczbą pozytywnych głosów, IMHO. Łatwy do odczytania, wykonuje pojedyncze przejście przez tablicę i nie przydziela ani ponownie przydziela tablic wyników partycji. Podoba mi się również to, że eksponuje wszystkie trzy typowe wartości filtrów funkcji filtrującej (wartość, indeks i całą tablicę). Dzięki temu ta funkcja będzie znacznie łatwiejsza do ponownego wykorzystania.
speckledcarp

Ten też bardzo mi się podoba ze względu na jego prostotę i elegancję. Powiedziawszy to, stwierdziłem, że jest znacznie wolniejszy niż prosta forpętla, jak w starej odpowiedzi @qwertymk. Np. Dla tablicy zawierającej 100 000 elementów był dwukrotnie wolniejszy w moim systemie.
tromgy

9

W funkcji filtrującej możesz umieścić swoje fałszywe pozycje w innej zmiennej poza funkcją:

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

Oczywiście value === falsetrzeba być prawdziwym porównaniem;)

Ale robi prawie taką samą operację jak forEach. Myślę, że powinieneś użyć go forEachdla lepszej czytelności kodu.


Zgadzam się z powyższym plakatem ... umieszczenie tego rodzaju logiki w funkcji filtra wydaje się nieco nadęte i trudne do zarządzania.
theUtherSide

Myślę, że filtr ma sens, chcesz usunąć je z oryginalnej tablicy, więc to filtr
Mojimi

5

Łatwy do odczytania.

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
Wadą jest to, że robisz 2 pętle zamiast tylko jednej
Laurent

głosuj za czytelnością, dla małych tablic ten jest bardziej przejrzysty, bardziej przyszłościowy
allan.simon

4

Spróbuj tego:

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

PRÓBNY


Zwracając uwagę, że funkcja filtrująca tablice nie jest w rzeczywistości obsługiwana we wszystkich przeglądarkach (PATRZĘ NA CIEBIE STARSZE IE)
TheZ

4

A co z tym?

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

Prawdopodobnie jest to bardziej wydajne niż operator rozprzestrzeniania

Albo trochę krócej, ale brzydszy

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


2

Wiele odpowiedzi tutaj używa Array.prototype.reducedo zbudowania zmiennego akumulatora i słusznie wskazuje, że w przypadku dużych tablic jest to bardziej wydajne niż, powiedzmy, użycie operatora rozprzestrzeniania do kopiowania nowej tablicy w każdej iteracji. Wadą jest to, że nie jest tak ładne jak „czyste” wyrażenie używające krótkiej składni lambda.

Ale sposobem na obejście tego jest użycie operatora przecinka. W językach podobnych do języka C przecinek jest operatorem, który zawsze zwraca operand po prawej stronie. Możesz użyć tego do utworzenia wyrażenia, które wywołuje funkcję void i zwraca wartość.

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

Jeśli wykorzystasz fakt, że wyrażenie boolowskie niejawnie rzutuje na liczbę jako 0 i 1, możesz uczynić to jeszcze bardziej zwięzłym, chociaż nie sądzę, że jest tak czytelny:

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

Stosowanie:

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

0

Partycja JEDNO LINIOWA

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

PRÓBNY

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

Dla maszynopisu

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

Skończyło się na tym, że zrobiłem to, ponieważ jest to łatwe do zrozumienia (i w pełni wpisane na maszynie).

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)
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.