Ruby使用rand递归shuffle

时间:2016-09-04 14:44:04

标签: ruby

我对Ruby和编程很新,我目前正在尝试使用Chris Pine的书。在第10章中,有一项任务要求您返回一个洗牌版本的数组。

我为shuffle方法本身尝试了以下代码,它起作用了:

def shuffle array
  recursive_shuffle array, []   
end

def recursive_shuffle unshuffled, shuffled
  if unshuffled.length == 1
    shuffled.push unshuffled[0]
    shuffled.pop                      
    return shuffled
  end

  rand_index = rand(unshuffled.length - 1)
  shuffled.push unshuffled[rand_index]
  unshuffled.delete_at(rand_index)
  recursive_shuffle(unshuffled, shuffled)
end

puts(shuffle(array))

我想知道两件事。

首先,我尝试不使用shuffled.poprand_index = rand(unshuffled.length)代替length - 1

然而,在答案中,最后一个元素之前的元素始终为空白,否则它被洗牌并且没有元素丢失。然后我认为问题应该在rand中,并添加了- 1

此时,数组再次正确地进行了混洗,但这次洗牌数组总是有一个空白的最后一个元素。这就是我添加shuffled.pop

的原因

它可以正常工作,但我不明白为什么它会在前两个实例中添加一个空白元素。

另外,如果我有一个包含2个元素的数组,则其长度应为2,而rand_index应为rand(1),但不是rand(1)应该始终为0?尽管如此,前面提到的代码仍然适当地改组了具有2个元素的数组。

我查看了有关Ruby,shuffle和rand的各种其他主题,但无法找到类似的问题。

3 个答案:

答案 0 :(得分:2)

随机播放方法中的第一个块:

if unshuffled.length == 1
  shuffled.push unshuffled[0]
  shuffled.pop                      
  return shuffled
end

没有做你想做的事。

如果unshuffled.length1,那么数组中只有一个元素,所以你只需将它添加到shuffled数组中,然后返回一个混洗数组:

if unshuffled.length == 1
  return shuffled.push(unshuffled.pop)
end

或坚持原来的方法:

if unshuffled.length == 1
  shuffled.push unshuffled[0]                   
  return shuffled
end

此外,调用push会将新元素推送到您正在调用push的数组末尾。例如。如果a = [1, 2],则a.push(3)会产生[1, 2, 3]

在数组上调用pop,"弹出"数组的最后一个元素。例如。如果a = [1, 2, 3]并且您致电a.pop,则a == [1, 2]

将这两个概念放在一起,您可以看到,如果您将某些东西推到一个阵列上,然后将其向右弹回,那么您将无法完成任何工作。例如。如果您拨打a = [1, 2]然后a.push(3),则a会显示为[1, 2, 3]。致电a.pop会让您回到[1, 2]

另一个问题是对rand的调用(如评论中提到的@pjs)。 rand将返回一个小于您提供的(非零)整数的随机整数,因此无需递减传递给它的length。{0}}。 rand行可以更改为:rand_index = rand(unshuffled.length - 1)

整合此更改应该会给您留下类似的内容:

def recursive_shuffle unshuffled, shuffled
  if unshuffled.length == 1
    return shuffled.push(unshuffled.pop)
  end

  rand_index = rand(unshuffled.length)
  shuffled.push unshuffled[rand_index]
  unshuffled.delete_at(rand_index)
  recursive_shuffle(unshuffled, shuffled)
end

哪个应该可以正常工作。

这可以进一步缩短为:

def recursive_shuffle(unshuffled, shuffled = [])
  return shuffled.push(unshuffled.pop) if unshuffled.length == 1
  shuffled.push(unshuffled.delete_at(rand(unshuffled.length)))
  recursive_shuffle(unshuffled, shuffled)
end

甚至:

def recursive_shuffle(arr)
  return arr if arr.length == 1
  shuffle = [arr.sample]
  shuffle + recursive_shuffle(arr - shuffle)
