Инъекция Ruby с индексом и скобками - программирование
Подтвердить что ты не робот

Инъекция Ruby с индексом и скобками

Я пытаюсь очистить свой код. В первой версии используется each_with_index. Во второй версии я попытался скомпоновать код с Enumerable.inject_with_index-construct, который я нашел здесь.

Теперь он работает, но мне кажется неясным, как первый код. Добавьте еще хуже, я не понимаю скобки вокруг элемента, индекс в

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

Ответ 1

Каковы преимущества этих скобок?

Это очень приятная черта рубина. Я называю это "destructuring array assign", но у него, вероятно, также есть официальное название.

Вот как это работает. Скажем, у вас есть массив

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

Или разрушите этот массив прямо в сигнатуре блока. Круглые скобки необходимы, чтобы дать рубину намек на то, что это единственный параметр, который нужно разделить на несколько.

Надеюсь, что это поможет.

Ответ 2

lines = %(a b c)
indexes = lines.each_with_index.inject([]) do |acc, (el, ind)|
  acc << ind - 1 if el == "b"
  acc
end

indexes # => [0]

Ответ 3

Каковы преимущества этих скобок?

Чтобы понять скобки, сначала вам нужно понять, как разрушение работает в рубине. Простейший пример я могу подумать об этом:

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=1 и b=3.

Теперь добавьте принимает два аргумента в параметре блока, поэтому он обычно выглядит как |a,b|. Таким образом, передавая такой параметр, как |group_container, (element,index)|, мы фактически принимаем первый как любой другой и уничтожаем второй в двух других (так что, если второй параметр [1,3], element=1 и index=3). Скобки нужны, потому что, если бы мы использовали |group_container, element, index|, мы бы никогда не знали, разрушаем ли мы первый или второй параметр, поэтому круглые скобки там работают как значения.

9 В самом деле, все работает немного по-другому в нижнем конце, но давайте скрыть это для данного заданного вопроса.)

Ответ 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

Если вы не пройдете начальное значение, будет использован первый элемент, поэтому не будет выполняться блок для первого элемента.