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

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

ただいまの
回答率

90.34%

  • JavaScript

    17467questions

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

  • ECMAScript

    127questions

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

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

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 431

teratailler

score 25

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

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

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

よろしくお願いします。

// あらかじめ定義された配列
const initialArray = ['a', 'b', 'c']; // 初期配列

// 更新後の配列(4パターン)
const updatedArray1 = ['a', 'c']; // 削除
const updatedArray2 = ['a', 'b', 'c', 'd']; // 追加
const updatedArray3 = ['b', 'd']; // 削除 & 追加
const updatedArray4 = []; // 全削除

let addedElements1 = updatedArray1.filter(x => !initialArray.includes(x));
let addedElements2 = updatedArray2.filter(x => !initialArray.includes(x));
let addedElements3 = updatedArray3.filter(x => !initialArray.includes(x));
let addedElements4 = updatedArray4.filter(x => !initialArray.includes(x));
console.log("----- added elements -----");
console.log(addedElements1);
console.log(addedElements2);
console.log(addedElements3);
console.log(addedElements4);

let removedElements1 = initialArray.filter(x => !updatedArray1.includes(x));
let removedElements2 = initialArray.filter(x => !updatedArray2.includes(x));
let removedElements3 = initialArray.filter(x => !updatedArray3.includes(x));
let removedElements4 = initialArray.filter(x => !updatedArray4.includes(x));
console.log("----- removed elements -----");
console.log(removedElements1);
console.log(removedElements2);
console.log(removedElements3);
console.log(removedElements4);


上記の実行結果です。

----- added elements -----
[]
["d"]
["d"]
[]
----- removed elements -----
["b"]
[]
["a", "c"]
["a", "b", "c"]
  • 気になる質問をクリップする

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 3

checkベストアンサー

+2

 「同時に抽出」とは?

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

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

 コード

一例として。

'use strict';
const updatedArray1 = ['a', 'c']; // 削除
const updatedArray2 = ['a', 'b', 'c', 'd']; // 追加
const updatedArray3 = ['b', 'd']; // 削除 & 追加
const updatedArray4 = []; // 全削除

function diff (beforeArray, afterArray) {
  const addValues = [], removeValues = [];

  for (let value of new Set(beforeArray.concat(afterArray))) {
    if (!beforeArray.includes(value)) {
      addValues.push(value);
    } else if (!afterArray.includes(value)) {
      removeValues.push(value);
    }
  }

  return {add: addValues, remove: removeValues};
}

const defaultArray = ['a', 'b', 'c'];

console.log(JSON.stringify(diff(defaultArray, ['a', 'c'])));  // {"add":[],"remove":["b"]}
console.log(JSON.stringify(diff(defaultArray,  ['a', 'b', 'c', 'd']))); // {"add":["d"],"remove":[]}
console.log(JSON.stringify(diff(defaultArray, ['b', 'd'])));  // {"add":["d"],"remove":["a","c"]}
console.log(JSON.stringify(diff(defaultArray, [])));  // {"add":[],"remove":["a","b","c"]}
console.log(JSON.stringify(diff(defaultArray, ['a', 'b', 'c', 'c'])));  // {"add":[],"remove":[]}

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

Re: teratailler さん

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2018/05/28 14:57

    回答ありがとうございました!また、質問の説明が不十分ですみませんでした。

    提示いただいたコードにしっくりきました!
    また、jun68yktさんへ以下コメントいただいたように関数化するというのも1つの手段であることを再認識しました。

    > const diff = (a1,a2) => ({add: a2.filter(v => !a1.includes(v)), remove: a1.filter(v => !a2.includes(v))});

    キャンセル

+2

こんにちは。

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

_.difference(ar1, ar2)

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

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

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

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

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

同時に抽出する

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

たとえば、

const initialArray = ['a', 'b', 'c'];

const updatedArray3 = ['b', 'd']; // 削除 & 追加

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

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

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

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

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

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


補足

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

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


補足2

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

同時に

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

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

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


補足3

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

const _ = require('lodash');

const diff = (x, y) => {
  const promises =
    [[y, x], [x, y]].map(e =>
      new Promise(resolve => {
        setTimeout(() => { resolve(_.difference(...e)); }, 0);
      })
    );
  return Promise.all(promises);
};

const a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

const b = a.map(e => 2 * e);

diff(a, b).then(values => { console.log(values); });  // => [ [ 12, 14, 16, 18, 20 ], [ 1, 3, 5, 7, 9 ] ]

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2018/05/28 06:34

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

    キャンセル

  • 2018/05/28 06:38 編集

    > think49さん

    ご指摘ありがとうございます。

    > 「同時に抽出する」の定義が曖昧なのも問題ですが…。

    その点は私も気になったので、回答に
      
    ※ 「一回の関数呼び出しで、追加分と削除分の両方が得られる」ということをもって、
     質問者様にとっての、"同時に" といってよいとすれば、ですが。
      
    という注記を追加しました。

    キャンセル

  • 2018/05/28 07: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 14:51 編集

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

    キャンセル

  • 2018/06/02 23:19 編集

    返信遅れましたが、解決されたようでよかったです。

    > 質問の説明が不十分ですみませんでした。

    いえいえ。どうかお気になさらずに。

    質問の解釈の仕方が回答者によって異なり、それぞれの回答者さんとしては自分の解釈と
    実装方法に自信があった上で、様々な回答があり得るのがteratailの面白いところですし、
    回答者にとっても勉強になるところです。

    ところで、私にとって、これが一番、”同時に”感(?)のあると思う実装方法を
    補足3 に書きましたのでご参考まで。

    キャンセル

  • 2018/06/03 12:40

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

    キャンセル

+1

https://github.com/flitbit/diff

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

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2018/05/28 14:49

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

    キャンセル

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

  • JavaScript

    17467questions

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

  • ECMAScript

    127questions

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