23

我尝试清理我的代码。第一个版本使用each_with_index. 在第二个版本中,我尝试使用在此处Enumerable.inject_with_index-construct找到的 压缩代码。

它现在可以工作,但在我看来和第一个代码一样晦涩难懂。更糟糕的是,我不明白 element,index in 周围的括号

.. .inject(groups) do |group_container, (element,index)|

但它们是必要的

  • 这些括号有什么用?
  • 如何使代码清晰易读?

第一个版本——带有“each_with_index”

class Array

  # splits as good as possible to groups of same size
  # elements are sorted. I.e. low elements go to the first group,
  # and high elements to the last group
  # 
  # the default for number_of_groups is 4 
  # because the intended use case is
  # splitting statistic data in 4 quartiles
  # 
  # a = [1, 8, 7, 5, 4, 2, 3, 8]
  # a.sorted_in_groups(3) # => [[1, 2, 3], [4, 5, 7], [8, 8]]
  # 
  # b = [[7, 8, 9], [4, 5, 7], [2, 8]] 
  # b.sorted_in_groups(2) {|sub_ary| sub_ary.sum } # => [ [[2, 8], [4, 5, 7]], [[7, 8, 9]] ]
  def sorted_in_groups(number_of_groups = 4)
    groups = Array.new(number_of_groups) { Array.new }
    return groups if size == 0

    average_group_size = size.to_f / number_of_groups.to_f
    sorted = block_given? ? self.sort_by {|element| yield(element)} : self.sort

    sorted.each_with_index do |element, index|
      group_number = (index.to_f / average_group_size).floor 
      groups[group_number] << element
    end

    groups
  end
end

第二版——带有“注入”和索引

class Array
  def sorted_in_groups(number_of_groups = 4)
    groups = Array.new(number_of_groups) { Array.new }
    return groups if size == 0

    average_group_size = size.to_f / number_of_groups.to_f
    sorted = block_given? ? self.sort_by {|element| yield(element)} : self.sort

    sorted.each_with_index.inject(groups) do |group_container, (element,index)|
      group_number = (index.to_f / average_group_size).floor
      group_container[group_number] << element
      group_container
    end
  end
end
4

4 回答 4

33

这些括号有什么用?

这是 ruby​​ 的一个非常好的特性。我称之为“解构数组赋值”,但它可能也有一个正式的名称。

这是它的工作原理。假设你有一个数组

arr = [1, 2, 3]

然后将此数组分配给名称列表,如下所示:

a, b, c = arr
a # => 1
b # => 2
c # => 3

你看,数组被“解构”成它的各个元素。现在,到each_with_index. 如您所知,它就像一个常规的each,但也返回一个索引。inject不关心这一切,它接受输入元素并将它们按原样传递给它的块。如果输入元素是一个数组(来自 的元素/索引对each_with_index),那么我们可以在块体中将其拆开

sorted.each_with_index.inject(groups) do |group_container, pair|
  element, index = pair

  # or
  # element = pair[0]
  # index = pair[1]

  # rest of your code
end

或者直接在块签名中解构该数组。括号有必要给 ruby​​ 一个提示,这是一个需要拆分为多个的单个参数。

希望这可以帮助。

于 2013-05-06T19:14:42.380 回答
17
lines = %w(a b c)
indexes = lines.each_with_index.inject([]) do |acc, (el, ind)|
  acc << ind - 1 if el == "b"
  acc
end

indexes # => [0]
于 2017-03-08T13:02:20.200 回答
0

这些括号有什么用?

要理解括号,首先您需要了解破坏在 ruby​​ 中是如何工作的。我能想到的最简单的例子是:

1.8.7 :001 > [[1,3],[2,4]].each do |a,b|
1.8.7 :002 >     puts a, b
1.8.7 :003?>   end
1
3
2
4

您应该知道each函数是如何工作的,并且该块接收一个参数。那么当你传递两个参数时会发生什么?它获取第一个元素[1,3]并尝试将其拆分(破坏)为两部分,结果为a=1and b=3

现在,inject在 block 参数中有两个参数,所以它通常看起来像|a,b|. 所以像我们这样传递一个参数|group_container, (element,index)|实际上是把第一个当作其他参数,并在另外两个中破坏第二个(所以,如果第二个参数是[1,3],element=1index=3)。括号是必需的,因为如果我们使用|group_container, element, index|我们永远不会知道我们是在破坏第一个参数还是第二个参数,所以那里的括号起到消歧的作用。

9事实上,事情在底端有点不同,但让我们隐藏这个给定的问题。)

于 2013-05-06T19:15:32.497 回答
0

似乎已经给出了一些很好的解释的答案。我想添加一些关于清晰易读的信息。

除了您选择的解决方案,还可以扩展 Enumerable 并添加此功能。

module Enumerable
  # The block parameter is not needed but creates more readable code.
  def inject_with_index(memo = self.first, &block)
    skip = memo.equal?(self.first)
    index = 0
    self.each_entry do |entry|
      if skip
        skip = false
      else
        memo = yield(memo, index, entry)
      end
      index += 1
    end
    memo
  end
end

这样你就可以inject_with_index这样调用:

# m = memo, i = index, e = entry
(1..3).inject_with_index(0) do |m, i, e|
  puts "m: #{m}, i: #{i}, e: #{e}"
  m + i + e
end
#=> 9

如果您不传递初始值,则将使用第一个元素,因此不会执行第一个元素的块。

于 2016-05-26T10:23:20.773 回答