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

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

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

ECMAScriptとは、JavaScript類の標準を定めるために作られたスクリプト言語です。

JavaScript

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

Q&A

解決済

3回答

870閲覧

初期配列から追加・削除された要素をそれぞれ抽出したい

退会済みユーザー

退会済みユーザー

総合スコア0

ECMAScript

ECMAScriptとは、JavaScript類の標準を定めるために作られたスクリプト言語です。

JavaScript

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

0グッド

0クリップ

投稿2018/05/26 10:43

編集2018/05/26 10:55

以下ソースコードの通り、あらかじめ定義された配列と、更新後の配列(4パターン)を比較し、追加された要素と削除された要素を抽出したいです。

今の実装では、追加された要素と削除された要素をfilterを使って別々に抽出できておりますが、これを同時に抽出する方法がないかと思いご質問させていただきました。

理想は同時に抽出する方法ですが、それは難しいのではないかと思っているいたりするので、よりスマートに別々に抽出する方法がありましたら、そちらだけでもご回答いただけると幸いです。

よろしくお願いします。

Javascript

1// あらかじめ定義された配列 2const initialArray = ['a', 'b', 'c']; // 初期配列 3 4// 更新後の配列(4パターン) 5const updatedArray1 = ['a', 'c']; // 削除 6const updatedArray2 = ['a', 'b', 'c', 'd']; // 追加 7const updatedArray3 = ['b', 'd']; // 削除 & 追加 8const updatedArray4 = []; // 全削除 9 10let addedElements1 = updatedArray1.filter(x => !initialArray.includes(x)); 11let addedElements2 = updatedArray2.filter(x => !initialArray.includes(x)); 12let addedElements3 = updatedArray3.filter(x => !initialArray.includes(x)); 13let addedElements4 = updatedArray4.filter(x => !initialArray.includes(x)); 14console.log("----- added elements -----"); 15console.log(addedElements1); 16console.log(addedElements2); 17console.log(addedElements3); 18console.log(addedElements4); 19 20let removedElements1 = initialArray.filter(x => !updatedArray1.includes(x)); 21let removedElements2 = initialArray.filter(x => !updatedArray2.includes(x)); 22let removedElements3 = initialArray.filter(x => !updatedArray3.includes(x)); 23let removedElements4 = initialArray.filter(x => !updatedArray4.includes(x)); 24console.log("----- removed elements -----"); 25console.log(removedElements1); 26console.log(removedElements2); 27console.log(removedElements3); 28console.log(removedElements4);

上記の実行結果です。

----- added elements ----- [] ["d"] ["d"] [] ----- removed elements ----- ["b"] [] ["a", "c"] ["a", "b", "c"]

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

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

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

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

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

guest

回答3

0

こんにちは。

lodashdifference を使うと、2つの配列 ar1, ar2 を集合と考えたときに、差集合 ar1 - ar2 に相当する配列を

javascript

1_.difference(ar1, ar2)

で取得できます。
これを使って、次のような関数 diff を定義します。

javascript

1const diff = (ar1, ar2) => [[ar2,ar1], [ar1,ar2]].map(e => _.difference(...e));

上記の関数 diff(ar1, ar2) は、配列 ar1, ar2の差分を、

**[ 差集合(ar2-ar1)の配列, 差集合(ar1-ar2)の配列 ] **

という、配列の配列として返す関数になっており、これがご質問にある、

同時に抽出する

という働きをしてくれるものになっているかと思います。
(※ 「一回の関数呼び出しで、追加分と削除分の両方が得られる」ということをもって、
質問者様にとっての、"同時に" といってよいとすれば、ですが。)

たとえば、

javascript

1const initialArray = ['a', 'b', 'c']; 2 3const updatedArray3 = ['b', 'd']; // 削除 & 追加

に対して、diff(initialArray, updatedArray3) は以下のような配列を返します。

javascript

1[ [ 'd' ], [ 'a', 'c' ] ]

上記は、2つの配列を要素とする配列で

 ・1つ目の配列 [ 'd' ] は、 initialArray に追加されたもの、

 ・2つ目の配列 [ 'a', 'c' ] は、 initialArray から削除されたもの

となります。
以上参考になれば幸いです。


補足

この回答のやり方で実装したサンプルコードを、以下のURLに上げておきました。

https://jsfiddle.net/jun68ykt/pLuzg0u4/2/


補足2

質問者様のイメージされる、

同時に

の趣旨からすると、私の回答では「同時に抽出することになっていない」というご評価でしたら、
ご質問に

理想は同時に抽出する方法ですが、それは難しいのではないかと思っているいたりするので、よりスマートに別々に抽出する方法がありましたら、そちらだけでもご回答いただけると幸いです。

とあるので、上記の方法、すなわち、別々に抽出する方法のリファクタ案のひとつとして
お考え頂ければと思います。


補足3

「追加された要素の配列と、削除された要素の配列を同時に作る」というのが与件ですが、
私見としましては、より**”同時に”感** (?)のあるコードは、以下のようなものになります。

javascript

1const _ = require('lodash'); 2 3const diff = (x, y) => { 4 const promises = 5 [[y, x], [x, y]].map(e => 6 new Promise(resolve => { 7 setTimeout(() => { resolve(_.difference(...e)); }, 0); 8 }) 9 ); 10 return Promise.all(promises); 11}; 12 13const a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; 14 15const b = a.map(e => 2 * e); 16 17diff(a, b).then(values => { console.log(values); }); // => [ [ 12, 14, 16, 18, 20 ], [ 1, 3, 5, 7, 9 ] ]

