検索あれこれ

作成日 : 2020-03-07
最終更新日:

検索あれこれ

JavaScript の検索について調べた。 これから述べる関数を調べておけば、都度ループを回して検索する関数を自作する必要を減らせる。 対象となる次のデータ構造について述べる。

オブジェクトの検索

連想配列では、オリジナルの検索関数は用意されていない(と思うが勘違いかもしれない)。 次の 3 種類のメソッドのいずれかで配列を得てから配列の検索と組み合わせる。

Object.keys() は、指定されたオブジェクトが持つプロパティのキーの配列を返す。下記参照。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
Object.values() は、指定されたオブジェクトが持つプロパティの値の名前の配列を返す。下記参照。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/values
Object.entries() は、指定されたオブジェクトが持つプロパティの組 [key, value] からなる配列を返す。 下記参照。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/entries

arr を連想配列とするとき、連想配列の値 given_value を与えて、 対応するキーで最初に現れるキー result_key を得る処理は次のように書ける。

const result_key = Object.keys(arr).filter(k => arr[k] === given_value )[0];

given_valuearr で見つからなかったときは、result_key は undefined となる。 これでよい理由については、最後の項に書いている。

配列の検索

JavaScrtipt の配列は、Array オブジェクトである。この Array オブジェクトにたいして適用できる検索方法は、 大きく分けて検索する値を示す方法(値系)と検索の条件を関数で示す方法(関数系)がある。

配列検索:値系

indexOf() メソッドは引数に与えられた内容と同じ内容を持つ配列要素のなかで、 最初のものの添字を返す。存在しない場合は -1 を返す。

arr.indexOf(searchElement [, fromIndex])
searchElement は検索する配列要素。fromIndex は検索を始める添字 である。 省略時は 0 である。

    let fruktoj = ['pomo', 'banano', 'ananaso', 'banano']
    let ind = fruktoj.indexOf('banano');  // 1

lastIndexOf() メソッドは引数に与えられた内容と同じ内容を持つ配列要素のなかで、 最後のものの添字を返す。存在しない場合は -1 を返す。

arr.lastIndexOf(searchElement[, fromIndex])
searchElement は検索する配列要素で、fromIndex は検索を始める添字 である。 省略時には arr.length - 1 である。

    let fruktoj = ['pomo', 'banano', 'ananaso', 'banano']
    let ind = fruktoj.lastIndexOf('banano');  // 3

includes() メソッドは、特定の要素が配列に含まれているか否かを true または false で返す。 与えられた要素が見つかるかどうかを計算するために、 SameValueZero (ゼロの同値) アルゴリズムを使用する。

arr.includes(valueToFind[, fromIndex])
valueToFind は検索する値。 fromIndex はこの配列内で valueToFind を探し始める位置。既定値は 0。

    ['pomo', 'banano', 'ananaso', 'banano'].includes('banano');  // true

配列検索:関数系

find

find() メソッドは、配列内の要素が引数として与えられたテスト関数を満たす場合、 配列内の最初の要素の値を返す。見つからなかった場合は undefined を返す。

arr.find(callback(element[, index[, array]])[, thisArg])

callback は、配列の各要素に対して実行するテスト関数である。 この関数には高々 3 つの引数が必要である。
element
配列内の現在の要素。
index(任意)
配列内の現在の要素の添字。
array(任意)
find を呼び出した元の配列。
thisArg(任意)
callback を実行するときに this として使う値。

    let fruktoj = ['pomo', 'banano', 'ananaso', 'banano']
    let frukto = fruktoj.find(el => el === 'banano');  // 'banano'

findIndex

findIndex() メソッドは、配列内の要素が引数として与えられたテスト関数を満たす場合、 配列内の最初の要素の添字(インデックス)を返す。見つからなかった場合は -1 を返す。

arr.findIndex(callback(element[, index[, array]])[, thisArg])

callback は、配列の各要素に対して実行するテスト関数である。 この関数には高々 3 つの引数が必要である。
element
配列内の現在の要素。
index(任意)
配列内の現在の要素の添字。
array(任意)
find を呼び出した元の配列。
thisArg(任意)
callback を実行するときに this として使う値。

    let fruktoj = ['pomo', 'banano', 'ananaso', 'banano']
    let frukto = fruktoj.findIndex(el => el === 'banano');  // 1

filter

