[#44310] プログラムに対して意図したとおりの文字列を渡す方法 — "Information Kanasansoft" <kanasansoft@...>

kanasanです。

9 messages 2007/12/05

[#44332] クラス:相互参照系の作成方法について質問です — "Saburoh Sakai" <sabroh@...>

はじめまして、さかいと申します。

12 messages 2007/12/11

[#44366] Rake改善プロジェクト — "Hajime Hoshi" <hajimehoshi@...>

東京大学修士 1 年の星一と申します。

14 messages 2007/12/19

[ruby-list:44361] Re: 配列のシャッフル

From: "Takehiro Nagai" <lukesilvia@...>
Date: 2007-12-18 09:11:43 UTC
List: ruby-list #44361
永井です。

>> 最初,aの要素は,5個あるので,collect は5回実行されると思うので,
 ここが問題です。実際、問題のrandomize!では、

[1,2,3,4,5].randomize!

としてもブロック内の処理は5回は実行されません。

※collectが5回というよりは、collect内の処理(yield)が配列の各要素に対して1回ずつ、
計5回実行されます。

以下のようにすると、5回実行されていないのが分かります。

--------------------------------
class Array
    def randomize!
      result = collect { p "a"; slice!(rand(length)) }
      replace result
    end

    def randomize
        arr = self.dup
        arr.randomize!
        arr
    end
end

a = [1,2,3,4,5]
p a.randomize!
--------------------------------
C:\>ruby problem.rb
"a"
"a"
"a"
[3, 4, 5]
--------------------------------


成瀬さんが貼って下さったソースを見ると、5回実行されない理由が分かります。

--------------------------------
static VALUE
rb_ary_collect(ary)
    VALUE ary;
{
    long i;
    VALUE collect;

    if (!rb_block_given_p()) {
        return rb_ary_new4(RARRAY(ary)->len, RARRAY(ary)->ptr);
    }

    collect = rb_ary_new2(RARRAY(ary)->len);
    for (i = 0; i < RARRAY(ary)->len; i++) {
        rb_ary_push(collect, rb_yield(RARRAY(ary)->ptr[i]));
    }
    return collect;
}
--------------------------------

注目すべきは for (i = 0; i < RARRAY(ary)->len; i++) { です。
RARRAY(ary)->lenはRubyで言えば、array.lengthメソッドですね。

例えば、a = [1,2,3,4,5]として、問題のrandomize!を実行したとしましょう。
すると、最初の状態で、for (i = 0; i < RARRAY(ary)->len; i++) { の部分は下記のようになります。

for (i = 0; 0 < 5; i++) {

ここで、forループが1回終わると、iとRARRAY(ary)->lenの値は次のようになります。

i                          #=> 1
RARRAY(ary)->len #=> 4

ここでRARRAY(ary)->len が5から4に減った理由は、slice!で要素が取り除かれたためです。
そして、これが問題です。

2回目のループ後には
 i                          #=> 2
RARRAY(ary)->len #=> 3

 3回目のループ後には
 i                          #=> 3
RARRAY(ary)->len #=> 2

ここでfor (i = 0; i < RARRAY(ary)->len; i++) に値をいれてみると
for (i = 0; 3 < 2; i++) となり、ループの継続条件が「偽」になるので、4回目の処理には行かず、ループは終了します。

よって、5回実行されるはずだった処理が3回で終わってしまい、結果の配列の長さは3となってしまいます。

 いかがでしょうか?


07/12/18 に NARUSE, Yui <naruse@airemix.com> さんは書きました:
>
> 成瀬です。
>
> Maehara Masahide (前原正英) wrote:
> >> 最初,aの要素は,5個あるので,collect は5回実行されると思うので,
> >> a.slice!(rand(a.length)) を5回繰り返してみました。
> >
> > 以下の私の実行例だと,(*)の部分以下がダウトですね。
> >
> >> ====================================================================
> >> irb(main):027:0> a = [1,2,3,4,5]
> >> => [1, 2, 3, 4, 5]
> >> irb(main):028:0> a.slice!(rand(a.length))
> >> => 4
> >> irb(main):029:0> a
> >> => [1, 2, 3, 5]
> >> irb(main):030:0> a.slice!(rand(a.length))
> >> => 5
> >> irb(main):031:0> a
> >> => [1, 2, 3]
> >> irb(main):032:0> a.slice!(rand(a.length))
> >> => 1
> >> irb(main):033:0> a
> >> => [2, 3]
> >
> > (*)
> > ここの時点で,aの長さが2になっており,collect できない!
> > 私が最初,勘違いをしたのは,collect は,しだいに減っていく a に対して
> > 新たに実行されていくものと思っていました。
> > このような理解をしたのですが,よろしいでしょうか?
>
> たぶんソースを見た方が早いので、
> 以下に array.c の該当部分のソースを挙げておきました。
>
> static VALUE
> rb_ary_collect(ary)
>     VALUE ary;
> {
>     long i;
>     VALUE collect;
>
>     if (!rb_block_given_p()) {
>         return rb_ary_new4(RARRAY(ary)->len, RARRAY(ary)->ptr);
>     }
>
>     collect = rb_ary_new2(RARRAY(ary)->len);
>     for (i = 0; i < RARRAY(ary)->len; i++) {
>         rb_ary_push(collect, rb_yield(RARRAY(ary)->ptr[i]));
>     }
>     return collect;
> }
>
>
> --
> NARUSE, Yui  <naruse@airemix.com>
> DBDB A476 FDBD 9450 02CD 0EFC BCE3 C388 472E C1EA
>
>

In This Thread