Pythonic方式合并两个重叠列表,保留顺序

时间:2015-05-05 14:29:54

标签: python list python-3.x merge

好吧,所以我有两个名单,如下:

  • 他们可以并且会有重叠的项目,例如[1, 2, 3, 4, 5][4, 5, 6, 7]
  • 重叠中是其他项目,例如,会发生:[1, 2, 3, 4, 5][3.5, 4, 5, 6, 7]
  • 列表不一定是有序的也不是唯一的。 [9, 1, 1, 8, 7][8, 6, 7]

我想合并列表,以便保留现有订单,并在最后一个可能的有效位置合并,以便不丢失任何数据。此外,第一个列表可能很大。我目前的工作代码是这样的:

master = [1,3,9,8,3,4,5]
addition = [3,4,5,7,8]

def merge(master, addition):
    n = 1
    while n < len(master):
        if master[-n:] == addition[:n]:
            return master + addition[n:]
        n += 1
    return master + addition

我想知道的是 - 有更有效的方法吗?它有效,但我对此有点怀疑,因为它可能会在我的应用程序中遇到大的运行时 - 我正在合并大量的字符串列表。

编辑:我预计[1,3,9,8,3,4,5],[3,4,5,7,8]合并为:[1,3,9] ,8,的 3,4,5- 下,7,8]。为清楚起见,我突出了重叠部分。

<9> [1,1,1,8,7],[8,6,7]应合并为[9,1,1,8,7,8,6,7]

9 个答案:

答案 0 :(得分:17)

您可以尝试以下操作:

>>> a = [1, 3, 9, 8, 3, 4, 5]
>>> b = [3, 4, 5, 7, 8]

>>> matches = (i for i in xrange(len(b), 0, -1) if b[:i] == a[-i:])
>>> i = next(matches, 0)
>>> a + b[i:]
[1, 3, 9, 8, 3, 4, 5, 7, 8]

