web-dev-qa-db-ja.com

Pythonのバイナリ検索(二分)

リスト/タプルでバイナリ検索を実行し、見つかった場合はアイテムの位置を返し、見つからない場合は「False」(-1、Noneなど)を返すライブラリ関数はありますか?

bisectモジュール で関数bisect_left/rightを見つけましたが、アイテムがリストになくても位置を返します。意図した使用法ではこれで十分ですが、リストに項目が含まれているかどうかを知りたいだけです(何も挿入したくない)。

bisect_leftを使用して、その位置にあるアイテムが検索しているものと等しいかどうかをチェックすることを考えましたが、それは面倒です私のリストで)。より良い方法があれば、それについて知りたいです。

Editこれに必要なものを明確にするために:辞書はこれに非常に適していることを知っていますが、可能な限り低いメモリ消費。私の意図する使用法は、ある種の双方向のルックアップテーブルです。テーブルには値のリストがあり、それらのインデックスに基づいて値にアクセスできる必要があります。また、特定の値のインデックス、または値がリストにない場合はNoneを検索できるようにしたいです。

これに辞書を使用するのが最も速い方法ですが、メモリ要件は(ほぼ)2倍になります。

私はPythonライブラリの何かを見落としているかもしれないと考えてこの質問をしていました。 Moeが提案したように、私は自分のコードを書かなければならないようです。

167
rslite
from bisect import bisect_left

def binary_search(a, x, lo=0, hi=None):  # can't use a to specify default for hi
    hi = hi if hi is not None else len(a)  # hi defaults to len(a)   
    pos = bisect_left(a, x, lo, hi)  # find insertion position
    return (pos if pos != hi and a[pos] == x else -1)  # don't walk off the end
228
Dave Abrahams

Bisect_left/rightのコードを見て、目的に合わせて調整してみませんか。

このような:

def binary_search(a, x, lo=0, hi=None):
    if hi is None:
        hi = len(a)
    while lo < hi:
        mid = (lo+hi)//2
        midval = a[mid]
        if midval < x:
            lo = mid+1
        Elif midval > x: 
            hi = mid
        else:
            return mid
    return -1
54
Moe

これは少しトピックから外れています(Moeの答えはOPの質問に対して完全に思えるので)が、手順全体の複雑さを端から端まで見る価値があるかもしれません。ソートされたリスト(バイナリ検索が役立つ場所)に物を保存していて、存在を確認するだけの場合(指定されない限り最悪の場合):

ソート済みリスト

  • O( n log n) to initially create the list (if it's unsorted data. O(n), if it's sorted )
  • O( log n) lookups (this is the binary search part)
  • O( n ) insert / delete (might be O(1) or O(log n) average case, depending on your pattern)

一方、 set() を使用すると、

  • 作成するO(n)
  • O(1)ルックアップ
  • O(1)挿入/削除

ソートされたリストが実際に取得するのは、「次」、「前」、および「範囲」(範囲の挿入または削除を含む)であり、O(1)またはO(| range |)開始インデックス。これらの種類の操作を頻繁に使用しない場合は、セットとして保存し、表示用に並べ替えることをお勧めします。 set() Pythonでは追加のオーバーヘッドはほとんど発生しません。

37
Gregg Lind

Bisectのドキュメントが検索の例を提供していることに言及する価値があるかもしれません: http://docs.python.org/library/bisect.html#searching-sorted-lists

(-1またはNoneを返す代わりにValueErrorを上げることは、よりPythonicです。たとえば、list.index()はそれを行います。しかし、もちろん、あなたのニーズに合わせて例を適応させることができます。)

12
Petr Viktorin

最も簡単なのは bisect を使用し、1つの位置をチェックして、アイテムが存在するかどうかを確認することです。

def binary_search(a,x,lo=0,hi=-1):
    i = bisect(a,x,lo,hi)
    if i == 0:
        return -1
    Elif a[i-1] == x:
        return i-1
    else:
        return -1
11
Imran

これはマニュアルから正しいです:

http://docs.python.org/2/library/bisect.html

8.5.1。ソート済みリストの検索

上記のbisect()関数は、挿入ポイントを見つけるのに役立ちますが、一般的な検索タスクに使用するには扱いにくい場合があります。次の5つの関数は、それらをソート済みリストの標準ルックアップに変換する方法を示しています。

def index(a, x):
    'Locate the leftmost value exactly equal to x'
    i = bisect_left(a, x)
    if i != len(a) and a[i] == x:
        return i
    raise ValueError

したがって、わずかな変更を加えると、コードは次のようになります。

