やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

Pythonで二分探索する

 ソート済みリストの中から指定した値の位置を探す。高速に。

情報源

コード

 挿入位置を取得する。

import bisect
l = ['A', 'B', 'C', 'D', 'E'] # 予めソートしておくこと
bisect.bisect_left(l, 'D');
>>> import bisect
>>> l = ['A', 'B', 'C', 'D', 'E'] # 予めソートしておくこと
>>> bisect.bisect_left(l, 'D');
3

 存在しない値は末尾を指す。

import bisect
l = ['A', 'B', 'C', 'D', 'E'] # 予めソートしておくこと
bisect.bisect_left(l, 'Z');
>>> import bisect
>>> l = ['A', 'B', 'C', 'D', 'E'] # 予めソートしておくこと
>>> bisect.bisect_left(l, 'Z');
5

二分探索

 中間にある値より小さいか大きいかを繰り返すことで、高速に検索できる。

追ってみる

 こんな感じで検索するはず。

位置 リスト 検索位置
0 A
1 B
2 C
3 D
4 E
5 F
6 G
7 H
8 I
  1. 先頭A〜末尾Iの中間を探す
  2. 中間が探索値ならインデックスを返して終了する
  3. 中間が探索値より小さいなら、先頭〜中間-1の範囲内でこれをくりかえす
  4. 中間が探索値より大きいなら、中間+1〜末尾の範囲内でこれをくりかえす

 探索値Dは中間値Eより小さい。よって、先頭A〜中間-1Dの範囲を対象に、これをくりかえす。

位置 リスト 検索位置
0 A
1 B
2 C
3 D

 偶数のため中間はない。そこで中間-1を中間とする。

 検索対象Dは中間値Bより大きい。よって、中間+1C〜末尾の範囲を対象に、これをくりかえす。

位置 リスト 検索位置
2 C
3 D

 偶数のため中間はない。そこで中間-1を中間とする。

 検索対象Dは中間値Cより大きい。よって、中間+1D〜末尾の範囲を対象に、これをくりかえす。

位置 リスト 検索位置
3 D

 1件しかないため中間値はない。検索対象に一致するならインデックスを返す。さもなくばNoneなり-1なり例外なり、無効を意味する値を返す。

 検索対象Dに一致。これのインデックス3を返す。

 今回の場合、4回目でヒット。これは先頭から順にやったのと同じ回数。ぜんぜん高速じゃない。が、件数が増えるほど、二分探索のほうが少なくて済むはず。

keyを使いたい

 複数のキーを対象に位置検索したい。

 できないことはないのかもしれない。でもよくわからない。コードもわけわからんくなる。Pythonあるある。

 どうしよう……。

複数キーは不可

 以下のように公開日時とURLの2キーで一意確認したいのだが、できない。これだとO(N)操作となり高速でなくなるようだ……。

ins_idx = bisect.bisect_left(news_ids, (db_latest_published, db_latest_url))

 それならlist.index(探索値)でいいわ。

結論

 二分探索は複数キーで使えない。

対象環境

$ uname -a
Linux raspberrypi 4.19.42-v7+ #1218 SMP Tue May 14 00:48:17 BST 2019 armv7l GNU/Linux

前回まで