ヨーキョクデイ

いろいろ雑食

配列の差分をとりたい

ある状態を配列で持つことにして、ある状態と別のある状態を比較し、消えた要素と現れた要素を知りたい(ただしどのインデックスの要素が消えたりしたのかは気にしない)という状況を想定。各配列は重複する要素を持たないものとする。
Ruby の配列は集合のように扱うことができることを活用すると、単純にこんな感じで書ける。

def array_diff(older, newer)  # 引数はどっちも配列
  removed = older - newer  # 配列 older から配列 newer に存在する要素を除去
  added = newer - older  # 配列 newer から配列 older に存在する要素を除去

  return removed, added  # 消えた要素からなる配列と現れた要素からなる配列を返す
end

p array_diff(["a", "b", "c", "d", "e", "f"], ["a", "bb", "c", "e", "g"])  # => [["b", "d", "f"], ["bb", "g"]]

つまりは差集合の演算。Ruby には集合を扱う Set クラスもあって同様の演算ができるようなのだが、今回は順序付きである必要があったので。

ところで、同様の関数を JavaScript で作ろうとするともうちょっと複雑になる。今回は JavaScript 1.6 で追加された Array の新メソッドを 2 つ(indexOf()filter())使って配列の差分をとり、さらに JavaScript 1.7 で追加された分割代入 (destructuring assignment) とその使用例として複数の値を返す関数を使う。

// JavaScript 1.7

function arrayDiff(aOlder, aNewer){
    function f(aElement, aIndex, aArray){  // コールバック関数
        /*
           filter の第 1 引数の各要素についてこの関数は繰り返し実行される。
           filter の第 2 引数がこの関数内の this になる。
        */
        return (this.indexOf(aElement) == -1);  // 配列 this にその要素が含まれなければ true
    }

    /*
       filter メソッドは対象の配列の各要素について反復し、
       各要素について第 1 引数のコールバック関数(今回は f)が実行され、
       その関数が true を返した要素からなる新たな配列を返す。
    */
    var removed = aOlder.filter(f, aNewer);  // aOlder の要素のうち aNewer に含まれないものからなる配列
    var added = aNewer.filter(f, aOlder);  // aNewer の要素のうち aOlder に含まれないものからなる配列

    return [removed, added];  // 複数の値を返す
}

var [removed, added] = arrayDiff(["a", "b", "c", "d", "e", "f"], ["a", "bb", "c", "e", "g"]);  // 分割代入

こんな感じで書ける。