2012-06-13 13 views
10

Rubyの本当に美しい例をいくつか見てきました。私はRubyを賞賛するのではなく、Rubyを制作できるように思考を変えようとしています。私はそれがメモリにファイル全体の内容を格納することなくちゃが短く、よりエレガントな方法でこれを行うには可能性がありますように感じるRuby:テキストファイルから無作為な行を選択するエレガントな方法はありますか?

def pick_random_line 
    random_line = nil 
    File.open("data.txt") do |file| 
    file_lines = file.readlines() 
    random_line = file_lines[Random.rand(0...file_lines.size())] 
    end 

    random_line                                        
end 

:ここで私は、ファイルのうち、ランダムな行を取り出すために思い付くことが最高です。ある?

+2

これは、「これをRuby_でどうやって行うのですか」の質問、または「これをO(N)space_未満の質問で行う方法」の詳細ですか?後者の場合は、[リザーバサンプリング](http://gregable.com/2007/10/reservoir-sampling.html)を調べてください。 – zwol

+0

私の簡単な実装は、ファイル内のランダムな位置を探して、改行に進むことです。 –

+0

@SamSaffronこれは、すべての行がまったく同じ長さでない限り、一様にランダムな行を与えません。 – zwol

答えて

13

ランダムな行の現在の候補以外のものを保存せずに実行できます。

def pick_random_line 
    chosen_line = nil 
    File.foreach("data.txt").each_with_index do |line, number| 
    chosen_line = line if rand < 1.0/(number+1) 
    end 
    return chosen_line 
end 

したがって、最初の行は確率1/1 = 1で選択されます。第2の線は確率1/2で選択されるので、半分の時間は第1の半分を維持し、第2の線は第2の線に切り替える。

次に、3行目は1/3の確率で選択されます。つまり、3分の1の時間が選択され、残りの2/3が最初の2つのいずれかを選択します。それぞれ2行目で50%の確率で選択されていたので、3行目で1/3の確率で選択されます。

などです。 N行目では、1-Nからのすべての行が選択される確率は1/Nであり、ファイル全体を通して保持されます(ファイルが1/)がイプシロンよりも小さい:))。そして、ファイルを1回だけ通過させ、一度に2つ以上の行を保存することはありません。あなたはこのアルゴリズムで本物の簡潔なソリューションを取得するつもりはないが、あなたがしたい場合は、ワンライナーにそれを回すことができ

EDIT:

def pick_random_line 
    File.foreach("data.txt").each_with_index.reduce(nil) { |picked,pair| 
    rand < 1.0/(1+pair[1]) ? pair[0] : picked } 
end 
+0

難しい選択ですが、質問の記憶部分に取り組む唯一の答えはあなたのものでした。 Daveの答えと同じように、これの冗長さを減らす方法があるのだろうかと思います。ありがとう! – Tres

+0

( '.to_f'は余計です)。時にはこれは何も返さないでしょうか? – steenslag

+0

@steenslag:true、 '1.0'を指定すると、' .to_f'は省略できます。しかし、いいえ、 'rand'は常に1未満であるので、常にゼロを返さないので、常に少なくとも最初の行を選択します。 –

2

これは、あなたが思い付いたものよりもはるかに良いではありませんが、少なくともそれは短いです:あなたのコードを作るために何ができる

def pick_random_line 
    lines = File.readlines("data.txt") 
    lines[rand(lines.length)] 
end 

一つのことよりRubyishは、括弧を省略しています。 readlines()size()の代わりにreadlinessizeを使用してください。

+0

あなたのコメントをありがとう。ええ、私は私の答えを書いたときにそれについて疑問に思っていた。私は、ファイルを適切に閉じている間、簡潔な構文を維持する方法があるかどうかを調べようとしています。 – Mischa

+0

あなたはそれを見つけました、それは;-)のように見えます –

35

ランダムエントリが既にありますセレクタはRuby Arrayクラスに組み込まれています:sample()。

