きにきじ」:今日の気になる記事をきまぐれにご紹介

Rubyで1からnまでの整数をランダムに並び替える(処理速度の比較まで)

Posted at 02:48 on July 24, 2010

Last updated at 05:09 on February 21, 2011

Category: Non-News, Note

Tags: , , , , ,


最近 Ruby の勉強を始めました。で、「Ruby を使って、1から n までの整数をランダム1な順番に並べ替える」ということを考える機会があったので備忘録代わりにポストしておきます。

やり方はとりあえず2つ思い付きました。単純に2つの方法でやってみて「できたー」じゃおもしろくないので、2つの方法それぞれで処理にかかる時間を比較してみました。

基本的なやり方は、n 個の整数をランダムにかつ重複しないように生成していくという感じです。

方法1. すでに出た数か判断して重複ならやり直す

1つ目のやり方は、n 以下の整数をランダムに生成してみて、もしそれがすでに出た数であればもう1度やり直すというものです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@n = ARGV[0].to_i   #要素数を取得
 
def sort1           #方法1. すでに出た数か判断して重複ならやり直す
  sorted1 = []
  @n.times do
    number1 = rand(@n) + 1
    if sorted1.include?(number1)
      redo
    end
    sorted1 << number1
  end
  #print "\n["
  #for i in 1..(@n - 1)
  #  print sorted1[i - 1], ", "
  #end
  #print sorted1[@n - 1], "]\n"
end

1行目 @n = ARGV[0].to_i はいくつの整数を並び替えるのか入力させるための記述です。本質の部分ではないので、3行目 def sort1 から見ていきます。

まず、空の配列 sorted1 を作ります。これは生成された乱数を格納するための配列です。

それから、処理を n 回(整数の数分)繰り返します。繰り返す処理は、まず乱数を発生させ、それがすでに配列 sorted1 に格納済みかどうか評価します。格納済みであればもう1度やり直し、まだ格納されていなければ新たに格納します。

12行目以降の print 関係は、並び替えられた整数を配列っぽく([1, 2, 3, 4] のように)表示する記述です。# を外してコメントじゃなくすれば表示されます。

▲上に戻る▲

方法2. 出た数を除外していく

2つ目のやり方は、出た数を乱数候補から除外していくというものです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@n = ARGV[0].to_i   #要素数を取得
 
def sort2           #方法2. 出た数を除外していく
  nums = []
  1.upto(@n) do |j|
    nums << j
  end
  sorted2 = []
  @n.times do
    number2 = rand(nums.size)
    sorted2 << nums[number2]
    nums.delete_at(number2)
  end
  #print "["
  #for k in 1..(@n - 1)
  #  print sorted2[k - 1], ", "
  #end
  #print sorted2[@n - 1], "]\n"
end

1行目は方法1とまったく同じです。

方法2では、まず1から n までを要素とする配列 nums を作ります。この配列に格納されている数=まだ出ていない乱数候補となります。

それから空の配列 sorted2 を作り、n 回処理を繰り返します。繰り返す処理は、乱数候補の数(nums の要素数)より小さい乱数を発生させ、発生した乱数番目にあたる数を乱数候補 nums から sorted2 に格納し、乱数候補 nums からその数を除外します。

▲上に戻る▲

2つの方法の処理にかかる時間を比較

以上、2つの方法を見てきました。どちらも一応ランダムな並び替えができます。で、処理速度に違いはあるのかな? ということで比較してみました。

結果から言うと、方法2の圧勝でした。以下で結果を見てみましょう。

まず、要素数が100個程度の場合です。これくらいならどちらの方法でもほとんど差がなく、ほぼ0秒で処理が終わりました。

要素数を500個に増やしてみます。方法1が0.02~0.03秒くらいかかるのに対し、方法2はまだほぼ0秒でした。

要素数を1,000個にしてみます。このへんになると体感できるくらいの差が出てきました。方法1だと0.3~0.4秒くらいですが、方法2はまだほぼ0秒でした。

