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

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

新規登録して質問してみよう
ただいま回答率
85.48%
HTML5

HTML5 (Hyper Text Markup Language、バージョン 5)は、マークアップ言語であるHTMLの第5版です。

JavaScript

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

Q&A

解決済

3回答

359閲覧

JavaScriptにおける配列の並び替えと抽出について

tetsu777

総合スコア39

HTML5

HTML5 (Hyper Text Markup Language、バージョン 5)は、マークアップ言語であるHTMLの第5版です。

JavaScript

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

0グッド

1クリップ

投稿2019/08/30 05:16

●やりたいこと
以下のarr配列[カテゴリ1、カテゴリ2、カテゴリ3、数値、日付]を
1.カテゴリ1>カテゴリ2>カテゴリ3と昇順になるように、かつ日付を降順で並び替え
2.下記(完成予定)の様にカテゴリ1、カテゴリ2、カテゴリ3が重複しない、かつ日付が最新の行をarr2配列として抽出したい

(完成予定)
["a","a","a",1,"2019/02/02"]
["a","a","c",1,"2019/01/01"]
["a","b","a",1,"2019/01/01"]
["b","a","a",1,"2019/01/01"]
["b","a","b",1,"2019/01/01"]
["b","b","a",1,"2019/01/01"]
["b","b","b",1,"2019/01/01"]
["c","a","c",1,"2019/07/01"]

●質問内容
上記1やりたいこと1は思うように並び替え出来た様ですが、やりたいこと2の抽出がfilterで抽出すると思うのですが、どの様に条件付ければ良いか分かりません。
ご教示お願いします。

<!DOCTYPE html><html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width" /> </head> <body> <div id="test"></div> <script> //arr配列[カテゴリ1、カテゴリ2、カテゴリ3、数値、日付] var arr = [ ["a","a","a",1,"2019/01/01"], ["a","b","a",1,"2019/01/01"], ["b","a","a",1,"2019/01/01"], ["a","a","c",1,"2019/01/01"], ["c","a","c",1,"2019/01/01"], ["b","b","b",1,"2019/01/01"], ["b","b","a",1,"2019/01/01"], ["c","a","c",1,"2019/07/01"], ["b","a","b",1,"2019/01/01"], ["a","a","a",1,"2019/02/02"] ]; //並び替え arr.sort(function(a, b) { if(a[0] > b[0]){return 1;} else if(a[0] < b[0]){return -1;} else if(a[1] > b[1]){return 1;} else if(a[1] < b[1]){return -1;} else if(a[2] > b[2]){return 1;} else if(a[2] < b[2]){return -1;} else if(a[4] > b[4]){return -1;} else if(a[4] < b[4]){return 1;} else{return 0;} }); //ここからどの様にしたら良いのでしょうか? var arr2 = arr.filter(function (x, i, self) {return self.indexOf(x) === i;}); console.log(arr2); for (var i = 0; i < arr2.length; i++) { test.innerHTML += arr[i] + "<br>"; } </script> </body> </html>

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

jun68ykt

2019/08/30 14:06

こんにちは。お手数ですが、一点、要件を確認させてください。カテゴリ1, 2, 3 の値である文字列は、ご質問のコードにあるように、 "a" , "b", "c" といった、アルファベット一文字と考えてよいでしょうか? ちなみに私の回答では、ご質問に挙げられているコードから読み取らせて頂きました仕様として、カテゴリの値はアルファベット一文字であることを前提としていますので、もし二文字以上の文字列もあり得るのでしたら、回答を修正いたします。
tetsu777

2019/08/31 04:17

早速のご回答ありがとうございます。質問に際し詳細に記載しておらず申し訳ございません。カテゴリ値は二文字以上の文字列もあり得ます。
jun68ykt

2019/08/31 04:48

ご回答ありがとうございます。修正箇所を、回答のほうに追記しましたので、ご確認頂ければと思います。
guest

