标准化表示(组合)项链的字符串

时间:2015-03-02 14:41:27

标签: python combinatorics string-comparison string-matching

我试图通过查找线性表示来匹配Python中的"necklaces"符号,我使用普通字符串。例如,字符串"AABC""ABCA""BCAA""CAAB"都代表相同的项链(如图)。

symbol loop "AABC"

为了概述,我只存储给定项链的等效字符串的一个作为“代表”。至于检查我是否存储了候选项链,我需要一个函数来规范化任何给定的字符串表示。作为一种伪代码,我在Python中编写了一个函数:

import collections

def normalized(s):
    q = collections.deque(s)
    l = list()
    l.append(''.join(q))
    for i in range(len(s)-1):
        q.rotate(1)
        l.append(''.join(q))
    l.sort()
    return l[0]

对于上面示例项链中的所有字符串表示形式,此函数返回"AABC",它首先按字母顺序排列。

由于我对Python比较陌生,我想知道 - 如果我开始在Python中实现一个应用程序 - 这个函数对于生产代码来说已经“足够好”吗?换句话说:有经验的Python程序员会使用这个函数,还是有明显的缺陷?

3 个答案:

答案 0 :(得分:5)

如果我理解正确,首先需要构造输入序列的所有循环排列,然后确定(按字典顺序)最小元素。这是符号循环的根。 试试这个:

def normalized(s):
    L = [s[i:] + s[:i] for i in range(len(s))]
    return sorted(L)[0]

此代码仅适用于字符串,队列和字符串之间没有代码中的转换。快速计时测试显示此代码在30-50%的时间内运行。 了解应用程序中s的长度会很有趣。由于所有排列都必须临时存储,因此临时列表L需要2个字节。希望这不是你的情况下的约束。

修改 今天我偶然发现,如果你将原始字符串连接到自身,它将包含所有旋转作为子字符串。所以代码将是:

def normalized4(s):
    ss = s + s      # contains all rotations of s as substrings
    n = len(s)
    return min((ss[i:i+n] for i in range(n)))

这确实会更快,因为只剩下一个连接加上n个切片。使用10到10 ** 5的stringlength,与使用生成器的min()版本相比,我的机器上的运行时间在55%到66%之间。

请注意,您需要权衡内存消耗的速度(2x),这在这里并不重要,但可能在不同的设置中。

答案 1 :(得分:2)

您可以使用min而不是排序:

def normalized2(s):
    return min(( s[i:] + s[:i] for i in range(len(s)) ))

但仍然需要复制字符串len(s)次。更快的方法是过滤最小char的起始索引,直到只得到一个。有效地搜索最小的循环:

def normalized3(s):
    ssize=len(s)
    minchar= min(s)
    minindexes= [ i for i in range(ssize) if minchar == s[i] ]
    for offset in range(1,ssize):
        if len( minindexes ) == 1 :
            break
        minchar= min( s[(i+offset)%ssize] for i in minindexes )
        minindexes= [i for i in minindexes if minchar == s[(i+offset)%ssize]]
    return s[minindexes[0]:] + s[:minindexes[0]]

对于长字符串,这要快得多:

In [143]: loop = [ random.choice("abcd") for i in range(100) ]
In [144]: timeit normalized(loop)
1000 loops, best of 3: 237 µs per loop
In [145]: timeit normalized2(loop)
10000 loops, best of 3: 91.3 µs per loop
In [146]: timeit normalized3(loop)
100000 loops, best of 3: 16.9 µs per loop

但是如果我们有很多重复,这种方法就没有效率了:

In [147]: loop = "abcd" * 25
In [148]: timeit normalized(loop)
1000 loops, best of 3: 245 µs per loop
In [149]: timeit normalized2(loop)
100000 loops, best of 3: 18.8 µs per loop
In [150]: timeit normalized3(loop)
1000 loops, best of 3: 612 µs per loop

我们也可以向前扫描字符串,但我怀疑它可能更快,没有一些奇特的算法。

答案 2 :(得分:1)

这样的事情怎么样:

patterns = ['abc', 'bca', 'cab']
normalized = lambda p: ''.join(sorted(p))
normalized_patterns = set(normalized(p) for p in patterns)

示例输出:

In [1]: normalized = lambda p: ''.join(sorted(p))

In [2]: normalized('abba')
Out[2]: 'aabb'

In [3]: normalized('CBAE')
Out[3]: 'ABCE'