Чому сума набагато швидша за введення (: +)?


129

Тому я запустив деякі орієнтири в Ruby 2.4.0 і зрозумів це

(1...1000000000000000000000000000000).sum

обчислює відразу, тоді як

(1...1000000000000000000000000000000).inject(:+)

займає стільки часу, що я щойно перервав операцію. У мене було враження, що Range#sumце псевдонім, Range#inject(:+)але здається, що це неправда. Отже, як sumпрацює, і чому це так швидше, ніж inject(:+)?

Примітка . Документація Enumerable#sum(яка реалізована Range) не говорить нічого про ледачу оцінку чи щось подібне.

Відповіді:


227

Коротка відповідь

Для цілого діапазону:

  • 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#+.


17
Це справді хороша оптимізація, оскільки обчислення суми діапазону чисел є тривіальним, якщо ви використовуєте правильну формулу, і вибачливо, якщо ви йдете про це ітераційно. Це як намагатися реалізувати множення як серію операцій додавання.
тадман

Тож підвищення продуктивності лише для n+1діапазонів? У мене не встановлено 2.4, або я би перевірив себе, але інші численні об'єкти обробляються базовим доповненням, оскільки вони будуть inject(:+)мінус накладними символами для proc.
engineersmnky

8
Читачі, пригадайте зі своєї математики середньої школи, що n, n+1, n+2, .., mє арифметичним рядом , сума якого дорівнює (m-n+1)*(m+n)/2. Аналогічним чином , сума геометричної прогресії , n, (α^1)n, (α^2)n, (α^3)n, ... , (α^m)n. можна обчислити з виразу закритої форми.
Cary Swoveland

4
\ begin {nitpick} Числова сума - O ((log n) ^ 2), а вводити O (n log n), коли ваші номери можуть бути обмежені без обмеження. \ end {nitpick}
k_g

6
@EliSadoff: Це означає, що це дуже велика кількість. Це означає числа, які не відповідають архітектурному слову, тобто не можуть бути обчислені однією інструкцією та однією операцією в ядрі процесора. Кількість розміру N може бути кодована log_2 N бітами, тому додавання є операцією O (logN), а множення - O ((logN) ^ 2), але може бути O ((logN) ^ 1,585) (Karasuba) або навіть O (logN * log (logN) * ​​log (log (LogN)) (FFT).
Hynek -Pichi- Vychodil
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.