• ベストアンサー

3行ずつ足す

AWK を使っていあのですが、perl への移行を目指して勉強しています。 (1) 行数が3の倍数 (2) 列数は分からない(スペース区切り。固定列数) (3) # はコメント行 というデータがあります。 このデータを perl に読み込ませて、  三行ずつ足して出力する ようなプログラムをつくっています。 例えば、6行4列のデータ test.dat # comment 1 2 3 5 3 2 1 6 2 2 2 7 4 5 6 7 6 5 4 6 5 5 5 5 を cat test.dat | sum3row.pl のように perl のプログラム sum3row.pl に読みこませて、三行ずつ足して # comment 6 6 6 18 18 18 18 18 という出力を得たいのです。 次の点で困ってます。 ●AWK の場合、今読み込んでいる行の列数は NF という変数で分かるのですが、perl ではよく分かりません。データへのアクセス自体は $data[2] のようにすれば良いことは分かっているのですが・・。 ●AWK の場合、今読み込んでいる行の番号は NR という変数で分かるのですが、perl ではよく分かりません。 すみませんが、よろしくお願いします。。 サンプルプログラムでも助かります(読んで自分で勉強しますので)。

質問者が選んだベストアンサー

  • ベストアンサー
  • g_p_
  • ベストアンサー率53% (28/52)
回答No.5

こんにちは、#2です。 皆さん短く書くのがお好きなようなので、 もう一度チャレンジです。(暇ですね~私) map してますが、ループが一つなのでわかり易いかな、と。 単に標準入力から読み込んで、計算して、 3回計算したら、出力してカウンターとバッファをクリア。 最後に3回計算する前に読み込みが終了した場合の出力があるのがちょっとカッコ悪い気もしますが。 データ行が必ず3行セットなら、最終行は要りません。 無理すればもうちょっと短く書けるんでしょうが、このくらいが 分かりやすいかな?と思っています。 #! /usr/bin/perl use strict; use warnings; my( $i , @sum ); while ( <> ) {   next if /^#/ and print;   chomp;   my @col = split /\s/;   map { $sum[$_] += $col[$_] } ( 0 .. $#col );   if ( ++ $i == 3 ) {     print join(' ' , @sum) , "\n";     ( $i , @sum ) = undef;   } } print join(' ' , @sum) , "\n" if @sum;

white-tiger
質問者

お礼

みなさん、ありがとうございます。大変勉強になりました。 能力不足で、みなさんのエレガントな解を全ては理解できなかったのですが、#5さんのをベースに下記のものを作ってみました。 #! /usr/bin/perl -w use warnings; if (@ARGV == 0) { $period = -1; } elsif (@ARGV == 1) { $period = $ARGV[0]; } else { &usage; } my $i = 0; while (<STDIN>) { next if /^#/ and print; chomp; my @col = split /\s/; map { $sum[$_] += $col[$_] } ( 0 .. $#col ); $i++; if ( $i == $period ) { print join(' ' , @sum) , "\n"; $i = 0; @sum = undef; } } if ($period == -1) { print join(' ' , @sum) , "\n"; } elsif ($i > 0) { print qq{The number of data rows is not a multiple of $period!!\n}; } sub usage { my $str = "usage:\n"; $str .= "cat data.txt | sumColsPeriodically.pl <n>\n"; $str .= "<n> : number of rows to sum up periodically.\n"; $str .= " (default = number of all rows).\n"; die qq{$str\n}; }

その他の回答 (4)

  • W_H
  • ベストアンサー率47% (21/44)
回答No.4

