12

「input.txt」と呼ばれる次のようなテキスト ファイルを持つ

some field1a | field1b | field1c
...another approx 1000 lines....
fielaNa | field Nb | field Nc

任意のフィールド区切り文字を選択できます。

スクリプトが必要です。すべての行が使用されるまで、個別の実行ごとに、このファイルから一意の (繰り返されない) ランダムな行が 1 つ取得されます。

私の解決策:ファイルに1列追加したので、

0|some field1a | field1b | field1c
...another approx 1000 lines....
0|fielaNa | field Nb | field Nc

次のコードで処理します。

use 5.014;
use warnings;
use utf8;
use List::Util;
use open qw(:std :utf8);
my $file = "./input.txt";

#read all lines into array and shuffle them
open(my $fh, "<:utf8", $file);
my @lines = List::Util::shuffle map { chomp $_; $_ } <$fh>;
close $fh;

#search for the 1st line what has 0 at the start
#change the 0 to 1
#and rewrite the whole file

my $random_line;
for(my $i=0; $i<=$#lines; $i++) {
    if( $lines[$i] =~ /^0/ ) {
        $random_line = $lines[$i];
        $lines[$i] =~ s/^0/1/;
        open($fh, ">:utf8", $file);
        print $fh join("\n", @lines);
        close $fh;
        last;
    }
}
$random_line = "1|NO|more|lines" unless( $random_line =~ /\w/ );

do_something_with_the_fields(split /\|/, $random_line))
exit;

これは実用的なソリューションですが、次の理由から、あまり良いものではありません。

  • スクリプトを実行するたびに行の順序が変わる
  • 同時スクリプト実行セーフではありません。

より効果的かつエレガントに書く方法は?

4

3 に答える 3

8

行番号のシャッフルされたリストを別のファイルに保持し、使用するたびに最初の行番号を削除するのはどうですか?スクリプトを同時に実行する際の安全性を確保するために、ある程度のロックが必要になる場合があります。

于 2012-07-23T12:24:10.837 に答える
4

perlfaq5から。

ファイルからランダムな行を選択するにはどうすればよいですか?

ファイルをデータベースにロードしたり、ファイル内の行を事前にインデックス付けしたりする以外に、できることがいくつかあります。

以下は、Camel Book のリザーバー サンプリング アルゴリズムです。

srand;
rand($.) < 1 && ($line = $_) while <>;

これは、ファイル全体を読み取るよりも、スペースにおいて大きな利点があります。この方法の証明は、Donald E. Knuth による The Art of Computer Programming、Volume 2、Section 3.4.2 で見つけることができます。

そのアルゴリズムの関数を提供する File::Random モジュールを使用できます。

use File::Random qw/random_line/;
my $line = random_line($filename);

もう 1 つの方法は、ファイル全体を配列として扱う Tie::File モジュールを使用することです。ランダムな配列要素にアクセスするだけです。

すべての Perl プログラマーは、時間を取って FAQ を読む必要があります。

更新:状態を保存する必要があるたびに、一意のランダム行を取得します。状態を保存する最も簡単な方法は、使用した行をファイルから削除することです。

于 2012-07-23T12:47:17.597 に答える
3

このプログラムは、Tie::Fileモジュールを使用してinput.txtファイルとファイルを開きますindices.txt

indices.txtが空の場合input.txt、シャッフルされた順序ですべてのレコードのインデックスで初期化されます。

実行するたびに、リストの最後のインデックスが削除され、対応する入力レコードが表示されます。

use strict;
use warnings;

use Tie::File;
use List::Util 'shuffle';

tie my @input, 'Tie::File', 'input.txt'
        or die qq(Unable to open "input.txt": $!);

tie my @indices, 'Tie::File', 'indices.txt'
        or die qq(Unable to open "indices.txt": $!);

@indices = shuffle(0..$#input) unless @indices;

my $index = pop @indices;
print $input[$index];

アップデート

このソリューションを変更してindices.txt、以前のように空の場合ではなく、まだ存在しない場合にのみ新しいファイルを作成するようにしました。つまり、indices.txtファイルを削除するだけで、新しい一連のレコードを印刷できます。

use strict;
use warnings;

use Tie::File;
use List::Util 'shuffle';

my ($input_file, $indices_file) = qw( input.txt indices.txt );

tie my @input, 'Tie::File', $input_file
        or die qq(Unable to open "$input_file": $!);

my $first_run = not -f $indices_file;

tie my @indices, 'Tie::File', $indices_file
        or die qq(Unable to open "$indices_file": $!);

@indices = shuffle(0..$#input) if $first_run;

@indices or die "All records have been displayed";
my $index = pop @indices;
print $input[$index];
于 2012-07-23T14:47:34.723 に答える