私は2つの大きなデータセットに取り組んでおり、私の質問は次のとおりです。
2つのリストがあるとします。
_list1 = [A,B,C,D]
_
_list2 = [B,D,A,G]
_
O(n以外のPythonを使用して、一致するインデックスを効率的に見つけるにはどうすればよいですか?2)検索?結果は次のようになります。
matching_index(list1,list2) -> [(0,2),(1,0),(3,1)]
オブジェクトがハッシュ可能で、リストに重複がない場合は、最初のリストの逆インデックスを作成してから、2番目のリストをトラバースできます。これは各リストを1回だけトラバースするため、O(n)
になります。
_def find_matching_index(list1, list2):
inverse_index = { element: index for index, element in enumerate(list1) }
return [(index, inverse_index[element])
for index, element in enumerate(list2) if element in inverse_index]
find_matching_index([1,2,3], [3,2,1]) # [(0, 2), (1, 1), (2, 0)]
_
以前のソリューションを拡張して、重複を考慮することができます。 set
を使用すると、複数のインデックスを追跡できます。
_def find_matching_index(list1, list2):
# Create an inverse index which keys are now sets
inverse_index = {}
for index, element in enumerate(list1):
if element not in inverse_index:
inverse_index[element] = {index}
else:
inverse_index[element].add(index)
# Traverse the second list
matching_index = []
for index, element in enumerate(list2):
# We have to create one pair by element in the set of the inverse index
if element in inverse_index:
matching_index.extend([(x, index) for x in inverse_index[element]])
return matching_index
find_matching_index([1, 1, 2], [2, 2, 1]) # [(2, 0), (2, 1), (0, 2), (1, 2)]
_
残念ながら、これはもはやO(n)ではありません。 _[1, 1]
_および_[1, 1]
_を入力する場合を考えてみます。出力は[(0, 0), (0, 1), (1, 0), (1, 1)]
です。したがって、出力のサイズによって、最悪のケースはO(n^2)
を超えることはできません。
ただし、重複がない場合、この解決策はO(n)
のままです。
ここで、オブジェクトがハッシュ可能ではなく、比較可能な場合があります。ここでの考え方は、各要素のOriginインデックスを保持する方法でリストを並べ替えることです。次に、一致する要素のシーケンスをグループ化して、一致するインデックスを取得できます。
次のコードではgroupby
とproduct
を多用しているので、長いリストでメモリ効率を上げるために_find_matching_index
_がジェネレーターを返すようにしました。
_from itertools import groupby, product
def find_matching_index(list1, list2):
sorted_list1 = sorted((element, index) for index, element in enumerate(list1))
sorted_list2 = sorted((element, index) for index, element in enumerate(list2))
list1_groups = groupby(sorted_list1, key=lambda pair: pair[0])
list2_groups = groupby(sorted_list2, key=lambda pair: pair[0])
for element1, group1 in list1_groups:
try:
element2, group2 = next(list2_groups)
while element1 > element2:
(element2, _), group2 = next(list2_groups)
except StopIteration:
break
if element2 > element1:
continue
indices_product = product((i for _, i in group1), (i for _, i in group2), repeat=1)
yield from indices_product
# In version prior to 3.3, the above line must be
# for x in indices_product:
# yield x
list1 = [[], [1, 2], []]
list2 = [[1, 2], []]
list(find_matching_index(list1, list2)) # [(0, 1), (2, 1), (1, 0)]
_
時間の複雑さはそれほど影響を受けないことがわかります。もちろん、並べ替えにはO(n log(n))
を使用しますが、groupby
は、リストを2回だけトラバースすることですべての要素を回復できるジェネレーターを提供します。結論は、私たちの複雑さは主にproduct
の出力のサイズによって制限されるということです。したがって、アルゴリズムがO(n log(n))
である最良のケースと、再びO(n^2)
である最悪のケースが与えられます。
オブジェクトがハッシュ可能ではないが順序付け可能な場合は、sorted
を使用して両方のリストを照合することを検討してください。
リストのインデックスを並べ替え、結果をペアにすることができます
indexes1 = sorted(range(len(list1)), key=lambda x: list1[x])
indexes2 = sorted(range(len(list2)), key=lambda x: list2[x])
matches = Zip(indexes1, indexes2)
両方を同時にソートし、ソート中はインデックスを保持できます。次に、連続する重複をキャッチすると、それらが異なるリストにあることがわかります
biglist = list(enumerate(list1)) + list(enumerate(list2))
biglist.sort(key=lambda x: x[1])
matches = [(biglist[i][0], biglist[i + 1][0]) for i in range(len(biglist) - 1) if biglist[i][1] == biglist[i + 1][1]]
解決策を検証する以外に理由がない場合、この問題に対する強引な答えの1つは次のように与えられます。
[(xi, xp) for (xi, x) in enumerate(list1) for (xp, y) in enumerate(list2) if x==y]
これをどのように最適化する必要があるかは、データボリュームとメモリ容量に大きく依存するため、これらのリストがどれほど大きいかを理解しておくと役立ちます。以下で説明する方法は、少なくとも数百万の値を持つリストに適していると思います。
辞書へのアクセスはO(1)なので、2番目のリストの要素をそれらの位置にマッピングすることは価値があるように思えます。同じ要素を繰り返すことができると仮定すると、collections.defaultdict
を使用すると、必要な辞書を簡単に作成できます。
l2_pos = defaultdict(list)
for (p, k) in enumerate(list2):
l2_pos[k].append(p)
式l2_pos[k]
は、要素k
が発生するlist2
の位置のリストになりました。これらのそれぞれをlist1
の対応するキーの位置とペアにするだけです。リスト形式の結果は
[(p1, p2) for (p1, k) in enumerate(list1) for p2 in l2_pos[k]]
ただし、これらの構造が大きい場合は、ジェネレーター式の方が適している可能性があります。上記のリスト内包表記内の式に名前をバインドするには、次のように記述します。
values = ((p1, p2) for (p1, k) in enumerate(list1) for p2 in l2_pos[k])
次にvalues
を反復処理すると、すべての値を含むリストを作成するオーバーヘッドが回避されるため、Pythonのメモリ管理とガベージコレクションの負荷が軽減されます。これは、問題の解決に関する限り、ほとんどすべてのオーバーヘッドです。
大量のデータを処理し始めるとき、ジェネレーターを理解することは、問題を解決するために十分なメモリを持っているかどうかの違いを意味します。多くの場合、リスト内包表記よりも明らかに有利です。
編集:順序の変更が有害でない限り、位置を保持するためにリストではなくセットを使用することにより、この手法をさらに加速できます。この変更は、読者のための演習として残されています。
dict
を使用すると、検索時間が短縮され、collections.defaultdict
専門化は簿記に役立ちます。目標はdict
で、その値は後のインデックスのペアです。重複する値は、リスト内の以前の値を上書きします。
import collections
# make a test list
list1 = list('ABCDEFGHIJKLMNOP')
list2 = list1[len(list1)//2:] + list1[:len(list1)//2]
# Map list items to positions as in: [list1_index, list2_index]
# by creating a defaultdict that fills in items not in list1,
# then adding list1 items and updating with with list2 items.
list_indexer = collections.defaultdict(lambda: [None, None],
((item, [i, None]) for i, item in enumerate(list1)))
for i, val in enumerate(list2):
list_indexer[val][1] = i
print(list(list_indexer.values()))
以下は、defaultdict
を使用した簡単なアプローチです。
与えられた
import collections as ct
lst1 = list("ABCD")
lst2 = list("BDAG")
lst3 = list("EAB")
str1 = "ABCD"
コード
def find_matching_indices(*iterables, pred=None):
"""Return a list of matched indices across `m` iterables."""
if pred is None:
pred = lambda x: x[0]
# Dict insertion
dd = ct.defaultdict(list)
for lst in iterables: # O(m)
for i, x in enumerate(lst): # O(n)
dd[x].append(i) # O(1)
# Filter + sort
vals = (x for x in dd.values() if len(x) > 1) # O(n)
return sorted(vals, key=pred) # O(n log n)
デモ
2つのリストで一致を検索(OPごと):
find_matching_indices(lst1, lst2)
# [[0, 2], [1, 0], [3, 1]]
結果の異なるインデックスで並べ替えます。
find_matching_indices(lst1, lst2, pred=lambda x: x[1])
# [[1, 0], [3, 1], [0, 2]]
3つ以上の反復可能アイテム(オプションで可変長)のアイテムに一致:
find_matching_indices(lst1, lst2, lst3, str1)
# [[0, 2, 1, 0], [1, 0, 2, 1], [2, 2], [3, 1, 3]]
詳細
辞書挿入
各項目はdefaultdictのリストに追加されます。結果は次のようになり、後でフィルターされます。
defaultdict(list, {'A': [0, 2], 'B': [1, 0], 'C': [2], 'D': [3, 1], 'G': [3]})
一見すると、二重のfor
ループから、時間の複雑さがO(n²)であると言いたくなるかもしれません。ただし、外側のループ内のコンテナーのリストの長さはm
です。内部ループは、長さn
の各コンテナーの要素を処理します。最終的な複雑さが何かはわかりませんが、 この答え に基づいて、O(n * m)または少なくともO(n²)未満であると思います。
フィルタリング
一致しないもの(長さ1のリスト)はフィルターで除外され、結果がソートされます(主にPython <3.6の無秩序な辞書用)。
sorted
を介して timsort アルゴリズムを使用して、dict値(リスト)をインデックスでソートすると、最悪のケースはO(n log n)になります。 dictキーの挿入はPython 3.6+)で保持されるため、事前にソートされたアイテムは複雑度O(n)を減らします。
全体として、最良の場合の時間の複雑さはO(n)です。 Python <3.6でsorted
を使用する場合、最悪のケースはO(n log n)です。それ以外の場合は、O(n * m)です。