Ruby: проблема ввода при повороте массива в хэш

a = [[1, 'a'],[2, 'b'],[3, 'c'], [4, 'd']]
a.inject({}) {|r, val| r[val[0]] = val[1]}

Когда я запускаю это, я получаю индексную ошибку

Когда я меняю блок на

a.inject({}) {|r, val| r[val[0]] = val[1]; r}

Затем он работает.

Как рубин обрабатывает первую попытку впрыска, которая не получает то, что я хочу?
Есть ли лучший способ сделать это?

Ответ 1

Просто потому, что Ruby динамически и неявно напечатан, не означает, что вам не нужно думать о типах.

Тип Enumerable#inject без явного аккумулятора (обычно это называется reduce) - это что-то вроде

reduce :: [a] → (a → a → a) → a

или в более рубиновой нотации, которую я только что составил

Enumerable[A]#inject {|A, A| A } → A

Вы заметите, что все типы одинаковы. Тип элемента Enumerable, два типа аргументов блока, тип возврата блока и тип возвращаемого значения общего метода.

Тип Enumerable#inject с явным аккумулятором (обычно это называется fold) - это что-то вроде

fold :: [b] → a → (a → b → a) → a

или

Enumerable[B]#inject(A) {|A, B| A } → A

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

Эти два правила обычно передают вам все проблемы, связанные с Enumerable#inject:

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

В этом случае это правило № 1, которое вас укусит. Когда вы делаете что-то вроде

acc[key] = value

в вашем блоке, присваивания оцениваются назначенным значением, а не получателем задания. Вам придется заменить это на

acc.tap { acc[key] = value }

См. также Почему метод инъекции Ruby не может суммировать длины строк без начального значения?


BTW: вы можете использовать деструктурирующее связывание, чтобы сделать ваш код более читаемым:

a.inject({}) {|r, (key, value)| r[key] = value; r }

Ответ 2

Существует более простой способ -

a = [[1, 'a'],[2, 'b'],[3, 'c'], [4, 'd']]
b = Hash[a] # {1=>"a", 2=>"b", 3=>"c", 4=>"d"}

Причина, по которой первый метод не работает, заключается в том, что инъекция использует результат блока как r в следующей итерации. Для первой итерации r устанавливается аргумент, который вы передаете ему, что в данном случае равно {}.

Ответ 3

Первый блок возвращает результат присваивания обратно в inject, второй возвращает хэш, поэтому он действительно работает.

Многие считают это использование inject анти-шаблоном; рассмотрите each_with_object вместо.

Ответ 4

a.inject({}) {| r, val | r.merge({val [0] = > val [1]})}