電子書籍の厳選無料作品が豊富!

perl初心者です。
下記のような同時刻の2つのデータを1つのデータに統合させたいのですが
うまく出来ず困っています。
どうかお知恵を貸していただけないでしょうか。

データは時刻(時:分:秒), 値1, 値2になっています。
test1.txtの同時刻の後ろにtest2.txtの値1と値2を入れ、
欠測値には-999を入れるプログラムを作っています。
厄介なのは、
開始時刻がtest1.txtよりtest2.txtが早い場合や
終了時刻がtest2.txtよりtest1.txtが遅い場合がある事です。

test1.txt
10:13:14, 3.1, 0.1
10:13:15, 6.1, 0.3
10:13:16, 8.7, 0.2
10:13:17, 12.8, 0.3
10:13:18, 13.4, 0.5
10:13:19, 15.2, 0.4

test2.txt
10:13:16, 32.5, 0.01
10:13:17, 33.1, 0.03
10:13:18, 36.2, 0.02
10:13:19, 34.3, 0.01
10:13:20, 33.8, 0.04
10:13:21, 32.6, 0.09
10:13:22, 32.1, 0.08


希望結果
test.txt
10:13:14, 3.1, 0.1, -999.0, -999.00
10:13:15, 6.1, 0.3, -999.0, -999.00
10:13:16, 8.7, 0.2, 32.5, 0.01
10:13:17, 12.8, 0.3, 33.1, 0.03
10:13:18, 13.4, 0.5, 36.2, 0.02
10:13:19, 15.2, 0.4, 34.3, 0.01
10:13:20, -999.0, -999.0, 33.8, 0.04
10:13:21, -999.0, -999.0, 32.6, 0.09
10:13:22, -999.0, -999.0, 32.1, 0.08

以下が自分が作成したプログラムです。
open IN_1, "test1.txt";
open IN_2, "test2.txt";
open OUT, ">test.txt";

while ($input1 = <IN_1>) { # test1.txtの処理
chomp $input1;# 改行削除
@input1 = split(/,/, $input1); # カンマ区切り
$n = $n + 1;
$time1[$n] = @input1[0];
@time1 = split(/:/, $time1[$n]); # 時刻をコロン区切り
@hours1[$n] = @time_l[0];
@min1[$n] = @time_l[1];
@sec1[$n] = @time_l[2];
@a[$n] = @input1[1]; # 値1
@b[$n] = @input1[2]; # 値2
}

while ($input2 = <IN_2>) { # test2.txtの処理
chomp $input2;
@input2 = split(/,/, $input2);
$m = $m + 1;
$time2[$m] = @input2[0];
@time2 = split(/:/, $time2[$m]);
@hours2[$m] = @time2[0];
@min2[$m] = @time2[1];
@sec2[$m] = @time2[2];
@c[$m] = @input2[1]; # 値1
@d[$m] = @input2[2]; # 値2
}

# 開始と終了時刻の計算
# test1.txtの時刻
$hours1_S = @hours1[1]; # 開始時
$hours1_E = @hours1[$n]; # 終了時
$min1_S = @min1[1]; # 開始分
$min1_E = @min1[$n]; # 終了分
$sec1_S = @sec1[1]; # 開始秒
$sec1_E = @sec1[$n]; # 終了秒
$time1_S = $hours1_S*3600 + $min1_S*60 + $sec1_S; # 開始時刻を秒に計算
$time1_E = $hours1_E*3600 + $min1_E*60 + $sec1_E; # 終了時刻を秒に計算

# test2.txtの時刻
$hours2_S = @hours2[1];
$hours2_E = @hours2[$m];
$min2_S = @min2[1];
$min2_E = @min2[$m];
$sec2_S = @sec2[1];
$sec2_E = @sec2[$m];
$time2_S = $hours2_S*3600 + $min2_S*60 + $sec2_S;
$time2_E = $hours2_E*3600 + $min2_E*60 + $sec2_E;

if($time1_S <= $time2_S){ # 開始時刻の比較
$starttime = $time1_S;
}else{
$starttime = $time2_S;
}

if($time1_E <= $time2_E){ # 終了時刻の比較
$endtime = $time2_E;
}else{
$endtime = $time1_E;
}
$j = $endtime - $starttime;# 全体のデータ個数

