配列のマッピングとフィルタリングを同時に行う


155

新しいフィルター処理された配列を生成するために繰り返し処理するオブジェクトの配列があります。しかし、また、パラメーターに応じて、新しい配列からいくつかのオブジェクトをフィルターで除外する必要があります。私はこれを試しています:

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

それは良いアプローチですか?より良い方法はありますか?lodashなどの任意のライブラリを使用できます。


「object.keys」アプローチはどうですか?developer.mozilla.org/fr/docs/Web/JavaScript/Reference/...
Julo0sS


3
.reduce().filter(...).map(...)他の場所で提案された方法よりも確実に高速です。私はJSPerfテストをセットアップして、stackoverflow.com
Justin L.

回答:


218

このために使用する必要がありますArray.reduce

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>


または、レデューサーは次のように純粋な関数にすることもできます

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

2
私にとって、最初の引数filteredはオブジェクトです。だからfiltered.push私にとっては未定義です。
Rahmathullah M 2018

4
filteredオブジェクトであることにも問題がありました。これは、「初期値」([]reduce関数の後の空の配列())を渡していないためです。例:不 var reduced = options.reduce(function(filtered, option) { ... }); 正解 var reduced = options.reduce(function(filtered, option) { ... }, []);
ジョンヒギンズ

ここには理由はありません。すべての反復で配列を変更reduceするforEachため、filtered純粋に機能的ではないため、使用することもできます。より読みやすくコンパクトなイベントになります。
Marko

1
@Marko純粋なレデューサーも導入しました。PTAL。
thefourtheye

今純粋なヤップ。あなたの努力のために+1。私が純粋な関数型プログラミングの擁護者であるというわけではありません。それからは、私は要点を見ることができませんでした:)しかし、実際には、特定の状況で非常に便利になったので、それを見てから使用しました。しかし、このタスクについては、flatMap関数を確認することをお勧めします。答えを出した後、それが標準になったと思います(そのため、一部のブラウザーではサポートされていない場合もあります)。このように配列を連結すると、O(n)タスクからO(n ^ 2)タスクが作成されるため、パフォーマンスが向上するはずです。
マルコ

43

ルーク、リデュースを使って!

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

30

2019年以降、Array.prototype.flatMapが適切なオプションです。

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

上記のMDNページから:

flatMapマップ中にアイテムを追加および削除する(アイテム数を変更する)方法として使用できます。つまり、常に1対1ではなく、(各入力項目を個別に処理することにより)多くの項目を多くの項目にマップできます。この意味で、フィルターの逆のように機能します。1要素の配列を返してアイテムを保持するか、複数要素の配列を返してアイテムを追加するか、0要素の配列を返してアイテムを削除します。


5
素晴らしい解決策!ただし、読者flatMapは最近導入されたものであり、ブラウザのサポートが制限されていることを覚えておいてください。公式のポリフィル/シム:flatMapflatを使用することもできます。
Arel

24

ES6を使用すると、非常に短時間で実行できます。

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


25
これにより、2つのループが実行されます。ただし、どのフィルターの戻り値によってもメモリが消費される場合があります。
ホーガン2017年

1
@hoganしかし、これは元のクエリに対する正しい答えです(最適なソリューションではない可能性があります)
vikramvi

1
@vikramviご注意ありがとうございます。ことは、私たちはたくさんの方法で同じことを達成することができ、私は最高のものを好みます。
ホーガン

10

reduceES6ファンシースプレッド構文の 1行がここにあります!

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
本当に素敵な@Maxim!私はこれに賛成です!しかし...追加された要素ごとに、すべての要素を展開する必要がありresultます... filter(assigned).map(name)ソリューションのようなものです
0zkr PM

良いですね。何が起こっているのかを理解するのに1秒かかりました...assigned ? [name] : []-より読みやすいかもしれません...(assigned ? [name] : [])
johansenja

5

私はコメントしますが、必要な評判がありません。マキシムクズミンの小さな改善、それ以外の場合は非常に良い答えをより効率的にする:

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

説明

反復ごとに結果全体を何度も分散するのではなく、実際に挿入する値がある場合にのみ、配列に追加するだけです。


3

Array.prototy.filter自体を使用する

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

2

ある時点で、より簡単に(または同じくらい簡単に) 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>

ただし、and 関数を組み合わせたmalter()or fap()関数があったらいいですね。フィルターのように機能しますが、trueまたはfalseを返す代わりに、オブジェクトまたはnull /未定義を返します。mapfilter


ミームをチェックしたいかもしれません... ;-)
Arel

2

私は次の点で答えを最適化しました:

  1. if (cond) { stmt; }として書き換えcond && stmt;
  2. ES6の矢印関数を使用する

2つのソリューションを提示します。1つはforEachを使用し、もう1つはreduceを使用します

解決策1: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);

解決策2:reduceを使用する

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

どの解決策をとるかはあなたに任せています。


1
cond && stmt;いったいなぜあなたは使うのでしょうか?これははるかに読みにくく、まったくメリットがありません。
jlh 2018

1

reduceを使用すると、これを1つのArray.prototype関数で実行できます。これにより、配列からすべての偶数がフェッチされます。

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>

同じ方法を使用して、次のようにオブジェクトに一般化できます。

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

arr これで、フィルタリングされたオブジェクトが含まれます。


0

の直接使用は.reduce読みにくい場合があるため、レデューサーを生成する関数を作成することをお勧めします。

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

次のように使用します。

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'];
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.