我试图合并多个序列,如下例所示:
x = ['one', 'two', 'four']
y = ['two', 'three', 'five']
z = ['one', 'three', 'four']
merged = ['one', 'two', 'three', 'four', 'five']
给定序列是相同的无重复序列的所有子序列(未给出)。如果无法确定订单 - 与示例中的'four'
和'five'
一样,也可以反转 - 任何解决方案都可以。
这个问题类似于多序列比对,但我怀疑有一个(算法上)更简单的解决方案,因为它更受限制(没有重复,没有交叉边缘)。例如。当从所有元素的并集开始时,我只需要对元素进行排序 - 但我似乎找不到从输入序列推导出基础顺序的合适方法。
示例是在Python中,也是一个理想的解决方案,但问题在于算法的一般性。
答案 0 :(得分:2)
这是一个非常低效的方法,可以做你想要的:
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']
进入混音会将one-point-five
放在列表上four
之上,但由于您只在v
中引用了一次,因此没有足够的上下文来完成更好的工作。
答案 1 :(得分:1)
你的问题是关于离散数学中的关系,你的数组中的所有组合对都有传递关系,这意味着if a>b and b>c then a>c
。所以你可以创建下面的列表,所以在长度为5的集合中,最小元素应该在这对中的4对中(如果我们有一对这样的对数)所以首先我们需要创建由第一个元素goruped的这对为此,我们可以使用groupby
模块中的chain
和itertools
函数:
>>> 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。现在我们转到第二部分,我们需要找到一个长度为{{ 1}} 4 or 3
为3,因此找到了第4个元素,因此第5个元素应为four
。
编辑:作为一种更优雅,更快捷的方式,您可以使用five
:
collections.defaultdict
答案 2 :(得分:1)
为了完整起见,这就是我最终解决问题的方法:
正如@DSM所指出的,这个问题与拓扑排序有关。那里有第三方模块,例如。 toposort(普通的Python,没有依赖关系)。
需要将序列转换为映射格式,类似于其他答案中使用/建议的格式。 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']