过滤字符串列表,忽略其他项的子字符串

时间:2014-05-26 10:28:56

标签: python string algorithm

如何筛选包含字符串和子字符串的列表,只返回最长的字符串。 (如果列表中的任何项目是另一个项目的子字符串,则只返回较长的字符串。)

我有这个功能。有更快的方法吗?

def filterSublist(lst):
    uniq = lst
    for elem in lst:
        uniq = [x for x in uniq if (x == elem) or (x not in elem)]
    return uniq

lst = ["a", "abc", "b", "d", "xy", "xyz"]
print filterSublist(lst)

> ['abc', 'd', 'xyz']
> Function time: 0.000011

4 个答案:

答案 0 :(得分:8)

一个简单的二次时间解决方案就是:

res = []
n = len(lst)
for i in xrange(n):
    if not any(i != j and lst[i] in lst[j] for j in xrange(n)):
        res.append(lst[i])

但我们可以做得更好:

$成为不出现在任何字符串中的字符,其值低于所有实际字符。

S成为所有字符串的串联,其中包含$。在您的示例中,S = a$abc$b$d$xy$xyz

您可以在线性时间内构建S lst。您还可以使用我所描述的suffix array更简单的O(n log ^ 2 n)构造算法。

现在对于def findFirst(lo, hi, pred): """ Find the first i in range(lo, hi) with pred(i) == True. Requires pred to be a monotone. If there is no such i, return hi. """ while lo < hi: mid = (lo + hi) // 2 if pred(mid): hi = mid; else: lo = mid + 1 return lo # uses the algorithm described in https://stackoverflow.com/a/21342145/916657 class SuffixArray(object): def __init__(self, s): """ build the suffix array of s in O(n log^2 n) where n = len(s). """ n = len(s) log2 = 0 while (1<<log2) < n: log2 += 1 rank = [[0]*n for _ in xrange(log2)] for i in xrange(n): rank[0][i] = s[i] L = [0]*n for step in xrange(1, log2): length = 1 << step for i in xrange(n): L[i] = (rank[step - 1][i], rank[step - 1][i + length // 2] if i + length // 2 < n else -1, i) L.sort() for i in xrange(n): rank[step][L[i][2]] = \ rank[step][L[i - 1][2]] if i > 0 and L[i][:2] == L[i-1][:2] else i self.log2 = log2 self.rank = rank self.sa = [l[2] for l in L] self.s = s self.rev = [0]*n for i, j in enumerate(self.sa): self.rev[j] = i def lcp(self, x, y): """ compute the longest common prefix of s[x:] and s[y:] in O(log n). """ n = len(self.s) if x == y: return n - x ret = 0 for k in xrange(self.log2 - 1, -1, -1): if x >= n or y >= n: break if self.rank[k][x] == self.rank[k][y]: x += 1<<k y += 1<<k ret += 1<<k return ret def compareSubstrings(self, x, lx, y, ly): """ compare substrings s[x:x+lx] and s[y:y+yl] in O(log n). """ l = min((self.lcp(x, y), lx, ly)) if l == lx == ly: return 0 if l == lx: return -1 if l == ly: return 1 return cmp(self.s[x + l], self.s[y + l]) def count(self, x, l): """ count occurences of substring s[x:x+l] in O(log n). """ n = len(self.s) cs = self.compareSubstrings lo = findFirst(0, n, lambda i: cs(self.sa[i], min(l, n - self.sa[i]), x, l) >= 0) hi = findFirst(0, n, lambda i: cs(self.sa[i], min(l, n - self.sa[i]), x, l) > 0) return hi - lo def debug(self): """ print the suffix array for debugging purposes. """ for i, j in enumerate(self.sa): print str(i).ljust(4), self.s[j:], self.lcp(self.sa[i], self.sa[i-1]) if i >0 else "n/a" def filterSublist(lst): splitter = "\x00" s = splitter.join(lst) + splitter sa = SuffixArray(s) res = [] offset = 0 for x in lst: if sa.count(offset, len(x)) == 1: res.append(x) offset += len(x) + 1 return res 中的每个字符串,检查它是否恰好在后缀数组中出现一次。您可以执行两次二进制搜索以查找子字符串的位置,它们在后缀数组中形成连续范围。如果字符串出现多次,则将其删除。

预先计算LCP信息,也可以在线性时间内完成。

