Bir diziyi aynı anda eşleyin ve filtreleyin


178

Yeni bir filtrelenmiş dizi oluşturmak için yinelemek istediğim bir dizi nesnem var. Ama ayrıca, bir parametreye bağlı olarak bazı nesneleri yeni diziden filtrelemem gerekiyor. Bunu deniyorum:

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

Bu iyi bir yaklaşım mı? Daha iyi bir yöntem var mı? Lodash gibi herhangi bir kütüphaneyi kullanmaya açığım.


"object.keys" yaklaşımına ne dersiniz? developer.mozilla.org/fr/docs/Web/JavaScript/Reference/…
Julo0sS


3
.reduce()kesinlikle .filter(...).map(...)başka bir yerde önerildiğini gördüğüm bir şeyi yapmaktan daha hızlı . Stackoverflow.com/a/47877054/2379922
Justin L.

Yanıtlar:


252

Bunun Array.reduceiçin kullanmalısınız .

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>


Alternatif olarak, indirgeyici bunun gibi saf bir işlev olabilir

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

3
Benim için ilk argüman filteredbir nesnedir. böylece filtered.pushbenim için tanımlanmamış.
Rahmathullah M

5
filteredNesne olmakla ilgili bir sorunum da vardı . Bunun nedeni, "başlangıç ​​değeri" ni - []azaltma işlevinden sonraki boş dizi ( ) 'yi geçmememdi. örneğin, yanlış var reduced = options.reduce(function(filtered, option) { ... }); doğru var reduced = options.reduce(function(filtered, option) { ... }, []);
Jon Higgins

1
@Marko da saf bir redüktör getirdim. PTAL.
thefourtheye

1
Şimdi saf olan yap. Çabanız için +1. Tamamen işlevsel programlama savunucusu olduğumdan değil, bundan çok uzak, sadece noktayı göremedim :) Ama aslında numaranızı gördükten sonra kullandım, çünkü verilen durumda çok kullanışlı oldu. Ancak bu görev için flatMap işlevine bir göz atmak isteyebilirsiniz, sanırım cevabınızı verdikten sonra standart haline geldi (bu nedenle bazı tarayıcılar tarafından desteklenmeyebilir). Dizileri bu şekilde birleştirmek, O (n ^ 2) görevini O (n) görevinden çıkardığından daha performanslı olmalıdır.
Marko

1
Belki okunabilirlik konusunda haklısınız, ancak boş diziyi döndürmek tam da aklımdaki şeydi, tamamen yasal kullanım durumu.
Marko

46

Azalt, Luke!

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

43

2019'dan beri Array.prototype.flatMap iyi bir seçenektir.

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

Yukarıda bağlantısı verilen MDN sayfasından:

flatMapbir harita sırasında öğe eklemek ve kaldırmak (öğe sayısını değiştirmek) için bir yol olarak kullanılabilir. Başka bir deyişle, her zaman bire bir yerine birçok öğeyi (her bir girdi öğesini ayrı ayrı ele alarak) birçok öğeyle eşlemenize olanak tanır. Bu anlamda filtrenin tersi gibi çalışır. Öğeyi korumak için 1 öğeli bir dizi, öğe eklemek için çok öğeli bir dizi veya öğeyi kaldırmak için 0 öğeli bir dizi döndürmeniz yeterlidir.


6
Harika çözüm! Ancak okuyucular, flatMapbunun yakın zamanda tanıtıldığını ve tarayıcı desteğinin sınırlı olduğunu akılda tutuyor . Resmi polyfill / şimleri kullanmak isteyebilirsiniz: flatMap ve flat .
Arel

27

ES6 ile bunu çok kısa sürede yapabilirsiniz:

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


27
Bu, hangi filtre dönüşlerinin hafızayı tüketebileceğine bağlı olarak iki döngü gerçekleştirecektir.
Hogan

1
@hogan, ancak bu orijinal sorgunun doğru cevabı (en uygun çözüm olmayabilir)
vikramvi

1
@vikramvi notunuz için teşekkür ederim. Mesele şu ki, aynı şeyleri tonlarca yolla başarabiliriz ve ben en iyisini tercih ederim.
Hogan

Bu tam olarak doğru değildir, çünkü burada başlangıçta konumlandırılan değerlerin dizini kaybediyorsunuz ve bu bilgi, fn haritasını çalıştırırken gerçekten yararlı olabilir.
Luis Gurmendez

12

reduceES6 fantezi yayılma sözdizimine sahip bir satır burada!

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


1
Gerçekten güzel @Maxim! Buna oy verdim! Ama ... eklenen her bir unsurda, tüm unsurları içine yayması gerekiyor result... filter(assigned).map(name)çözüm gibidir
0zkr PM

1
Güzel. Neler olduğunu anlamam bir saniyemi aldı ...assigned ? [name] : []- daha okunaklı olabilir...(assigned ? [name] : [])
anlamam bir saniyemi aldı

7

Bir yorum yapardım ama gerekli itibara sahip değilim. Maxim Kuzmin'in daha verimli hale getirmek için başka türlü çok iyi cevabında küçük bir gelişme:

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

Açıklama

Her bir yineleme için tüm sonucu tekrar tekrar yaymak yerine, yalnızca diziye ekleriz ve yalnızca gerçekten eklenecek bir değer olduğunda.


3

Bir noktada, bir 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>

Bununla birlikte , ve işlevlerini birleştiren bir malter()veya fap()işlev olsaydı iyi olurdu . Doğru veya yanlış döndürmek yerine bir filtre gibi çalışır, herhangi bir nesneyi veya boş / tanımsız döndürür.mapfilter


Memlerinizi kontrol etmek isteyebilirsiniz ... ;-)
Arel

Ama o zaman, sizden başka kimsenin anlamadığı 1 satırlık 133t JS'nizi gösteremezsiniz.
Half_Duplex

3

Cevapları aşağıdaki noktalarla optimize ettim:

  1. yeniden Yazma if (cond) { stmt; }olarakcond && stmt;
  2. ES6 Ok İşlevlerini Kullanın

Biri forEach'i , diğeri düşürmeyi kullanan iki çözüm sunacağım :

1.Çözüm: forEach'i kullanma

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

2.Çözüm: Azaltmayı kullanma

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

Hangi çözüme gideceğinize karar vermeyi size bırakıyorum.


1
Neden kullanırsın ki cond && stmt;? Bunun okunması çok daha zordur ve hiçbir faydası yoktur.
jlh

2

Array.prototy.filter'ın kendisini kullanın

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

2

Azalt'ı kullanarak bunu bir Array.prototype işlevinde yapabilirsiniz. Bu, bir dizideki tüm çift sayıları alır.

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>

Aynı yöntemi kullanabilir ve nesneleriniz için genelleştirebilirsiniz, bunun gibi.

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

arr şimdi filtrelenmiş nesneleri içerecek.


0

Doğrudan kullanımının .reduceokunması zor olabilir, bu nedenle sizin için redüktörü oluşturan bir işlev oluşturmanızı tavsiye ederim:

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

Bunu şu şekilde kullanın:

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