我们的想法是检查ib)的第一个b[:i]元素以及i的{​​{1}}个元素a )。我们从a[-i:]到{1 i的长度开始按降序排列b,因为我们希望尽可能匹配。我们使用xrange(len(b), 0, -1)获取第一个i,如果找不到,我们使用零值(next)。从我们找到next(..., 0)的那一刻起,我们就会从i添加a b的元素。

答案 1 :(得分:9)

可以进行一些简单的优化。

  1. 您不需要从master [1]开始,因为最长的重叠从master [-len(加法)]开始

  2. 如果您添加对list.index的调用,则可以避免创建子列表并比较每个索引的列表:

  3. 这种方法使代码也易于理解(并且通过使用cython或pypy更容易优化):

    master = [1,3,9,8,3,4,5]
    addition = [3,4,5,7,8]
    
    def merge(master, addition):
        first = addition[0]
        n = max(len(master) - len(addition), 1)  # (1)
        while 1:
            try:
                n = master.index(first, n)       # (2)
            except ValueError:
                return master + addition
    
            if master[-n:] == addition[:n]:
                return master + addition[n:]
            n += 1
    

答案 2 :(得分:6)

一个简单的优化不会遍历整个master列表。即,将while n < len(master)替换为for n in range(min(len(addition), len(master)))(并且不要在循环中增加n)。如果没有匹配,您的当前代码将遍历整个master列表,即使被比较的切片甚至不是相同的长度。

另一个问题是,您需要对masteraddition进行切片才能进行比较,每次创建两个新列表,并不是必需的。此解决方案(受Boyer-Moore启发)不使用切片:

def merge(master, addition):
    overlap_lens = (i + 1 for i, e in enumerate(addition) if e == master[-1])
    for overlap_len in overlap_lens:
        for i in range(overlap_len):
            if master[-overlap_len + i] != addition[i]:
                break
        else:
            return master + addition[overlap_len:]
    return master + addition

这里的想法是生成masteraddition的最后一个元素的所有索引,并为每个元素添加1。由于有效重叠必须以master的最后一个元素结束,因此只有这些值是可能重叠的长度。然后我们可以检查它们中的每个元素是否也排成一行。

该功能目前假定master超过addition(如果不是IndexError,您可能会master[-overlap_len + i] overlap_lens。如果您无法保证,请向merge([1, 2, 2], [2, 2, 3])生成器添加条件。

它也非贪婪,即它寻找最小的非空重叠([1, 2, 2, 2, 3]将返回overlap_lens)。我认为这意味着&#34;合并到最后可能的有效位置&#34;。如果您想要一个贪婪的版本,请反转Map<Boolean, List<E>> map = data.stream().collect(partitioningBy(predicate)); 生成器。

答案 3 :(得分:6)

我不提供优化,而是另一种查看问题的方法。对我来说,这似乎是http://en.wikipedia.org/wiki/Longest_common_substring_problem的一个特例,其中子字符串始终位于列表/字符串的末尾。以下算法是动态编程版本。

def longest_common_substring(s1, s2):
    m = [[0] * (1 + len(s2)) for i in xrange(1 + len(s1))]
    longest, x_longest = 0, 0
    for x in xrange(1, 1 + len(s1)):
        for y in xrange(1, 1 + len(s2)):
            if s1[x - 1] == s2[y - 1]:
                m[x][y] = m[x - 1][y - 1] + 1
                if m[x][y] > longest:
                    longest = m[x][y]
                    x_longest = x
            else:
                m[x][y] = 0
    return x_longest - longest, x_longest

master = [1,3,9,8,3,4,5]
addition = [3,4,5,7,8]
s, e = longest_common_substring(master, addition)
if e - s > 1:
    print master[:s] + addition

master = [9, 1, 1, 8, 7]
addition = [8, 6, 7]
s, e = longest_common_substring(master, addition)
if e - s > 1:
    print master[:s] + addition
else:
    print master + addition

[1, 3, 9, 8, 3, 4, 5, 7, 8]
[9, 1, 1, 8, 7, 8, 6, 7]

答案 4 :(得分:5)

这实际上并不太难。毕竟,基本上你所做的只是检查A末尾的子串与B的子串对齐。

def merge(a, b):
    max_offset = len(b)  # can't overlap with greater size than len(b)
    for i in reversed(range(max_offset+1)):
        # checks for equivalence of decreasing sized slices
        if a[-i:] == b[:i]:
            break
    return a + b[i:]

我们可以通过以下方式测试您的测试数据:

test_data = [{'a': [1,3,9,8,3,4,5], 'b': [3,4,5,7,8], 'result': [1,3,9,8,3,4,5,7,8]},
             {'a': [9, 1, 1, 8, 7], 'b': [8, 6, 7], 'result': [9, 1, 1, 8, 7, 8, 6, 7]}]

all(merge(test['a'], test['b']) == test['result'] for test in test_data)

这将贯穿切片的每个可能组合,这些切片可能导致重叠,并且如果找到重叠,则会记住重叠的结果。如果找不到任何内容,则会使用i的最后结果,该结果始终为0。无论哪种方式,它都返回a加上b[i]以外的所有内容(在重叠的情况下,这是非重叠部分。在非重叠的情况下,它是一切)

请注意,我们可以在极端情况下进行一些优化。例如,这里最糟糕的情况是它在整个列表中运行而没有找到任何解决方案。您可以在开头添加一个可能会使最坏情况短路的快速检查

def merge(a, b):
    if a[-1] not in b:
        return a + b
    ...

事实上,您可以更进一步采用该解决方案,并可能使您的算法更快

def merge(a, b):
    while True:
        try:
            idx = b.index(a[-1]) + 1  # leftmost occurrence of a[-1] in b
        except ValueError:  # a[-1] not in b
            return a + b
        if a[-idx:] == b[:idx]:
            return a + b[:idx]

然而,在以下情况下,这可能找不到最长的重叠:

a = [1,2,3,4,1,2,3,4]
b = [3,4,1,2,3,4,5,6]
# result should be [1,2,3,4,1,2,3,4,5,6], but
# this algo produces [1,2,3,4,1,2,3,4,1,2,3,4,5,6]

您可以修复使用rindex代替index来匹配最长的切片而不是最短的切片,但我不确定这对您的速度有什么影响。它肯定比较慢,但可能无关紧要。你也可以记住结果并返回最短的结果,这可能是一个更好的主意。

def merge(a, b):
    results = []
    while True:
        try:
            idx = b.index(a[-1]) + 1  # leftmost occurrence of a[-1] in b
        except ValueError:  # a[-1] not in b
            results.append(a + b)
            break
        if a[-idx:] == b[:idx]:
            results.append(a + b[:idx])
    return min(results, key=len)

哪个应该有效,因为合并最长的重叠应该在所有情况下产生最短的结果。

答案 5 :(得分:4)

首先,为了清楚起见,您可以使用for循环替换while循环:

def merge(master, addition):
    for n in xrange(1, len(master)):
        if master[-n:] == addition[:n]:
            return master + addition[n:]
    return master + addition

然后,您不必比较所有可能的切片,而只需要比较master的切片以addition的第一个元素开头的切片:

def merge(master, addition):
    indices = [len(master) - i for i, x in enumerate(master) if x == addition[0]]
    for n in indices:
        if master[-n:] == addition[:n]:
            return master + addition[n:]
    return master + addition

所以不要比较像这样的切片:

1234123141234
            3579
           3579
          3579
         3579
        3579
       3579
      3579
     3579
    3579
   3579
  3579
 3579
3579

你只是在进行这些比较:

1234123141234
  |   |    |
  |   |    3579
  |   3579
  3579

这会加快程序的速度取决于数据的性质:列表中重复的元素越多越好。

您还可以生成addition的索引列表,以便它自己的切片始终以master的最后一个元素结束,这进一步限制了比较次数。

答案 6 :(得分:4)

基于https://stackoverflow.com/a/30056066/541208

def join_two_lists(a, b):
  index = 0
  for i in xrange(len(b), 0, -1):
    #if everything from start to ith of b is the 
    #same from the end of a at ith append the result
    if b[:i] == a[-i:]:
        index = i
        break

  return a + b[index:]

答案 7 :(得分:0)

所有上述解决方案在使用for / while循环进行合并任务方面类似。我首先尝试了@JuniorCompressor和@TankorSmash的解决方案,但这些解决方案对于合并两个大型列表(例如包含大约数百万个元素的列表)来说太慢了。

我发现使用pandas连接大尺寸的列表更加节省时间:

import pandas as pd, numpy as np

trainCompIdMaps = pd.DataFrame( { "compoundId": np.random.permutation( range(800) )[0:80], "partition": np.repeat( "train", 80).tolist()} )

testCompIdMaps = pd.DataFrame( {"compoundId": np.random.permutation( range(800) )[0:20], "partition": np.repeat( "test", 20).tolist()} )

# row-wise concatenation for two pandas
compoundIdMaps = pd.concat([trainCompIdMaps, testCompIdMaps], axis=0)

mergedCompIds = np.array(compoundIdMaps["compoundId"])

答案 8 :(得分:0)

您需要的是像 Needleman-Wunsch 这样的序列比对算法。

Needleman-Wunsch 是一种基于动态规划的全局序列比对算法: Needleman-Wunsch matrix; Source: Wikipedia

我发现这个很好的实现可以在 python 中合并任意对象序列: https://github.com/ajnisbet/paired

const interval = 200;
let timeout = interval;
    
{props.chain.map((value, index, elements) =>
   <div>   
      {setTimeout(() => {
        return <img src={elements[index].image_url} className="evol_img" />
        timeout = timeout + interval;
       }, interval);
   </div>                          
)}