質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.74%

  • JavaScript

    15234questions

    JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

  • Node.js

    1728questions

    Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

2つの配列からやや複雑な条件でデータを抽出したい

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 372
退会済みユーザー

退会済みユーザー

以下のようなリストから、その下に掲載している配列を抽出する為には、
どのようにするのが良いのでしょうか?

やりたいこととしては、orderList配列の中の各オブジェクトの中にある、
products配列の中にあるプロダクトIDとitemList配列の中の各オブジェクトの中のidの値が同一な場合に、
そのitemList配列内の同一プロダクトidを持つオブジェクト内のitems配列の中にある各オブジェクトを取り出し、
それらを1つの配列の中に1次元配列としてまとめたいです。

文章にすると、ややこしいですが、やりたいことはシンプルなのです。

一応、自分で得たい結果は取得するコードは既に書いたのですが、

  1. mapファンクションでまずorderListの中から各products配列を抽出
  2. mapファンクションで抽出した配列は2次元になってしまってるので、それをreduceファンクションを使って、array.concat()で1次元配列化したものを抽出
    3. 2で取得したプロダクトIDリストから重複するプロダクトIDを削除(フィルターファンクションを使いました)
    4. 3で取得したプロダクトIDリストとマッチするitemList内のitem配列をmapファンクションで抽出
    5. ....
    6. ....

と6ステップかけてようやく得たい結果を超手続き型な感じで取り出すことができました。。。

なんか違う感があるのですが、皆様ならどのように書くのでしょうか?

※補足: なんか違う感というのは、mapなどのメソッド使うと直ぐに多次元配列になってしまって、それをまた、他の処理の為に1次元配列に戻さないといけなかったり、更にそういうのが二回必要だったりと、もっと短く出来る方法ってないのかなと思いまして。冗長に感じるということです。

 入力

   const itemList = [
     {
       id: 'p1',
       items: [
         { id: 1 },
         { id: 2 },
         { id: 3 }
       ]
     },
     {
       id: 'p2',
       items: [
         { id: 1 },
         { id: 10 },
         { id: 33 }
       ]
     },
     {
       id: 'p3',
       items: [
         { id: 11 },
         { id: 22 },
         { id: 33 }
       ]
     }
   ];

   const orderList = [
     {
       id: '1',
       products: [
         'p1'
       ]
     },
     {
       id: '2',
       products: [
         'p3'
       ]
     }
   ];

 得たい結果

const items = [
  { id: 1 },
  { id: 2 },
  { id: 3 },
  { id: 11 },
  { id: 22 },
  { id: 33 }
]

 補足

lodash使えば一発だよ!みたいな回答でもありがたいです。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • think49

    2017/09/29 12:25

    コードを求める理由は、言葉だけのやりとりでは齟齬が発生する可能性があること、コード制作依頼ではないことをはっきりさせるため、です。「皆さんならどのように書くのか」は「コードを書いて下さい」とほぼ同義だと私は思います。コードに限定していないので、方法を書くだけで要件を満たしてはいますが、アルゴリズムを書くことはコードを書くよりも手間がかかりますので、コードだけの回答になりがちです。コードを書いた方が間違いなく質問者に伝わることを経験的に知っている人は尚更にコードを書きます。方法だけを書いた回答をして、それが質問者に伝わるのか、満足するのか、は別問題なので、回答に反応する形で質問を受け付けなければならず、それは回答者の手間となります。

    キャンセル

  • think49

    2017/09/29 12:29

    コメントの補足の件、理解しました。読み過ごしてしまい、すみませんでした。ただ、質問スレは後で同じ質問に遭遇した人が参考にする共有資産なので、本文にも反映した方がよいと思います。

    キャンセル

  • 退会済みユーザー

    退会済みユーザー

    2017/09/29 13:11

    > 本文にも反映した方がよいと思います。反映致しました。

    キャンセル

回答 3

checkベストアンサー

+3