示例O(n log ^ 2 n)实现,改编自in another answer

S

然而,解释开销可能导致这比O(n ^ 2)方法慢,除非{{1}}非常大(大约10 ^ 5个字符或更多)。

答案 1 :(得分:2)

您可以将矩阵形式的问题构建为:

import numpy as np

lst = np.array(["a", "abc", "b", "d", "xy", "xyz"], object)
out = np.zeros((len(lst), len(lst)), dtype=int)
for i in range(len(lst)):
    for j in range(len(lst)):
        out[i,j] = lst[i] in lst[j]

out获取:

array([[1, 1, 0, 0, 0, 0],
       [0, 1, 0, 0, 0, 0],
       [0, 1, 1, 0, 0, 0],
       [0, 0, 0, 1, 0, 0],
       [0, 0, 0, 0, 1, 1],
       [0, 0, 0, 0, 0, 1]])

然后,答案将是lst的索引,其中列中òut的总和为1(字符串仅为其中):

lst[out.sum(axis=1)==1]
#array(['abc', 'd', 'xyz'], dtype=object)

编辑: 您可以通过以下方式更有效地完成:

from numpy.lib.stride_tricks import as_strided
from string import find

size = len(lst)
a = np.char.array(lst)
a2 = np.char.array(as_strided(a, shape=(size, size),
                                 strides=(a.strides[0], 0)))

out = find(a2, a)
out[out==0] = 1
out[out==-1] = 0
print a[out.sum(axis=0)==1]
# chararray(['abc', 'd', 'xyz'], dtype='|S3')

a[out.sum(axis=0)==1]

答案 2 :(得分:2)

订单是否重要?如果没有,

a = ["a", "abc", "b", "d", "xy", "xyz"]

a.sort(key=len, reverse=True)
n = len(a)

for i in range(n - 1):
    if a[i]:
        for j in range(i + 1, n):
            if a[j] in a[i]:
                a[j] = ''


print filter(len, a)  # ['abc', 'xyz', 'd']

效率不高,但很简单。

答案 3 :(得分:0)

O(n)溶液:

import collections
def filterSublist1(words):
    longest = collections.defaultdict(str)
    for word in words:                                 # O(n)
        for i in range(1, len(word)+1):                # O(k)
            for j in range(len(word) - i + 1):         # O(k)
                subword = word[j:j+i]                  # O(1)
                if len(longest[subword]) < len(word):  # O(1)
                    longest[subword] = word            # O(1)

    return list(set(longest.values()))                 # O(n)
                                                       # Total: O(nk²)

说明:

为了理解时间复杂度,我给出了上述代码中每行复杂度的上限。瓶颈发生在for循环中,因为它们是嵌套的,整体时间复杂度将是 O(nk²) ,其中n是列表中的单词数量k }是平均/最长单词的长度(例如,在上面的代码n = 6k = 3中)。但是,假设单词不是任意长的字符串,我们可以将k绑定一些小值 - 例如k=5如果我们考虑average word length in the english dictionary。因此,由于k受一个值限制,因此它不包含在时间复杂度中,我们将运行时间设为 O(n) 。当然,k的大小会增加常数因素,尤其是k不小于n时。对于英语词典,这意味着当n >> k² = 25开始看到比其他算法更好的结果时(参见下图)。

该算法的工作原理是将每个唯一的子字段映射到包含该子字的最长字符串。例如,'xyz'会查找longest['x']longest['y']longest['z']longest['xy']longest['yz']longest['xyz']并设置它们全部等于'xyz'。当对列表中的每个单词执行此操作时,longest.keys()将是所有单词的所有唯一子词的集合,longest.values()将仅是不是其他单词的子词的单词的列表。最后,longest.values()可以包含重复项,因此可以通过包装set来删除它们。

可视化时间复杂性

下面我已经测试了上面的算法(以及您的原始算法),以便在使用英语单词时显示此解决方案确实 O(n) 。我在list of up to 69000 English words上使用timeit对此进行了测试。我已将此算法filterSublist1和您的原始算法filterSublist2标记为。

enter image description here

该图显示在对数 - 对数轴上,这意味着该输入集的算法的时间复杂度由线的斜率给出。对于filterSublist1,斜率为~1表示O(n1),对于filterSublist2,斜率为~2表示O(n2)

注意:我错过了69000字的filterSublist2()时间,因为我不想等待。