Membagi array dengan fungsi filter

92

Saya memiliki array Javascript yang ingin saya bagi menjadi dua berdasarkan apakah fungsi yang dipanggil pada setiap elemen kembali trueatau false. Pada dasarnya, ini adalah array.filter, tapi saya ingin juga ada di tangan unsur-unsur yang disaring keluar .

Saat ini, rencana saya adalah menggunakan array.forEachdan memanggil fungsi predikat pada setiap elemen. Bergantung pada apakah ini benar atau salah, saya akan mendorong elemen saat ini ke salah satu dari dua larik baru. Apakah ada cara yang lebih elegan atau lebih baik untuk melakukan ini? Di array.filtermana kemauan mendorong elemen ke array lain sebelum kembali false, misalnya?

Mike Chen
sumber
Jika Anda dapat memposting beberapa kode sampel, akan membantu memberikan jawaban yang lebih baik!
Mark Pieszak - Trilon.io
Tidak peduli implementasi apa yang Anda gunakan Javascript akan selalu harus: loop melalui item, menjalankan fungsi, mendorong item ke dalam array. Saya tidak berpikir ada adalah cara untuk membuat lebih efisien.
TheZ
3
Anda dapat melakukan apa pun yang Anda inginkan dalam panggilan balik yang diteruskan .filtertetapi efek samping seperti itu sulit dilacak dan dipahami. Lakukan iterasi pada array dan dorong ke satu array atau lainnya.
Felix Kling

Jawaban:

76

Dengan ES6 Anda dapat menggunakan sintaks spread dengan mengurangi:

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

Atau dalam satu baris:

const [pass, fail] = a.reduce(([p, f], e) => (e > 5 ? [[...p, e], f] : [p, [...f, e]]), [[], []]);
braza
sumber
8
Bagi saya, partisi lodash atau hanya forEach akan lebih mudah dipahami, tetapi meskipun demikian, usaha yang bagus
Toni Leigh
15
Ini akan membuat dua larik baru untuk setiap elemen dalam aslinya. Sementara satu array hanya akan memiliki dua elemen, yang lain bertambah dengan ukuran array. Ini akan menjadi sangat lambat dan menghabiskan banyak memori. (Anda bisa melakukan hal yang sama dengan dorongan dan itu akan lebih efisien.)
Stuart Schechter
Terima kasih, hemat waktu saya!
7urkm3n
Ini sangat berguna, braza. Catatan untuk yang lain: ada beberapa versi yang lebih bisa dibaca di bawah.
Raffi
38

Anda dapat menggunakan 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']]

atau 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
sumber
16

Anda dapat menggunakan pengurangan untuk itu:

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

Memperbarui. Menggunakan sintaks ES6, Anda juga dapat melakukannya menggunakan rekursi:

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
sumber
3
IMHO, solusi pertama (push) adalah kinerja yang lebih baik seiring bertambahnya ukuran array.
ToolmakerSteve
@ToolmakerSteve bisakah Anda menjelaskan lebih lanjut mengapa? Saya juga membaca komentar di jawaban atas tetapi masih bingung mengapa
buncis
2
@buncis. Pendekatan pertama memeriksa setiap elemen satu kali, cukup mendorong elemen itu ke array yang sesuai. Pendekatan kedua membangun [...left, current]atau [...right, current]- untuk setiap elemen. Saya tidak tahu internal persisnya, tetapi saya yakin bahwa konstruksinya lebih mahal daripada sekadar mendorong elemen ke array. Selain itu, sebagai aturan umum, rekursi lebih mahal daripada iterasi , karena melibatkan pembuatan "bingkai tumpukan" setiap kali.
ToolmakerSteve
14

Ini terdengar sangat mirip dengan metode RubyEnumerable#partition .

Jika fungsi tidak dapat memiliki efek samping (yaitu, tidak dapat mengubah array asli), maka tidak ada cara yang lebih efisien untuk mempartisi array daripada mengulang setiap elemen dan mendorong elemen tersebut ke salah satu dari dua array Anda.