では要素数3,000個ではどうでしょうか。方法1では3秒ほど、方法2では0.005秒ほどでした。

無駄に表にまとめると以下のような結果になりました。

要素数 方法1 方法2
100個 約0.002秒 約0.000秒
500個 約0.025秒 約0.001秒
1,000個 約0.350秒 約0.001秒
3,000個 約3.000秒 約0.005秒

よく言われることですが、今回方法1で使っている include?if などの条件判断系は処理が遅いようです。しかも include? の場合は要素数に比例して処理時間がどんどん増えてしまいますね。ということで、今回の場合は乱数候補を減らしていくという方法2がいいようです。

あぁ、ちなみにこれ、「1から n までの整数」じゃなくて「m から n までの整数」みたいに簡単にできますね。そうすればよかったかな……orz

▲上に戻る▲

※ 追記(July 24, 2010)

すばらしいコメントをいただきました(下のコメント欄参照)。

実は、Ruby には配列の要素をシャッフルする Array#shuffle メソッドが用意されていました! 実際にランダムな並び替えをしたいときはこのメソッドを使うほうがよさそうです。

Array#shuffle メソッドがあるということがわかったので今回考えたことが無駄になったように思えますが、「Ruby に親しむ」とか「自分の頭で考えてみる」という意味では無駄ではありませんでした。今回の経験は自分でメソッドを作らないといけなくなったときにきっと役に立つはず!w

今回考えたコード、それにコメントを反映させたのが以下のものです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
############################################################
#   1からnまでの数をランダムな順番に並び替える
############################################################
 
require "benchmark"
 
Benchmark.bm do |x|
 
  n = ARGV[0].to_i
  sorted1, sorted2, sorted3 = [], [], []
 
  x.report() do       # 方法1. すでに出た数か判断して重複ならやり直す
    n.times do
      number1 = rand(n) + 1
      if sorted1.include?(number1)
        redo
      end
      sorted1 << number1
    end
  end
 
  x.report() do       # 方法2. 出た数を除外していく
    nums = (1..n).to_a
    n.times do
      number2 = rand(nums.size)
      sorted2 << nums[number2]
      nums.delete_at(number2)
    end
  end
 
  x.report() do       # 方法3. shuffleメソッドを使う
    nums = (1..n).to_a
    sorted3 = nums.shuffle
  end
 
  #puts ""
  #p sorted1
  #p sorted2
  #p sorted3
 
end

▲上に戻る▲

※ さらに追記(September 18, 2010)

もう1つ、1から n までの整数をランダムに並べ替える方法を思いつきました。1から n までの整数と乱数を組み合わせた二次元配列を作り、乱数で並べ替えるというものです。

これを方法4として追加したら以下のようなコードになります。相変わらず Array#shuffle メソッドが最速ですが、方法4は方法2よりも高速なようです。n を10,000にした場合、方法2は約2.3秒、方法4は約0.5秒でした。ちなみに方法3は0.02秒ほど。ケタが違いますw また、これくらいの要素数で方法1を試すと時間がかかりすぎていつまで経っても処理が終わらないのでご注意を。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
############################################################
#   1からnまでの数をランダムな順番に並び替える
############################################################
 
require "benchmark"
 
Benchmark.bm do |x|
 
  n = ARGV[0].to_i
  sorted1, sorted2, sorted3, sorted4 = [], [], [], []
 
  x.report() do       # 方法1. すでに出た数か判断して重複ならやり直す
    n.times do
      number1 = rand(n) + 1
      if sorted1.include?(number1)
        redo
      end
      sorted1 << number1
    end
  end
 
  x.report() do       # 方法2. 出た数を除外していく
    nums = (1..n).to_a
    n.times do
      number2 = rand(nums.size)
      sorted2 << nums[number2]
      nums.delete_at(number2)
    end
  end
 
  x.report() do       # 方法3. shuffleメソッドを使う
    nums = (1..n).to_a
    sorted3 = nums.shuffle
  end
 
  x.report() do       # 方法4. 2次元配列で並び替える
    n.times do |num|
      sorted4 << [num + 1, rand()]
    end
    sorted4 = sorted4.sort_by{|key| key[1]}.collect{|key| key[0]}
  end
 
  #puts ""
  #p sorted1
  #p sorted2
  #p sorted3
  #p sorted4
 
