【问题标题】:Ruby inject with index and brackets带有索引和括号的 Ruby 注入
【发布时间】:2013-05-06 19:02:59
【问题描述】:

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

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

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

【问题讨论】:

标签: ruby


【解决方案1】:

这些括号有什么用?

这是 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​​ 一个提示,即这是一个需要拆分为多个的单个参数。

希望这会有所帮助。

【讨论】:

  • 现在我明白了。很好的解释。非常感谢。
【解决方案2】:
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]

【讨论】:

    【解决方案3】:

    这些括号有什么用?

    要了解括号,首先需要了解 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

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

    9事实上,底部的工作方式有点不同,但对于这个给定的问题,让我们隐藏它。)

    【讨论】:

    • 我认为 each_slice 对我不起作用,因为使用 each_slice,最后一个切片或组可以比其他组小得多,我需要大小几乎相同的组。
    • @ovhaag [1, 8, 7, 5, 4, 2, 3, 8].sort.each_slice(3) 结果为 [[1, 2, 3], [4, 5, 7], [8, 8]] ,这是您的测试用例。你能给我一个不起作用的例子,然后我可以改进我的答案吗?
    • [1, 2, 3, 4, 5, 6, 7].sort.each_slice(3) # =&gt;[[1, 2, 3], [4, 5, 6], [7]] 不行,最后一组太小[1, 2, 3, 4, 5, 6, 7].sort.each_slice(2) # =&gt;[[1, 2], [3, 4], [5, 6], [7]] -- 不行,需要太多组:[1, 2, 3, 4, 5, 6, 7].in_groups(3) # =&gt; [[1, 2, 3], [4, 5], [6, 7]] -- 好的 3 组,最大组的大小 - 最小组的大小 = 1
    • 切片大小应该是种群大小除以组数(self 也是免费的):sort.each_slice((size.to_f/number_of_groups).ceil).to_a
    • @ovhaag 测试 dbenhur 提示也不适用于您的上一个示例。我将从我的答案中删除代码,以免出错,因为我想不出更好的方法来做到这一点。
    【解决方案4】:

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

    除了您选择的解决方案之外,还可以扩展 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
    

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-12-02
      • 1970-01-01
      • 2010-09-30
      • 1970-01-01
      • 1970-01-01
      • 2016-10-03
      • 1970-01-01
      相关资源
      最近更新 更多