Toplam neden enjekte etmekten çok daha hızlı (: +)?


129

Ruby 2.4.0'da bazı karşılaştırmalar çalıştırıyordum ve şunu fark ettim:

(1...1000000000000000000000000000000).sum

hemen hesaplar oysa

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

o kadar uzun sürdü ki ameliyatı iptal ettim. Bunun Range#sumbir takma ad olduğu izlenimi altındaydım Range#inject(:+)ama bu doğru değil gibi görünüyor. Peki nasıl sumçalışır ve neden bundan çok daha hızlıdır inject(:+)?

NB Belgeleri Enumerable#sum(tarafından uygulanmaktadır Range) tembel değerlendirme veya bu doğrultuda herhangi bir şey söylemez.

Yanıtlar:


227

Kısa cevap

Bir tam sayı aralığı için:

  • Enumerable#sum İadeler (range.max-range.min+1)*(range.max+range.min)/2
  • Enumerable#inject(:+) her öğeyi yineler.

teori

1 ile arasındaki tam sayıların toplamına üçgen sayın denir ve buna eşittir .n*(n+1)/2

Arasında tamsayı toplamı nve müçgen sayısı meksi üçgen sayısı n-1eşittir, m*(m+1)/2-n*(n-1)/2ve yazılabilir (m-n+1)*(m+n)/2.

Ruby 2.4'te numaralandırılabilir # toplam

Bu özellik, Enumerable#sumtamsayı aralıklarında kullanılır:

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 buna benzer :

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);

bu şuna eşdeğerdir:

(range.max-range.min+1)*(range.max+range.min)/2

söz konusu eşitlik!

karmaşa

Bu bölüm için @k_g ve @ Hynek-Pichi-Vychodil'e çok teşekkürler!

toplam

(1...1000000000000000000000000000000).sum üç toplama, bir çarpma, bir çıkarma ve bir bölme gerektirir.

Bu sabit bir işlem sayısıdır, ancak çarpma O ((log n) ²), dolayısıyla Enumerable#sumbir tamsayı aralığı için O ((log n) ²).

iğne yapmak

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

999999999999999999999999999998 eklemeler gerektirir!

Toplama O (log n), yani Enumerable#injectO (n log n).

İle 1E30, girdi olarak injectasla geri ile. Güneş çok önceden patlayacak!

Ölçek

Ruby Tamsayılarının eklenip eklenmediğini kontrol etmek kolaydır:

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

Nitekim enum.cyorumlardan:

Enumerable#sumyöntem "+" gibi yöntemlerin yeniden tanımlanmasına saygı duymayabilir Integer#+.


17
Bu, gerçekten iyi bir optimizasyondur, çünkü doğru formülü kullanırsanız bir dizi sayının toplamını hesaplamak önemsizdir ve yinelemeli olarak devam ederseniz dayanılmazdır. Bu, çarpmayı bir dizi toplama işlemi olarak uygulamaya çalışmak gibidir.
— tadman

Yani performans artışı n+1sadece aralıklar için mi? Yüklü 2.4'üm yok veya kendimi test edecektim, ancak inject(:+)proc için sembolün eksi eksi olacak şekilde temel eklemeyle işlenen diğer Numaralandırılabilir Nesnelerdir .
— engineersmnky

8
Okuyucular, toplamı eşit olan n, n+1, n+2, .., mbir aritmetik diziyi oluşturan lise matematiğinizi hatırlayın (m-n+1)*(m+n)/2. Benzer şekilde, bir toplamı geometrik dizi , n, (α^1)n, (α^2)n, (α^3)n, ... , (α^m)n. kapalı formlu bir ifadeden hesaplanabilir.
— Cary Swoveland

4
\ begin {nitpick} Numaralandırılabilir # toplamı O ((log n) ^ 2) ve sayılarınızın sınırsız olmasına izin verildiğinde enjekte O (n log n) 'dir. \ end {nitpick}
— k_g

6
@EliSadoff: Gerçekten büyük sayılar demek . Mimari kelimeye uymayan sayılar, yani CPU çekirdeğindeki bir komut ve bir işlemle hesaplanamayacağı anlamına gelir. N boyutunun sayısı log_2 N bit ile kodlanabilir, bu nedenle toplama O (logN) işlemidir ve çarpma O ((logN) ^ 2) ancak O ((logN) ^ 1.585) (Karasuba) veya hatta O (logN *) olabilir. günlük (logN) * ​​günlük (günlük (LogN)) (FFT).
— Hynek -Pichi- Vychodil
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.