投稿2018/05/27 14:34

編集2018/06/02 22:50
jun68ykt

総合スコア9058

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

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

think49

2018/05/27 21:34

To: jun68ykt さん 内部的には _.difference() が2回呼び出されていませんか。 _.difference(ar1,ar2); _.difference(ar2,ar1); 「同時に抽出する」の定義が曖昧なのも問題ですが…。
jun68ykt

2018/05/27 21:40 編集

> think49さん ご指摘ありがとうございます。 > 「同時に抽出する」の定義が曖昧なのも問題ですが…。 その点は私も気になったので、回答に    ※ 「一回の関数呼び出しで、追加分と削除分の両方が得られる」ということをもって、  質問者様にとっての、"同時に" といってよいとすれば、ですが。    という注記を追加しました。
think49

2018/05/27 22:06

To: jun68ykt さん > (※ 「一回の関数呼び出しで、追加分と削除分の両方が得られる」ということをもって、 その定義ですと、「filter() 2回呼び出しを一つの関数に収める」で解決するので、別の意味のように感じています。 const diff = (a1,a2) => ({add: a2.filter(v => !a1.includes(v)), remove: a1.filter(v => !a2.includes(v))});
退会済みユーザー

退会済みユーザー

2018/05/28 05:57 編集

回答ありがとうございました!また、質問の説明が不十分ですみませんでした。 そんなライブラリがあったのですね! よりスマートに書ける感じで大変気に入りました。参考にさせていただきます!
jun68ykt

2018/06/03 02:17 編集

返信遅れましたが、解決されたようでよかったです。 > 質問の説明が不十分ですみませんでした。 いえいえ。どうかお気になさらずに。 質問の解釈の仕方が回答者によって異なり、それぞれの回答者さんとしては自分の解釈と 実装方法に自信があった上で、様々な回答があり得るのがteratailの面白いところですし、 回答者にとっても勉強になるところです。 ところで、私にとって、これが一番、”同時に”感(?)のあると思う実装方法を 補足3 に書きましたのでご参考まで。
think49

2018/06/03 03:40

> 補足3 このコードは何を意図して書いたものでしょうか。 setTimeout が並列処理を意図しているのだとすれば、期待に反して直列処理になります。 navigator.serviceWorker を使えば、マルチスレッドになります。
guest

0

ベストアンサー

「同時に抽出」とは?

今の実装では、追加された要素と削除された要素をfilterを使って別々に抽出できておりますが、これを同時に抽出する方法がないか]

同時に抽出のアルゴリズムに言及して貰わなければ、適切な回答が付かないと思います。

コード

一例として。

JavaScript

1'use strict'; 2const updatedArray1 = ['a', 'c']; // 削除 3const updatedArray2 = ['a', 'b', 'c', 'd']; // 追加 4const updatedArray3 = ['b', 'd']; // 削除 & 追加 5const updatedArray4 = []; // 全削除 6 7function diff (beforeArray, afterArray) { 8 const addValues = [], removeValues = []; 9 10 for (let value of new Set(beforeArray.concat(afterArray))) { 11 if (!beforeArray.includes(value)) { 12 addValues.push(value); 13 } else if (!afterArray.includes(value)) { 14 removeValues.push(value); 15 } 16 } 17 18 return {add: addValues, remove: removeValues}; 19} 20 21const defaultArray = ['a', 'b', 'c']; 22 23console.log(JSON.stringify(diff(defaultArray, ['a', 'c']))); // {"add":[],"remove":["b"]} 24console.log(JSON.stringify(diff(defaultArray, ['a', 'b', 'c', 'd']))); // {"add":["d"],"remove":[]} 25console.log(JSON.stringify(diff(defaultArray, ['b', 'd']))); // {"add":["d"],"remove":["a","c"]} 26console.log(JSON.stringify(diff(defaultArray, []))); // {"add":[],"remove":["a","b","c"]} 27console.log(JSON.stringify(diff(defaultArray, ['a', 'b', 'c', 'c']))); // {"add":[],"remove":[]}

このコードは、new Set で変換し、一度走査してから再走査する為、パフォーマンス面でのメリットはないと思われます(不明なので、必要であれば、ご自分で計測して下さい)。
質問文のコードからある仕様ですが、同じ値が複数要素にわたっていると、追加/削除判定が漏れます

Re: teratailler さん

投稿2018/05/27 09:44

編集2018/05/27 21:40
think49

総合スコア18189

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

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

退会済みユーザー

退会済みユーザー

2018/05/28 05:57

回答ありがとうございました!また、質問の説明が不十分ですみませんでした。 提示いただいたコードにしっくりきました! また、jun68yktさんへ以下コメントいただいたように関数化するというのも1つの手段であることを再認識しました。 > const diff = (a1,a2) => ({add: a2.filter(v => !a1.includes(v)), remove: a1.filter(v => !a2.includes(v))});
guest

0

https://github.com/flitbit/diff

配列同士の差分を求めたいという質問と理解しています。
こういったケースは適切なdiffライブラリを使うことをオススメします。

投稿2018/05/27 08:28

keroxp

総合スコア114

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

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

退会済みユーザー

退会済みユーザー

2018/05/28 05:49

回答ありがとうございました!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.37%

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

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

質問する

関連した質問