【问题标题】:Recursive solution doesn't iterate correctly递归解决方案无法正确迭代
【发布时间】:2015-08-31 01:01:34
【问题描述】:

我正在解决 Ruby 中的一个玩具问题:如何生成所有可能的 10 位电话号码,其中每个连续的号码都与键盘上的最后一个号码相邻。我已经表示了数字之间的相邻关系,并且有一个递归函数,但是我的方法并没有遍历整个解决方案空间。它只是找到第一个解决方案并返回。

这是我的代码:

adjacencies = { 1 => [2, 4],
                2 => [1, 3, 5],
                3 => [2, 6],
                4 => [1, 5, 7],
                5 => [2, 4, 6, 8],
                6 => [3, 5, 9],
                7 => [4, 8],
                8 => [5, 7, 9, 0],
                9 => [6, 8],
                0 => [8]
              }

def append_number(partial_phone_number, to_append, adjacencies)
  phone_length = 10
  partial_phone_number = partial_phone_number + to_append.to_s
  if (partial_phone_number.length == phone_length)
    return partial_phone_number
  else
    adjacencies[to_append].each do |a|
      return append_number(partial_phone_number, a, adjacencies)
    end
  end
end

(0..9).each do |n|
  puts append_number("", n, adjacencies)
end

这是我运行它时的输出:

0852121212
1212121212
2121212121
3212121212
4121212121
5212121212
6321212121
7412121212
8521212121
9632121212

【问题讨论】:

  • 看起来你告诉它执行 10 次 (0..9) 而它执行了 10 次?
  • 我不知道关于 ruby​​ 的第一件事,但是如果这部分 [adjacencies[to_append].each 做 |a| return append_number(partial_phone_number, a, adjacencies)] 不是所有相邻数字的 for,那么应该是!
  • @AshkanKzme 是——我查过了。
  • 您能说说预期的输出吗?
  • 这是一个要点,我修改后的代码现在可以正常运行,但会产生重复的值,然后我使用 Array#uniq 将其删除:gist.github.com/bee4eab8243ee22ea488

标签: ruby algorithm search recursion


【解决方案1】:

第一次输入adjacencies[to_append].each,你立即从方法中return,因此循环永远不会被执行超过一次。

你需要

  1. 返回电话号码列表,而不仅仅是一个电话号码
  2. 在递归调用中以某种方式构建该列表

【讨论】:

    【解决方案2】:

    each 迭代器中的 return 语句在第一次迭代时退出递归调用。不要在那里使用return。一种可能的解决方案是当您到达递归基本情况时,将结果附加到一个列表(您通过参数传递)。

    【讨论】:

      【解决方案3】:

      这是对递归方法的修改。 FIRST_DIGITn-digit 电话号码的可能第一位数字的数组,n 是方法recurse 的第一个参数。您希望确定recurse(10)

      ADJ = { 1 => [2, 4],
              2 => [1, 3, 5],
              3 => [2, 6],
              4 => [1, 5, 7],
              5 => [2, 4, 6, 8],
              6 => [3, 5, 9],
              7 => [4, 8],
              8 => [5, 7, 9, 0],
              9 => [6, 8],
              0 => [8]
            }
      
      FIRST_DIGIT = (1..9).to_a
        #=> [1, 2, 3, 4, 5, 6, 7, 8, 9]
      
      def recurse(n, nxt=FIRST_DIGIT)
        nxt.each_with_object([]) do |i,a|
          is = i.to_s
          if n==1
            a << is
          else
            recurse(n-1, ADJ[i]).each { |s| a << is + s }
          end
        end
      end
      

      recurse 1
        #=> ["1", "2", "3", "4", "5", "6", "7", "8", "9"] 
      recurse 2
        #=> ["12", "14", "21", "23", "25", "32", "36", "41", "45", 
        #    "47", "52", "54", "56", "58", "63", "65", "69",
        #    "74", "78", "85", "87", "89", "80", "96", "98"] 
      recurse 3
        #=> ["121", "123", "125", "141", "145", "147",
        #    "212", "214", "232", "236", "252", "254", "256", "258",
        #    "321", "323", "325", "363", "365", "369",
        #    "412", "414", "452", "454", "456", "458", "474", "478",
        #    "521", "523", "525", "541", "545", "547", "563", "565",
        #    "569", "585", "587", "589", "580",
        #    "632", "636", "652", "654", "656", "658", "696", "698",
        #    "741", "745", "747", "785", "787", "789", "780",
        #    "852", "854", "856", "858", "874", "878", "896", "898", "808",
        #    "963", "965", "969", "985", "987", "989", "980"] 
      recurse(10).size
        #=> 117529 
      

      [编辑: OP 询问了修改代码以避免循环的可能性。这并不难。相同的修改也可以用于强制执行其他规则(例如,没有666),所有这些都会减少要考虑的组合数量。我们可以通过向recurse 添加一个参数so_far 来做到这一点,该参数是一个数组(或者它可以是一个字符串),其中包含到目前为止选择的所有数字:

      def recurse(n, so_far=[], nxt=FIRST_DIGIT)
        nxt.each_with_object([]) do |i,a|
          is = i.to_s
          if n==1
            a << is
          else
            < construct array 'permitted' from ADJ[i] and other rules > 
            recurse(n-1, so_far+[i], permitted).each { |s| a << is + s }
          end
        end
      end
      

      请注意,有两个带默认值的参数不是问题,因为 recurse 最初将仅使用第一个参数调用,然后将使用所有三个参数调用。

      【讨论】:

      • 有趣。我很好奇这如何权衡单个电话号码中先前数字的可见性。您将如何修改此代码以确保没有长度为 2 的循环(即,防止电话号码像“1212121212”?
      • 另外,你的变量名让我很难理解你的意图。我仍在尝试弄清楚这是如何工作的。
      • 为了更好地理解递归的工作原理,您可能会发现在我的代码中添加一些 puts 语句会很有帮助。例如,您可以在方法recurse 的第一行之前插入puts "n=#{n}, nxt=#{nxt}",在该行之后插入puts "i=#{i}, a=#{a}",依此类推。
      • Duncan,我进行了编辑以解决您关于循环的问题。关于变量名,iFIRST_DIGIT 的一个元素,a 是由each_with_object 构建的初始空数组,isi 转换为字符串。 recurse(n-1, ADJ[i]) 返回一个字符串数组;这些字符串中的每一个都由seach 的块中表示。是的,变量名称很简洁,但随着您对 Ruby 的了解越来越多,您可能会喜欢。
      猜你喜欢
      • 2013-07-22
      • 2020-06-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-11
      • 1970-01-01
      相关资源
      最近更新 更多