【问题标题】:How can I quickly generate all permutations of a string in Ruby?如何在 Ruby 中快速生成字符串的所有排列?
【发布时间】:2017-01-01 17:47:06
【问题描述】:

我目前正在使用这个功能,代码完全可以正常工作。
self.chars.permutation.map(&:join).uniq.group_by(&:chr)

但是,一旦字符串超过 10 个字符,生成所有排列需要很长时间。我怎样才能更快地生成排列?

【问题讨论】:

  • 我认为你不能。字符串越长排列越多。更多的排列需要更多的时间和内存来生成。为什么需要生成这种排列?这可能是解决您的问题的更有效方法。
  • 我正在生成排列以检查字典中是否有任何字符串的字谜
  • 我认为你需要一个不同的算法
  • 字典有多大?缓存一组字母按字母顺序排序的字典单词可能更具成本效益。如果这样做,您可以非常快速地检查已排序的字符串与集合。
  • @mike:你犯了和 OP 一样的错误,你大大低估了 n 的速度!成长。即使您可以为每个 CPU 周期生成一个排列,并且您有 100 个内核,并且这些内核以 5GHz 运行,仍然需要一百万年才能生成 25 个字符的字符串的排列。并行化不是答案,而是一种不同的算法。或者,换一种说法:你从字面上回答了 OP 的问题(“我如何加速生成排列”),这没关系,但真的没有意义:无论你加快多少速度,n!会长大的。

标签: ruby math permutation


【解决方案1】:

不是计算每个单词的所有排列,更好的方法是首先从字典中创建一个散列,其键是按字符排序的字符串,其值是包含字典中所有单词的数组,这些单词是键的变位词。当单词在字典中不包含任何字谜(除了它本身)时,数组为空。

words      = %w| god act bat tar a lion stop |
  #=> ["god", "act", "bat", "tar", "a", "lion", "stop"]
dictionary = %w| cat dog a fowl bat god act lion pig donkey loin post pots
                 spot stop tops| 
  #=> ["cat", "dog", "a", "fowl", "bat", "god", "act", "lion", "pig",
  #    "donkey", "loin", "post", "pots", "spot", "stop", "tops"]

h = dictionary.each_with_object(Hash.new { |h,k| h[k] = [] }) do |w,h|
  h[w.each_char.sort.join] << w
end
  #=> {"act"=>["cat", "act"], "dgo"=>["dog", "god"], "a"=>["a"], "flow"=>["fowl"],
  #    "abt"=>["bat"], "ilno"=>["lion", "loin"], "gip"=>["pig"], "deknoy"=>["donkey"],
  #    "opst"=>["post", "pots", "spot", "stop", "tops"]} 

然后,我们可以通过按字符对单词进行排序并查看这是否是哈希中的键来获取words 中每个单词的所有字谜。

words.each_with_object({}) do |w,g|
  key = w.downcase.chars.sort.join
  values = h.key?(key) ? (h[key]-[w]) : []
  g[w] = values
end
  #=> {"god"=>["dog"], "act"=>["cat"], "bat"=>[], "tar"=>[], "a"=>[],
  #    "lion"=>["loin"], "stop"=>["post", "pots", "spot", "tops"]} 

【讨论】:

  • 不错。再一次,我们有非常相似的解决方案。您基本上可以使用each_with_object 解决所有问题:)
  • @Eric,我只学习了 23 种 Ruby 方法,而 each_with_object 恰好是其中之一。
  • 仔细选择这 23 种方法,你可以在需要其他任何东西之前走很长的路!
【解决方案2】:

我目前正在使用这个功能,代码完全可以正常工作。
self.chars.permutation.map(&amp;:join).uniq.group_by(&amp;:chr)

但是,一旦字符串超过 10 个字符,生成所有排列需要很长时间。我怎样才能更快地生成排列?

你不能。好吧,也许有一些方法可以加快速度,但实际上没有意义:排列的数量太大了。只有 25 个字符,即使我们假设您可以为每个 CPU 周期生成一个排列,即使我们假设您有一个 5GHz CPU,即使我们假设您的 CPU 有 100 个内核,即使我们假设工作可以完美地分布在这些核心之间,它仍然需要接近一百万年才能生成。就这么多。

简而言之:即使尝试加快算法速度也是没有意义的。您需要完全避免生成排列。

【讨论】:

    【解决方案3】:

    理论

    不需要排列:

    • 对字符串中的字母进行排序
    • 对字典中每个单词的字母进行排序
    • 寻找相同排序的字母
    • 完成!

    实施

    class String
      def sorted_letters
        downcase.chars.sort.join
      end
    end
    
    class AnagramFinder
      @dict = '/usr/share/dict/american-english'
      class << self
        def get_anagrams(word)
          sorted_dict[word.sorted_letters]
        end
    
        def all
          sorted_dict.values.select { |anagrams| anagrams.size > 1 }
        end
    
        def sorted_dict
          @sorted_dict ||= generate_sorted_dict
        end
    
        private
    
        def generate_sorted_dict
          File.foreach(@dict).with_object(Hash.new { |h, k| h[k] = [] }) do |word, sorted_dict|
            word.chomp!
            sorted_dict[word.sorted_letters] << word
          end
        end
      end
    end
    
    p AnagramFinder.get_anagrams('impressiveness')
    #=> ["impressiveness", "permissiveness"]
    p AnagramFinder.get_anagrams('castor')
    #=> ["Castor", "Castro", "Croats", "actors", "castor", "costar", "scrota"]
    p AnagramFinder.all.last(5)
    #=> [["wist", "wits"], ["withers", "writhes"], ["woodworm", "wormwood"], ["wriest", "writes"], ["wrist", "writs"]]
    p AnagramFinder.all.max_by(&:length)
    #=> ["Stael", "Tesla", "least", "slate", "stale", "steal", "tales", "teals"]
    

    这个例子在我的慢速服务器上需要 0.5 秒,而且大部分时间都花在了构建排序字典上。完成后,查找几乎是瞬间完成的。

    "impressiveness" 有 14 个字符,您需要 非常 很长时间才能生成所有排列 (14! = 87178291200)。

    【讨论】:

      【解决方案4】:

      也许lazy 可能是一个选项。在检查特殊条件之前,它不需要像生成所有排列那样多的内存。

      类似:

      'my_string'.chars.permutation.lazy.map(&:join).each do |permutation|    
        puts permutation if dictionary.include?(permutation)
      end
      

      【讨论】:

      • 如果 my_string #=&gt; "aardvark", lazy 会加快查找排列匹配的速度,但不幸的是,该匹配可能是单词 "aardvark" 本身。即使您跳过了单词本身的排列,仍然会有大量排列来检查大多数较长的单词。将字典转换为集合当然会有所帮助,但我不认为计算排列,即使是懒惰的,是要走的路。
      【解决方案5】:

      如果我们查看Permutation,我们会看到没有重复字母的 11 个字母单词的排列数为 39,916,800。但是对于密西西比州,它是 11! /(1!* 4!* 4!* 2!)= 34,650。第一个将花费很长时间,但是如果您可以使用重复字符来减少搜索空间,它可能会变得更易于管理。标准排列方法不会删除重复。

      搜索“没有重复的红宝石排列”可能会出现一些算法。

      【讨论】:

      • 你的观点很好,但在最后一句话中只提出了一种可能性。如果您不建议特定算法,这应该是评论,而不是答案。
      猜你喜欢
      • 2013-12-23
      • 1970-01-01
      • 2011-02-06
      • 2023-03-04
      • 2011-05-13
      • 1970-01-01
      • 2018-01-03
      • 1970-01-01
      相关资源
      最近更新 更多