迭代字符串替换后可能的最短结果长度

时间:2014-07-09 18:33:59

标签: java performance algorithm optimization

如何通过在输入序列中重复应用替换来合理有效地找到最短的输出?我相信(如果我错了,请纠正我),这是最坏情况下的指数时间,但由于下面的第二个限制,我不确定。天真的方法当然是。

我尝试编写天真的方法(对于所有可能的替换,对于所有有效位置,在该位置应用替换后递归输入的副本。返回所有有效递归中的最短值和输入,缓存为捕获等效替换序列的函数),但它(不可行)慢,而且我很确定它是算法问题而不是实现。

可能(或可能不会)产生影响的一些事情:

  • 令牌是枚举类型。
  • 地图中每个条目的输出长度严格小于条目的输入。
  • 需要做什么替换以及在哪里,只是结果序列。

所以,作为一个例子,每个角色都是一个代币(为了简单起见),如果我有替换地图为aaba - > aaaa - > ababa - > bb,我应用了minimalString(' aaaaa'),我希望得到' a'。

实际的方法签名大致如下:

List<Token> getMinimalAfterReplacements(List<Token> inputList, Map<List<Token>, List<Token>> replacements) {
    ?
}

有比蛮力更好的方法吗?如果没有,例如,是否有可以利用的SAT库或类似物?是否有任何地图预处理可以通过不同的令牌列表多次调用但使用相同的替换映射来使其更快?

2 个答案:

答案 0 :(得分:0)

下面的代码是一个Python版本,可以找到最短的缩减。它是非递归的,但与天真算法相差不远。在每一步中,它都会尝试所有可能的单个缩减,从而获得一组字符串以便在下一步中减少。

一种优化,有助于有&#34;符号吃的情况&#34;规则如&#34; aa&#34; - &GT; &#34;&#34;是检查重复的下一组字符串。

另一个优化(未在下面的代码中实现)将处理替换规则到有限自动机中,该自动机通过输入字符串单次传递找到所有可能的单个减少的位置。但这对主树搜索算法的指数性质没有帮助。

class Replacer:
  def __init__(self, replacements):
    self.replacements = [[tuple(key), tuple(value)] for key, value in replacements.items()]

  def get_possible_replacements(self, input):
    "Return all possible variations where a single replacement was done to the input"
    result = []
    for replace_what, replace_with in self.replacements:
      #print replace_what, replace_with
      for p in range(1 + len(input) - len(replace_what)):
        if input[p : p + len(replace_what)] == replace_what:
          input_copy = list(input[:])
          input_copy[p : p + len(replace_what)] = replace_with
          result.append(tuple(input_copy))
    return result

  def get_minimum_sequence_list(self, input):
    "Return the shortest irreducible sequence that can be obtained from the given input"
    irreducible = []
    to_reduce = [tuple(input)]
    to_reduce_new = []
    step = 1
    while to_reduce:
      print "Reduction step", step, ", number of candidates to reduce:", len(to_reduce)
      step += 1
      for current_input in to_reduce:
        reductions = self.get_possible_replacements(current_input)
        if not reductions:
          irreducible.append(current_input)
        else:
          to_reduce_new += reductions
      to_reduce = set(to_reduce_new[:]) # This dramatically reduces the tree width by removing duplicates
      to_reduce_new = []

    irreducible_sorted = sorted(set(irreducible), key = lambda x: len(x))
    #print "".join(input), "could be reduced to any of", ["".join(x) for x in irreducible_sorted]
    return irreducible_sorted[0]

  def get_minimum_sequence(self, input):
    return "".join(self.get_minimum_sequence_list(list(input)))

input = "aaaaa"

replacements = {
  "aaba" : "a",
  "aaa" : "ab",
  "aba" : "bb",
}

replacer = Replacer(replacements)
replaced = replacer.get_minimum_sequence(input)
print "The shortest string", input, "could be reduced to is", replaced

答案 1 :(得分:-1)

一个简单的想法可能会减少分支:使用像

这样的规则
ba -> c
ca -> b

之类的字符串
aaabaacaa
   ^  ^

你可以做两次换人,他们的顺序并不重要。这已经被记忆所覆盖,但是,生成无用的字符串仍然是一个相当大的开销。所以我建议遵循以下规则:

在对位置p进行替换后,只考虑位置q上的替换,以便

q + length(lhs_of_the_rule) > p

即,不要从之前的替换开始,或者重叠。


作为简单的低级优化,我建议将List<Token>替换为String或(或封装的byte[]short[]或其他) 。较低的内存占用量应该有助于缓存,您可以通过字符串元素(或两个)索引数组,以找出适用于它的规则。