Инъекция 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
Ответы
Ответ 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
Если вы не пройдете начальное значение, будет использован первый элемент, поэтому не будет выполняться блок для первого элемента.