【问题标题】:Normalize strings that represent (combinatorical) necklaces [closed]标准化代表(组合)项链的字符串[关闭]
【发布时间】:2015-03-02 14:41:27
【问题描述】:

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

为了获得概览,我只存储给定项链的 一个 等效字符串作为“代表”。至于检查我是否存储了一条候选项链,我需要一个函数来规范任何给定的字符串表示。作为一种伪代码,我用 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 程序员会使用这个函数,还是有明显的缺陷?

【问题讨论】:

  • the patterns [abc] [bca] [cab] should all being considered identical 好的,但是您的程序返回一个字符串...这是为什么呢?
  • 我没明白你的意思?
  • 对不起,这确实有点混乱。我希望,现在好多了。
  • “python 程序员将如何解决这个问题?” - 既然你已经编写了一个 Python 程序来解决这个问题,你还没有回答自己的问题吗?如果您想查看您的代码,请考虑codereview.stackexchange.com
  • @Wolf 性能改进相关问题通常是代码审查的主题。

标签: python combinatorics string-comparison string-matching


【解决方案1】:

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

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

此代码仅适用于字符串,不会像您的代码那样在队列和字符串之间进行转换。快速计时测试显示此代码在 30-50% 的时间内运行。 知道您的应用程序中s 的长度会很有趣。由于必须临时存储所有排列,因此临时列表L 需要 len(s)^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 个切片。与带有生成器的 min() 版本相比,使用 10 到 10**5 的字符串长度,我的机器上的运行时间在 55% 到 66% 之间。

请注意,您以速度换取内存消耗 (2x),这在此处无关紧要,但在不同的设置中可能会这样做。

【讨论】:

  • 通过在生成器表达式上使用min而不是对列表进行排序,难道不能提高效率吗?
  • min() 在生成器上规避了字符串长度 > 1000 时明显的内存问题。感谢 Arpegius 对 Python 的改进!
  • @user1016274 是的,这正是我想问的 ;-) 我认为长度将低于 10。我想展示一些关于排列的东西。
【解决方案2】:

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

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

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

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

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

【讨论】:

  • 是的,我还想到了过滤以及生成排列。我希望对于具有丰富字母表的长循环,这将是唯一的选择。
【解决方案3】:

这样的事情怎么样:

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'

【讨论】:

  • 对不起,我的问题不够清楚,请您再看一下?
  • 这就是我正在做的事情。我会尽力澄清:)
  • 再次,我很抱歉编辑。我希望问题现在清楚了吗?
  • @Wolf:你能给我一些输入/输出的例子吗?我相信我的代码已经解决了你的问题,如果不是我想知道:)
  • 另外两个样本:norm("abba") ==> "aabb", norm("CBAE") ==> "AECB"
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-06
  • 1970-01-01
  • 1970-01-01
  • 2021-07-04
相关资源
最近更新 更多