filter() メソッドは、引数として与えられたテスト関数を各配列要素に対して実行し、 それに合格した配列要素すべてからなる新しい配列を生成する。

let newArray = arr.filter(callback(element[, index[, array]])[, thisArg])

callback は、配列の各要素に対して実行するテスト関数である。 この関数が true を返した要素は残され、 false を返した要素は取り除かれる。この関数には高々 3 つの引数が必要である。
element
配列内の現在の要素。
index(任意)
配列内の現在の要素の添字。
array(任意)
filter メソッドを実行している配列。
thisArg(任意)
callback を実行するときに this として使う値。

例:

    const fruktoj = ['pomo', 'banano', 'ananaso', 'frago', 'oranĝo']
    const rezulto = fruktoj.filter(frukto => frukto.length > 5);  // ["banano", "ananaso", "oranĝo"]

some

some() メソッドは、 引数として与えられたテスト関数を各配列要素にたいして実行した結果、 少なくとも配列の 1 つの要素がテストに通るかどうかをテストする。 少なくとも 1 つの要素がテスト関数を満たす場合は true を、 そうでない場合は false を返す。

arr.some(callback(element[, index[, array]])[, thisArg])

callback は、配列の各要素に対して実行するテスト関数である。
element
配列内の現在の要素。
index(任意)
配列内の現在の要素の添字。
array(任意)
some メソッドを実行している配列。
thisArg(任意)
callback を実行するときに this として使う値。

every

every() メソッドは、 引数として与えられたテスト関数を各配列要素にたいして実行した結果、 配列要素すべてががテストに通るかどうかをテストする。 要素すべてがテスト関数を満たす場合(truthy値を返した場合)は true を、 そうでない場合は false を返す。

arr.every(callback(element[, index[, array]])[, thisArg])

callback は、配列の各要素に対して実行するテスト関数である。
element
配列内の現在の要素。
index(任意)
配列内の現在の要素の添字。
array(任意)
every メソッドを実行している配列。
thisArg(任意)
callback を実行するときに this として使う値。

例題

    
        const sobas = ['もり', 'かけ', 'ざる', 'たぬき', 'きつね', 'かけ', ’カレー’];
    
        console.log(sobas.indexOf('かけ'));
        // 期待出力: 1 。
        
        // 添字を 2 からはじめる
        console.log(sobas.indexOf('かけ', 2));
        // 期待出力: 5
        
        console.log(sobas.indexOf('にしん'));
        // 期待出力: -1

        console.log(sobas.lastIndexOf('かけ'));
        // 期待出力: 5 。
        
        console.log(sobas.lastIndexOf('かけ', 4));
        // 期待出力: 1 。

        const result = sobas.filter(soba => soba.length > 2);
        // 期待出力: Array ["たぬき", "きつね", "カレー"]
    

文字列の検索

文字列は、配列よりさらに検索の幅が広がる。まとめてみたのが次の図である。

検索関連における使用可能な関数の比較
メソッドオブジェクト配列文字列
keys--
values--
entries--
indexOf-
lastIndexOf-
includes-
search--
match--
matchAll--
startsWith--
endsWith--

search や match などでは正規表現が使える。search と match のみ概要を述べる。他のメソッドの解説は省略する。

search() メソッドは、 引数として与えられた正規表現オブジェクトを与えられた文字列にたいして適用した結果、 文字列と正規表現の間で最初にマッチした箇所のインデックスを返す。 マッチしなかった場合は -1 を返す。

match() メソッドは、 引数として与えられた正規表現オブジェクトを与えられた文字列にたいして適用した結果、 正規表現に g フラグがある場合、キャプチャグループを除いた、正規表現にマッチしたすべての結果を返す。 g フラグがない場合、最初のマッチとそれに対するキャプチャグループのみを返す。 この場合、返される要素には下記のプロパティが追加される:

例題 1

次のような配列はどうだろうか?

    let arr1 = [100, 101, 102, 103, 104];
    arr1['204b'] = 250;
    const key1 = arr1.indexOf(100);
    // => 期待しているのは key1 = 1。この通り。
    const key2 = arr1.indexOf(250);
    // => 期待しているのは key2 = '204b' だが実際は -1 
    const key3 = Object.keys(arr1).filter(function(k) { return arr1[k] === 250 })[0];
    // => 期待しているのは key3 = '204b' が得られる
    const key4 = Object.keys(arr1).filter(k => arr1[k] === 250 })[0];
    // => 期待しているのは key4 = '204b' が得られる
    

