Memetakan dan memfilter array pada saat yang bersamaan

155

Saya memiliki array objek yang ingin saya iterate untuk menghasilkan array yang difilter baru. Tetapi juga, saya perlu menyaring beberapa objek dari array baru tergantung dari suatu parameter. Saya mencoba ini:

function renderOptions(options) {
    return options.map(function (option) {
        if (!option.assigned) {
            return (someNewObject);
        }
    });   
}

Apakah itu pendekatan yang baik? Apakah ada metode yang lebih baik? Saya terbuka untuk menggunakan perpustakaan apa pun seperti lodash.

Daniel Calderon Mori
sumber
bagaimana dengan pendekatan "object.key"? developer.mozilla.org/fr/docs/Web/JavaScript/Reference/…
Julo0sS
3
.reduce()pasti lebih cepat daripada melakukan .filter(...).map(...)yang saya lihat disarankan di tempat lain. Saya membuat Tes JSPerf untuk mendemonstrasikan stackoverflow.com/a/47877054/2379922
Justin L.

Jawaban:

220

Anda harus menggunakannya Array.reduceuntuk ini.

var options = [
  { name: 'One', assigned: true }, 
  { name: 'Two', assigned: false }, 
  { name: 'Three', assigned: true }, 
];

var reduced = options.reduce(function(filtered, option) {
  if (option.assigned) {
     var someNewValue = { name: option.name, newProperty: 'Foo' }
     filtered.push(someNewValue);
  }
  return filtered;
}, []);

document.getElementById('output').innerHTML = JSON.stringify(reduced);
<h1>Only assigned options</h1>
<pre id="output"> </pre>


Atau, peredam bisa menjadi fungsi murni, seperti ini

var reduced = options.reduce(function(result, option) {
  if (option.assigned) {
    return result.concat({
      name: option.name,
      newProperty: 'Foo'
    });
  }
  return result;
}, []);
thethourtheye
sumber
2
Bagi saya, arg pertama, filteredadalah sebuah objek. jadi filtered.pushtidak ditentukan untuk saya.
Rahmathullah M
4
Saya juga punya masalah dengan filteredmenjadi objek. Ini karena saya tidak meneruskan "nilai awal" - array kosong ( []) setelah fungsi pengurangan. mis salah var reduced = options.reduce(function(filtered, option) { ... }); benar var reduced = options.reduce(function(filtered, option) { ... }, []);
Jon Higgins
Tidak ada alasan untuk di reducesini, Anda bisa menggunakan forEachkarena di setiap iterasi Anda mengubah array filtereddan dengan demikian itu tidak berfungsi murni. Itu akan menjadi acara yang lebih mudah dibaca dan kompak.
Marko
1
@ Marko Saya memperkenalkan peredam murni juga. PTAL.
theouroureye
Yap itu murni sekarang. +1 untuk upaya Anda. Bukannya saya pendukung pemrograman fungsional murni, jauh dari itu, saya tidak bisa mengerti intinya :) Tapi sebenarnya saya telah menggunakan trik Anda setelah melihatnya, karena itu sangat berguna dalam situasi yang diberikan. Tetapi untuk tugas ini Anda mungkin ingin melihat fungsi flatMap, saya pikir itu menjadi standar setelah Anda membuat jawaban Anda (juga karena alasan itu mungkin tidak didukung oleh beberapa browser). Seharusnya lebih berkinerja karena menggabungkan array seperti ini membuat O (n ^ 2) tugas keluar dari O (n) tugas.
Marko
43

Gunakan pengurangan, Luke!

function renderOptions(options) {
    return options.reduce(function (res, option) {
        if (!option.assigned) {
            res.push(someNewObject);
        }
        return res;
    }, []);   
}
Zuker
sumber
30

Sejak 2019, Array.prototype.flatMap adalah pilihan yang baik.

options.flatMap(o => o.assigned ? [o.name] : []);

Dari halaman MDN yang ditautkan di atas:

flatMapdapat digunakan sebagai cara untuk menambah dan menghapus item (memodifikasi jumlah item) selama peta. Dengan kata lain, ini memungkinkan Anda untuk memetakan banyak item ke banyak item (dengan menangani setiap item input secara terpisah), daripada selalu satu-ke-satu. Dalam hal ini, ini berfungsi seperti kebalikan dari filter. Cukup kembalikan array 1 elemen untuk menyimpan item, array multi-elemen untuk menambahkan item, atau array 0-elemen untuk menghapus item.

Trevor Dixon
sumber
5
Solusi bagus! Tapi, ingatlah bahwa pembaca flatMapbaru saja diperkenalkan dan dukungan browsernya terbatas . Anda mungkin ingin menggunakan polyfill / shims resmi: flatMap dan flat .
Arel
24

Dengan ES6 Anda dapat melakukannya dengan sangat singkat:

options.filter(opt => !opt.assigned).map(opt => someNewObject)

Natividad Lara Diaz
sumber
25
Ini akan melakukan dua loop, tergantung pada pengembalian filter apa yang masih dapat memakan memori.
hogan
1
@ hogan tetapi ini adalah jawaban yang benar (mungkin bukan solusi optimal) untuk permintaan asli
vikramvi
1
@vikramvi terima kasih atas catatan Anda. Masalahnya adalah, kita dapat mencapai hal yang sama dengan banyak cara dan saya lebih suka yang terbaik.
hogan
10

Satu baris reducedengan sintaksis penyebaran mewah ES6 ada di sini!