end

ちょっと調べてみると、array.sort_by{rand} でランダムソートできるみたいですね。速度は Array#shuffle のほうが上ですが、Array#shuffle 以外の方法であれば array.sort_by{rand} がいいかもしれません。

  • 1 この記事では「ランダム」=「人の恣意性を含まない」程度に考えてください。

▲上に戻る▲


よろしければ以下の関連(してそうな)記事もどうぞ!


5 Responses to “Rubyで1からnまでの整数をランダムに並び替える(処理速度の比較まで)”

  1. takutakku より:

    書いた:: Rubyで1からnまでの整数をランダムに並び替える(処理速度の比較まで) http://j.mp/9DMQUK

      

    » このコメントを引用してコメントする

  2. rubyist_bot より:

    RT $Takutakku: 書いた:: Rubyで1からnまでの整数をランダムに並び替える(処理速度の比較まで) http://j.mp/9DMQUK

      

    » このコメントを引用してコメントする

  3. Ruby好き より:

    Ruby1.8.7以降ならArray#shuffleを使うのが無難ではないでしょうか?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    #!ruby -Ks
    # -*- coding: Windows-31J -*-
    require 'benchmark'
     
    Benchmark.bm do |x|
     
      m, n = ARGV
      array = (m..n).to_a
      sh, sa = [], []
     
      # 1.8.7 or later
      x.report(":shuffle --&gt; ") {sh = array.shuffle}
    #  p sh
    #  puts "[#{sh*', '}]"
     
      # 1.8.8 or later
    #  x.report(":sample  --&gt; ") {sa = array.sample(array.size)}
    #  p sa
    #  puts "[#{sa*', '}]"
     
    end

      

    » このコメントを引用してコメントする

    • Ruby好きさん

      コメントありがとうございます。

      shuffle なんてメソッドがあったとは……。「こんな処理してくれる機能ありそうだよな~」とは思っていましたが、ずばりあったのですね。

      それと、コードの書き方という点でRuby好きさんのコメントは非常に参考になります。m, n = ARGV のように書けること、to_a メソッドで簡単に配列を作れること、printputs メソッドではなく p メソッドを使えば配列を普通に [1, 2, 3, 4] のように表示できることなどを知りませんでした。puts "[#{sh*', '}]" という書き方は(今回は実は使う必要がありませんが)目から鱗でした。

      自分が書いたコードよりもスマートなコードを見たとき、僕は美しい芸術作品に出会ったような感動を覚えます。Ruby好きさんのコメントでこの感動を味わうことができました。ありがとうございます!

        

      » このコメントを引用してコメントする

  4. odamasaki より:

    素晴らしい‼ 学んだことをスグにアウトプットする姿勢、処理速度まで考える深い思考、さすがです‼ RT @Takutakku: 書いた:: Rubyで1からnまでの整数をランダムに並び替える(処理速度の比較まで) http://j.mp/9DMQUK

      

    » このコメントを引用してコメントする

Leave a Reply


Copyright © 2008-2012 鍵山琢実 (KAGIYAMA, Takumi). All rights reserved.

This site's design was checked by IE 6.0+, Firefox 3.5+, GChrome 2.0+, Safari 4.0+, Opera 10.0+, and Sleipnir 2.8+ (all for Windows).
And JavaScript is used for some details. I am so sorry if your browser is not supported.

正当なCSSです! 私はチーム・マイナス6%です

↓ Today's My Favorite Phrase ↓

「I'm always feeling something warm inside you」

From: RADWIMPS - ラバボー