def pick_random_line 
    File.readlines("data.txt").sample 
end 
+0

ワンダフルで、メソッドを1行に減らしました! (自分でも 'sample'について知っていたはずです) – Mischa

+5

警告:ファイルが大きかったら苦しんでしまうでしょう。 –

+2

まだ、Ruby 1.8を使用しているそれらの保持者については、 'sample'は' choice'と呼ばれていました。 –

-1

ファイルを解析し、ゼロとファイルサイズの間の乱数を選択し、ファイル内のそのバイトを探します。次の改行までスキャンし、次の行を読み込んで返します(ファイルの最後ではないと仮定します)。

+1

これは悪い考えです。なぜなら、最初の行が決して選ばれず、長い行の後の行が選択される可能性が高いから、ランダムではないからです。 – Mischa

+0

良い点。私はあなたがちょうど後方にスキャンして、あなたがヒットした場所でその行を読むために転送することができると思います。ファイルが巨大であるか、パフォーマンス要件が愚かでない限り、その努力に値するものではありません。 – Bill

0

1つのライナー:

def pick_random_line(file) 
    `head -$((${RANDOM} % `wc -l < #{file}` + 1)) #{file} | tail -1` 
end 

あなたはそれがRubyのないことを抗議した場合、Rubyはバナナとは違ってを題した今年のEurukoで話を探しに行きます。

PS:不正な構文の強調表示を無視します。ここで

+0

それ以外は、Ruby on Windowsでは動作しません。 – Azolo

+0

True。RubyをUnixの外で使うのは不自然だと私はMartiの指摘しています。 –

+0

私はそれがあまりにも不自然ではないことを願っています。もしWindowsが[Ruby supported platform list](http://bugs.ruby-lang.org/projects/ruby-trunk/wiki/SupportedPlatforms)我々はそれを修正するためのコミュニティとして働くだろう。しかし、それはstackoverflowの問題ではないので、私はそれを残しておきます。 – Azolo

0

マルコ優秀な答えの短いバージョン、ない

def pick_random_line number=1, chosen_line="" 
    File.foreach("data.txt") {|line| chosen_line = line if rand < 1.0/number+=1} 
    chosen_line 
end 
+0

これは、短くても必ずしも良いとは限りません。ローカル変数を引数に変更するだけで行を保存する??? – Mischa

3

この機能は、あなたが必要とする正確に何をしてもDaveのと短いです。

これはワンライナーではありません。しかし、それは任意のサイズのテキストファイル(ゼロサイズ、多分:)を除いて動作します。

def random_line(filename) 
    blocksize, line = 1024, "" 
    File.open(filename) do |file| 
    initial_position = rand(File.size(filename)-1)+1 # random pointer position. Not a line number! 
    pos = Array.new(2).fill(initial_position) # array [prev_position, current_position] 
    # Find beginning of current line 
    begin 
     pos.push([pos[1]-blocksize, 0].max).shift # calc new position 
     file.pos = pos[1] # move pointer backward within file 
     offset = (n = file.read(pos[0] - pos[1]).rindex(/\n/)) ? n+1 : nil 
    end until pos[1] == 0 || offset 
    file.pos = pos[1] + offset.to_i 
    # Collect line text till the end 
    begin 
     data = file.read(blocksize) 
     line.concat((p = data.index(/\n/)) ? data[0,p.to_i] : data) 
    end until file.eof? or p 
    end 
    line 
end 

それを試してみてください。

filename = "huge_text_file.txt" 
100.times { puts random_line(filename).force_encoding("UTF-8") } 

無視できる(私見)欠点:それは

  1. 長いライン、高いチャンスを選んだことでしょうが。

  2. では、 "\ r"という行区切り文字(Windows固有)は考慮されていません。 Unixスタイルの行末でファイルを使用する!

+0

大きなファイルには最適なオプションがあります。それ以外の場合は、読み込み時間が大幅に変わります。 –

関連する問題