web-dev-qa-db-ja.com

キーでbisect.insort_leftを使用する方法?

ドキュメントには例がありません...キーに基づいて_bisect.insort_left)__をどのように使用しますか?

キーに基づいて挿入しようとしています。

_bisect.insort_left(data, ('brown', 7))
_

_data[0]_に挿入を挿入します。

ドキュメントから...

_bisect.insort_left(_a、x、lo = 0、hi = len(a) _)_

挿入xaにソート順に挿入します。 aがすでにソートされていると仮定すると、これはa.insert(bisect.bisect_left(a, x, lo, hi), x)と同等です。 O(log n)検索は、低速なO(n)挿入ステップによって支配されることに注意してください。

使用例:

_>>> data = [('red', 5), ('blue', 1), ('yellow', 8), ('black', 0)]
>>> data.sort(key=lambda r: r[1])
>>> keys = [r[1] for r in data]         # precomputed list of keys
>>> data[bisect_left(keys, 0)]
('black', 0)
>>> data[bisect_left(keys, 1)]
('blue', 1)
>>> data[bisect_left(keys, 5)]
('red', 5)
>>> data[bisect_left(keys, 8)]
('yellow', 8)
>>>
_

_('brown', 7)_を使用して、dataのソートされたリストで_('red', 5)_の後に_bisect.insort_left_を配置したい。現時点ではbisect.insort_left(data, ('brown', 7))は_('brown', 7)_を_data[0]_に配置します。なぜなら、挿入にキーを使用していないためです...ドキュメントは、キーを使用して挿入を行うことを示していません。

31
Merlin

これは基本的に _SortedCollection recipe_bisectのドキュメントで言及することと同じことを行います以下も参照してください:キー機能をサポートする最後のセクション。

行われているのは、パフォーマンスを向上させるために、ソートされたkeysリストと並行して個別のソートされたdataリストが維持されることです(各挿入の前にキーリストを作成するよりも高速ですが、保持して更新することは厳密には必要ありません)。 ActiveStateレシピはこれをクラス内にカプセル化しましたが、以下のコードでは、渡される2つの独立した独立したリストにすぎません(両方が保持されている場合よりも、同期が外れるのは簡単です)レシピのクラスのインスタンス)。

_from bisect import bisect_left

def insert(seq, keys, item, keyfunc=lambda v: v):
    """Insert an item into a sorted list using a separate corresponding
       sorted keys list and a keyfunc() to extract the key from each item.

    Based on insert() method in SortedCollection recipe:
    http://code.activestate.com/recipes/577197-sortedcollection/
    """
    k = keyfunc(item)  # Get key.
    i = bisect_left(keys, k)  # Determine where to insert item.
    keys.insert(i, k)  # Insert key of item to keys list.
    seq.insert(i, item)  # Insert the item itself in the corresponding place.

# Initialize the sorted data and keys lists.
data = [('red', 5), ('blue', 1), ('yellow', 8), ('black', 0)]
data.sort(key=lambda r: r[1]) # Sort data by key value
keys = [r[1] for r in data]   # Initialize keys list
print(data)  # -> [('black', 0), ('blue', 1), ('red', 5), ('yellow', 8)]

insert(data, keys, ('brown', 7), keyfunc=lambda x: x[1])
print(data)  # -> [('black', 0), ('blue', 1), ('red', 5), ('brown', 7), ('yellow', 8)]
_

次の質問:
_bisect.insort_left_は使用できますか?

いいえ、bisect.insort_left()関数を使用してこれを行うことはできません。キー関数をサポートするように記述されていないためです。代わりに、渡されたアイテム全体を比較して挿入します。x 、その_if a[mid] < x:_ステートメントの配列内のアイテム全体の1つを使用します。 _Lib/bisect.py_bisectモジュールのソースを見ると、私の意味がわかります。

関連する抜粋は次のとおりです。

_def insort_left(a, x, lo=0, hi=None):
    """Insert item x in list a, and keep it sorted assuming a is sorted.

    If x is already in a, insert it to the left of the leftmost x.

    Optional args lo (default 0) and hi (default len(a)) bound the
    slice of a to be searched.
    """

    if lo < 0:
        raise ValueError('lo must be non-negative')
    if hi is None:
        hi = len(a)
    while lo < hi:
        mid = (lo+hi)//2
        if a[mid] < x: lo = mid+1
        else: hi = mid
    a.insert(lo, x)
_

上記を変更して、オプションのキー関数引数を受け入れ、使用することができます。

_def my_insort_left(a, x, lo=0, hi=None, keyfunc=lambda v: v):
    x_key = keyfunc(x)  # Get comparison value.
    . . .
        if keyfunc(a[mid]) < x_key: # Compare key values.
            lo = mid+1
    . . .
_

...次のように呼び出します。

