タプルの2 つのソートされたコピーを保持する余裕があればO(log(n) + m)
、時間内にそれを行うことができます。つまり、単純なアプローチよりも漸近的に遅くなりますが、特定の数のクエリを実行する必要がある場合(ほぼ確実に非常に小さい)、それは報われます。n
m
O(nlog(n))
log(n)
二分法を使用して、正しい最初の値と正しい 2 番目の値を持ち、これらのセットと交差する候補を見つけることができるという考え方です。
ただし、奇妙な種類の比較が必要であることに注意してください。指定された引数で始まるすべての文字列を処理します。これは単純に、最も右側の出現箇所を検索するときに、キーに9
s を入力する必要があることを意味します。
完全に機能する (あまりテストされていませんが) コード:
from random import randint
from operator import itemgetter
first = itemgetter(0)
second = itemgetter(1)
sa = [(str(randint(0, 1000000)), str(randint(0, 1000000))) for _ in range(300000)]
f_sorted = sorted(sa, key=first)
s_sorted = sa
s_sorted.sort(key=second)
max_length = max(len(s) for _,s in sa)
# See: bisect module from stdlib
def bisect_right(seq, element, key):
lo = 0
hi = len(seq)
element = element.ljust(max_length, '9')
while lo < hi:
mid = (lo+hi)//2
if element < key(seq[mid]):
hi = mid
else:
lo = mid + 1
return lo
def bisect_left(seq, element, key):
lo = 0
hi = len(seq)
while lo < hi:
mid = (lo+hi)//2
if key(seq[mid]) < element:
lo = mid + 1
else:
hi = mid
return lo
def lookup_set(x_appr, y_appr):
x_left = bisect_left(f_sorted, x_appr, key=first)
x_right = bisect_right(f_sorted, x_appr, key=first)
x_candidates = f_sorted[x_left:x_right + 1]
y_left = bisect_left(s_sorted, y_appr, key=second)
y_right = bisect_right(s_sorted, y_appr, key=second)
y_candidates = s_sorted[y_left:y_right + 1]
return set(x_candidates).intersection(y_candidates)
そして、最初のソリューションとの比較:
In [2]: def lookup_set2(x_appr, y_appr):
...: return [n for n in sa if n[0].startswith(x_appr) and n[1].startswith(y_appr)]
In [3]: lookup_set('123', '124')
Out[3]: set([])
In [4]: lookup_set2('123', '124')
Out[4]: []
In [5]: lookup_set('123', '125')
Out[5]: set([])
In [6]: lookup_set2('123', '125')
Out[6]: []
In [7]: lookup_set('12', '125')
Out[7]: set([('12478', '125908'), ('124625', '125184'), ('125494', '125940')])
In [8]: lookup_set2('12', '125')
Out[8]: [('124625', '125184'), ('12478', '125908'), ('125494', '125940')]
In [9]: %timeit lookup_set('12', '125')
1000 loops, best of 3: 589 us per loop
In [10]: %timeit lookup_set2('12', '125')
10 loops, best of 3: 145 ms per loop
In [11]: %timeit lookup_set('123', '125')
10000 loops, best of 3: 102 us per loop
In [12]: %timeit lookup_set2('123', '125')
10 loops, best of 3: 144 ms per loop
ご覧のとおり、このソリューションは240-1400
(これらの例では) 単純なアプローチよりも約 1 倍高速です。
マッチの数が多い場合:
In [19]: %timeit lookup_set('1', '2')
10 loops, best of 3: 27.1 ms per loop
In [20]: %timeit lookup_set2('1', '2')
10 loops, best of 3: 152 ms per loop
In [21]: len(lookup_set('1', '2'))
Out[21]: 3587
In [23]: %timeit lookup_set('', '2')
10 loops, best of 3: 182 ms per loop
In [24]: %timeit lookup_set2('', '2')
1 loops, best of 3: 212 ms per loop
In [25]: len(lookup_set2('', '2'))
Out[25]: 33053
ご覧のとおり、一致の数が合計サイズの約 10% であっても、このソリューションの方が高速です。ただし、すべてのデータを一致させようとすると、次のようになります。
In [26]: %timeit lookup_set('', '')
1 loops, best of 3: 360 ms per loop
In [27]: %timeit lookup_set2('', '')
1 loops, best of 3: 221 ms per loop
これは非常に特殊なケースですが、(それほどではありませんが)遅くなります。ほぼすべての要素に頻繁に一致するとは思えません。
データにかかる時間sort
は非常に短いことに注意してください。
In [13]: from random import randint
...: from operator import itemgetter
...:
...: first = itemgetter(0)
...: second = itemgetter(1)
...:
...: sa2 = [(str(randint(0, 1000000)), str(randint(0, 1000000))) for _ in range(300000)]
In [14]: %%timeit
...: f_sorted = sorted(sa2, key=first)
...: s_sorted = sorted(sa2, key=second)
...: max_length = max(len(s) for _,s in sa2)
...:
1 loops, best of 3: 881 ms per loop
ご覧のとおり、2 つの並べ替えられたコピーを実行するのに 1 秒もかかりません。実際には、上記のコードは 2 番目のコピーを「その場で」ソートするため、わずかに高速になります (ただし、tim-sort にはまだO(n)
メモリが必要な場合があります)。
これは、約 6 ~ 8 個を超えるクエリを実行する必要がある場合、このソリューションの方が高速であることを意味します。
注: python 化された標準ライブラリはbisect
モジュールを提供します。ただし、パラメーターは許可されていませんkey
(Guido がそれを望んでいたと読んだことは覚えていますが、将来追加される可能性があります)。したがって、直接使用したい場合は、「decorate-sort-undecorate」イディオムを使用する必要があります。
それ以外の:
f_sorted = sorted(sa, key=first)
やったほうがいい:
f_sorted = sorted((first, (first,second)) for first,second in sa)
つまり、キーをタプルの最初の要素として明示的に挿入します。その後、('123', '')
as element を使用してbisect_*
関数に渡すことができ、正しいインデックスが見つかるはずです。
私はこれを避けることにしました。モジュールのソースからコードをコピーして貼り付け、わずかに変更して、ユースケースによりシンプルなインターフェイスを提供します。
最後の注意: タプル要素を整数に変換できれば、比較はより高速になります。ただし、ほとんどの場合、セットの交差を実行するのにまだ時間がかかるため、パフォーマンスがどの程度向上するかは正確にはわかりません。