如何将一个范围分成N个部分

5
我想知道如何在Ruby中将一个范围分成N个部分,同时将它们添加到一个哈希表中,并为生成的每个范围使用从零开始的值。
例如:
range = 1..60
p split(range, 4)
#=> {1..15 => 0, 16..30 => 1, 31..45 => 2, 46..60 => 3}

我已经阅读了有关如何在Ruby中将范围切片为数组的文章,例如How to return a part of an array in Ruby?,以及其他一些有关如何将切片转换回范围的文章,但我似乎无法将所有这些部分拼凑在一起,创建出我想要的方法。
谢谢您的帮助。
1个回答

6
range = 1..60

range.each_slice(range.last/4).with_index.with_object({}) { |(a,i),h|
  h[a.first..a.last]=i }
  #=> {1..15=>0, 16..30=>1, 31..45=>2, 46..60=>3}

步骤如下:
enum0 = range.each_slice(range.last/4)
  #=> range.each_slice(60/4)
  #   #<Enumerator: 1..60:each_slice(15)> 

您可以将此枚举器转换为数组,以查看它将生成并传递给each_with_index的(4)个元素:
enum0.to_a
  #=> [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
  #    [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30],
  #    [31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45],
  #    [46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60]] 

enum1 = enum0.with_index
  #=> #<Enumerator: #<Enumerator: 1..60:each_slice(15)>:with_index>
enum1.to_a
  #=> [[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 0],
  #    [[16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30], 1],
  #    [[31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45], 2],
  #    [[46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60], 3]] 

enum2 = enum1.with_object({})
  #=> #<Enumerator: #<Enumerator: #<Enumerator: 1..60:each_slice(15)>
  #     :with_index>:with_object({})>
enum2.to_a
  #=> [[[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 0], {}],
  #    [[[16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30], 1], {}],
  #    [[[31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45], 2], {}],
  #    [[[46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60], 3], {}]]

仔细检查enum1enum2的计算结果。你可能希望将它们视为“复合”枚举器。每个enum2四个数组的第二个和最后一个元素是空哈希,由块变量h表示。该哈希表将在随后的计算中构建。
enum2.each { |(a,i),h| h[a.first..a.last]=i }
  #=> {1..15=>0, 16..30=>1, 31..45=>2, 46..60=>3} 
< p > 在执行 enum.each... 之前,each 传递给代码块的 enum2 的第一个元素是:

arr = enum2.next
  #=>[[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 0], {}]

使用并行赋值(有时称为多重赋值)将块变量分配给arr的元素。

(a,i),h = arr
  #=> [[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 0], {}] 
a #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] 
i #=> 0 
h #=> {}

因此,块计算是这样的。
h[a.first..a.last]=i 
  #=> h[1..15] = 0

现在
h #=> {1..15=>0} 

计算方式与由enum2生成的其他3个元素类似。

表达式

enum2.each { |(a,i),h| h[(a.first..a.last)]=i }

也可以用另一种方式来书写

enum2.each { |((f,*_,l),i),h| h[(f..l)]=i }

或者.each_slice(15).map.with_index { |d, i| { (d.first..d.last) => i } } 可能更易读. - Ilya
@llya,需要哈希表而不是每个哈希表都有一个键值对的数组。你建议传递范围是一个好主意,我会将其纳入我的答案中。 - Cary Swoveland
谢谢您的回答!不幸的是,在我的机器上 ruby 2.2.3p173 (2015-08-18 revision 51636) [x64-mingw32],您的代码返回了 {[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]=>0, [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]=>1, [31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45]=>2, [46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60]=>3} 而不是 {1..15=>0, 16..30=>1, 31..45=>2, 46..60=>3}。是否有一些小疏忽?如果没有,如何检测单个整数是否落在键范围内并返回其值。再次感谢。 - randy newfield
续前:我之前使用 hash.detect {|k,v| k === 4}.last 来返回 4 所在范围的值。由于哈希表的类型是 array=>integer 而不是 range=>integer,这种方法将不再起作用。有没有其他方法来解决这个问题的一部分?编辑:目前 hash.detect {|k,v| k.include?(4)}.last 可以工作。再次感谢。 - randy newfield
Randy,抱歉给你带来不便。我已经回滚到我的原始答案,我相信它是正确的。我曾经做了一个我认为可以简化的更改,但没有充分测试它以查看它是否错误。我将重新添加解释。 - Cary Swoveland

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接