# test.txtへ出力
for($i=1; $i<=$j; $i++){
if(@hours1[$i] != @hours2[$i] && @min1[$i] != @min2[$i] && @sec1[$i] != @sec2[$i]){
printf OUT "%2d:%2d:%2d %5.1f% 5.1f %5.1f %7.2f\n",
@hours1[$i], @min1[$i], @sec1[$i], @a[$i], @b[$i], -999, -999;
}elsif(@hours1[$i] == @hours2[$i] && @min1[$i] == @min2[$i] && @sec1[$i] == @sec2[$i]){
printf OUT "%2d:%2d:%2d %5.1f% 5.1f %5.1f %7.2f\n",
@hours1[$i], @min1[$i], @sec1[$i], @a[$i], @b[$i], @c[$i], @d[$i];
}else{
printf OUT "%2d:%2d:%2d %5.1f% 5.1f %5.1f %7.2f\n",
@hours2[$i], @min2[$i], @sec2[$i], -999@, -999, @c[$i], @d[$i];
}
}

close IN_1;
close IN_1;
close OUT;


とても汚いプログラムになってしまいました…
このプログラム以外でも構いませんのでどうかよろしくお願い致します。

A 回答 (4件)

use strict;


use warnings;

# 時刻をキーとする出力用ハッシュ
my %data;

open(my $fh1, '<', 'test1.txt') or die($!);
while(<$fh1>) {
chomp;
my($time, $data1, $data2) = split /,\s*/;
# 配列のリファレンスを生成し、$timeをキーとするハッシュに値として代入する
$data{$time} = [$data1, $data2, "-999.0", "-999.00"];
}
close($fh1);

open(my $fh2, '<', 'test2.txt') or die($!);
while(<$fh2>) {
chomp;
my($time, $data3, $data4) = split /,\s*/;
if(exists $data{$time}) {
# ハッシュのキーがすでに存在する = test1.txtに同じ時刻の値があった場合
# 配列のリファレンスを参照して配列の値を一部上書きする
$data{$time}->[2] = $data3;
$data{$time}->[3] = $data4;
} else {
# ハッシュのキーが存在しない = test1.txtに同じ時刻の値が無かった場合
# 配列のリファレンスを生成し、$timeをキーとするハッシュに値として代入する
$data{$time} = ["-999.0", "-999.0", $data3, $data4];
}
}
close($fh2);

open(my $fh_out, '>', 'test.txt') or die($!);
# ハッシュのキー(=時刻文字列)をソートしてループを回す
foreach my $time (sort keys %data) {
# 配列のリファレンスを配列に戻したものを時刻と一緒にカンマ区切りでくっつける
print $fh_out join(', ', $time, @{$data{$time}})."\n";
}
close($fh_out);

exit;

=comment
・Perlで「値の重複チェック」をするなら、まず最初にハッシュを使うアルゴリズムを検討します。
ハッシュとは、(誤解を恐れずに言うのなら)任意の文字列を添字に持つことが出来る配列変数のようなものです。
同一のキーがすでに存在するかどうかを高速に(ほぼ一瞬で)調べることができるという利点があります。
ただし、キー(とペアになる値)の順序情報はハッシュ単体では持っていませんので、
データの順序関係が必要になる場合は、(今回の回答サンプルのように)キーでソートしたり、
別途ソート用の配列を作るなどの工夫が必要です。

・ハッシュは配列と同様に、1つのキー(添字)につき1つの値しか持つことができませんが、
リファレンスという仕組みを使うことで「配列のハッシュ」や「配列の配列」などを実現できます。
サンプルではハッシュの値として配列データを持つ「配列のハッシュ」を作りました。詳細は検索してみてください。

・今回のサンプルでは、元データのデータ検証や出力フォーマットの再整形が不要に見えましたので
簡略化のために全て文字列データとして扱っています。
再整形の必要があるならprintfでどうぞ。

・時刻文字列(hh:mm:ss)は秒単位での一致確認をする限りにおいては分割の必要がありません。
桁ごとに数値比較するよりは、まとめて文字列比較したほうが簡単で速いです。
例として、分単位でまとめる場合には、時刻をばらして「hh:mm」をキーとするハッシュを作ることになるでしょう。

・利用する変数を必ずmyで宣言するなど、(Perl4ではなく)Perl5の文法で書くことをお勧めします。
use strict; use warnings; と組み合わせれば、変数名の間違いや初期化忘れに対して警告やエラーを出してくれます。
一番厄介な「エラーの出ない不具合」をある程度未然に防ぐことができます。
質問者さんのサンプルで言うと、$mや$nを0で初期化せずに使っている部分などが警告対象となります。

・詳細は検索してみてください。
参考になるキーワード:「Perl ハッシュ」「Perl 変数 リファレンス」「Perl5 use strict」など。
=cut
    • good
    • 0
この回答へのお礼

ご回答ありがとうございました。
お礼が遅くなり申し訳ありませんでした。

N60-BASICさまのプログラムを参考にしてして
テストデータより複雑な本データの処理が無事にできました。

