こんにちはゲストさん。会員登録(無料)して質問・回答してみよう!

解決済みの質問

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 ではよく分かりません。

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

投稿日時 - 2007-07-07 12:21:01

QNo.3146577

困ってます

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

こんにちは、#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;

投稿日時 - 2007-07-11 16:54:51

お礼

みなさん、ありがとうございます。大変勉強になりました。
能力不足で、みなさんのエレガントな解を全ては理解できなかったのですが、#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};
}

投稿日時 - 2007-07-24 13:42:55

ANo.5

このQ&Aは役に立ちましたか?

1人が「このQ&Aが役に立った」と投票しています

回答(5)

ANo.4

W_H

今回、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を後置インクリメントで、変数を中身そのままで一度処理して、その後増加させているところでしょうか。非常に微妙なラインの数字遊びと化しています。
データは綺麗にそろっているようなので、数字判定などは入れてません。
ちょっと自分的に迷わないコメントをつけていたらごちゃごちゃしました。意外に難しい処理ですね。

投稿日時 - 2007-07-08 16:18:04

お礼

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

投稿日時 - 2007-07-24 13:59:01

ANo.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";
}

投稿日時 - 2007-07-08 03:24:49

お礼

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

投稿日時 - 2007-07-24 13:45:46

ANo.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
と表示されます。

投稿日時 - 2007-07-07 17:43:49

お礼

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

投稿日時 - 2007-07-24 13:46:53

ANo.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__

投稿日時 - 2007-07-07 12:54:52

お礼

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

投稿日時 - 2007-07-24 13:47:36

あなたにオススメの質問