この最後の書き方は、 連想配列の値からキーを取得する方法はありませんか? (teratail.com) のベストアンサーで知った。 ベストアンサーでは、Array.filter() と Array.reduce() を用いた回答が紹介されているが、 key3 では filter() メソッドを用いた回答を用いた。変更した点は、 == を === に変更したことである。 key4 ではさらに、function 構文をアレー関数を用いて書き直した。内容は同じである。 その後知ったが、key4 と同等の回答が、tky_bpp 氏による javascriptの連想配列の値からキーを取得する方法の【2】 で記載されている。

最初から連想配列を前提とした検索であれば上記でよい。 しかし、今回は通常の配列にむりやり連想配列をまぜた構造を作ってしまったので、往生することになった。 最初から連想配列でない、つまり indexOf メソッドで単純に添字の値を検索できるようにシステムを設計すべきだったのだ。 それが反省点である。 (2020-03-07)

例題 2

次のような配列はどうだろうか?

    let arr2 = [
    "  K.   1     L. 366  P.  57 ",
    "  K.   2     L. 388  P.  58 ",
    "  K.   3     L. 378  P.  59 ",
    "  K. 204a            P. 170 ",
    "  K. 204b            P. 255 ",
    "  K. 205     L. S23  P. 171 "];
    const idx5 = arr2.indexOf("  K.   1     L. 366  P.  57 ");
    // => 期待しているのは idx5 = 0。この通り。
    const idx6 = arr2.findIndex(function(value) { return value ===  "  K.   1     L. 366  P.  57 "});
    // => 期待しているのは idx6 = 0。この通り。
    const idx7 = arr2.findIndex(value => value === "  K.   1     L. 366  P.  57 ");
    // => 期待しているのは idx7 = 0。この通り。
    const idx8 = arr2.findIndex(value => value.substring(0,9) === "  K.   1 ");
    // => 期待しているのは idx8 = 0。この通り。
    const idx9 = arr2.findIndex(value => value.search(/ K\. +1 /) >= 0);
    // => 期待しているのは idx9 = 0。この通り。
    const idx10 = arr2.findIndex(value => value.search(/ K\. +204b /) >= 0);
    // => 期待しているのは idx10 = 4。この通り。
    const idx11 = arr2.findIndex(value => value.search(/ P\. +170 /) >= 0);
    // => 期待しているのは idx11 = 3。この通り。
    const idx12 = arr2[3].match(/ P\. +([0-9]+) /);
    // => 期待しているのは idx12[0] = " P. 170 " および idx12[1] = "170"。この通り。
    const idx13 = arr2[3].match(/ K\. +([0-9]+(a|b)?) /);
    // => 期待しているのは idx13[0] = " K. 204a " および idx13[1] = "204a"、idx13[2] = "a"。この通り。
    const idx14 = arr2[0].match(/ K\. +([0-9]+(a|b)?) /);
    // => 期待しているのは idx14[0] = " K.   1 " および idx14[1] = "1"、idx14[2] = ""。この通り。

上記例題 1 の反省点で述べたように、添字の値で検索できるようにするための準備である。 つまり、(K|L|P)\. *[0-9]+(a|b)? のような検索文字列が与えられたとき、正規表現で合致した値もつ添字を、 返し、これをキーとするように考えている。 検索の値として K.1 も、K.204a も許すように、配列の値である文字列とその値の部分文字列を整えている。 当然、検索時には、英数字とピリオド、空白文字(0x20)の位置関係を定めておく必要がある。

例題3 範囲の検索

今までは合致した値を返す検索を考えてきた。こんどは範囲に収まる検索を考える。次の配列を考える。

    let arr3 = [0, 50, 97, 153, 213, 273, 333, 397, 457, 516];

曲1, 曲2, …、曲n,があり、それぞれ第 m 巻には arr3[m - 1] + 1 以上、arr3[m] 以下の曲が収められているとする。 曲 i が収められている巻 j はどこだろうか。かりに i = 516 とする。

    const idx15 = arr3.findIndex(value => value >= 516);
    // => 期待しているのは idx15 = 9 であり、実際にもこの通りとなる。

応用先

スカルラッティ作品番号相互参照プログラムにおいて、ここで得た知見を生かしている。


まりんきょ学問所JavaScript 手習い > 検索あれこれ