負のインデックス
array.cを少し。pop、shift、[]=あたりを見ていると、何だかRubyがCに見えてくる。Arrayオブジェクトって、結局はVALUE型、つまりunsigned longの配列でしかない。ちょっと今さら驚いたのは、memcpy、memmoveがコピー先、コピー元で重複があってもいいというところとか。そうだったのか。
822 /* 823 * call-seq: 824 * array.unshift(obj, ...) -> array 825 * 826 * Prepends objects to the front of <i>array</i>. 827 * other elements up one. 828 * 829 * a = [ "b", "c", "d" ] 830 * a.unshift("a") #=> ["a", "b", "c", "d"] 831 * a.unshift(1, 2) #=> [ 1, 2, "a", "b", "c", "d"] 832 */ 833 834 static VALUE 835 rb_ary_unshift_m(int argc, VALUE *argv, VALUE ary) 836 { 837 long len; 838 839 if (argc == 0) return ary; 840 rb_ary_modify(ary); 841 if (ARY_CAPA(ary) <= (len = RARRAY_LEN(ary)) + argc) { 842 ary_resize_capa(ary, len + argc + ARY_DEFAULT_SIZE); 843 } 844 845 /* sliding items */ 846 MEMMOVE(RARRAY_PTR(ary) + argc, RARRAY_PTR(ary), VALUE, len); 847 MEMCPY(RARRAY_PTR(ary), argv, VALUE, argc); 848 ARY_INCREASE_LEN(ary, argc); 849 850 return ary; 851 }
unshiftだとニュルンとarrayを引き伸ばしてるけど、逆にpopだとarrayのメモリ上のサイズを縮めたりしている。
703 VALUE 704 rb_ary_pop(VALUE ary) 705 { 706 long n; 707 rb_ary_modify_check(ary); 708 if (RARRAY_LEN(ary) == 0) return Qnil; 709 if (ARY_OWNS_HEAP_P(ary) && 710 RARRAY_LEN(ary) * 3 < ARY_CAPA(ary) && 711 ARY_CAPA(ary) > ARY_DEFAULT_SIZE) 712 { 713 ary_resize_capa(ary, RARRAY_LEN(ary) * 2); 714 } 715 n = RARRAY_LEN(ary)-1; 716 ARY_SET_LEN(ary, n); 717 return RARRAY_PTR(ary)[n]; 718 } 719
ちょっとおもしろいなと思ったのは、709〜711行目。ARY_CAPAは確保済みのメモリ容量から来る利用可能サイズ。即値埋め込みの場合もあるのでマクロになっているっぽい。で、arrayの長さが確保済みの長さの3分の1以下で、かつARY_DEFAULT_SIZE(16)よりも大きい場合には、713行目で縮めている。この辺の、いつ、どの程度伸び縮みさせるかって経験則で決めてるのかな。
Array#[]=を見て、ちょっとあれっと思った。idxが負のとき、お尻から数えるのはいいとして、配列サイズの絶対値より大きい場合には、例外を上げている。
567 void 568 rb_ary_store(VALUE ary, long idx, VALUE val) 569 { 570 if (idx < 0) { 571 idx += RARRAY_LEN(ary); 572 if (idx < 0) { 573 rb_raise(rb_eIndexError, "index %ld out of array", 574 idx - RARRAY_LEN(ary)); 575 }
>> a = [1,2,3] => [1, 2, 3] >> a[-1] = 9 => 9 >> a => [1, 2, 9] >> a[-3] = 10 => 10 >> a => [10, 2, 9] >> a[-5] = 10 IndexError: index -5 out of array from (irb):6:in `[]=' from (irb):6 from /usr/local/bin/irb:12:in `<main>'
正方向に範囲外を指定したときには配列を自動的に伸ばすのに負方向のときは例外というのは一貫性がないようにも感じる。
>> a = [1,2,3] => [1, 2, 3] >> a[5] = 10 => 10 >> a => [1, 2, 3, nil, nil, 10]
なのだから、
>> a = [1,2,3] => [1, 2, 3] >> a[-5] = 10 => 10 >> a => [10,nil,1, 2, 3]
でもいいような気がするんだけど。