何万回、何百万回と範囲検索を行う必要がある場合、DBを使って検索を行うと、遅すぎて話にならないことがあります。
そんな時は、二分探索法というやり型を実装すると、大幅な速度アップが見込める場合があります。
そんな二分探索法をJavaで実装してみました。
一応Githubにもコミットしてあります。
https://github.com/2KB/range-search/tree/master/range-search
参考にさせて頂いたサイト:
http://blog.akagi.jp/archives/179.html
https://www.grapecity.com/tools/support/powernews/column/clang/054/page03.htm
#実装
範囲データ保持クラスのインターフェース
/**
* 範囲検索をするためのメソッドを定義したインターフェース
*/
public interface RangeSearchInterface {
/**
* From値を取得
* @return
*/
abstract long getFromValue();
/**
* To値を取得
* @return
*/
abstract long getToValue();
}
二分探索部分
/**
* 二分探索法を使って範囲検索してマッチした値を取得する
*/
public T getVal(long searchVal) {
// 検索中の最小Index
int lowid = 0;
// 検索中の最大Index
int highid = rangeSearchList.size() - 1;
// 検索中の最小indexの値が、最大indexの値以下である間は繰り返す
while(lowid <= highid){
// 中間Index
int midid = (lowid + highid) / 2;
// 中間の値を取得
T midResult = rangeSearchList.get(midid);
long midFromVal = midResult.getFromValue();
long midToVal = midResult.getToValue();
if(midFromVal <= searchVal && searchVal <= midToVal){
// 取得した中間値が検索値内だったら、取得した値を返す
return midResult;
}else if(midToVal < searchVal){
// 取得した中間値の上限値が検索値より小さかった場合は、最小Indexを今回の中間Indexより大きくする
lowid = midid + 1;
}else{
// 取得した中間値の上限値が検索値より大きかった場合は、最大Indexを今回の中間Indexより小さくする
highid = midid - 1;
}
}
// 検索範囲内には含まれなかった場合
return null;
}
#使い方
- RangeSearchInterfaceをimplementしたクラスを用意します。
public class RangeSearchTestDto implements RangeSearchInterface{
@Override
public long getFromValue() {
return fromVal;
}
/**
* To値を取得
*/
@Override
public long getToValue() {
return toVal;
}
- そのクラスに範囲データを設定してリストにします。範囲データは値の昇順にします。
List<RangeSearchTestDto> rangeList = new ArrayList<RangeSearchTestDto>();
rangeList.add(new RangeSearchTestDto(100, 199));
rangeList.add(new RangeSearchTestDto(200, 299));
rangeList.add(new RangeSearchTestDto(300, 499));
- 範囲データをコンストラクタにわたし、RangeSearchUtilのインスタンスを生成します。
RangeSearchUtil<RangeSearchTestDto> rangeSearchUtil = new RangeSearchUtil<RangeSearchTestDto>(rangeList);
- 生成したインスタンスに検索したい値を渡し、検索値を取得します。
RangeSearchTestDto result = rangeSearchUtil.getVal(100);
実行例
// 範囲データ取得
List<RangeSearchTestDto> rangeList = getRangeListOrderByFromVal();
// 範囲データを元に、範囲検索Utilを作成
RangeSearchUtil<RangeSearchTestDto> rangeSearchUtil = new RangeSearchUtil<RangeSearchTestDto>(rangeList);
// 範囲検索実行
{
long searchVal = 100;
// 範囲検索Utilを使って範囲検索実行
RangeSearchTestDto result = rangeSearchUtil.getVal(searchVal);
System.out.println("searchVal : " + searchVal + " resultfrom : " + result.fromVal + " resultTo : " + result.toVal);
}
{
long searchVal = 201;
RangeSearchTestDto result = rangeSearchUtil.getVal(searchVal);
System.out.println("searchVal : " + searchVal + " resultfrom : " + result.fromVal + " resultTo : " + result.toVal);
}
出力結果
searchVal : 100 resultfrom : 100 resultTo : 199
searchVal : 201 resultfrom : 200 resultTo : 299
searchVal : 1999 resultfrom : 1000 resultTo : 1999
searchVal : 3500 resultfrom : 3000 resultTo : 3990
searchVal : 2000 Not found.
searchVal : 9999 Not found.
#用途
最初の参考リンクにもありましたが、大量のアクセスログデータに対し、大量のIPから国を引いたりするときなどに便利です。
IPの範囲と国のマッピングデータを作っておき、そのマッピングデータから、アクセス元IPと国をひもづけるための
範囲検索を、大量に高速に行う場合などに、大幅な速度向上が出来ることがあります。
そんな人は少ないと思いますが、大量の範囲検索がある処理で、パフォーマンスに困っている人は参考にしてみてください。