LoginSignup
16
13

More than 5 years have passed since last update.

Javaで二分探索法を使って高速に範囲検索を行う

Last updated at Posted at 2014-08-28

何万回、何百万回と範囲検索を行う必要がある場合、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

実装

範囲データ保持クラスのインターフェース

RangeSearchInterface.java

/**
 * 範囲検索をするためのメソッドを定義したインターフェース
 */
public interface RangeSearchInterface {

    /**
     * From値を取得
     * @return
     */
    abstract long getFromValue();

    /**
     * To値を取得
     * @return
     */
    abstract long getToValue();
}

二分探索部分

RangeSearchUtil.java

/**
 * 二分探索法を使って範囲検索してマッチした値を取得する
 */
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したクラスを用意します。
RangeSearchTestDto.java

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);

実行例

RangeSearchMain.java

// 範囲データ取得
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と国をひもづけるための
範囲検索を、大量に高速に行う場合などに、大幅な速度向上が出来ることがあります。

そんな人は少ないと思いますが、大量の範囲検索がある処理で、パフォーマンスに困っている人は参考にしてみてください。

16
13
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
13