【问题标题】:Shortest possible resulting length after iterated string replacement迭代字符串替换后可能产生的最短长度
【发布时间】:2014-08-30 22:20:21
【问题描述】:

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

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

有几件事可能会(或可能不会)有所作为:

  • 令牌是枚举类型。
  • 映射中每个条目的输出长度严格小于条目的输入。
  • 我确实需要完成了哪些替换以及在哪里完成了替换,只需要生成的序列。

因此,作为每个字符都是令牌的示例(为简单起见),如果我将替换映射为 aaba -> aaaa -> ababa - > bb,我申请了 minimumString('aaaaa'),我想得到 'a'。

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

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

有没有比暴力破解更好的方法?如果没有,是否有可以利用的 SAT 库或类似的库?当使用不同的令牌列表但使用相同的替换地图多次调用时,是否可以对地图进行任何预处理以使其更快?

【问题讨论】:

  • 我希望您从问题陈述开始。所以你从aaaaaa 通过a(aaa)a -&gt; a(ab)a = (aaba) -&gt; a,是这样吗?
  • 第二个约束使它可判定(直观地说,如果某些替换使字符串变长,那么最短字符串的路径可能会通过任意大的字符串;如果没有替换扩展字符串存在有限数量的可能性)。整个事情看起来很像识别上下文相关的语法,它是 PSPACE 完备的,因此即使您相信 P=NP,也不太可能在多项式时间内实现。但是,这只是针对最坏情况的复杂性。您的大多数实际问题可能更简单。另外,启发式方法可以接受吗?
  • @Ben - 正确。我没有把它放在第一位,因为要么我必须在示例中处理 Java 的(荒谬的)冗长,要么不清楚我正在处理一个标记列表而不是字符串。我应该编辑它以将问题陈述放在第一位吗?
  • 我可以想象一些低级技巧可以带来一些加速因子,例如 4 或 10。它对你有用吗?目前的时间是什么,需要什么?多任务处理呢?有多少不同的标记,规则如何,输入长度是多少?所有这些信息都可以帮助进行低级优化。
  • @maaartinus 指数增长的诅咒是只有一个非常狭窄的窗口可以帮助这种恒定的因子改进。最迟当添加单个令牌会增加数年的计算时间时,即使是 1000 倍的加速也无济于事。 也许 OP的应用程序都落入那个狭窄的窗口,但必须先检查。

标签: java performance algorithm optimization


【解决方案1】:

下面的代码是一个 Python 版本,用于找到尽可能短的缩减。它是非递归的,但与朴素算法相距不远。在每一步中,它都会尝试所有可能的单个归约,从而获得一组字符串以用于下一步归约。

当存在像“aa”->“a”这样的“符号吃”规则时,一个有助于检查下一组字符串是否重复的优化。

另一个优化(未在下面的代码中实现)是将替换规则处理成一个有限自动机,该自动机通过输入字符串单次遍历找到所有可能的单次归约的位置。不过,这无助于主树搜索算法的指数性质。

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

【讨论】:

    【解决方案2】:

    只是一个可能减少分支的简单想法:使用类似的规则

    ba -> c
    ca -> b
    

    和类似的字符串

    aaabaacaa
       ^  ^
    

    您可以进行两次替换,它们的顺序无关紧要。记忆化已经涵盖了这一点,但是,生成无用的字符串仍然存在相当大的开销。所以我建议以下规则:

    在位置 p 上替换后,仅考虑位置 q 上的替换,这样

    q + length(lhs_of_the_rule) > p
    

    即,不会从先前替换的左侧开始或它们重叠。


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

    【讨论】:

    • 我随后将此问题视为算法复杂性问题,而不是对具体实现的讨论。因此,关于某些语法的 PSPACE 完全性质的 cmets。他描述的规则与您链接的非收缩语法相反,因此他有兴趣了解可以实现的最大减少,以及如何在小于 O(n^x) 的最坏情况复杂度内实现。
    • @MatthewFranglen 我认为这个问题主要是一个实际问题。 如果这是一个纯粹的理论问题,那么它会是misplaced here 知道这是一个 PSPACE 问题肯定很重要 - 它告诉我们我们运气不好。我们不会找到快速的解决方案。不过,没有理由不尝试尽可能快。
    • @MatthewFranglen 您似乎误解了我的链接。它只是表明甚至一个非收缩语法可以转换为一个收缩语法。这仅意味着您关于可判定性的评论是错误的,仅此而已。我错过了什么,为什么对迄今为止唯一未删除的答案投反对票?
    • 我不明白您的链接如何证明可以将非合同语法转换为合同语法。它显示了如何将其转换为上下文相关的语法。毕竟,如果语法没有办法减少输入的长度,那它怎么会变成收缩的呢?我对您的答案投了反对票,原因与我删除自己的答案相同 - 它没有回答问题,我将其解读为“[在最坏的情况下,[有没有比指数时间更好的方法””
    • @MatthewFranglen 我想我的链接有误。但是我无法按照您理解问题的方式进行操作。 OP 要求加快速度,没有任何迹象表明“比指数更好”。他们甚至要求 SAT 图书馆,它确实有指数级的最差时间。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-15
    • 2023-03-10
    • 2023-03-10
    相关资源
    最近更新 更多