簡體   English   中英

Pythonic方式合並兩個重疊列表,保留順序

[英]Pythonic way to merge two overlapping lists, preserving order

好的,所以我有兩個列表,如下:

  • 它們可以並且將具有重疊項目,例如, [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,8,7],[8,6,7]應合並為[9,1,1,8,7,8,6,7]

您可以嘗試以下方法:

>>> 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]

我們的想法是,我們檢查第一i的元素bb[:i]與最后i的元件aa[-i:] )。 我們把i按遞減順序,從長度開始b ,直到1( xrange(len(b), 0, -1)因為我們想盡可能地匹配。 我們通過使用next來獲取第一個這樣的i ,如果我們找不到它,我們使用零值( next(..., 0) )。 從我們找到i的那一刻起,我們就從索引i添加a b的元素。

有幾種簡單的優化是可能的。

  1. 你不需要從master [1]開始,因為最長的重疊從master [-len(加法)]開始

  2. 如果添加對list.index的調用,則可以避免創建子列表並比較每個索引的列表:

這種方法使代碼也很容易理解(並且通過使用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

一個簡單的優化不是遍歷整個master列表。 即, while n < len(master)替換for n in range(min(len(addition), len(master))) (並且不要在循環中遞增n )。 如果沒有匹配,則當前代碼將遍歷整個master列表,即使被比較的切片甚至不是相同的長度。

另一個問題是你要使用master片和addition片來比較它們,每次創建兩個新列表,並不是真的有必要。 此解決方案(受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的最后一個元素結束,因此只有那些值是可能重疊的長度。 然后我們可以檢查它們中的每個元素是否也排成一行。

該函數當前假定masteraddition更長(如果不是,你可能會在master[-overlap_len + i]得到一個IndexError )。 如果您不能保證,請向overlap_lens生成器添加條件。

它也是非貪婪的,即它尋找最小的非空重疊( merge([1, 2, 2], [2, 2, 3])將返回[1, 2, 2, 2, 3] )。 我認為這就是“在最后可能的有效位置合並”的意思。 如果你想要一個貪婪的版本,請反轉overlap_lens生成器。

我不提供優化,而是另一種查看問題的方法。 對我來說,這似乎是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]

這實際上並不是非常困難。 畢竟,基本上你所做的只是檢查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)

哪個應該起作用,因為合並最長的重疊應該在所有情況下產生最短的結果。

首先,為了清楚起見,您可以使用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的最后一個元素結束,這進一步限制了比較的數量。

基於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:]

所有上述解決方案在使用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"])

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM