Коротка відповідь
Для цілого діапазону:
Enumerable#sum
повертає (range.max-range.min+1)*(range.max+range.min)/2
Enumerable#inject(:+)
повторюється над кожним елементом.
Теорія
Сума цілих чисел між 1 і n
називається трикутним числом і дорівнює n*(n+1)/2
.
Сума цілих чисел між n
і m
є трикутним числом m
мінус трикутне число n-1
, яке дорівнює m*(m+1)/2-n*(n-1)/2
, і може бути записане (m-n+1)*(m+n)/2
.
Чисельна # сума в Ruby 2.4
Це властивість, яке використовується Enumerable#sum
для цілих діапазонів:
if (RTEST(rb_range_values(obj, &beg, &end, &excl))) {
if (!memo.block_given && !memo.float_value &&
(FIXNUM_P(beg) || RB_TYPE_P(beg, T_BIGNUM)) &&
(FIXNUM_P(end) || RB_TYPE_P(end, T_BIGNUM))) {
return int_range_sum(beg, end, excl, memo.v);
}
}
int_range_sum
виглядає так:
VALUE a;
a = rb_int_plus(rb_int_minus(end, beg), LONG2FIX(1));
a = rb_int_mul(a, rb_int_plus(end, beg));
a = rb_int_idiv(a, LONG2FIX(2));
return rb_int_plus(init, a);
що еквівалентно:
(range.max-range.min+1)*(range.max+range.min)/2
вищезгадана рівність!
Складність
Велике спасибі @k_g та @ Hynek-Pichi-Vychodil за цю частину!
сума
(1...1000000000000000000000000000000).sum
вимагає трьох доповнень, множення, віднімання та ділення.
Це постійна кількість операцій, але множення є O ((log n) ²), так Enumerable#sum
само O ((log n) ²) для цілого діапазону.
вводити
(1...1000000000000000000000000000000).inject(:+)
потрібно 99999999999999999999999999999998 доповнень!
Додавання дорівнює O (log n), так Enumerable#inject
само O (n log n).
З 1E30
введенням, inject
без повернення. Сонце вибухне задовго до цього!
Тест
Легко перевірити, чи додані цілі рубі:
module AdditionInspector
def +(b)
puts "Calculating #{self}+#{b}"
super
end
end
class Integer
prepend AdditionInspector
end
puts (1..5).sum
#=> 15
puts (1..5).inject(:+)
# Calculating 1+2
# Calculating 3+3
# Calculating 6+4
# Calculating 10+5
#=> 15
Дійсно, з enum.c
коментарів:
Enumerable#sum
метод може не поважати метод перевизначення "+"
методів, таких як Integer#+
.