คำตอบสั้น ๆ
สำหรับช่วงจำนวนเต็ม:
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(:+)
ต้องเพิ่ม 999999999999999999999999999998!
การเพิ่มคือ O (log n) ดังนั้นEnumerable#inject
O (n log n) ก็เช่นกัน
ด้วยการ1E30
ป้อนข้อมูลinject
โดยไม่ส่งคืน ดวงจะระเบิดนานก่อน!
ทดสอบ
ง่ายต่อการตรวจสอบว่ามีการเพิ่ม Ruby Integers หรือไม่:
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#+