>・利用する変数を必ずmyで宣言するなど、(Perl4ではなく)Perl5の文法で書くことをお勧めします。
>use strict; use warnings; と組み合わせれば、変数名の間違いや初期化忘れに対して警告やエラーを出してくれます。
はい、C言語などでしたらコンパイルエラーですね…
「動くから」ではなく今後気をつけてプログラムを作成していきたいと思います。


丁寧な解説や参考キーワードなどを教えていただき本当にありがとうございました。

お礼日時:2011/11/25 17:25

use strict;


use warnings;

my %data;
my @infiles = ("test1.txt", "test2.txt");
my $outfile = "test.txt";
my @dummy = ("-999.0","-999.0");

#取得
foreach my $infile (@infiles) {
 open(my $in, "<", $infile) or die $!;
 my $line;
 while( $line = <$in>){
  chomp($line);
  my ($time, @list) = split(/\s*,\s*/, $line);
  $data{ $time }{ $infile } = \@list;
 }
 close($in);
}

#出力
open(my $out, ">", $outfile) or die $!;
foreach my $time ( sort keys %data ) { #時刻の順序は文字列ソートで決定
 my @list = ($time);
 foreach my $infile (@infiles) {
  if( exists $data{ $time }{ $infile } ) { push(@list, @{ $data{ $time }{ $infile } }); }
  else { push(@list, @dummy); }
 }
 print $out join(", ", @list) . "\n";
}
close($out);

#全角空白注意
    • good
    • 0
この回答へのお礼

ご回答いただきありがとうございました。

こんなにすっきりとしたプログラムになるのですね。
「なるほど」と目から鱗でした。
とても参考になりました。
ありがとうございます。

お礼日時:2011/11/25 17:32

時刻をキーとするマッチング処理を使うと、よいのではないかと思います。



処理の概要はキーとなる時刻の大小関係を比較して、
 1.キーとなる時刻が両方にある場合
 2.test1.txtだけにある場合
 3.test2.txtだけにある場合
に分けて出力内容を変更していきます。 

スクリプトのサンプルを以下に挙げます。

# ファイルのオープン
open(IN1,"test1.txt");   #マスタファイル
open(IN2,"test2.txt");   #トランザクションファイル
open(OUT1,">test.txt");   #出力ファイル

# 初期値設定
$high_value = pack("h8","ffffffff"); #終了判定
$in1_key = undef; #マスタキー
$in2_key = undef; #トランザクションキー

# 1件目のデータ入力
s_in1();
s_in2();

# 主処理
until ($in1_key eq $high_value && $in2_key eq $high_value) {

# マッチングの時(両方のファイルにデータがある)
 if  ($in1_key eq $in2_key) {
   s_match();
   s_in1();
   s_in2();
 }

# マスタオンリーの時(test1.txtだけにデータがある)
 elsif ($in1_key lt $in2_key) {
   s_master_only();
   s_in1();
 }

# トランザクションオンリーの時(test2.txtだけにデータがある)
 elsif ($in1_key gt $in2_key) {
   s_trans_only();
   s_in2();
 }
}

# マスタファイル入力
sub s_in1 {
 if  ($line1  = <IN1>) {
   chomp($line1);
   @in1 = split(",",$line1);
   $in1_key = $in1[0];
 }
 else {     #マスタファイルが終了のとき
   $in1_key = $high_value;
 }
}

# トランザクションファイル入力
sub s_in2 {
 if  ($line2  = <IN2>) {
   chomp($line2);
   @in2 = split(",",$line2);
   $in2_key = $in2[0];
 }
 else {     #トランザクションファイルが終了のとき
   $in2_key = $high_value;
 }
}

# マッチング時の処理
sub s_match  {
  $out1 = join(",",@in1,$in2[1],$in2[2]);
  print OUT1 "$out1\n";
}

# マスタオンリー時の処理
sub s_master_only {
  $out1 = join(",",@in1,"-999.0","-999.0");
  print OUT1 "$out1\n";
}

# トランザクションオンリー時の処理
sub s_trans_only {
  $out1 = join(",",$in2[0],"-999.0","-999.0",$in2[1],$in2[2]);
  print OUT1 "$out1\n";
}

# ファイルのクローズ
close(IN1);
close(IN2);
close(OUT1);


※上記のスクリプトは字下げを行うために全角スペースを入れてありますので、
 そのままコピー・ペーストしても正常に動きませんので、ご注意ください。
    • good
    • 0
この回答へのお礼

とても丁寧な回答をしていただきありがとうございました。
テストデータでは正常に動きました。
これから本データに応用してみます。

お礼日時:2011/11/25 13:55

時刻をキーにしてハッシュを作ればいいだけでは?

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

アドバイス、ありがとうございました。
申し訳ありません、その肝心のハッシュを使うことができなかったため質問させていただきました。

お礼日時:2011/11/25 13:44

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