def index(a, x):
    'Locate the leftmost value exactly equal to x'
    i = bisect_left(a, x)
    if i != len(a) and a[i] == x:
        return i
    return -1
7
arainchi

@ DaveAbrahams's answer bisectモジュールを使用するのが正しいアプローチであることに同意します。彼は答えの中で一つの重要な詳細に言及しなかった。

docsbisect.bisect_left(a, x, lo=0, hi=len(a))から

バイセクションモジュールでは、事前に検索配列を事前に計算する必要はありません。デフォルトのbisect.bisect_leftlen(a)を使用して、エンドポイントを0の代わりに提示することができます。

私の使用にとってさらに重要なのは、特定の関数のエラーが最小化されるような値Xを探すことです。それを行うには、代わりにbisect_leftのアルゴリズムから計算を呼び出す方法が必要でした。これは本当に簡単です。

__getitem__aとして定義するオブジェクトを提供するだけです

たとえば、二等分アルゴリズムを使用して、任意の精度の平方根を見つけることができます!

import bisect

class sqrt_array(object):
    def __init__(self, digits):
        self.precision = float(10**(digits))
    def __getitem__(self, key):
        return (key/self.precision)**2.0

sa = sqrt_array(4)

# "search" in the range of 0 to 10 with a "precision" of 0.0001
index = bisect.bisect_left(sa, 7, 0, 10*10**4)
print 7**0.5
print index/(10**4.0)
6
paulluap

存在するかどうかだけを見たい場合は、リストを辞書に変えてみてください:

# Generate a list
l = [n*n for n in range(1000)]

# Convert to dict - doesn't matter what you map values to
d = dict((x, 1) for x in l)

count = 0
for n in range(1000000):
    # Compare with "if n in l"
    if n in d:
        count += 1

私のマシンでは、「if n in l」は37秒かかり、「if n in d」は0.4秒かかりました。

4
jrb

これは:

  • 再帰的ではない(ほとんどの再帰的アプローチよりもメモリ効率になります)
  • 実際にはworking
  • 高速なので不要なif'sおよび条件なしで実行
  • 数学的アサーションに基づくその(低+高)/ 2のフロアは常にhighここで、lowは下限であり、highが上限です。
  • テスト済み:D

def binsearch(t, key, low = 0, high = len(t) - 1):
    # bisecting the range
    while low < high:
        mid = (low + high)//2
        if t[mid] < key:
            low = mid + 1
        else:
            high = mid
    # at this point 'low' should point at the place
    # where the value of 'key' is possibly stored.
    return low if t[low] == key else -1
3

Dave Abrahamsのソリューションは優れています。私はそれを最小限にしたでしょうが:

def binary_search(L, x):
    i = bisect.bisect_left(L, x)
    if i == len(L) or L[i] != x:
        return -1
    return i
2
Florent

Pythonには明示的なバイナリ検索アルゴリズムはありませんが、バイナリ検索を使用して並べ替えられたリスト内の要素の挿入ポイントを見つけるために設計されたモジュールbisectがあります。これをバイナリサーチの実行に「仕掛ける」ことができます。これの最大の利点は、ほとんどのライブラリコードと同じ利点です-高性能で十分にテストされており、動作します(特にバイナリ検索は 非常にうまく実装するのは非常に困難です -特にEdgeの場合は慎重に検討しないでください)。

基本タイプ

Stringsやintのような基本型の場合、それは非常に簡単です-必要なのはbisectモジュールとソートされたリストだけです:

>>> import bisect
>>> names = ['bender', 'fry', 'leela', 'nibbler', 'zoidberg']
>>> bisect.bisect_left(names, 'fry')
1
>>> keyword = 'fry'
>>> x = bisect.bisect_left(names, keyword)
>>> names[x] == keyword
True
>>> keyword = 'arnie'
>>> x = bisect.bisect_left(names, keyword)
>>> names[x] == keyword
False

これを使用して重複を見つけることもできます。

...
>>> names = ['bender', 'fry', 'fry', 'fry', 'leela', 'nibbler', 'zoidberg']
>>> keyword = 'fry'
>>> leftIndex = bisect.bisect_left(names, keyword)
>>> rightIndex = bisect.bisect_right(names, keyword)
>>> names[leftIndex:rightIndex]
['fry', 'fry', 'fry']

必要に応じて、そのインデックスの値ではなく、インデックスを返すこともできます。

オブジェクト

カスタム型またはオブジェクトの場合、少し注意が必要です。バイセクトを正しく比較するには、豊富な比較メソッドを実装する必要があります。

