• ベストアンサー

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 ではよく分かりません。 すみませんが、よろしくお願いします。。 サンプルプログラムでも助かります(読んで自分で勉強しますので)。

  • Perl
  • 回答数5
  • ありがとう数6

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

  • ベストアンサー
  • 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

  • Windows版Perlの標準入力&標準出力

    Windows2000にActive Perl5.6をインストールしています。 標準入力をそのまま標準出力するプログラムを作っているのですが うまく動きません。 DOSコマンドの使い方が間違っているのか、Perlの書き方が間違って いるのか、それともWindows版のPerlではこのような使い方はできない のか、教えてください。 ■プログラムソース(c:\test.pl) while(<>){ print; } ■実行方法 c:\data.dat|test.pl>data2.dat ■データ(data.datの中身) こんにちわ ※実行するとdata2.datにdata.datの内容がCOPYされる 予定なのですが、正しく動きません。

    • ベストアンサー
    • Perl
  • test.htm の133048行目だけを削除したい

    10MBぐらいあるファイル(test.htm) の、133048行目を、単に削除したいのですが、perl script (del.pl等のファイル)で、どのように書くのでしょうか?  awkとかsed とかを昔使った経験がありますが、perlのことをはじめたばかりで、さっぱり解りません. またperl初心者に向く基礎的なことを記したURLがありますでしょうか? またperl以外で、こうすれば、良い、という別手法情報も今後の参考には、ありがたいです、よろしくお願いします.

    • ベストアンサー
    • CGI
  • シェルの変数値を読み込む

    シェル(sh)で持っている変数($1~$5)をperlのプログラムに渡したいのですが方法がわかりません。 ご存じの方いらっしゃいましたらお願いします。 Q(1) test.sh: $1、$2、$3、$4、$5 にそれぞれ値が格納 ./test.pl $1 $2 $3 $4 $5 ↑シェル側出力はこうですか? Q(2) test.pl: どうやって変数値($1~$5)を受け取るのでしょうか? 以上、よろしくお願いいたします。

    • ベストアンサー
    • Perl
  • perlスクリプト内の変数を別のCプログラム中の変数に代入するには?

    次のようなことは可能でしょうか。もし可能でしたらどのようなスクリプトを書けばよいでしょうか。 test.cというCプログラムがあってこの中ではprintf("x=%d",k);と出力するようになっています。 perlスクリプト内で同様に変数kを定義してforループでまわします。そのループ内でtest.cファイルを開きtest.c内の変数kにperlで定義した変数kの値が代入されてコンパイル・実行するようなスクリプトを組みたいのですが。 なぜこのようなことをしているかというと太郎君がk歳のときのj月のときの身長を記録すると言うようなことをやっているためです。身長の計算は別のCプログラムでやっています。perlスクリプトで1歳、2歳、3歳というディレクトリを作って、各ディレクトリに1月身長.dat、2月身長.dat、・・・12月身長.datというデータをおくことを考えています。

  • perl で複数のデータ列を結合して出力する方法

    perl で、他のいくつかのプログラムが出力するデータ列を結合して出力する方法はないでしょうか? 例えば、三つのプログラム programA, programB, programC の出力が % programA 1 2 3 4 % programB 1 4 9 16 % programC 1 8 27 64 だとします。 perl スクリプトでこれらのプログラムを呼び出して、 % test.pl programA programB programC 1 1 1 2 4 8 3 9 27 4 16 64 のようにしたいのです。 よろしくお願いします。

    • ベストアンサー
    • Perl
  • ファイル入力

    1 20 2 30 3 95 4 52 5 90 3 Maximum  上記の様な入力ファイルinput.datのうち1~5行目のデータを表示するプログラムを作っています。  6行目は空白で、7行目は2列目の中で最大値をとる行の1列目の数値が入っています。  1列目の数値は最後の行を除いて重複することはありません。  入力データの行数はファイルによって最大20行まで変動します。列数は2列で固定です。  以下のプログラムのままでは6行目以降のデータも読み取ってしまい、出力がおかしくなってしまいます。  1~5行目のデータのみ出力するにはどうしたらよいでしょうか。  ご存知の方、お手数ですが教えてください。よろしくお願いします。 #include <stdio.h> #include <stdlib.h> #define row 20 #define col 2 int main(){ int i,j,data[20][2]; FILE *fp; if((fp=fopen("input.dat","r"))==NULL){ fprintf(stderr,"Cannot open file input.dat\n"); exit(1); } for(i=0;i<row;i++){ for(j=0;j<col;j++){ fscanf(fp,"%d",&data[i][j]); printf("%d ",data[i][j]); } printf("\n"); } fclose(fp); return 0; }

  • ファイルの3行目までを出力したい

    Perl初心者です。 test.txtというファイルがあって、その中の1行目から3行目までを 出力したい場合はどうしたらいいでしょうか? open(FILE,"test.txt") || die "Open Error.\n"; @data = <FILE>; close(FILE); foreach (@data) { print $_; } これだと、ファイルの中身が全て出力されてしまいます。

    • ベストアンサー
    • Perl
  • WindowsでPerlをする際,1行目の"#! ~"はどのように?

    Perlの参考書・本等ではプログラムファイルの第1行目は #! /usr/local/bin/perl という1行がよく有りますが,WindowsXPを用いている場合では,この1行をどう直せばよいのでしょうか? #! (perl.exeが存在するフォルダのパス) でよいのでしょうか? 私はWindowsXPを使っていて,この1行を使わずにプログラムを書いておりましたので,この1行の意味がよく分かりません。 ある参考書には,"#!はその行に書いたコマンドに,ファイルの残りの部分を渡して実行すると言う性質を持っている"と有りました。だから,試しにfile1.plとfile2.txtを準備し, file1.plの中身  #! (perlの存在するフォルダのパス)\perl.exe  while(<STDIN>){   print;  } file2.txtの中身  hello world として,コマンドプロンプトで file1.pl < file2.txt としたのですが正しく動作しませんでした.(perl file1.pl < file2.txt と入力した場合は正しく"hello world"となりました)

    • ベストアンサー
    • Perl
  • while(<ハンドラ>) {} で行数をカウント

    こんにちは。 掲示板をperlで作るという課題に取り組んでいるのですが、下記の部分の 動きだけがどうしても期待する動作がえられず、困っています。 なにか試した方が良い事がありましたら、ご指摘いただけないでしょうか。 プログラムの説明: POST されたコメントを data.dat に追記していくプログラムです。 ファイルハンドラから一行づつ読み込んで、行数を $count でカウントし、 『X行目&&コメント』のようにコメントの先頭に行数を表示します。 #!/usr/bin/perl $myfile = 'data.dat'; (省略) sub piyo{ open(HOGE, ">> $myfile"); flock(HOGE, 2); my $count = 1; while(<HOGE>){ $count++; }; print HOGE "$count行目&&in{'comment'}\n"; close(HOGE); }; data.dat の期待する結果は下記です。 1行目&&コメント 2行目&&コメント 3行目&&コメント ..... 実際に data.dat に書き込まれた結果は下記でした。 1行目&&コメント 1行目&&コメント 1行目&&コメント ..... 試した事1: open(HOGE, ">> $myfile"); を open(HOGE, "+>> $myfile"); した。 試した事2: while(<HOGE>){ $count = $count + 1; }; とした。 どちらでも結果に違いがありませんでした。 よろしくお願いします。

    • ベストアンサー
    • Perl
  • pythonでファイルのコメント行を削除したい

    pythonを勉強し始めたばかりなのですが、ちょっと困っています。 pythonでデータファイルのコメント行を削除して、データだけ後の計算に回したいのですが、コメント行を削除する方法がわからなくて困っています。 データファイルは以下のようになっています。 #test1 #test2 test2 #test3 teset3 test3 0, 10 1, 2 2, 9 3, 3 4, 4 5, 7 コメント行は頭に#が付くようになっていますが、この例のように3行ではなく、他の行数になった場合でも対処できるようにしたいです。