Resposta curta
Para um intervalo inteiro:
Enumerable#sum
retorna (range.max-range.min+1)*(range.max+range.min)/2
Enumerable#inject(:+)
itera sobre cada elemento.
Teoria
A soma dos números inteiros entre 1 e n
é chamada de número triangular e é igual a n*(n+1)/2
.
A soma dos números inteiros entre n
e m
é o número triangular de m
menos o número triangular de n-1
, que é igual a m*(m+1)/2-n*(n-1)/2
, e pode ser gravado (m-n+1)*(m+n)/2
.
# Sum enumerável no Ruby 2.4
Esta propriedade é usada em Enumerable#sum
para intervalos inteiros:
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
se parece com isso :
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);
que é equivalente a:
(range.max-range.min+1)*(range.max+range.min)/2
a igualdade acima mencionada!
Complexidade
Muito obrigado a @k_g e @ Hynek-Pichi-Vychodil por esta parte!
soma
(1...1000000000000000000000000000000).sum
requer três adições, uma multiplicação, uma subtração e uma divisão.
É um número constante de operações, mas a multiplicação é O ((log n) ²), assim Enumerable#sum
como O ((log n) ²) para um intervalo inteiro.
injetar
(1...1000000000000000000000000000000).inject(:+)
requer 999999999999999999999999999998 adições!
A adição é O (log n), assim Enumerable#inject
como O (n log n).
Com 1E30
como entrada, inject
sem retorno. O sol vai explodir muito antes!
Teste
É fácil verificar se estão sendo adicionados números inteiros Ruby:
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
De fato, a partir dos enum.c
comentários:
Enumerable#sum
O método pode não respeitar a redefinição de "+"
métodos como Integer#+
.
n+1
apenas para intervalos? Eu não tenho o 2.4 instalado ou gostaria de testar a mim mesmo, mas são outros objetos enumeráveis manipulados por adição básica, pois estariaminject(:+)
menos a sobrecarga do símbolo para proc.n, n+1, n+2, .., m
constitui uma série aritmética cuja soma é igual(m-n+1)*(m+n)/2
. Da mesma forma, a soma de uma série geométrica ,n, (α^1)n, (α^2)n, (α^3)n, ... , (α^m)n
. pode ser calculado a partir de uma expressão de forma fechada.