>>> import bisect
>>> class Tag(object):  # a simple wrapper around strings
...     def __init__(self, tag):
...         self.tag = tag
...     def __lt__(self, other):
...         return self.tag < other.tag
...     def __gt__(self, other):
...         return self.tag > other.tag
...
>>> tags = [Tag('bender'), Tag('fry'), Tag('leela'), Tag('nibbler'), Tag('zoidbe
rg')]
>>> key = Tag('fry')
>>> leftIndex = bisect.bisect_left(tags, key)
>>> rightIndex = bisect.bisect_right(tags, key)
>>> print([tag.tag for tag in tags[leftIndex:rightIndex]])
['fry']

これは少なくともPython 2.7-> 3.3で動作するはずです

2
stephenfin

このコードは、整数リストで再帰的に機能します。最も単純なケースシナリオを探します。リストの長さが2未満です。これは、回答が既に存在し、正しい回答を確認するためのテストが実行されることを意味します。そうでない場合は、中間値が設定され、正しいとテストされます。そうでない場合は、関数を再度呼び出して二分しますが、中間値を左または右にシフトして上限または下限として設定します。

 def binary_search(intList、intValue、lowValue、highValue):
 if(highValue-lowValue)<2:
 return intList [lowValue] == intValueまたはintList [highValue] = = intValue 
 middleValue = lowValue +((highValue-lowValue)/ 2)
 if intList [middleValue] == intValue:
 return True 
 if intList [middleValue ]> intValue:
 return binary_search(intList、intValue、lowValue、middleValue-1)
 return binary_search(intList、intValue、middleValue + 1、highValue)
1
rct

値が実際のオブジェクトへのポインタに過ぎないため、dictを使用しても、格納するオブジェクトが本当に小さい場合を除き、メモリ使用量が2倍になることはありません。

>>> a = 'foo'
>>> b = [a]
>>> c = [a]
>>> b[0] is c[0]
True

その例では、「foo」は一度だけ保存されます。それはあなたに違いをもたらしますか?とにかく正確にいくつのアイテムについて話しているのでしょうか?

1
Kirk Strauser

ウィキペディアの例をご覧ください http://en.wikipedia.org/wiki/Binary_search_algorithm

def binary_search(a, key, imin=0, imax=None):
    if imax is None:
        # if max amount not set, get the total
        imax = len(a) - 1

    while imin <= imax:
        # calculate the midpoint
        mid = (imin + imax)//2
        midval = a[mid]

        # determine which subarray to search
        if midval < key:
            # change min index to search upper subarray
            imin = mid + 1
        Elif midval > key:
            # change max index to search lower subarray
            imax = mid - 1
        else:
            # return index number 
            return mid
    raise ValueError
1
jdsantiagojr
  • sはリストです。
  • binary(s, 0, len(s) - 1, find)は最初の呼び出しです。
  • 関数は、クエリされたアイテムのインデックスを返します。そのようなアイテムがない場合、-1を返します。

    def binary(s,p,q,find):
        if find==s[(p+q)/2]:
            return (p+q)/2
        Elif p==q-1 or p==q:
            if find==s[q]:
                return q
            else:
                return -1
        Elif find < s[(p+q)/2]:
            return binary(s,p,(p+q)/2,find)
        Elif find > s[(p+q)/2]:
            return binary(s,(p+q)/2+1,q,find)
    
0
AV94

pythonのバイナリ検索とDjangoモデルのジェネリック検索が必要でした。 Djangoモデルでは、1つのモデルが別のモデルへの外部キーを持つことができるため、取得したモデルオブジェクトで検索を実行したいと考えました。これを使用できる次の関数を書きました。

def binary_search(values, key, lo=0, hi=None, length=None, cmp=None):
    """
    This is a binary search function which search for given key in values.
    This is very generic since values and key can be of different type.
    If they are of different type then caller must specify `cmp` function to
    perform a comparison between key and values' item.
    :param values:  List of items in which key has to be search
    :param key: search key
    :param lo: start index to begin search
    :param hi: end index where search will be performed
    :param length: length of values
    :param cmp: a comparator function which can be used to compare key and values
    :return: -1 if key is not found else index
    """
    assert type(values[0]) == type(key) or cmp, "can't be compared"
    assert not (hi and length), "`hi`, `length` both can't be specified at the same time"

    lo = lo
    if not lo:
        lo = 0
    if hi:
        hi = hi
    Elif length:
        hi = length - 1
    else:
        hi = len(values) - 1

    while lo <= hi:
        mid = lo + (hi - lo) // 2
        if not cmp:
            if values[mid] == key:
                return mid
            if values[mid] < key:
                lo = mid + 1
            else:
                hi = mid - 1
        else:
            val = cmp(values[mid], key)
            # 0 -> a == b
            # > 0 -> a > b
            # < 0 -> a < b
            if val == 0:
                return mid
            if val < 0:
                lo = mid + 1
            else:
                hi = mid - 1
    return -1
0
sonus21

上記の多くの良い解決策がありますが、バイナリ検索を行うためにPythonビルトイン/ジェネリックbisect関数を単純に(KISSが単純に(私がそうだからです)愚かに使用しているのを見ませんでした。上記の解決策のいくつかはこれを暗示している/言うが、願わくば下記の簡単なコードが私のような混乱した人の助けになることを願っている。だった。

Python bisectは、新しい値/検索項目をソート済みリストに挿入する場所を示すために使用されます。リスト/配列内の検索項目が見つかった場合にヒットのインデックスを返すbisect_leftを使用する以下のコード(注bisectおよびbisect_rightは、ヒットまたはマッチ後の要素のインデックスを挿入ポイントとして返します)見つからない場合、bisect_leftは、==検索値ではないソート済みリスト内の次のアイテムへのインデックスを返します。他の唯一のケースは、検索項目がリストの末尾に移動し、返されるインデックスがリスト/配列の末尾を超え、コード内でPythonで早期終了する場合です「および」ロジックハン​​ドル。 (最初の条件False Pythonは後続の条件をチェックしません)

#Code
from bisect import bisect_left
names=["Adam","Donny","Jalan","Zach","Zayed"]
search=""
lenNames = len(names)
while search !="none":
    search =input("Enter name to search for or 'none' to terminate program:")
    if search == "none":
        break
    i = bisect_left(names,search)
    print(i) # show index returned by Python bisect_left
    if i < (lenNames) and names[i] == search:
        print(names[i],"found") #return True - if function
    else:
        print(search,"not found") #return False – if function
##Exhaustive test cases:
##Enter name to search for or 'none' to terminate program:Zayed
##4
##Zayed found
##Enter name to search for or 'none' to terminate program:Zach
##3
##Zach found
##Enter name to search for or 'none' to terminate program:Jalan
##2
##Jalan found
##Enter name to search for or 'none' to terminate program:Donny
##1
##Donny found
##Enter name to search for or 'none' to terminate program:Adam
##0
##Adam found
##Enter name to search for or 'none' to terminate program:Abie
##0
##Abie not found
##Enter name to search for or 'none' to terminate program:Carla
##1
##Carla not found
##Enter name to search for or 'none' to terminate program:Ed
##2
##Ed not found
##Enter name to search for or 'none' to terminate program:Roger
##3
##Roger not found
##Enter name to search for or 'none' to terminate program:Zap
##4
##Zap not found
##Enter name to search for or 'none' to terminate program:Zyss
##5
##Zyss not found
0
Bob

バイナリ検索:

// List - values inside list
// searchItem - Item to search
// size - Size of list
// upperBound - higher index of list
// lowerBound - lower index of list
def binarySearch(list, searchItem, size, upperBound, lowerBound):
        print(list)
        print(upperBound)
        print(lowerBound)
        mid = ((upperBound + lowerBound)) // 2
        print(mid)
        if int(list[int(mid)]) == value:
               return "value exist"
        Elif int(list[int(mid)]) < value:
             return searchItem(list, value, size, upperBound, mid + 1)
        Elif int(list[int(mid)]) > value:
               return searchItem(list, value, size, mid - 1, lowerBound)

//上記の関数を呼び出すには:

list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
searchItem = 1        
print(searchItem(list[0], item, len(list[0]) -1, len(list[0]) - 1, 0))
0
jitsm555
'''
Only used if set your position as global
'''
position #set global 

def bst(array,taget): # just pass the array and target
        global position
        low = 0
        high = len(array)
    while low <= high:
        mid = (lo+hi)//2
        if a[mid] == target:
            position = mid
            return -1
        Elif a[mid] < target: 
            high = mid+1
        else:
            low = mid-1
    return -1

私はこれがずっと良くて効果的だと思います。私を修正してください:)。ありがとうございました

0
iraycd
def binary_search_length_of_a_list(single_method_list):
    index = 0
    first = 0
    last = 1

    while True:
        mid = ((first + last) // 2)
        if not single_method_list.get(index):
            break
        index = mid + 1
        first = index
        last = index + 1
    return mid
0
user3412550