首页 > 解决方案 > Ruby - 排列,缺乏输出

问题描述

def permutations(str)
  str.split(//).permutation.with_index.to_a.delete_if{|x,i| x[i].eql? x[i+1]}.flatten(1).to_a.delete_if{|x| x.class == Integer}.map{|x| x.join}
end

描述:“在这个 kata 中,您必须创建输入字符串的所有排列并删除重复项(如果存在)。这意味着,您必须以所有可能的顺序打乱输入中的所有字母。”

预期输入/输出:

permutations('aabb') # => ['aabb', 'abab', 'abba', 'baab', 'baba', 'bbaa']

我的输入/输出:

permutations('aabb') # =>  ["aabb", "abab", "abba"]

所以有一个问题:

'baab', 'baba', 'bbaa'

你有什么想法吗?如果我的代码看起来不清晰,我很抱歉,但我可以设置这样的方法吗?:

str.split(//)
   .to_a
   .delete_if 
   .etc

我不确定,但我认为这部分代码有问题,但我无法弄清楚:

.delete_if{|x,i| x[i] == x[i+1]}

标签: ruby

解决方案


当遇到这样的问题时,通常是一个好主意

  • 将代码分解成更小、更简单的部分
  • 编写很多很多很多的测试来编码你所有的假设你认为代码在做什么
  • 用笔和纸跟踪代码的执行
  • 在调试器中单步执行代码,将其与从笔和纸调试中获得的结果进行比较

我将主要关注#1 和#3。让我们首先重新格式化代码以使其更易于阅读:

str.
  split(//).
  permutation.
  with_index.
  to_a.
  delete_if { |x, i| x[i].eql? x[i + 1] }.
  flatten(1).
  to_a.
  delete_if { |x| x.class == Integer }.
  map { |x| x.join }

现在,让我们分解代码,为每个单独的部分分配一个显示意图的名称。我们要做的第一件事就是splitString空字符串分开,换句话说,我们将它分成单个字符。所以,让我们称之为characters(这基本上只是对现有方法的重新实现String#chars

characters = str.split(//)
#=> ['a', 'a', 'b', 'b']

在下一步中,我们将生成此字符数组的所有排列:

permutations = characters.permutation

这会返回一个Enumerator,然后我们将其转换为Array

permutations_array = permutations.to_a
#=> [["a", "a", "b", "b"],
#    ["a", "a", "b", "b"],
#    ["a", "b", "a", "b"],
#    ["a", "b", "b", "a"],
#    ["a", "b", "a", "b"],
#    ["a", "b", "b", "a"],
#    ["a", "a", "b", "b"],
#    ["a", "a", "b", "b"],
#    ["a", "b", "a", "b"],
#    ["a", "b", "b", "a"],
#    ["a", "b", "a", "b"],
#    ["a", "b", "b", "a"],
#    ["b", "a", "a", "b"],
#    ["b", "a", "b", "a"],
#    ["b", "a", "a", "b"],
#    ["b", "a", "b", "a"],
#    ["b", "b", "a", "a"],
#    ["b", "b", "a", "a"],
#    ["b", "a", "a", "b"],
#    ["b", "a", "b", "a"],
#    ["b", "a", "a", "b"],
#    ["b", "a", "b", "a"],
#    ["b", "b", "a", "a"],
#    ["b", "b", "a", "a"]]

现在我们将每个排列与其索引配对:

permutations_with_index = permutations_array.with_index

这将再次返回给我们一个Enumerator,我们将其转换为一个Array

permutations_with_index_array = permutations_with_index.to_a
#=> [[["a", "a", "b", "b"], 0],
#    [["a", "a", "b", "b"], 1],
#    [["a", "b", "a", "b"], 2],
#    [["a", "b", "b", "a"], 3],
#    [["a", "b", "a", "b"], 4],
#    [["a", "b", "b", "a"], 5],
#    [["a", "a", "b", "b"], 6],
#    [["a", "a", "b", "b"], 7],
#    [["a", "b", "a", "b"], 8],
#    [["a", "b", "b", "a"], 9],
#    [["a", "b", "a", "b"], 10],
#    [["a", "b", "b", "a"], 11],
#    [["b", "a", "a", "b"], 12],
#    [["b", "a", "b", "a"], 13],
#    [["b", "a", "a", "b"], 14],
#    [["b", "a", "b", "a"], 15],
#    [["b", "b", "a", "a"], 16],
#    [["b", "b", "a", "a"], 17],
#    [["b", "a", "a", "b"], 18],
#    [["b", "a", "b", "a"], 19],
#    [["b", "a", "a", "b"], 20],
#    [["b", "a", "b", "a"], 21],
#    [["b", "b", "a", "a"], 22],
#    [["b", "b", "a", "a"], 23]]

下一步是有趣的地方。

正在进行某种过滤。但目前尚不清楚该过滤究竟在做什么。因此,我们遵循与上述相同的步骤:我们将所有内容分解成小块并单独检查每一块:

mystery = permutations_with_index_array.delete_if do |mystery1, mystery2| 
  mystery3 = mystery1[mystery2]
  mystery4 = mystery1[mystery2 + 1]
  mystery5 = mystery3.eql?(mystery4)
end

那么,让我们看看这些碎片是什么。s 数组的每个元素到块。因此, ed的第一个元素是。Array#delete_if yieldyield[["a", "a", "b", "b"], 0]

因为我们的块实际上有两个参数,mystery1并且mystery2,这个元素(它本身是一个数组)被解构为第一个和第二个元素,这意味着mystery1是字符数组并且mystery2是索引。所以,我们已经弄清楚了我们最初的两个谜团:

mystery = permutations_with_index_array.delete_if do |character_array, index| 
  mystery3 = character_array[index]
  mystery4 = character_array[index + 1]
  mystery5 = mystery3.eql?(mystery4)
end

现在,让我们仔细看看mystery3mystery4mystery5,并使用一个具体的例子。实际上,由于这是一个循环,让我们使用三个具体示例:第一次迭代、中间某处的迭代和最后一次迭代。

在第一次迭代中,character_arrayis['a', 'a', 'b', 'b']indexis 0。这意味着mystery3是 , 的第一个元素character_array'a'并且也是mystery4, 的第二个元素。character_array'a'

由于'a'等于'a', mystery5is true,这意味着整个块是true,这意味着整个排列被丢弃。

现在让我们看看第五次迭代。character_array['a', 'b', 'a', 'b']index4。那就是说mystery3第五元素character_array但是没有第五元素!请求一个实际上不存在的数组索引返回nil,因此mystery3nil。并且mystery4是 的第六个元素character_array,也就是nil

由于nil等于nil, mystery5is true,这意味着整个块是true,这意味着整个排列被丢弃。事实上,从这次迭代开始,所有元素都会被丢弃,因为index只会越来越大。你永远不会得到超过四个排列(在这个例子中)。

更一般地说,你总是会得到至少一个排列,即第str.length - 1th 个排列,因为在第str.length - 1th 次迭代中,x[i]将是一个字符,并且x[i + 1]将是nil,它们永远不会相等。而且您最多 str.length - 1会得到排列,因为从str.length第 th 次迭代开始,两者x[i]x[i + 1]将永远是nil,因此永远是相等的。

我将在这里停止查看其余代码,因为我们可以清楚地看到我们已经找到了问题以及问题所在:您将数组的索引与所有排列混淆,而数组是单个排列.

这很容易解决,但是我们会遇到一个不同的问题:不仅你“做错了”,甚至你试图做的事情也是错误的。您试图通过查看当前索引和下一个索引来确定重复项。但是,如果两个东西不相邻,它们仍然是重复的!这才是真正Enumerable#uniq的目的。


推荐阅读