【问题标题】:Merge sequences of unique elements合并独特元素的序列
【发布时间】:2015-06-09 16:36:27
【问题描述】:

我正在尝试合并多个序列,如下例所示:

x = ['one', 'two', 'four']
y = ['two', 'three', 'five']
z = ['one', 'three', 'four']

merged = ['one', 'two', 'three', 'four', 'five']

给定的序列都是相同的、无重复序列(未给出)的子序列。如果无法确定顺序——如示例中的'four''five',也可以颠倒——任何一种解决方案都可以。

这个问题类似于多序列对齐,但我怀疑有一个(算法上)更简单的解决方案,因为它受到更多限制(没有重复,没有交叉边缘)。例如。当从所有元素的并集开始时,我只需要对元素进行排序 - 但我似乎无法找到一种体面的方法来从输入序列中推断出底层顺序。

这个例子是用 Python 编写的,一个期望的解决方案也是,但问题是一般算法性质的。

【问题讨论】:

  • 双字提示:拓扑排序。
  • @NickBailey 我知道这不是 - 我很高兴看到 DSM 已经提供了两个词的提示。

标签: python merge subsequence


【解决方案1】:

这是一个非常低效的方法,应该做你想做的:

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)

输出:

(“零”,13) (“一”,12) ('二', 6) ('三', 3) ('四', 1) ('五', 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 中引用它一次,因此没有足够的上下文来做得更好。

【讨论】:

    【解决方案2】:

    您的问题完全与离散数学中的关系有关,即数组中的所有组合对都具有传递关系,这意味着if a>b and b>c then a>c。因此,您可以创建以下列表,因此在长度为 5 的集合中,最小元素应该在其中的 4 对中——如果我们有这样数量的对。所以首先我们需要创建这些按第一个元素分组的对,为此我们可以使用 itertools 模块中的 groupbychain 函数:

    >>> 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来计算这些元素的数量。 p>

    >>> [(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)]
    

    【讨论】:

    • 谢谢,我喜欢你提出传递关系方面(@DSM 在问题中的评论也暗示了这一点),但恐怕我真的无法理解你的解释......还有:单行字很好,但是如果您需要滚动那么宽才能阅读该行,那就很难读了
    • @lenz 不客气!但逻辑很简单。但是如果你不熟悉groupbycombinations,你需要先阅读它们。我也会添加更多解释。
    • Kasra,我知道您从序列中提取所有有序对,这类似于@jedwards 答案中的中间步骤(在三向嵌套for 循环中创建的映射)。但是我看不到如何使用这些信息来创建元素之间的排序。看来你是在文中说的,但我看不懂,代码里也看不到。
    【解决方案3】:

    为了完整起见,这就是我最终解决问题的方式:

    正如@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']
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-05-16
      • 2015-11-19
      • 1970-01-01
      • 1970-01-01
      • 2011-05-02
      • 2014-10-23
      相关资源
      最近更新 更多