Karena itu, membuat metode Arrayuntuk menjalankan fungsi ini bisa dibilang lebih "elegan" . Dalam contoh ini, fungsi filter dijalankan dalam konteks array asli (yaitu, thisakan menjadi array asli), dan menerima elemen dan indeks elemen sebagai argumen (mirip dengan metode 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 ] ]
Brandan
sumber
11
Untuk pembaca modern, PLEASE jangan menambahkan metode ke objek pustaka standar global. Ini berbahaya, dan kemungkinan akan ditimpa, menyebabkan perilaku misterius dan rusak. Fungsi lama yang biasa, dengan cakupan yang tepat, jauh lebih aman, dan memanggil myFunc (array) tidak kalah "elegan" dari array.myFunc ().
Emmett R.
14

Saya datang dengan pria kecil ini. Ini digunakan untuk setiap dan semua yang seperti yang Anda jelaskan, tetapi terlihat bersih dan ringkas menurut saya.

//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
sumber
1
Solusi ini jauh lebih baik daripada beberapa solusi dengan suara positif lebih banyak, IMHO. Mudah dibaca, membuat satu lintasan melalui larik, dan tidak mengalokasikan dan mengalokasikan kembali larik hasil partisi. Saya juga suka itu mengekspos ketiga nilai filter umum ke fungsi filter (nilai, indeks, dan seluruh array). Ini akan membuat fungsi ini jauh lebih dapat digunakan kembali.
speckledcarp
Saya juga sangat menyukai yang ini karena kesederhanaan dan keanggunannya. Karena itu, saya merasa itu jauh lebih lambat daripada forloop sederhana seperti dalam jawaban lama oleh @qwertymk. Misalnya untuk sebuah array yang berisi 100.000 elemen, itu dua kali lebih lambat di sistem saya.
tromgy
9

Dalam fungsi filter, Anda dapat mendorong item palsu Anda ke variabel lain di luar fungsi:

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

Tentu saja value === falseperlu perbandingan nyata;)

Tapi itu melakukan operasi yang hampir sama seperti forEach. Saya pikir Anda harus menggunakan forEachpembacaan kode yang lebih baik.

codename-
sumber
Setuju dengan poster di atas ... menempatkan logika semacam ini dalam fungsi filter terasa agak membengkak dan sulit untuk dikelola.
theUtherSide
Saya pikir filter masuk akal, Anda ingin menghapusnya dari larik asli sehingga menjadi filter
Mojimi
5

Mudah dibaca.

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
sumber
6
Sisi negatifnya adalah Anda membuat 2 loop, bukan hanya satu
Laurent
suara positif untuk keterbacaan, untuk array kecil yang satu ini lebih jelas bukti masa depan
allan.simon
4

Coba ini:

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.dll
sumber
Menunjukkan bahwa fungsi filter array sebenarnya tidak didukung di semua browser (SAYA MELIHAT ANDA LEBIH LAMA IEs)
TheZ
4

Bagaimana dengan ini?

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

Mungkin ini lebih efisien daripada operator penyebaran

Atau sedikit lebih pendek, tapi lebih jelek

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

Vereb
sumber
2

Banyak jawaban di sini digunakan Array.prototype.reduceuntuk membangun akumulator yang bisa berubah, dan dengan tepat menunjukkan bahwa untuk larik yang besar, ini lebih efisien daripada, katakanlah, menggunakan operator penyebaran untuk menyalin larik baru setiap iterasi. Sisi negatifnya adalah bahwa ini tidak secantik ekspresi "murni" yang menggunakan sintaks lambda pendek.

Tapi jalan keluarnya adalah dengan menggunakan operator koma. Dalam bahasa mirip C, koma adalah operator yang selalu mengembalikan operan tangan kanan. Anda dapat menggunakan ini untuk membuat ekspresi yang memanggil fungsi void dan mengembalikan nilai.

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

Jika Anda memanfaatkan fakta bahwa ekspresi boolean secara implisit diubah menjadi angka 0 dan 1, dan Anda dapat membuatnya lebih ringkas, meskipun menurut saya tidak dapat dibaca:

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

Pemakaian:

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

Partisi ONE-LINER

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

Untuk Ketikan

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), [[], []]);
nkitku
sumber
Versi Ketikan menunjukkan kesalahan kode untuk .pushparameter q: "Argumen tipe 'T' tidak dapat ditetapkan ke parameter jenis 'never'.ts (2345)"
Steve Goossens
-1

Saya akhirnya melakukan ini karena mudah dimengerti (dan sepenuhnya diketik dengan skrip ketikan).

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
sumber