Bir diziyi filtre işlevine göre bölme


92

Her öğede çağrılan bir işlevin trueveya dönmesine bağlı olarak ikiye bölmek istediğim bir Javascript dizim var false. Esasen, bu bir olduğunu array.filter, ama aynı zamanda taraftan filtrelenen unsurları olmasını istiyorum dışarı .

Şu anda, planım array.forEachher elemanda yüklem işlevini kullanmak ve çağırmak. Bunun doğru veya yanlış olmasına bağlı olarak, mevcut elemanı iki yeni diziden birine göndereceğim. Bunu yapmanın daha zarif veya daha iyi bir yolu var mı? Bir array.filtergeri döndürür önce burada başka bir dizi üzerine elemanı itecektir falseÖrneğin,?


Bazı örnek kodlar gönderebilirseniz, size daha iyi bir yanıt vermenize yardımcı olur!
Mark Pieszak - Trilon.io

Javascript'i kullandığınız uygulama ne olursa olsun her zaman şunları yapmak zorunda olacaktır: öğeler arasında döngü yapmak, işlevi çalıştırmak, öğeyi bir diziye itmek. Orada sanmıyorum olduğunu o daha verimli hale getirmek bir yol.
TheZ

3
Aktarılan geri aramada istediğinizi yapabilirsiniz, .filterancak bu tür yan etkilerin izlenmesi ve anlaşılması zordur. Sadece dizi üzerinde yineleyin ve bir diziye veya diğerine itin.
Felix Kling

Yanıtlar:


75

ES6 ile, yayılma sözdizimini azaltarak kullanabilirsiniz:

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

Veya tek bir satırda:

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

8
Benim için, lodash bölme ya da sadece her biri için anlaşılması daha kolay olurdu, ama yine de iyi bir çaba
Toni Leigh

15
Bu, orijinaldeki her eleman için iki yeni dizi yaratacaktır. Bir dizinin yalnızca iki öğesi olurken, diğeri dizinin boyutuyla birlikte büyür. Bu nedenle bu çok yavaş olacak ve çok fazla hafıza israfına neden olacaktır. (Aynı şeyi bir itme ile de yapabilirsiniz ve daha verimli olur.)
Stuart Schechter

Teşekkürler, zamanımı kurtardım!
7urkm3n

Bu gerçekten yararlı braza. Diğerleri için not: Aşağıda daha okunabilir bazı sürümler var.
Raffi

38

Lodash.partition kullanabilirsiniz

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

veya 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

Bunun için azaltmayı kullanabilirsiniz:

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

Güncelleme. ES6 sözdizimini kullanarak, bunu özyinelemeyi kullanarak da yapabilirsiniz:

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, ilk çözüm (itme), dizi boyutu büyüdükçe daha iyi performanstır.
ToolmakerSteve

@ToolmakerSteve neden daha fazla ayrıntı verebilir misiniz? Ayrıca en iyi cevaba yapılan yorumu da okudum ama neden hala kafam karıştı
buncis

2
@buncis. İlk yaklaşım, her bir öğeyi bir kez inceler, basitçe bu öğeyi uygun diziye iter. İkinci yaklaşım , her öğe için [...left, current]veya [...right, current]- oluşturur. Tam olarak iç kısımları bilmiyorum ama eminim ki yapım, bir elemanı bir diziye basitçe itmekten daha pahalı. Ayrıca, genel bir kural olarak, özyineleme , her seferinde bir "yığın çerçevesi" oluşturmayı içerdiğinden, yinelemeden daha pahalıdır .
ToolmakerSteve

14

Bu Ruby'ninEnumerable#partition yöntemine çok benziyor .

İşlevin yan etkileri yoksa (yani, orijinal diziyi değiştiremezse), diziyi bölümlemenin her bir öğe üzerinde yinelemekten ve öğeyi iki dizinizden birine itmekten daha etkili bir yolu yoktur.

