アプリ版:「スタンプのみでお礼する」機能のリリースについて

__DATA__を読み取り、過去読んだものと照合計算し期待する結果を出力するにはどのようにプログラムにするのが良いでしょうか?

また、もし良ければ、こういったアルゴリズム?を思いつくにはどのように勉強するのが良いでしょうか?良い方法をご存じでしたら教えて下さい。

宜しくお願い致します。


---期待する結果---
1+5+7
2+6+8
3+7+9
4+8+10
以下続く

---code.pl---
while(<DATA>){
処理;
}
__DATA__
1
2
3
4
5
6
7
8
9
10
以下続く

A 回答 (10件)

そんなの簡単と・・思って補足やお礼を見て・・・そりゃ無理だわ。


そんな膨大なデータは読み込んで処理なんて無理です。
一行ずつ処理して、別のデータに書き出していき、それを改めて処理するとか・・

そうではなく、そのような場合はsed ストリームエディタ
sed( https://ja.wikipedia.org/wiki/Sed_(%E3%82%B3%E3% … )
 を使うべきです。
【引用】____________ここから
sedは、入力を行単位で読み取り、sedスクリプトと呼ばれるシンプルな命令文に従ってテキスト変換などの編集をおこない、また行単位で出力する。基本的には照合ルールに従い場合分けをおこなうフィルタと捉えることができる。
・・・【中略】・・・
 大量のテキストファイルに対して一括で定形の処理をおこなう場合に大きな威力を発揮する。正規表現に対応しており、ある条件の範囲内の文字列を探し出して処理することができる。特定の条件に合った文字列を検索し置換するなどの用途に用いる。
 ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ここまで[sed (コンピュータ) - Wikipedia( https://ja.wikipedia.org/wiki/Sed_(%E3%82%B3%E3% … )]より

sedの使い方は、たくさんネットに転がっている。システムにsedがあれば、Perlからsedに渡せばよい。
    • good
    • 0

これ, 計算するためには少なくとも 7行分のデータは必要なんだから, 「最初の 6行はデータをためておくだけにして, 7行目から実際の処理を行う」と考えることもできる. 例えば


my @data;
for my $i (1 .. 6) {
chomp(my $line = <DATA>);
push @data, $line;
}
while (<DATA>) {
chomp;
push @data, $_;
print $data[0]+$data[4]+$data[6], "\n";
shift @data;
}
とか, ね.
    • good
    • 0
この回答へのお礼

ありがとうございます。
直感的に更に分かりやすい方法ありがとうございます!
カウントさせないことで更にすっきりと見やすくなりましたね。同じ事をするにも色々な方法がある物ですね。

お礼日時:2016/06/21 20:40

#7です。


>コードを写経してみたのですが、
>if($lno > 7){
>shift (@temp);
>}
>が私には思いつきそうにありませんでした。
>もしよろしければ、どのような思考で作られたのか教えて頂けると幸いです。当方独学(ネットからのコピペが多い)で自分では>このアイデアを思いつきそうにありませんでした。

今回、配列内のデータを1つずつ格納し、古いデータを押し出す必要がありますが、
そのようなことを1命令で行う機能は
perlでは残念ながら(今のところは)提供されていません。
その為、1データを押し出し(shift)、その後格納(push)する必要があります。
しかし、データの押し出しは、
データが配列内に7個存在した場合のみ可能であり、その時はシフトして6個にすることが出来ます。
その為、データが1~6個の場合は、シフトしてはいけません。
となると、8行目以降の場合にシフトすることになります。よって、
if($lno>7){
shift(@temp);
}
となります。

又、配列内に7個存在した場合なので、
if (scalar(@temp) == 7){
shift(@temp);
}
でも、同じことになります。(scalar(@temp) で、@temp内の要素の数が取得できます。)
    • good
    • 0
この回答へのお礼

ありがとうございます。とてもよく分かりました。

お礼日時:2016/06/21 20:41

>__DATA__と書きましたのは汎用性のある処理部分の仕方を聞きたいというのが主であったためです。

実際はファイルを分けて質問すべきでした。大変失礼致しました。
>ランダムと言うよりも、7行ほど先読みする程度(8や9行になることはあっても、それ以上には飛ばない程度の汎用性)ですので、データベースを使うと逆に遅くなる気がしています。DBは殆ど利用したこともなく、クエリーを発行して受け取ってなど1億行もあるとものすごく遅くなるイメージがあります。

なるほど、そのような事情でしたか。
それであれば、今回の例であれば7件分を逐次配列に格納し、それを処理するのが、良いと考えます。
但し、以下の
1+5+7
2+6+8
3+7+9
のように処理するデータは規則性があることが前提です。
先読みというよりは、7行まで読み込んだとき、そのデータを配列に溜め込んでおき、それを処理する形になります。
8行目以降は、逐次、配列のデータをシフトしていきます。
以下、サンプルソースです。
--------------------------
$lno = 0;
@temp = ();
while(<DATA>){
chomp $_;
$lno++;
if ($lno > 7){
shift(@temp);
}
push(@temp,$_);
if ($lno >= 7){
$val = $temp[0] + $temp[4] + $temp[6];
print "line=$lno val=$val\n";
}
}

__DATA__
1
2
3
4
5
6
7
8
9
10
----------------------
以下、実行結果です。
line=7 val=13
line=8 val=16
line=9 val=19
line=10 val=22
    • good
    • 0
この回答へのお礼

スッキリとしたコードありがとうございます。
あとで読み返しても、読みやすそうなコードですね。

コードを写経してみたのですが、
if($lno > 7){
shift (@temp);
}
が私には思いつきそうにありませんでした。
もしよろしければ、どのような思考で作られたのか教えて頂けると幸いです。当方独学(ネットからのコピペが多い)で自分ではこのアイデアを思いつきそうにありませんでした。

お礼日時:2016/06/20 00:52

大量処理をするなら、メモリを大量に使用する富豪的プログラミングは厳禁です。


となると、どうしても泥臭い書き方になってしまうので、
少々読みづらくなりますがご容赦ください。

my @a;
my $i = 0;
my ($indexA, $indexB, $indexC, $r);
foreach (<DATA>) {
_ $a[$i%7] = $_; # 7要素の配列にて shift/push を一回ですませる書き方
_ $i++;
_ next if ($i < 7);
_ $indexA = ($i-7)%7; # shift を使わないで $a[0] を表わす
_ $indexB = ($i-3)%7;
_ $indexC = ($i-1)%7;
_ $r = $a[$indexA] + $a[$indexB] + $a[$indexC];
_ print $r . "\n";
}
    • good
    • 0
この回答へのお礼

実際のサンプルコードありがとうございます!!
私が書いた物よりもだいぶスッキリしていますね。

お礼日時:2016/06/20 00:55

#4です。


>ただ、この方法ですとデータ量が増えると(例えば1億行など)非常に膨大にありますと、エラーが出まして上手く処理が出来ないのです。

ということは、__DATA__のあとに、1億行があるようなスクリプトを作成されているということでしょうか?
そのようなスクリプトを作成すること自体に問題があると思いますがいかがでしょうか。
データが1億件あり、それらをランダムに処理したいということであれば、通常はデータベースに格納してそれを参照するのが一般的です。
どうしても、perl単体で(データベース等を使用しないで)行いたいということであれば、ファイルにデータを格納し、そこから
データを逐次取り出す方法があります。但し、ファイルは1データのサイズを固定(例えば10バイト)にする必要があります。
1データのサイズが固定であれば、n番目のデータを取り出すためには、読み込み位置を先頭からn番目に該当する位置にシークさせ、読み込むことが可能です。但し、毎回、ファイルにアクセスするので、処理時間は劇的に遅くなることが予想されます。
    • good
    • 0
この回答へのお礼

すみません。

__DATA__と書きましたのは汎用性のある処理部分の仕方を聞きたいというのが主であったためです。実際はファイルを分けて質問すべきでした。大変失礼致しました。

ランダムと言うよりも、7行ほど先読みする程度(8や9行になることはあっても、それ以上には飛ばない程度の汎用性)ですので、データベースを使うと逆に遅くなる気がしています。DBは殆ど利用したこともなく、クエリーを発行して受け取ってなど1億行もあるとものすごく遅くなるイメージがあります。

また、エラーログみたいな物ですので、データサイズは残念ながら固定ではありません。実は、Excelでやっていたのですが、データ量が増えてきたため同様のことをさせるには104万行の壁にぶちあたりPerlを使った感じです。
AccessなどDBも持っているのですが、以前質問した時、AccessではExcelのようにB1へ=A1+A5+A7を入れれば、オートフィルでスパッとは無理との話で仕方が無くperlに切り替えた感じおります。

お礼日時:2016/06/19 23:16

基本的には配列に全行のデータを格納し、それをあとから参照します。


以下のようにしてください。
--------------------------------------
@line=();
while(<DATA>){
chomp $_; #改行を削除
push(@line,$_);
}
$val = $line[0]+$line[4]+$line[6]; # 1+5+7
print "$val\n"; # 結果の印字
$val = $line[1]+$line[5]+$line[7]; # 2+6+8
print "$val\n"; # 結果の印字
#以下省略
__DATA__
1
2
3
4
5
6
7
8
9
10
------------------------------
以下、実行結果です。(スクリプト名をsample.plとします)
perl sample.pl
13
16
    • good
    • 0
この回答へのお礼

具体的なコードを書いて頂きありがとうございます!!
連続的に出力できるよう少しこちらで書き換えてみました。ただ、この方法ですとデータ量が増えると(例えば1億行など)非常に膨大にありますと、エラーが出まして上手く処理が出来ないのです。
更に、$val = $line[0]+$line[4]+$line[6]; # 1+5+7
のような折角分かりやすい記述からも遠くなりあとで何やってるのか分からなくなってしまうことがあり困るというのもあります。書いている時には理解しているのですが、数日経ったら例えば、
unless($line[$cnt+6]){last;}
など何やってるんだろ?となったりしてしまいます。仕方のない部分はあると思いますが、可能であれば後日にでもコードを見るだけで何をやっているのか分かればと言う思いもあります。


# Windows 7 + active perl
use strict;
my @data;
for(1..100000000){
push (@data,$_); # ★Out of memory!が出る
}
# my @data = <DATA>; # ★行数が増えるとエラーになる
my @line = ();
while(@data){
chomp $_; #改行を削除
push(@line,$_);
}

my $cnt = 0;
for(;;){
unless($line[$cnt+6]){last;}
my $val = $line[$cnt]+$line[$cnt+4]+$line[$cnt+6]; # 1+5+7
print "$val\n";
$cnt++;
}
__DATA__
1
2
3
4
5
6
7
8
9
10
11
12

お礼日時:2016/06/19 21:58

> 7行も離れている場合どうすれば良いのか悩んでおります。



配列を用意して、行を読むごとにpushで配列の最後に追加、7行分たまったらshiftで配列の先頭から読み出しすれば、常に7行のデータのみ保持することで対応可能です。

http://perl.misty.ne.jp/05.html#b
    • good
    • 0
この回答へのお礼

ありがとうございます。
7行を判定する感じでやってみました。
アドバイスを元に書いてみたのですが、数日後読み返すと、これが何をやっているのか理解できない気がしています。コメントアウト無しで、もう少しわかりやすくできない物でしょうかね?
また、特に@line = &Calc(@line);など二回現れていますし、直感的にはどうも違和感を感じています。

# Windows 7 + active perl
use strict;
my @line;
while(<DATA>){
chomp $_; #改行を削除
push(@line,$_);
@line = &Calc(@line);
}
@line = &Calc(@line);

sub Calc(){
my @line = @_;
if(@line != 8){return @line;}
unless($line[6]){return ;}
my $val = $line[0]+$line[4]+$line[6]; # 1+5+7
print "$val\n";
shift @line;
return @line;
}
__DATA__
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

お礼日時:2016/06/19 22:15

> whileの代わりにfor(<DATA>)を使っただけでエラーが出てしまいました



これは、単に間違った使い方をしただけのことです。



<>は、スカラーのコンテキストでは1行読み込み、リストのコンテキストでは全行読み込み1行ずつのリストになります
※ Perlを使う上で、「コンテキスト」「スカラー」「リスト」というのは、とても大事な概念です。
覚えておきましょう。

例えば
@line = <DATA> ;
とすると、 リストのコンテキストになるので、<DATA>は全ての行を読み込み、1行が一つの要素になります。

この状態では
$line[0] は 1行目
$line[1] は 2行目
...
となります。
    • good
    • 0
この回答へのお礼

ありがとうございます。
@line = <DATA> ;でリストになることは何となく理解しているつもりなのですが、データ量が膨大(10GB、1億行程度)もりout of memory!となってしまいました。
その為、1行ずつ読み出せるようにするには、whileを使うべきだとありこちらを試しているところなのです。
先読みを7行目までして、それを保持して順次配列をずらす必要があり、どうしたら良い物かと思っているのです。
因みに、1文字ずらし計算するだけであれば、$tempを使いよくするのですが7行も離れている場合どうすれば良いのか悩んでおります。

while(<DATA>){
if($temp){
print $_ + $temp;
}
$temp = $_;
}

お礼日時:2016/06/19 18:34

メモリに余裕があるのなら、リストやハッシュに全データを取り込んでおけば、どのデータにもアクセスできます。

    • good
    • 0
この回答へのお礼

ありがとうございます。

実際にはもっと複雑で、whileの代わりにfor(<DATA>)を使っただけでエラーが出てしまいましたのでそれは無理でした。

また、一般に、今回の様に、7行など十分メモリに収まるような場合、どのように先読みすればいいのでしょうか?
しかも、その後はずらしながら連続して演算する必要があり、どのようにプログラムを書いたらいい物かと思っています。

お礼日時:2016/06/19 17:41

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!