var options = [
  { name: 'One', assigned: true }, 
  { name: 'Two', assigned: false }, 
  { name: 'Three', assigned: true }, 
];

const filtered = options
  .reduce((result, {name, assigned}) => [...result, ...assigned ? [name] : []], []);

console.log(filtered);

Maxim Kuzmin
sumber
1
@Maxim sangat bagus! Saya membenarkan ini! Tapi ... pada setiap elemen yang ditambahkan, itu harus menyebar semua elemen di result... seperti filter(assigned).map(name)solusi
0zkr PM
Bagus Butuh beberapa detik untuk menyadari apa yang sedang terjadi ...assigned ? [name] : []- mungkin lebih mudah dibaca...(assigned ? [name] : [])
johansenja
5

Saya akan berkomentar, tetapi saya tidak memiliki reputasi yang diperlukan. Sebuah peningkatan kecil untuk jawaban Maxim Kuzmin yang sebaliknya sangat baik untuk membuatnya lebih efisien:

const options = [
  { name: 'One', assigned: true }, 
  { name: 'Two', assigned: false }, 
  { name: 'Three', assigned: true }, 
];

const filtered = options
  .reduce((result, { name, assigned }) => assigned ? result.concat(name) : result, []);

console.log(filtered);

Penjelasan

Alih-alih menyebarkan seluruh hasil berulang untuk setiap iterasi, kami hanya menambahkan ke array, dan hanya ketika sebenarnya ada nilai untuk disisipkan.

matsve
sumber
3

Gunakan Array.prototy.filter itu sendiri

function renderOptions(options) {
    return options.filter(function(option){
        return !option.assigned;
    }).map(function (option) {
        return (someNewObject);
    });   
}
AmmarCSE
sumber
2

Pada titik tertentu, bukankah lebih mudah (atau semudah) menggunakan a forEach

var options = [
  { name: 'One', assigned: true }, 
  { name: 'Two', assigned: false }, 
  { name: 'Three', assigned: true }, 
];

var reduced = []
options.forEach(function(option) {
  if (option.assigned) {
     var someNewValue = { name: option.name, newProperty: 'Foo' }
     reduced.push(someNewValue);
  }
});

document.getElementById('output').innerHTML = JSON.stringify(reduced);
<h1>Only assigned options</h1>
<pre id="output"> </pre>

Namun alangkah baiknya jika ada malter()atau fap()fungsi yang menggabungkan mapdan filterfungsinya. Ini akan berfungsi seperti filter, kecuali bukannya mengembalikan benar atau salah, itu akan mengembalikan objek apa pun atau null / tidak terdefinisi.

Daniel
sumber
Anda mungkin ingin memeriksa meme Anda ... ;-)
Arel
2

Saya mengoptimalkan jawaban dengan poin-poin berikut:

  1. Menulis ulang if (cond) { stmt; }sebagaicond && stmt;
  2. Gunakan Fungsi Panah ES6

Saya akan menyajikan dua solusi, satu menggunakan forEach , yang lainnya dengan mengurangi :

Solusi 1: Menggunakan forEach

var options = [
  { name: 'One', assigned: true }, 
  { name: 'Two', assigned: false }, 
  { name: 'Three', assigned: true }, 
];
var reduced = []
options.forEach(o => {
  o.assigned && reduced.push( { name: o.name, newProperty: 'Foo' } );
} );
console.log(reduced);

Solusi 2: Menggunakan mengurangi

var options = [
  { name: 'One', assigned: true }, 
  { name: 'Two', assigned: false }, 
  { name: 'Three', assigned: true }, 
];
var reduced = options.reduce((a, o) => {
  o.assigned && a.push( { name: o.name, newProperty: 'Foo' } );
  return a;
}, [ ] );
console.log(reduced);

Saya serahkan kepada Anda untuk memutuskan solusi mana yang harus dicari.

Stephen Quan
sumber
1
Kenapa Anda akan menggunakan cond && stmt;? Ini jauh lebih sulit dibaca dan tidak menawarkan manfaat sama sekali.
jlh
1

Menggunakan pengurangan, Anda dapat melakukan ini dalam satu fungsi Array.prototype. Ini akan mengambil semua angka genap dari array.

var arr = [1,2,3,4,5,6,7,8];

var brr = arr.reduce((c, n) => {
  if (n % 2 !== 0) {
    return c;
  }
  c.push(n);
  return c;
}, []);

document.getElementById('mypre').innerHTML = brr.toString();
<h1>Get all even numbers</h1>
<pre id="mypre"> </pre>

Anda dapat menggunakan metode yang sama dan menggeneralisasikannya untuk objek Anda, seperti ini.

var arr = options.reduce(function(c,n){
  if(somecondition) {return c;}
  c.push(n);
  return c;
}, []);

arr sekarang akan berisi objek yang difilter.

Bhargav Ponnapalli
sumber
0

Penggunaan langsung .reducebisa sulit dibaca, jadi saya sarankan membuat fungsi yang menghasilkan peredam untuk Anda:

function mapfilter(mapper) {
  return (acc, val) => {
    const mapped = mapper(val);
    if (mapped !== false)
      acc.push(mapped);
    return acc;
  };
}

Gunakan seperti ini:

const words = "Map and filter an array #javascript #arrays";
const tags = words.split(' ')
  .reduce(mapfilter(word => word.startsWith('#') && word.slice(1)), []);
console.log(tags);  // ['javascript', 'arrays'];
Quelklef
sumber