end

答案 1 :(得分:2)

这里有一种类似Ruby的方式,您可以实现自己的shuffle版本。步骤如下:

  • 在类my_shuffle上创建实例方法Array,以模仿实例方法Array#shuffle的功能(但不需要实现替代方式shuffle可以已使用:shuffle(random: rng) → new_ary - 请参阅文档)。
  • self(要洗牌的数组)的副本进行操作,以便self不会发生变异" (改变的)。
  • 迭代枚举器enum = arr.size.times,生成数字序列0,1,...,arr.size-1。块中没有使用序列的值;重要的是该块执行arr.size次。
  • 通过随机删除a的元素并将其推送到arr来构建由块变量a表示的数组。

<强>代码

class Array
  def my_shuffle
    arr = self.dup
    arr.size.times.with_object([]) { |_,a| a << arr.delete_at(rand(arr.size)) }
  end
end

示例

[1,3,2,7,4,1].my_shuffle
  #=> [2, 3, 4, 1, 1, 7]

<强>解释

首先,我使用Enumerator#each_object仅仅保存了两个步骤(下面是a = []a)。它产生与以下相同的结果:

  def my_shuffle
    arr = self.dup
    a = []
    arr.size.times { a << arr.delete_at(rand(arr.size)) }
    a
  end

要洗牌的数组是:

ar = [1,3,2,7,4,1]

然后

arr = ar.dup
  #=> [1, 3, 2, 7, 4, 1] 
n = arr.size
  #=> 6 
enum0 = n.times
  #=> #<Enumerator: 6:times> 
enum1 = enum0.with_object([])
  #=> #<Enumerator: #<Enumerator: 6:times>:with_object([])>

仔细检查enum1的返回值。它可能被认为是一个复合调查员&#34;。我们可以通过将enum1转换为数组来看到enum1.to_a #=> [[0, []], [1, []], [2, []], [3, []], [4, []], [5, []]] 生成的元素:

a

每个数组的第二个元素对应于块变量[]。最初它是一个空数组,因为Enumerable#each_with_object的参数是enum1

我们可以将Enumerator#next发送到b,a = enum1.next #=> [0, []] 以生成枚举器的第一个元素,并将块变量设置为等于该值:

b

ab #=> 0 a #=> [] 的值是通过使用并行分配(又名多次分配)获得的:

c = arr.delete_at(rand(ar.size))
  #=> arr.delete_at(rand(6))
  #=> arr.delete_at(4) 
  #=> 4 
a << c
  #=> [4]

然后我们可以执行块计算:

a

[4]现在为arr[1, 3, 2, 7, 1]等于arb未更改。

按照惯例,我已使用下划线替换了块变量b,a = enum1.next #=> [1, [4]] c = arr.delete_at(rand(ar.size)) #=> arr.delete_at(rand(5)) #=> arr.delete_at(0) #=> 1 a << c #=> [4, 1] arr #=> [3, 2, 7, 1] ,因为它未在块计算中使用。

接下来我们有

protected void Application_Start()
{
    var jsonMediaTypeFormatter = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
    jsonMediaTypeFormatter.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.All;
    jsonMediaTypeFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
}

其余的计算方法类似。

答案 2 :(得分:2)

如果你要让数组洗牌的目标应该使用内置的shuffle方法,那么它的正确性和速度都比你要编写的任何东西都快。

如果您的目标是实现自己的版本,那么这不适合递归,您应该实现Fisher-Yates shuffle以避免bias

def fy_shuffle(arr)
  a_copy = arr.dup  # don't mutate original array!
  a_copy.each_index do |i|
    j = i + rand(a_copy.length - i)
    a_copy[i], a_copy[j] = a_copy[j], a_copy[i] if i != j
  end
end

如果您的目标是了解递归,那么dinjas的答案可以修复原始实现中的问题,并且只要数组不够大就无法完成堆栈就可以正常工作。