回答3

0

new Map で管理する

同じカテゴリ群を持つデータをグループ化すると、管理性が良くなると思います。
元のデータ構造を維持する理由がなければ、new Map のまま運用します。

JavaScript

1function toMap (array) { 2 const map = new Map; 3 4 for (let data of array) { 5 const key = JSON.stringify([data[0],data[1],data[2]]), 6 index = data[4].replace(///g, ''); 7 8 if (map.has(key)) { 9 map.get(key)[index] = data[3]; 10 } else { 11 const valueList = []; 12 13 valueList[index] = data[3]; 14 map.set(key, valueList); 15 } 16 } 17 18 return map; 19} 20 21const array = [ 22 ["a","a","a",1,"2019/01/01"], 23 ["a","b","a",1,"2019/01/01"], 24 ["b","a","a",1,"2019/01/01"], 25 ["a","a","c",1,"2019/01/01"], 26 ["c","a","c",1,"2019/01/01"], 27 ["b","b","b",1,"2019/01/01"], 28 ["b","b","a",1,"2019/01/01"], 29 ["c","a","c",1,"2019/07/01"], 30 ["b","a","b",1,"2019/01/01"], 31 ["a","a","a",1,"2019/02/02"] 32 ], 33 map = toMap(array); 34 35const result = Array.from(map.entries()).map(data => { 36 const valueList = data[1], date = valueList.length - 1, value = valueList[date]; 37 38 return [...JSON.parse(data[0]), value, date]; 39}); 40 41console.log(JSON.stringify(result)); // [["a","a","a",1,20190202],["a","b","a",1,20190101],["b","a","a",1,20190101],["a","a","c",1,20190101],["c","a","c",1,20190701],["b","b","b",1,20190101],["b","b","a",1,20190101],["b","a","b",1,20190101]]

Array.prototype.reduce

データ構造を変えず、検索の為の中間オブジェクトをその都度生成するならば、Array.prototype.reduce が適していると思われます。
(前節のコードもそうですが、カテゴリ1~カテゴリ3は String 型であれば、使用できない文字はありません)

JavaScript

1const array = [ 2 ["a","a","a",1,"2019/01/01"], 3 ["a","b","a",1,"2019/01/01"], 4 ["b","a","a",1,"2019/01/01"], 5 ["a","a","c",1,"2019/01/01"], 6 ["c","a","c",1,"2019/01/01"], 7 ["b","b","b",1,"2019/01/01"], 8 ["b","b","a",1,"2019/01/01"], 9 ["c","a","c",1,"2019/07/01"], 10 ["b","a","b",1,"2019/01/01"], 11 ["a","a","a",1,"2019/02/02"] 12 ]; 13 14const lastUpdate = array.reduce((entries, current) => { 15 const keys = entries.keys, 16 values = entries.values, 17 currentKey = JSON.stringify([current[0],current[1],current[2]]), 18 index = keys.indexOf(currentKey); 19 20 if (index === -1) { 21 keys.push(currentKey), values.push(current); 22 } else if (values[index][4] <= current[4]) { 23 keys[index] = currentKey, values[index] = current; 24 } 25 26 return entries; 27}, {keys: [], values: []}).values; 28 29console.log(JSON.stringify(lastUpdate)); // [["a","a","a",1,"2019/02/02"],["a","b","a",1,"2019/01/01"],["b","a","a",1,"2019/01/01"],["a","a","c",1,"2019/01/01"],["c","a","c",1,"2019/07/01"],["b","b","b",1,"2019/01/01"],["b","b","a",1,"2019/01/01"],["b","a","b",1,"2019/01/01"]]

投稿2019/08/30 13:15

編集2019/09/01 02:24
think49

総合スコア18164

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

jun68ykt

2019/09/01 09:06

tetsu777さんのご質問の文中に > 以下のarr配列[カテゴリ1、カテゴリ2、カテゴリ3、数値、日付]を > 1.カテゴリ1>カテゴリ2>カテゴリ3と昇順になるように、かつ日付を降順で並び替え とあり、またご質問に挙げられているコードでは、 sort()に与える比較関数の中で、カテゴリの比較が含まれていましたが、こちらの回答のコードでは、最終結果がカテゴリでソートされなくなっています。(Mapを使う方、reduceを使う方ともに)
think49

2019/09/01 11:06

To: jun68ykt さん > 上記1やりたいこと1は思うように並び替え出来た様ですが、やりたいこと2の抽出がfilterで抽出すると思うのですが、どの様に条件付ければ良いか分かりません。 とあったので、2. のみを実装するコードを書きました。 「1. -> 2.」「2. -> 1.」のどちらの処理順でも期待通りに動くと思いますが、いかがでしょうか。
jun68ykt

2019/09/03 05:50

@think49 さん > とあったので、2. のみを実装するコードを書きました。 なるほど。そのような意図があったのですね。拝承しました。
guest

0

ベストアンサー

こんにちは

filter を使って arr2 を作る箇所を、以下のように修正してみるといかがでしょうか?

javascript

1var arr2 = arr.filter(function(x, i, self) { 2 if (i === 0) 3 return true; 4 else { 5 const y = self[i-1]; 6 return `${x[0]}${x[1]}${x[2]}` !== `${y[0]}${y[1]}${y[2]}`; 7 } 8});

以上、参考になれば幸いです。

追記1

質問への追記・修正のほうから、以下のご回答を頂きました。

カテゴリ値は二文字以上の文字列もあり得ます。

上記に対応させるために、先に挙げたコードの else の場合の return文を、以下のように修正します。

修正前:

javascript

1return `${x[0]}${x[1]}${x[2]}` !== `${y[0]}${y[1]}${y[2]}`;

修正後:

javascript

1 return x[0] !== y[0] || x[1] !== y[1] || x[2] !== y[2];

追記2

別解を挙げます。要所要所で便利なライブラリの力を借ります。

ただし、こちらは、ご質問に挙げられているコードの filter する行だけではなく、その前の sort する処理含めて見直す案になりますので、ご質問の主題

●質問内容

上記1やりたいこと1は思うように並び替え出来た様ですが、やりたいこと2の抽出がfilterで抽出すると思うのですが、どの様に条件付ければ良いか分かりません。

からは外れます。それゆえ、あくまでご参考に留めて頂ければと思います。

配列やオブジェクトの操作で、便利なメソッドを提供しているライブラリ Lodashに、 _.groupBy というメソッドがあります。これを使うと配列の要素を何らかの値を基準にグルーピングしたオブジェクトを返してくれます。

このご質問の場合、カテゴリ1、カテゴリ2、カテゴリ3、それぞれの文字列を、たとえば / でjoinした文字列をその基準値に使ってグループ化することが考えられます。これは以下によって実現できます。

javascript

1_.groupBy(arr, ary => ary.slice(0,3).join('/'))

上記の _.groupBy が返すのは、下記のようなオブジェクトです。

javascript

1{ 2 "a/a/a":[ 3 [ "a", "a", "a", 1, "2019/01/01" ], 4 [ "a", "a", "a", 1, "2019/02/02" ] 5 ], 6 "a/b/a":[ 7 [ "a", "b", "a", 1, "2019/01/01" ] 8 ], 9 "b/a/a":[ 10 [ "b", "a", "a", 1, "2019/01/01" ] 11 ], 12 "a/a/c":[ 13 [ "a", "a", "c", 1, "2019/01/01" ] 14 ], 15 "c/a/c":[ 16 [ "c", "a", "c", 1, "2019/01/01" ], 17 [ "c", "a", "c", 1, "2019/07/01" ] 18 ], 19 "b/b/b":[ 20 [ "b", "b", "b", 1, "2019/01/01" ] 21 ], 22 "b/b/a":[ 23 [ "b", "b", "a", 1, "2019/01/01" ] 24 ], 25 "b/a/b":[ 26 [ "b", "a", "b", 1, "2019/01/01" ] 27 ] 28}

上記のように、 各カテゴリの値 ab/ を区切り文字として連結した文字列が、オブジェクトのキーになり、各キーに該当する要素(である配列)が配列としてまとめられます。なお上記では、/ を連結時の区切り文字としていますが、各カテゴリの文字列それ自体の中にも / が出現する可能性があるのであれば、何か他の、各カテゴリの文字列の中には出現しない文字(列)で連結します。

以下は、上記の _.groupBy を使って arr から arr2 を得るコード例です。

javascript

1// カテゴリ1,2,3をスラッシュを区切りとして連結した文字列をキーにグループ化したオブジェクトを得て、それのエントリ配列を取得 2var groupedEntries = Object.entries(_.groupBy(arr, ary => ary.slice(0,3).join('/'))); 3 4// 各キーに対して、日付が最新の要素を値とするマップを作成 5var keyToNewestDateMap = groupedEntries.reduce( 6 (map, [key, arrays]) => map.set(key, _.maxBy(arrays, ary => ary[4])), new Map()); 7 8// 上記で作成したマップのエントリから、キー(カテゴリを / で連結した文字列)でソートされた、値の配列を取得 9var arr2 = [...keyToNewestDateMap] 10 .sort(([key1], [key2]) => key1.localeCompare(key2)) 11 .map(([_, v]) => v);

上記のコードでは、 各キーに対して、日付が最新の要素(である配列)を取得するときに、Lodashの _.maxBy も使っています。

追記3

追記2 の修正版です。以下

は、 _.map_.sortBy を使って、中間生成物としてMapを作っていたのを省き、コード量を減らしたものです。

投稿2019/08/30 11:53

編集2019/09/01 01:14
jun68ykt

総合スコア9058

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

think49

2019/08/31 05:13 編集

> return `${x[0]}${x[1]}${x[2]}` !== `${y[0]}${y[1]}${y[2]}`; カテゴリ名が「可変長文字列」であった場合、意図せず重複扱いされるケースがあります。 var arr = [["ab","ac","a",1,"2019/01/01"],["a","ba","ca",1,"2019/01/01"],["aba","c","a",1,"2019/01/01"]];
jun68ykt

2019/08/31 05:55

> カテゴリ名が「可変長文字列」であった場合、意図せず重複扱いされるケースがあります。 上記の件、回答のほうに、追記1にて対応しました。ご指摘ありがとうございました。
guest

0

javascript

1var arr = [ 2["a","a","a",1,"2019/01/01"], 3["a","b","a",1,"2019/01/01"], 4["b","a","a",1,"2019/01/01"], 5["a","a","c",1,"2019/01/01"], 6["c","a","c",1,"2019/01/01"], 7["b","b","b",1,"2019/01/01"], 8["b","b","a",1,"2019/01/01"], 9["c","a","c",1,"2019/07/01"], 10["b","a","b",1,"2019/01/01"], 11["a","a","a",1,"2019/02/02"] 12]; 13 14arr.sort((x,y)=>{ 15 var ret; 16 if(x[0]==y[0]){ 17 if(x[1]==y[1]){ 18 if(x[2]==y[2]){ 19 if(x[3]==y[3]){ 20 ret=x[4]>y[4]; 21 }else{ 22 ret=x[3]>y[3]; 23 } 24 }else{ 25 ret=x[2]>y[2]; 26 } 27 }else{ 28 ret=x[1]>y[1]; 29 } 30 }else{ 31 ret=x[0]>y[0]; 32 } 33 return ret; 34}); 35console.log(arr);

投稿2019/08/30 05:54

yambejp

総合スコア114843

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問