Open11

配列に入ってるオブジェクトのある値を見て、それが重複してたら配列から除外するJavaScriptのスクリプトについて調べる

itsumoonazicodeitsumoonazicode

やりたいこと

対象は、箇条書きをマークアップした、その各要素。
li要素を取得して、そこに重複があればそれを除外したリストNodeListがほしい。

<h1>10/22 サーモンラン Wave1に出てきたオオモノ一覧</h1>
<ul>
  <li>テッキュウ</li>
  <li>カタパッド</li>
  <li>カタパッド</li>
  <li>コウモリ</li>
  <li>カタパッド</li>
</ul>
itsumoonazicodeitsumoonazicode

まず、配列の重複削除と聞いて真っ先に思い出したこの記事のコードを試してみた。
https://qiita.com/netebakari/items/7c1db0b0cea14a3d4419

var arr = $$("ul li").map(e => e.textContent);
console.log([...new Set(arr)])
// 結果:["テッキュウ","カタパッド","コウモリ"]

「リストのテキストだけを抜き取って、その中に重複があれば削除」はできた。
が、ほしいのは文字列の配列ではなくNodeListなのでもうちょっとどうにかしないといけない。。

itsumoonazicodeitsumoonazicode

※開発者ツールのコンソールで実行する前提
このコードから取得できる配列の中には、HTMLLIElementなるオブジェクトが入っている。つまり、「オブジェクトからなる配列の重複をチェック」しないといけない。

$$("ul li")
itsumoonazicodeitsumoonazicode

「js 重複削除 オブジェクト」などでググると、filter()findIndex()を組み合わせるコードがいくつかヒットした。下記2つ目以降がそれになる。

3つ目の記事にはコードとその説明まで載ってるので、「なぜそうなる?」まで理解する手掛かりになると思う。

itsumoonazicodeitsumoonazicode

じぶんの場合、結果的には下記コードで自分のやりたいことを実現できた。
これはmeebeさんの記事をそのまま使った形だ。

var res = $$("ul li").filter(function(e,i,a) {
    return a.findIndex(function(elm) {
        var text = elm.textContent
        return text === e.textContent
    }) === i
})
itsumoonazicodeitsumoonazicode

このコードが何をしているか、理解するまでに時間を要してしまったのだが、コジマノテックさんの記事に答えがすべて書いてあったのを見過ごしてしまっていた…。

https://kojimanotech.com/2020/03/20/208/

上記記事内「実装方針について」項にて、以下のように書かれている。

方針としては、条件に合致する1個目だけ取るようにします。

実際の実装はと言いますと、findIndexが条件を満たす1個目の番号を取得してくるということを利用して、
filterの条件にfindIndexを入れ、自身の配列と現在の要素を突き合わせるということをします。

findIndexで取得したindexと現在のindexが一致したら1個目の一致となるので取得する。
findIndexで取得したindexと現在のindexが一致したら2個目以降の一致となるので取得しない。
結果、重複を削除することができるわけです。

itsumoonazicodeitsumoonazicode

再度、コードを見てみる。

var res = $$("ul li").filter(function(e,i,a) {
    return a.findIndex(function(elm) {
        var text = elm.textContent
        return text === e.textContent
    }) === i
})

findIndex()filter()のコールバック関数の、3つ目の引数から渡ってきた配列に対して実行されている。3つ目の引数には何が入っているのか。

itsumoonazicodeitsumoonazicode

3つ目の引数には、$$("ul li")で取得できる配列がそのまま入っている。
つまり、findIndex()filter()も同じ配列に対して実行されている。

itsumoonazicodeitsumoonazicode

こんな感じで比較をおこなっているイメージ。
カタパッドは複数回ヒットするが、findIndex()は、「最初にヒットした要素の位置」のみを取得するので、2回目以降は取得されない。

1回目:テッキュウ v.s. ["テッキュウ","カタパッド","カタパッド","コウモリ","カタパッド"]
2回目:カタパッド v.s. ["テッキュウ","カタパッド","カタパッド","コウモリ","カタパッド"]
3回目:カタパッド v.s. ["テッキュウ","カタパッド","カタパッド","コウモリ","カタパッド"]
4回目:コウモリ v.s. ["テッキュウ","カタパッド","カタパッド","コウモリ","カタパッド"]
5回目:カタパッド v.s. ["テッキュウ","カタパッド","カタパッド","コウモリ","カタパッド"]

そのため、findIndex()で取得する2回目以降のカタパッドの位置は1となり、filter()のインデックスと合致しなくなる。

これにより、2回目以降のカタパッドは配列から除外される。
結果的に、テッキュウ・カタパッド・コウモリの3つのHTMLLIElementを含む配列が出来上がる。