いやー、そんな方法しかないと思いますよ。
あえて工夫するとするなら、とりあえずitemListを辞書化するとか?

  (
    ( itemDict, productsIds ) => 
      [].concat(
        ...productsIds.map( e => itemDict[e] )
      )
  )(
    Object.assign( ...itemList.map( ({id,items}) => ({[id]:items}) ) ),
    [...new Set(
      [].concat(
        ...orderList.map( ({products}) => products ) 
      )
    )]
  );

 コメントを受けてIDをユニークに

  (
    ( {itemDict, productsIds} ) => 
      [...new Set(
        [].concat(
          ...productsIds.map( e => itemDict[e] )
        ).map( e => JSON.stringify(e) )
      )].map( e => JSON.parse(e) )
  )({
    itemDict:
      Object.assign( ...itemList.map( ({id,items}) => ({[id]:items}) ) )
    ,
    productsIds:
      [...new Set(
        [].concat(
          ...orderList.map( ({products}) => products ) 
        )
      )]
    ,
  });

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/09/29 10:23

    [...new Set(array)]と[].concat(...array)のイディオム初めて見ました。
    めっちゃパワフルで捗りますね。

    因みに現回答だとorderListのproductsの中身をp1とp2にするとid1のオブジェクトが2個になってしまうので要調整ですね。
    例題だと別オブジェクト({id: 1} == {id: 1} // false)なんで[...new Set()]のイディオムが使えないのが厄介そうです。

    キャンセル

  • 2017/09/29 11:45

    ご回答ありがとうございます。...はspread operatorですよね?配列の中身が取り出されるやつですね。
    ()から始まる書き方に自分は馴染みがないので、ぱっと見て理解が自分には出来ていませんが、研究させていただきます!ありがとうございます。setなんて使ったことない!immutable.jsで使ったことあるくらいだ。

    キャンセル

  • 2017/09/29 11:48

    あー、やっぱりそこユニークにしないとダメですかねー、明示がなかったので手抜きましたが←

    > 別オブジェクト({id: 1} == {id: 1} // false)なんで[...new Set()]のイディオムが使えないのが厄介そう
    うええ……

    キャンセル

  • 2017/09/29 11:55

    美しいです。。。

    キャンセル

  • 2017/09/29 12:07

    [].concat(...arr)の使い方を学べたのはとても有り難いです!ありがとうございました♪

    キャンセル

  • 2017/09/29 13:30

    確かにユニーク云々は明示されてませんね、行き過ぎた指摘でした!
    もしユニークにしたければその時だけRamda.jsやLodashのuniqByを解禁すれば十分かもしれませんね。

    キャンセル

  • 2017/09/29 17:05

    > miyabi-sunさん
    JSONにして処理してから戻す、というメモリを食いそうなやり方で書いてみました!
    ついでに可読性に配慮して分割代入にしてみましたが、なんか余計に読みにくくなったかもです。

    > hayatomoさん
    BAありがとうございます。
    でも、あえてワンライナーを意識して書いたので、読みにくいコードなんです。こういうのは、本来は美しくないと思います。

    キャンセル

+3

仕様によっては調整が必要かもしれませんが
とりあえずこんな感じです

※items内の同じidのオブジェクトは除外するよう調整しました

<script>
var itemList = [
  {id: 'p1',items: [{id: 1},{id: 2},{id: 3},{id: 3}]},
  {id: 'p2',items: [{id: 1},{id:10},{id:33}]},
  {id: 'p3',items: [{id:11},{id:22},{id:33}]},
  ];
var orderList = [
  {id:'1',products: ['p1']},
  {id:'2',products: ['p2']},
  {id:'3',products: ['p2']},
  {id:'4',products: []},
  ];

var p=[];
orderList.map(function(i){
  Array.prototype.push.apply(p,i.products);
});
p=p.filter(function(i,j,k){return k.indexOf(i)==j;});
/*orderListのユニークなproductId*/
console.log(p);

var items=[];
itemList.map(function(i){
  if(p.indexOf(i.id)>=0){
    Array.prototype.push.apply(items,i.items);
  }
});

items=items.filter(function(i,j,k){
  return k.map(function(x){return x.id;}).indexOf(i.id)==j;
});
console.log(items);

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/09/29 11:17

    ご回答ありがとうございます!参考にさせて頂きます。applyやcallって使いこなせてないので、自分の引き出しに直ぐ使えるものとしてないんですよね。参考にさせて頂きます!

    キャンセル

  • 2017/09/29 11:32

    別解の補足にあるよう、
    itemsの中のオブジェクトがidをキーにユニークとのことなので調整しました

    キャンセル

  • 2017/09/29 11:53

    すみません。明確に情報をお伝え出来ておりませんで。。。

    キャンセル

+2

真正面からやるとこう、JavaScriptだと表現力が足りないからRamda.js使うことに…
ついでにポイントフリーで挑戦。

const R = require('ramda');
const itemList = [
  {id: 'p1', items: [{id: 1}, {id: 2}, {id: 3}]},
  {id: 'p2', items: [{id: 1}, {id: 10}, {id: 33}]},
  {id: 'p3', items: [{id: 11}, {id: 22}, {id: 33}]}
];
const orderList = [
  {id: '1', products: ['p1']},
  {id: '2', products: ['p3']}
];

const results = R.pipe(
  R.pluck('products'),
  R.flatten,
  R.map(R.pipe(R.propEq('id'), R.find(R.__, itemList))),
  R.filter(R.identity),
  R.pluck('items'),
  R.flatten,
  R.uniqBy(R.prop('id'))
)(orderList)

console.log(results)
[ { id: 1 },
  { id: 2 },
  { id: 3 },
  { id: 11 },
  { id: 22 },
  { id: 33 } ]

やることが複雑すぎてそんなに短くならない。
すぐに二重配列になるからflattenを何度も挟むことになるしね。

より関数的にするなら経由する関数に名称付けてこんな感じ

const R = require('ramda');
const itemList = [
  {id: 'p1', items: [{id: 1}, {id: 2}, {id: 3}]},
  {id: 'p2', items: [{id: 1}, {id: 10}, {id: 33}]},
  {id: 'p3', items: [{id: 11}, {id: 22}, {id: 33}]}
];
const orderList = [
  {id: '1', products: ['p1', 'p2']},
  {id: '2', products: ['p3']}
];

// この関数群を他のファイルにしてrequireすればテストしやすい
// toUniqProducts :: [{products: [String]}] -> [String]
const toUniqProducts = R.pipe(R.pluck('products'), R.flatten, R.uniq)
// toUniqItems :: [String] -> [Object]
const toUniqItems = R.pipe(
  R.map(R.pipe(R.propEq('id'), R.find(R.__, itemList))),
  R.filter(R.identity),
  R.pluck('items'),
  R.flatten,
  R.uniqBy(R.prop('id'))
)

R.compose(
  R.tap(console.log),
  toUniqItems,
  toUniqProducts,
)(orderList)

Ramda.jsって便利だね!
素のJSで頑張るならLhankor_Mhyさんのアプローチがエレガント。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/09/29 11:46

    ramda.jsなんてものがあるのですね!関数型プログラミングをちゃんと勉強したあとに、そのライブラリを触って見たいと思います。Lhankor_Mhyさんのアプローチ、研究します!

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.74%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • JavaScript

    15234questions

    JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

  • Node.js

    1728questions

    Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。