今回、NFとNRを使ってみました。white-tigerさんの肌に合えばと思います。 設定として、ファイルから読み込んだデータが@d1に一行ずつ入っていて、出力データは@outに入ります。コメントはそのまま出力されます。 @d1=("# comment", "1 2 3 5", "3 2 1 6", "2 2 2 7", "4 5 6 7", "6 5 4 6", "5 5 5 5"); @out=(); for($NF=0;$NF<=$#d1;){#行数は$NF my(@tmp);#データを仮に足していく配列 for($b=1;$b<=3;++$b){#三行一セット while($d1[$NF]=~/#/){push(@out,$d1[$NF]);++$NF;}#コメントの追加 my(@d2)=split(/\s/,$d1[$NF++]);#一行のデータを列に分割 for($NR=0;$NR<=$#d2;++$NR){列のデータを配列に足していく $tmp[$NR]+=$d2[$NR]; } } push(@out,join(" ",@tmp));#一時配列のデータを一行にする } foreach(@out){print "$_\n";}#出力 基本的な考え方は、データを一行読むごとに、一時的に作った配列に数字を足していくのですが、その時に$NRを上手く使って、縦に足します。 そしてそれ三回繰り返し、配列のデータをスペースを区切りとして、一行のデータにまとめ(join)出力配列に入れる。 それを一セットとして、何度も続ける感じです。 コツは始めのforの$NFで行数を数えず、中で数えるところ。 myを使って、こまめに配列のデータを綺麗に削除して、次に影響が出ないようにするところ。 それと列にデータを分割する際に、$NFを後置インクリメントで、変数を中身そのままで一度処理して、その後増加させているところでしょうか。非常に微妙なラインの数字遊びと化しています。 データは綺麗にそろっているようなので、数字判定などは入れてません。 ちょっと自分的に迷わないコメントをつけていたらごちゃごちゃしました。意外に難しい処理ですね。

white-tiger
質問者

お礼

分かりやすかったです。 ありがとうございます!

回答No.3

> 今読み込んでいる行の列数 @dataがすでにあるって前提だと、$cols = @data; とか scalar(@data) とかです。 > 今読み込んでいる行の番号 $. と言う名前の特殊変数に入ります。 ただ、今回の処理だとコメントがあるので $. % 3 == 0 が使えないですね。 一応処理も書いてみました。思ったよりスマートに書きにくい処理ですね。こんなになっちゃいました。 while(1){ my @lines = (); while( @lines < 3 && (my $l = scalar(<>)) ){ push @lines, [split(/ /, $l)] unless $l =~ /^#/ }; @lines or last; print join(' ', map {my $s = 0; for my $l (@lines) { $s += $l->[$_]; } $s;} (0 .. $#{ $lines[0] }) ), "?n"; }

white-tiger
質問者

お礼

ご回答ありがとうございました。 うーむ、修行せねば。

  • g_p_
  • ベストアンサー率53% (28/52)
回答No.2

こんにちは、 すでに回答が出ちゃってますが、 私も書いてみたので、参考までに。 #! /usr/bin/perl use strict; use warnings; while ( my @lines = &get_recs(3) ) {   my @sum;   for my $line ( @lines ) {     my @cols = split /\s/ , $line;     for my $i ( 0 .. $#cols ) {       $sum[$i] += $cols[$i];     }   }   print join(' ' , @sum) , "\n"; } exit; sub get_recs {   my $return_rows = shift;   my @lines = ();   while ( @lines < $return_rows ) {     my $line = <>;     last unless $line;     if ( $line =~ /^#/ ) {       print $line;       next;     }     chomp $line;     push @lines , $line;   }   return @lines; } #1さんよりごちゃごちゃしてますが、 とりあえず、 # comment 6 6 6 18 15 15 15 18 と表示されます。

white-tiger
質問者

お礼

ありがとうございます! 勉強になりました。

  • guci-ok
  • ベストアンサー率33% (49/146)
回答No.1

試しに書いてみました。完璧ではないかもしれませんが、 こんな感じです。 #!Perl use strict; while (<>) { print, next if /^#/; my @rec3 = ($_); # 1行目 my $rec1 = <>; # 2行目 my $rec2 = <>; # 3行目 push @rec3 => $rec1, $rec2; # ひとつに calc3print(@rec3); # 計算するサブルーチン } sub calc3print { my (@rec) = @_; my @col; for (@rec) { chomp; my @col_this = split; my $index = 0; $col[$index++] += $_ for @col_this; } print join(' ' => @col), "\n"; } __END__

white-tiger
質問者

お礼

ありがとうございます! 勉強になりました。

関連するQ&A

専門家に質問してみよう