得到组数后,说
ngroups = 3
代码将提示用户输入一系列名称,当用户没有更多名称可输入时,后跟"stop"。假设用户按指示的顺序输入以下字符串:
"Lucy\n", "Hank\n", "Billy-Bob\n", "Trixie\n", "Veronica\n", "Roxy\n",
"Norm\n", "Herb\n", "stop\n"
我们可以使用以下代码创建所需的数组:
0.step.with_object(Array.new(ngroups) { [] }) do |i,a|
print 'Please enter a name or "stop" if finished: '
reply = gets.chomp
break a if reply == "stop"
a[i % ngroups] << reply
end
#=> [["Lucy", "Trixie", "Norm"], ["Hank", "Veronica", "Herb"],
# ["Billy-Bob", "Roxy"]]
请注意,我没有要求用户为每个名称输入一个组。那是因为您说名称将按组顺序分配给组,在每次将名称分配给最后一个组后从第一个组恢复。
对于任何刚接触 Ruby 的人来说,这段代码一定看起来相当复杂。然而,它与 Ruby 非常相似,并且任何 Rubiest 在获得一些该语言的经验后都会立即理解它。让我分解步骤。耐心等待你应该能够理解发生了什么。
Numeric#step,一个枚举器
enum0 = 0.step #=> (0.step)
如果您检查此方法的文档,您会看到没有块的0.step 返回一个枚举器,它是类Enumerator 的一个实例。枚举器在 Ruby 中非常重要。它们生成的值可以传递给其他方法或阻塞。在这里,enum0 生成以下序列:
enum0.next #=> 0
enum0.next #=> 1
enum0.next #=> 2
...无限。见Enumerator#next。
同样,
e = 21.step(by: -3) #=> (21.step(by: -3))
e.next #=> 21
e.next #=> 18
e = 2.34.step #=> (2.34.step)
e.next #=> 2.34
e.next #=> 3.34
Enumerator#with_object
我们实际上并不需要使用这种方法。我们可以改为编写以下代码,得到相同的结果:
a = []
0.step do |i|
print 'Please enter a name or "stop" if finished: '
reply = gets.chomp
break a if reply == "stop"
a[i % ngroups] << reply
end
a
如您所见,使用each_object 仅节省了两行代码:最后是a = [] 和a。1。这种方法——以及它的表亲,Enumerable#each_with_object,被 Ruby 编码人员广泛使用,实际上非常简单。
另一个枚举器
接下来我们有:
enum1 = enum0.with_object(Array.new(ngroups) { [] })
#=> #<Enumerator: (0.step):with_object([[], [], []])>
从enum1 的返回值可以看出,enum1 可以被认为是一个复合枚举器,尽管 Ruby 没有这样的正式概念。让我们看看这个枚举器生成什么值:
enum1.next #=> [0, [[], [], []]]
enum1.next #=> [1, [[], [], []]]
enum1.next #=> [2, [[], [], []]]
您会看到生成的每个值都是一个由两个元素组成的数组,第一个是从零开始的计数器,第二个是一个由三组组成的数组。这些组现在是空的,但随着计算的进行,它们将被填写。
如果我们希望我们现在可以将原始代码编写如下1
enum1.each do |i,a|
print 'Please enter a name or "stop" if finished: '
reply = gets.chomp
break a if reply == "stop"
a[i % ngroups] << reply
end
见Enumerator#each。
将enum1生成的元素传递给块
当第一个元素生成并传递给块时,块变量i和a被赋值如下:
i, a = enum1.next #=> [0, [[], [], []]]
i #=> 0
a #=> [[], [], []]
将enum1.next 返回的数组分解为其组件的过程称为array decomposition。它是一个强大而有价值的工具。
我们现在可以执行块操作了。
print 'Please enter a name or "stop" if finished: '
reply = "Lucy" # = gets.chomp
break a if reply == "stop" # do not break
a[i % ngroups] << reply
#=> a[0 % 3] << "Lucy"
#=> a[0] << "Lucy"
#=> ["Lucy"]
接下来enum1 生成它的第二个值并将其传递给块,将值分配给块变量并执行块操作。
i, a = enum1.next #=> [1, [["Lucy"], [], []]]
i #=> 1
a #=> [["Lucy"], [], []]
请注意,a 已更新以反映 "Lucy" 已附加到 a[0]。继续,
print 'Please enter a name or "stop" if finished: '
reply = "Hank" # = gets.chomp
break a if reply == "stop" # do not break
a[i % ngroups] << reply
#=> a[1 % 3] << "Hank"
#=> a[1] << "Hank"
#=> ["Hank"]
现在,
a #=> => [["Lucy"], ["Hank"], []]
其余的计算类似。
1.在运行以下命令之前,我需要执行 enum1.rewind 来重新初始化枚举器。见Enumerator#rewind。