[英]Merge sequences of unique elements
我正在嘗試合並多個序列,如下例所示:
x = ['one', 'two', 'four']
y = ['two', 'three', 'five']
z = ['one', 'three', 'four']
merged = ['one', 'two', 'three', 'four', 'five']
給定的序列都是相同的無重復序列(未給出)的所有子序列。 如果順序無法確定——如示例中的'four'
和'five'
,它們也可以顛倒——任何一種解決方案都可以。
這個問題類似於多序列比對,但我懷疑有一個(算法上)更簡單的解決方案,因為它受到更多限制(沒有重復,沒有交叉邊緣)。 例如。 當從所有元素的並集開始時,我只需要對元素進行排序——但我似乎無法找到一種體面的方法來從輸入序列中推斷出底層順序。
該示例是在 Python 中編寫的,所需的解決方案也是如此,但該問題具有一般算法性質。
這是一種非常低效的方法,應該可以滿足您的需求:
w = ['zero', 'one']
x = ['one', 'two', 'four']
y = ['two', 'three', 'five']
z = ['one', 'three', 'four']
def get_score(m, k):
v = m[k]
return sum(get_score(m, kk) for kk in v) + 1
m = {}
for lst in [w,x,y,z]:
for (i,src) in enumerate(lst):
if src not in m: m[src] = []
for (j,dst) in enumerate(lst[i+1:]):
m[src].append(dst)
scored_u = [(k,get_score(m,k)) for k in m]
scored_s = sorted(scored_u, key=lambda (k,s): s, reverse=True)
for (k,s) in scored_s:
print(k,s)
輸出:
('zero', 13) ('one', 12) ('two', 6) ('three', 3) ('four', 1) ('five', 1)
該方法首先構建一個映射m
,其中鍵是列表的術語,值是發現跟在鍵后面的術語列表。
所以在這種情況下, m
看起來像:
{
'three': ['five', 'four'],
'two': ['four', 'three', 'five'],
'four': [],
'zero': ['one'],
'five': [],
'one': ['two', 'four', 'three', 'four']
}
從那里,它計算每個鍵的分數。 分數定義為已看到的元素的分數之和加上 1。
所以
get_score(m, 'four') = 1
get_score(m, 'five') = 1
# and thus
get_score(m, 'three') = 3 # (1(four) + 1(five) + 1)
它對輸入列表中的每個元素(在我的例子中為w,x,y,z
)執行此操作並計算總分,然后按分數降序對其進行排序。
我說這是低效的,因為這個get_score
可以被記憶,所以你只需要確定一個鍵的分數。 您可能會通過回溯來做到這一點——計算值為空列表的鍵的分數,然后向后工作。 在當前的實現中,它多次確定某些鍵的分數。
注意:所有這些保證是元素的分數不會低於“預期”的分數。 例如,添加
v = ['one-point-five', 'four']
混合將在列表中放置four
one-point-five
以上,但由於您只引用一次,在v
,沒有足夠的上下文來做得更好。
為了完整起見,這就是我最終解決問題的方式:
正如@DSM 所指出的,這個問題與拓撲排序有關。 那里有第三方模塊,例如。 toposort (純 Python,無依賴)。
序列需要轉換為映射格式,類似於其他答案中也使用/建議的格式。 toposort_flatten()
然后做剩下的toposort_flatten()
:
from collections import defaultdict
from toposort import toposort_flatten
def merge_seqs(*seqs):
'''Merge sequences that share a hidden order.'''
order_map = defaultdict(set)
for s in seqs:
for i, elem in enumerate(s):
order_map[elem].update(s[:i])
return toposort_flatten(dict(order_map))
用上面的例子:
>>> w = ['zero', 'one']
>>> x = ['one', 'two', 'four']
>>> y = ['two', 'three', 'five']
>>> z = ['one', 'three', 'four']
>>> merge_seqs(w, x, y, z)
['zero', 'one', 'two', 'three', 'five', 'four']
您的問題完全與離散數學中的關系有關,即數組中的所有組合對都具有傳遞關系,這意味着if a>b and b>c then a>c
。 因此,您可以創建以下列表,因此在長度為 5 的集合中,最小元素應該在這些對中的 4 對中——如果我們有這樣數量的對。 所以首先我們需要創建這些按第一個元素分組的對,為此我們可以使用itertools
模塊中的groupby
和chain
函數:
>>> from itertools import combinations,chain,groupby
>>> from operator import itemgetter
>>> l1= [list(g) for _,g in groupby(sorted(chain.from_iterable(combinations(i,2) for i in [x,y,z])),key=itemgetter(0))]
[[('one', 'four'), ('one', 'four'), ('one', 'three'), ('one', 'two')], [('three', 'five'), ('three', 'four')], [('two', 'five'), ('two', 'four'), ('two', 'three')]]
因此,如果我們有 len 4 ,3 ,2, 1 的組,那么我們已經找到了答案,但是如果我們沒有找到這樣的序列,我們可以反向進行前面的計算,以這種邏輯找到我們的元素,如果我們找到關系len 4 的組是最大的數字,...!
>>> l2= [list(g) for _,g in groupby(sorted(chain.from_iterable(combinations(i,2) for i in [x,y,z]),key=itemgetter(1)),key=itemgetter(1))]
[[('two', 'five'), ('three', 'five')], [('one', 'four'), ('two', 'four'), ('one', 'four'), ('three', 'four')], [('two', 'three'), ('one', 'three')], [('one', 'two')]]
所以我們可以做到以下幾點:
請注意,我們需要使用set(zip(*i)[1])
來獲取與我們的特定元素相關的元素集,然后使用len
來計算這些元素的數量。
>>> [(i[0][0],len(set(zip(*i)[1]))) for i in l1]
[('one', 3), ('three', 2), ('two', 3)]
>>> [(i[0][1],len(set(zip(*i)[0]))) for i in l2]
[('five', 2), ('four', 3), ('three', 2), ('two', 1)]
在第一部分我們找到了 4,2,3 所以現在我們只需要找到它可能是four or five
1。現在我們去第二部分,我們需要找到一個長度為4 or 3
的序列,即four
是3 所以已經找到了第 4 個元素,因此第 5 個元素應該是five
。
編輯:作為一種更優雅、更快的方式,您可以使用collections.defaultdict
完成這項工作:
>>> from collections import defaultdict
>>> d=defaultdict(set)
>>> for i,j in chain.from_iterable(combinations(i,2) for i in [x,y,z]) :
... d[i].add(j)
...
>>> d
defaultdict(<type 'set'>, {'three': set(['four', 'five']), 'two': set(['four', 'five', 'three']), 'one': set(['four', 'two', 'three'])})
>>> l1=[(k,len(v)) for k,v in d.items()]
>>> l1
[('three', 2), ('two', 3), ('one', 3)]
>>> d=defaultdict(set)
>>> for i,j in chain.from_iterable(combinations(i,2) for i in [x,y,z]) :
... d[j].add(i) #create dict reversely
...
>>> l2=[(k,len(v)) for k,v in d.items()]
>>> l2
[('four', 3), ('five', 2), ('two', 1), ('three', 2)]
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.