Ruby中使用索引和方括号的inject操作

24

我试图清理我的代码。第一个版本使用each_with_index。在第二个版本中,我尝试使用Enumerable.inject_with_index-construct压缩代码,我在这里找到了这个方法。

现在它可以工作了,但是对我来说仍然像第一段代码一样晦涩难懂。更糟糕的是,我不理解在element,index周围的括号。

.. .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

第二版 -- 使用“inject”和索引

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

我认为这里有一个更好的答案:https://dev59.com/d2Ei5IYBdhLWcg3wCoMp - Daniel Santos Dantas
5个回答

35

这些括号有什么用?

这是 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 这是一个需要分割成多个参数的单一参数。

希望这能有所帮助。


20
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]

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

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


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=1b=3
现在,在块参数中注入需要两个参数,通常看起来像|a,b|。因此,传递像|group_container,(element,index)|这样的参数实际上是将第一个参数视为任何其他参数,并将第二个参数解构为另外两个参数(因此,如果第二个参数是[1,3],则element=1index=3)。括号是必需的,因为如果我们使用|group_container,element,index|,我们永远不会知道我们正在解构第一个还是第二个参数,所以那里的括号起到消歧作用。
实际上,在底层,事情有点不同,但让我们为这个问题隐藏它。

我认为each_slice对我来说不适用,因为使用each_slice时,最后一组的大小可能比其他组要小得多,而我需要几乎相同大小的组。 - ovhaag
@ovhaag [1, 8, 7, 5, 4, 2, 3, 8].sort.each_slice(3) 的结果是 [[1, 2, 3], [4, 5, 7], [8, 8]],这是你的测试用例。你能给我一个不起作用的例子吗?然后我可以改进我的答案。 - fotanus
[1, 2, 3, 4, 5, 6, 7].sort.each_slice(3) # =>[[1, 2, 3], [4, 5, 6], [7]] 不行,最后一组太小了。[1, 2, 3, 4, 5, 6, 7].sort.each_slice(2) # =>[[1, 2], [3, 4], [5, 6], [7]] 不行,分组太多了。期望结果:[1, 2, 3, 4, 5, 6, 7].in_groups(3) # => [[1, 2, 3], [4, 5], [6, 7]] 可以,分成了3组,最大组的大小减去最小组的大小等于1。 - ovhaag
切片大小应该是人口数量除以组数(同时 self 是多余的):sort.each_slice((size.to_f/number_of_groups).ceil).to_a - dbenhur
@ovhaag 测试 dbenhur 的提示也没有对你的最后一个示例起作用。我将从我的答案中删除该代码,以免出错,因为我想不到更好的方法来解决它。 - fotanus

0
如果有人是来自2013年以后的,你可以使用each_with_objectwith_index来满足你的需求。
records.each_with_object({}).with_index do |(record, memo), index|
  memo[record.uid] = "#{index} in collection}"
end

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