是否有任何巧妙有效的算法来执行字符串分区空间的计算?

时间:2009-08-03 15:33:54

标签: python string partitioning

我正在开发一个统计项目,该项目涉及迭代所有可能的方法来对字符串集合进行分区并对每个字符串运行简单的计算。具体来说,每个可能的子字符串都有与之关联的概率,而我正试图获得分区中子字符串概率乘积的所有分区的总和。

例如,如果字符串是'abc',则可能存在'a','b','c','ab,'bc'和'abc'的概率。字符串有四种可能的分区:'abc','ab | c','a | bc'和'a | b | c'。算法需要找到每个分区的分量概率的乘积,然后对四个结果数求和。

目前,我已经编写了一个python迭代器,它使用分区的整数二进制表示(例如上面例子中的00,01,10,11),并简单地遍历整数。不幸的是,对于长度超过20个字符的字符串来说,这个速度非常慢。

有没有人能想到一种聪明的方法来执行此操作而不是一次只运行一个分区?我已经被困在这几天了。

回应一些评论,这里有更多信息:
字符串可以是任何东西,例如“foobar(foo2)” - 我们的字母表是小写字母数字加上所有三种类型的括号(“(”,“[”,“{”),连字符和空格。
目标是获得给出个别“单词”可能性的字符串的可能性。所以L(S ='abc')= P('abc')+ P('ab')P('c')+ P('a')P('bc')+ P('a')P ('b')P('c')(这里“P('abc')”表示'单词''abc'的概率,而“L(S ='abc')”表示观察的统计可能性字符串'abc')。

5 个答案:

答案 0 :(得分:5)

Dynamic Programming解决方案(如果我理解正确的问题):

def dynProgSolution(text, probs):
  probUpTo = [1]
  for i in range(1, len(text)+1):
    cur = sum(v*probs[text[k:i]] for k, v in enumerate(probUpTo))
    probUpTo.append(cur)
  return probUpTo[-1]

print dynProgSolution(
  'abc',
  {'a': 0.1, 'b': 0.2, 'c': 0.3,
   'ab': 0.4, 'bc': 0.5, 'abc': 0.6}
  )

复杂度为O(N 2 ),因此很容易解决N = 20的问题。

为什么这有效:

  • 您将乘以probs['a']*probs['b']的所有内容,您还将乘以probs['ab']
  • 感谢Distributive Property的乘法和加法,你可以将这两个加在一起,并将这个单一的和乘以它的所有连续。
  • 对于每个可能的最后一个子字符串,它通过将其概率乘以先前路径的所有概率之和来添加以该结尾的所有分割的总和。 (另外的措辞将不胜感激。我的python比我的英语更好..)

答案 1 :(得分:3)

首先,通过配置文件找到瓶颈。

如果瓶颈只是大量可能的分区,我建议并行化,可能通过multiprocessing。如果仍然不够,您可以查看Beowulf群集。

如果瓶颈只是计算速度慢,请尝试炮轰C.通过ctypes很容易做到。

另外,我不确定你是如何存储分区的,但你可以通过使用一个字符串和一个suffix array来压缩内存消耗。如果您的瓶颈是交换和/或缓存未命中,那可能是一个巨大的胜利。

答案 2 :(得分:1)

您的子字符串将被较长的字符串一遍又一遍地重复使用,因此使用memoizing技术缓存值似乎是一件显而易见的事情。这只是一个时空权衡。最简单的实现是在计算它们时使用字典来缓存值。为每个字符串计算执行字典查找;如果它不在字典中,则计算并添加它。后续调用将使用预先计算的值。如果字典查找比计算更快,那么你很幸运。

我意识到你正在使用Python,但是......作为一个可能感兴趣的附注,如果你在Perl中这样做,你甚至不需要编写任何代码;内置的Memoize module将为您进行缓存!

答案 3 :(得分:1)

基于算术(和字符串连接)的关联属性,您可以通过小型重构来略微减少计算量,但我不确定它是否会改变生命。核心思想如下:

考虑一个很长的字符串,例如'abcdefghik',10-long,确定性,不失一般性。在一个天真的方法中,你将p(a)乘以9尾的许多分区,p(ab)乘以8尾的许多分区等;特别是p(a)和p(b)将恰好乘以8尾(所有这些)的相同分区,因为p(ab)将 - 3次乘法和它们之间的两个和。因此,因此:

(p(ab) + p(a) * p(b)) * (partitions of the 8-tail)

我们已经减少了2次乘法和1次总和,保存了1个乘积和1个总和。用'b'右边的分割点覆盖所有分区。对于分区右侧为“c”的分区,

(p(abc) + p(ab) * p(c) + p(a) * (p(b)*p(c)+p(bc)) * (partitions of the 7-tail)
储蓄增加,部分归功于内部重构 - 尽管当然必须小心重复计算。我认为这种方法可能是一般化的 - 从中​​点开始并考虑所有分区,分别(和递归)左右部分,乘法和求和;然后添加那些没有拆分的分区,例如在这个例子中,左边是'abcde',右边是'fghik',第二部分是关于'ef'在一起而不是分开的所有分区 - 所以通过考虑'ef'来“崩溃”所有概率'作为一个新的'超级'X',你留下一个更短的字符串,'abcdXghik'(当然,THAT的子串直接映射到原始的概率,例如新字符串中的p(cdXg)是恰好是原始的p(cdefg)。

答案 4 :(得分:0)

您应该查看itertools模块。它可以为您创建一个非常快的发电机。给定输入字符串,它将为您提供所有可能的排列。根据您的需要,还有一个combinations()生成器。当你看“abc”时,我不太确定你是否正在看“b | ca”,但不管怎样,这个模块可能对你有用。