_my_insort_left(data, ('brown', 7), keyfunc=lambda v: v[1])
_

実際には、不要な一般性を犠牲にして効率を上げるためにカスタム関数を作成する場合は、一般的なキー関数の引数を追加せずに、すべてをハードコーディングしてデータで必要な方法を操作できますあなたが持っているフォーマット。これにより、挿入中にキー関数を繰り返し呼び出すオーバーヘッドを回避できます。

_def my_insort_left(a, x, lo=0, hi=None):
    x_key = x[1]   # Key on second element of each item in sequence.
    . . .
        if a[mid][1] < x_key: lo = mid+1  # Compare second element to key.
    . . .
_

... keyfuncを渡さずにこの方法で呼び出されました:

_my_insort_left(data, ('brown', 7))
_
15
martineau

__getitem__および__len__を実装するクラスでイテラブルをラップできます。これにより、bisect_leftでキーを使用する機会が得られます。イテラブルとキー関数を引数として取るようにクラスを設定した場合。

これをinsort_leftで使用できるように拡張するには、insertメソッドを実装する必要があります。ここでの問題は、その場合、insort_leftは、キーがメンバーであるオブジェクトを含むリストにキー引数を挿入しようとすることです。

例はより明確です

from bisect import bisect_left, insort_left


class KeyWrapper:
    def __init__(self, iterable, key):
        self.it = iterable
        self.key = key

    def __getitem__(self, i):
        return self.key(self.it[i])

    def __len__(self):
        return len(self.it)

    def insert(self, index, item):
        print('asked to insert %s at index%d' % (item, index))
        self.it.insert(index, {"time":item})

timetable = [{"time": "0150"}, {"time": "0250"}, {"time": "0350"}, {"time": "0450"}, {"time": "0550"}, {"time": "0650"}, {"time": "0750"}]

bslindex = bisect_left(KeyWrapper(timetable, key=lambda t: t["time"]), "0359")

islindex = insort_left(KeyWrapper(timetable, key=lambda t: t["time"]), "0359")

自分のinsertメソッドで、タイムテーブルディクショナリに固有にする必要がある方法を確認してください。それ以外の場合は、insort_left"0359"を挿入しようとします{"time": "0359"}を挿入しますか?

これを回避する方法は、比較用のダミーオブジェクトを作成し、KeyWrapperから継承してinsertをオーバーライドするか、何らかのファクトリ関数を渡してオブジェクトを作成することです。これらの方法はいずれも、慣用的なpythonの観点からは、特に望ましくありません。

したがって、最も簡単な方法は、KeyWrapperbisect_leftとともに使用することです。これにより、挿入インデックスが返され、自分で挿入を実行できます。これを専用の関数で簡単にラップできます。

例えば.

bslindex = bisect_left(KeyWrapper(timetable, key=lambda t: t["time"]), "0359")
timetable.insert(bslindex, {"time":"0359"})

この場合は、insertを実装しないようにしてください。誤ってKeyWrapperinsort_leftのような変更関数に誤って渡した場合、おそらくこれは実行されないはずです。正しいこと。

サンプルデータを使用するには

from bisect import bisect_left


class KeyWrapper:
    def __init__(self, iterable, key):
        self.it = iterable
        self.key = key

    def __getitem__(self, i):
        return self.key(self.it[i])

    def __len__(self):
        return len(self.it)

data = [('red', 5), ('blue', 1), ('yellow', 8), ('black', 0)]
data.sort(key=lambda c: c[1])

newcol = ('brown', 7)

bslindex = bisect_left(KeyWrapper(data, key=lambda c: c[1]), newcol[1])
data.insert(bslindex, newcol)

print(data)
7
Paul Rooney

あなたの目標がリストを維持することである場合キーでソートbisect insertのような通常の操作を実行し、削除して更新します sortedcontainers はあなたに合うと思いますも必要であり、O(n)挿入を回避します。

4
mmj

クラスに比較メソッドを追加します

これは、特にクラスが既にあり、そこからキーでソートしたい場合は特に、最も苦痛の少ない方法です。

#!/usr/bin/env python3

import bisect
import functools

@functools.total_ordering
class MyData:
    def __init__(self, color, number):
        self.color = color
        self.number = number
    def __lt__(self, other):
        return self.number < other .number
    def __str__(self):
        return '{} {}'.format(self.color, self.number)

mydatas = [
    MyData('red', 5),
    MyData('blue', 1),
    MyData('yellow', 8),
    MyData('black', 0),
]
mydatas_sorted = []
for mydata in mydatas:
    bisect.insort(mydatas_sorted, mydata)
for mydata in mydatas_sorted:
    print(mydata)

出力:

black 0
blue 1
red 5
yellow 8

参照: クラスの「有効化」比較

Python 3.5.2。