Bununla birlikte, Arraybu işlevi gerçekleştirmek için bir yöntem oluşturmak tartışmaya açık bir şekilde daha "zariftir" . Bu örnekte, filtre işlevi orijinal dizi bağlamında yürütülür (yani, thisorijinal dizi olacaktır) ve öğeyi ve öğenin dizinini argüman olarak alır ( jQuery eachyöntemine benzer ):

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
Modern okuyucu için LÜTFEN global standart kütüphane nesnelerine yöntem eklemeyin. Tehlikelidir ve üzerine yazılması, gizemli ve bozuk davranışlara yol açması muhtemeldir. Düzgün bir eski işlev, kapsamı uygun şekilde ayarlanmış, çok daha güvenlidir ve myFunc (dizi) 'yi çağırmak, array.myFunc () işlevinden daha az "zarif" değildir.
Emmett R.

14

Bu küçük adamla geldim. Tarif ettiğiniz gibi her biri için kullanıyor, ancak bence temiz ve özlü görünüyor.

//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
Bu çözüm, daha fazla oy alan bazılarından çok daha iyidir, IMHO. Okunması kolaydır, dizide tek bir geçiş yapar ve bölüm sonuç dizilerini tahsis etmez ve yeniden tahsis etmez. Ayrıca, üç ortak filtre değerini de filtre işlevine (değer, dizin ve tüm dizi) göstermesini seviyorum. Bu, bu işlevi çok daha yeniden kullanılabilir hale getirecektir.
speckledcarp

Sadeliği ve zarafeti nedeniyle bunu da çok beğeniyorum. Bunu söyledikten sonra, for@qwertymk tarafından verilen eski bir cevapta olduğu gibi basit bir döngüden önemli ölçüde daha yavaş olduğunu buldum. Örneğin, 100.000 öğe içeren bir dizi için sistemimde iki kat daha yavaştı.
tromgy

9

Filtre işlevinde, yanlış öğelerinizi başka bir değişken dışındaki işlevin içine itebilirsiniz:

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

Tabii ki value === falsegerçek bir karşılaştırma olmalı;)

Ama neredeyse aynı işlemi yapıyor forEach. Daha forEachiyi bir kod okunabilirliği için kullanmanız gerektiğini düşünüyorum .


Yukarıdaki afişle aynı fikirde ... Bu tür bir mantığı bir filtre işlevine koymak biraz şişkin ve yönetilmesi zor geliyor.
theUtherSide

Filtrenin mantıklı olduğunu düşünüyorum, onları orijinal diziden kaldırmak istiyorsunuz, böylece bir filtre olsun
Mojimi

5

Okuması kolay.

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
Olumsuz yanı, sadece bir yerine 2 döngü yapmanızdır
Laurent

okunabilirlik için olumlu oy verin, küçük dizi için bu açık bir şekilde daha
ileriye

4

Bunu dene:

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


Dizilerin filtre işlevinin aslında tüm tarayıcılarda desteklenmediğine işaret ederek (SİZE ESKİ
IE'lere BAKIYORUM

4

Peki buna ne dersin?

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

Muhtemelen bu, yayma operatöründen daha verimlidir

Ya da biraz daha kısa ama daha çirkin

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


2

Buradaki birçok cevap Array.prototype.reduce, değiştirilebilir bir toplayıcı oluşturmak için kullanılır ve haklı olarak, bunun, örneğin, her yinelemede yeni bir diziyi kopyalamak için bir yayılma operatörü kullanmaktan daha verimli olduğuna işaret edin. Olumsuz yanı, kısa lambda sözdizimini kullanan "saf" bir ifade kadar güzel olmamasıdır.

Ancak bunun bir yolu virgül operatörünü kullanmaktır. C benzeri dillerde virgül, her zaman sağ el operandını döndüren bir operatördür. Bunu, bir void işlevini çağıran ve bir değer döndüren bir ifade oluşturmak için kullanabilirsiniz.

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

Bir boole ifadesinin örtük olarak 0 ve 1 olarak bir sayıya dönüştürülmesinden yararlanırsanız ve okunabilir olduğunu düşünmeme rağmen onu daha da kısa hale getirebilirsiniz:

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

Kullanım:

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

0

ONE-LINER Bölme

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

DEMO

// 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 için

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

Bunu yaptım çünkü anlaşılması kolay (ve tamamen typcript ile yazılmış).

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)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.