Почему метод ввода Ruby не может суммировать длины строк без начального значения? - PullRequest
5 голосов
/ 30 ноября 2010

Почему следующий код выдает ошибку?

['hello','stack','overflow'].inject{|memo,s|memo+s.length}

TypeError: can't convert Fixnum into String
        from (irb):2:in `+'
        from (irb):2:in `block in irb_binding'
        from (irb):2:in `each'
        from (irb):2:in `inject'
        from (irb):2

Если передано начальное значение, оно работает нормально:

['hello','stack','overflow'].inject(0){|memo,s|memo+s.length}
=> 18

Ответы [ 3 ]

16 голосов
/ 30 ноября 2010

У вас есть ответ в apidock :

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

То есть без начального значения вы пытаетесь сделать 'hello' + 'stack'.length

6 голосов
/ 30 ноября 2010

Как уже сообщается в сообщении об ошибке, проблема в том, что у вас есть TypeError.Тот факт, что Ruby динамически и неявно типизирован, не означает, что вам не нужно думать о типах.

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

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

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

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

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

В вашем случае типы для блока просто не задаютсят сложить.Блок проходит два String с, и он должен вернуть String.Но вы вызываете метод + для первого аргумента (который является String) с аргументом, который является Integer.Но String#+ не требует Integer, требуется только String или, точнее, что-то, что может быть преобразовано в String, то есть что-то, что отвечает #to_str.Вот почему вы получаете TypeError для String#+.

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

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

или

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

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

Эти два правила обычно помогают вам решить все Enumerable#inject проблемы:

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

Правило # 1 чаще всего кусает вас, когда вы делаете что-то вроде

acc[key] = value

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

acc.tap { acc[key] = value }

В вашем конкретном случае два решения уже были упомянуты.Либо используйте явный аккумулятор

ary.reduce(0){|acc, e| acc + e.length }

или конвертируйте в целые числа first

ary.map(&:length).reduce(:+)
3 голосов
/ 30 ноября 2010

Без начального значения inject использует первый элемент в коллекции в качестве начального значения.

см. ruby-doc .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...