Как сложить массив чисел в Ruby?

564

У меня есть массив целых чисел.

Например:

array = [123,321,12389]

Есть ли хороший способ получить их сумму?

Я знаю это

sum = 0
array.each { |a| sum+=a }

должно сработать.

brainfck
источник
19
Обратите внимание, что в Ruby 2.4+ естьarray.sum
dawg
В Ruby 2.6 его нет. Руби дает, Руби забирает, кажется.
Лори
1
@ Лори хмм? ссылка
steenslag
Сожалею. В то время я ошибочно полагал, что использую 2.6 из-за ошибки rbenv с моей стороны.
Лори

Ответы:

613

Попробуй это:

array.inject(0){|sum,x| sum + x }

Смотрите перечисляемую документацию Ruby

(примечание: 0базовый случай необходим, чтобы 0вместо пустого массива он возвращался nil)

zenazn
источник
317
Джорни array.inject(:+)более эффективен.
Питер
3
array.inject(:+)кажется, вызывает проблемы в Ruby 1.8.6 Исключения «LocalJumpError: блок не указан» может появиться.
Камил Сзот
34
В рельсах array.sumможет дать вам сумму значений массива.
Камил Сзот
32
В большинстве случаев я предпочитаю использовать reduceпсевдоним inject(как в array.reduce( :+ )).
Борис Стиницкий,
3
@Boris Кроме того, Rubycop предупредит вас за использование, injectа не reduce.
Дрооганс
810

Или попробуйте Ruby 1.9:

array.inject(0, :+)

Примечание: 0базовый регистр необходим, иначе nilбудет возвращен на пустых массивах:

> [].inject(:+)
nil
> [].inject(0, :+)
0
jomey
источник
6
Как я могу использовать этот способ для суммирования атрибута из объекта. Мой массив [product1, product2] Я хочу суммировать product1.price + product2.price. Возможно ли использовать array.inject (: +)?
Пабло Кантеро
7
Подобный прием можно использовать с методом карты: array.map (&: price) .inject (: +)
markquezada
100
array.map(&:price).inject(0, :+)немного безопаснее Это гарантирует, что если у вас есть пустой список, вы получите 0, а не ноль .
Джон
11
использование array.map (...). inject (...) неэффективно, вы будете перебирать все данные дважды. Попробуйте array.inject(0) { |sum, product| sum += product.price }
everett1992
4
@ everett1992 и, как оказалось, даже не оптимизация вообще. Делать это в два этапа для меня постоянно быстрее. gist.github.com/cameron-martin/b907ec43a9d8b9303bdc
Кэмерон Мартин
290
array.reduce(0, :+)

В то время как эквивалент array.inject(0, :+), термин сокращение входит в более общеупотребительный язык с ростом моделей программирования MapReduce .

Инъекция , уменьшение , свертывание , накопление и сжатие - это синонимы как класс функций свертывания . Я считаю согласованность всей вашей кодовой базы наиболее важной, но, поскольку различные сообщества предпочитают одно слово другому, тем не менее полезно знать альтернативы.

Чтобы подчеркнуть словосочетание «уменьшить карту», ​​вот версия, которая немного более прощательна для того, что заканчивается в этом массиве.

array.map(&:to_i).reduce(0, :+)

Некоторые дополнительные соответствующие чтения:

Evan
источник
11
Я согласен, reduceговорит мне больше о том, что делает функция, но injectзвучит намного круче.
everett1992
1
Согласитесь с последним комментарием, вы дали мне лучший ответ.
Jerska
1
Один комментарий , который я сделал бы то , что reduceи mapв функции высшего порядка предшествует MapReduce. Вдохновение идет другим путем. И в смысле MapReduce, это несколько иная операция, чем простое функциональное сокращение, которое влияет на взаимодействие разных машин.
Acjay
Кен Айверсон представил оператор / называемый «оператор сокращения» на языке программирования APL. Источник: Айверсон, Кеннет. 1962. Язык программирования. Wiley. Другой источник: «Нотация как инструмент мышления», лекция ACM Turing Award 1979 года, Кеннет Э. Айверсон, dl.acm.org/ft_gateway.cfm?id=1283935&type=pdf
Фернандо
112

В качестве альтернативы (просто для сравнения), если у вас установлен Rails (фактически только ActiveSupport):

require 'activesupport'
array.sum
Майк Вудхаус
источник
12
Более новые версии activesupport на самом деле не загружают все расширения по умолчанию. Вы хотите , чтобы требовать либо только модуль суммы: require 'active_support/core_ext/enumerable.rb'или требовать все активной поддержки: require 'active_support/all'. Подробнее об этом здесь: API Docs
dcashman
2
Не важно , что activesupportэто массивное зависимость перетащить в проект , чтобы перейти от array.inject(:+)к array.sum.
meagar
1
Nitpick к хорошему комментарию: он должен быть require 'active_support/core_ext/enumerable'без .rbсуффикса, поскольку он добавлен неявно.
Пер Лундберг
72

Для Ruby> = 2.4.0 вы можете использовать sumиз Enumerables.

[1, 2, 3, 4].sum

Опасно для базовых классов mokeypatch. Если вам нравится опасность и вы используете более старую версию Ruby, вы можете добавить #sumв Arrayкласс:

class Array
  def sum
    inject(0) { |sum, x| sum + x }
  end
end
jrhicks
источник
1
Пожалуйста, не делайте этого
user3467349
@ user3467349 почему?
YoTengoUnLCD
15
Базовые классы обезьяноподготовки не очень приятны.
user3467349
1
Смысл его в том, что вам не нужно делать Monkey Patch для Ruby> = 2.4, и это исправление обезьяны опасно, и теперь вы можете суммировать перечислимые значения изначально, но есть и способ переназначить функциональность.
Питер Х. Болинг
Понижено, потому что ваша реализация возвращает ноль для пустых массивов.
Eldritch Conundrum
45

Новое для Ruby 2.4.0

Вы можете использовать метко названный метод Enumerable#sum. Он имеет много преимуществ по сравнению inject(:+)с некоторыми важными примечаниями, которые нужно прочитать в конце.

Примеры

Изменяется

(1..100).sum
#=> 5050

Массивы

[1, 2, 4, 9, 2, 3].sum
#=> 21

[1.9, 6.3, 20.3, 49.2].sum
#=> 77.7

Важная заметка

Этот метод не эквивалентен #inject(:+). Например

%w(a b c).inject(:+)
#=> "abc"
%w(a b c).sum
#=> TypeError: String can't be coerced into Integer

Также,

(1..1000000000).sum
#=> 500000000500000000 (execution time: less than 1s)
(1..1000000000).inject(:+)
#=> 500000000500000000 (execution time: upwards of a minute)

Смотрите этот ответ для получения дополнительной информации о том, почему sumэто так.

Эли Садофф
источник
20

Ruby 2.4+ / Rails - array.sumт.е.[1, 2, 3].sum # => 6

Ruby pre 2.4 - array.inject(:+)илиarray.reduce(:+)

* Примечание: этот #sumметод является новым дополнением для 2.4, enumerableтак что теперь вы сможете использовать его array.sumв чистом рубине, а не только в Rails.

Collect
источник
2
Ruby 2.4.0 был выпущен сегодня с этой функцией! Am
амеба
@ amoebe вы правы! Рад видеть, что эта полезная функция включена.
собирать
19

Просто ради разнообразия вы можете сделать это, если ваш массив - это не массив чисел, а массив объектов, которые имеют свойства, которые являются числами (например, количество):

array.inject(0){|sum,x| sum + x.amount}
HashFail
источник
3
Это эквивалентно делать: array.map(&:amount).inject(0, :+). Смотрите другие ответы.
Ричард Джонс
4
В некотором смысле, да. Однако использование mapзатем injectтребует от вас циклически проходить по массиву дважды: один раз для создания нового массива, другой для суммирования членов. Этот метод немного более многословен, но и более эффективен.
HashFail
Видимо, это не более эффективно, см. Gist.github.com/cameron-martin/b907ec43a9d8b9303bdc - благодарность за комментарии в этом ответе: stackoverflow.com/a/1538949/1028679
rmcsharry
18

Рубин 1.8.7 следующим образом:

array.inject(0, &:+) 
Vova
источник
Если вы прочитали мой комментарий 2011 года, и он по-прежнему актуален при использовании 1.8.6, пожалуйста, обновитесь!
Эндрю Гримм
16

Вы можете просто использовать:

    example = [1,2,3]
    example.inject(:+)
Ганеш Сагаре
источник
Почему это работает, inject(:+)а это нет inject :+?
Арнольд Роа
@ArnoldRoa "inject: +" это работает для меня, какой результат вы получили?
Ганеш Сагаре
6

Достаточно [1,2,3].inject('+')

Махеш Баблу
источник
5

Ruby 2.4.0 выпущен и имеет метод Enumerable # sum . Так что вы можете сделать

array.sum

Примеры из документов:

{ 1 => 10, 2 => 20 }.sum {|k, v| k * v }  #=> 50
(1..10).sum                               #=> 55
(1..10).sum {|v| v * 2 }                  #=> 110
Santhosh
источник
3

Также позволяет [1,2].sum{|x| x * 2 } == 6:

# http://madeofcode.com/posts/74-ruby-core-extension-array-sum
class Array
  def sum(method = nil, &block)
    if block_given?
      raise ArgumentError, "You cannot pass a block and a method!" if method
      inject(0) { |sum, i| sum + yield(i) }
    elsif method
      inject(0) { |sum, i| sum + i.send(method) }
    else
      inject(0) { |sum, i| sum + i }
    end
  end
end
грубее
источник
3

для массива с нулевыми значениями мы можем сделать компакт и затем ввести сумму

a = [1,2,3,4,5,12,23.45,nil,23,nil]
puts a.compact.inject(:+)
thedudecodes
источник
2
array.reduce(:+)

Работает и для диапазонов ... следовательно,

(1..10).reduce(:+) returns 55
MulleOne
источник
1

Если вы чувствуете гольф, вы можете сделать

eval([123,321,12389]*?+)

Это создаст строку «123 + 321 + 12389», а затем с помощью функции eval сделает сумму. Это только для целей игры в гольф , вы не должны использовать его в надлежащем коде.

Улисс Б.Н.
источник
1

Способ 1:

    [1] pry(main)> [1,2,3,4].sum
    => 10
    [2] pry(main)> [].sum
    => 0
    [3] pry(main)> [1,2,3,5,nil].sum
    TypeError: nil can't be coerced into Integer

Способ 2:

   [24] pry(main)> [].inject(:+)
   => nil
   [25] pry(main)> [].inject(0, :+)
   => 0
   [4] pry(main)> [1,2,3,4,5].inject(0, :+)
   => 15
   [5] pry(main)> [1,2,3,4,nil].inject(0, :+)
   TypeError: nil can't be coerced into Integer
   from (pry):5:in `+'

Способ 3:

   [6] pry(main)> [1,2,3].reduce(:+)
   => 6
   [9] pry(main)> [].reduce(:+)
   => nil
   [7] pry(main)> [1,2,nil].reduce(:+)
   TypeError: nil can't be coerced into Integer
   from (pry):7:in `+'

Способ 4: когда массив содержит нулевые и пустые значения, по умолчанию, если вы используете любые из вышеперечисленных функций, уменьшить, суммировать, внедрить все будет через

TypeError: nil не может быть приведен к Integer

Вы можете преодолеть это,

   [16] pry(main)> sum = 0 
   => 0
   [17] pry(main)> [1,2,3,4,nil, ''].each{|a| sum+= a.to_i }
   => [1, 2, 3, 4, nil, ""]
   [18] pry(main)> sum
   => 10

Метод 6: Eval

Оценивает выражения Ruby в строке.

  [26] pry(main)> a = [1,3,4,5]
  => [1, 3, 4, 5]
  [27] pry(main)> eval a.join '+'
  => 13
  [30] pry(main)> a = [1,3,4,5, nil]
  => [1, 3, 4, 5, nil]
  [31] pry(main)> eval a.join '+'
  SyntaxError: (eval):1: syntax error, unexpected end-of-input
  1+3+4+5+
Натараджа Б
источник
1

3 способа сделать сумму массива

1) array.inject(0){|sum,x| sum + x }

2) array.inject('+')

3) array.join('+')

Poonkodi
источник
0

Или вы можете попробовать этот метод:

def sum arr
  0 if arr.empty
  arr.inject :+
end
Рамин
источник
0

Это самый короткий путь. Попробуй это.

array.inject :+

Тедж Пудель
источник
0
number = [1..100]

number. each do |n|

    final_number = n.sum

    puts "The sum is #{final_number}"
end

* Это хорошо работает для меня, как нового разработчика. Вы можете настроить диапазон номеров, изменив значения в пределах []

Мэдлин Янг
источник
-1

Вы также можете сделать это простым способом

def sum(numbers)
  return 0 if numbers.length < 1
  result = 0
  numbers.each { |num| result += num }
  result
end
Прабхакар Ундурти
источник
-8

Вы можете использовать .map и .sum как:

array.map { |e| e }.sum
шабдара
источник
3
Какой смысл делать карту, возвращающую тот же элемент? это точно так же, какarray.sum
Арнольд Роа
Более того, array.sum не существует в ruby. См ответ Майк Вудхаус
Ulysse BN
Теперь он работает в Ruby 2.4.0
installero