![](http://oshiete.xgoo.jp/images/v2/pc/qa/question_title.png?e8efa67)
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;
とても汚いプログラムになってしまいました…
このプログラム以外でも構いませんのでどうかよろしくお願い致します。
No.3ベストアンサー
- 回答日時:
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
ご回答ありがとうございました。
お礼が遅くなり申し訳ありませんでした。
N60-BASICさまのプログラムを参考にしてして
テストデータより複雑な本データの処理が無事にできました。
>・利用する変数を必ずmyで宣言するなど、(Perl4ではなく)Perl5の文法で書くことをお勧めします。
>use strict; use warnings; と組み合わせれば、変数名の間違いや初期化忘れに対して警告やエラーを出してくれます。
はい、C言語などでしたらコンパイルエラーですね…
「動くから」ではなく今後気をつけてプログラムを作成していきたいと思います。
丁寧な解説や参考キーワードなどを教えていただき本当にありがとうございました。
No.4
- 回答日時:
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);
#全角空白注意
ご回答いただきありがとうございました。
こんなにすっきりとしたプログラムになるのですね。
「なるほど」と目から鱗でした。
とても参考になりました。
ありがとうございます。
No.2
- 回答日時:
時刻をキーとするマッチング処理を使うと、よいのではないかと思います。
処理の概要はキーとなる時刻の大小関係を比較して、
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);
※上記のスクリプトは字下げを行うために全角スペースを入れてありますので、
そのままコピー・ペーストしても正常に動きませんので、ご注意ください。
お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!
似たような質問が見つかりました
- Excel(エクセル) 表示形式、文字列セル(列)に数式を入力するには マクロ 1 2022/09/18 10:53
- Excel(エクセル) Excelにて、フォルダ内のTextファイルをマクロで統合すると文字化けしてしまう時の解消コード 4 2023/01/01 07:32
- CGI perlで書いたcgiでsqliteの使い方を教えてください 2 2023/05/08 21:29
- 統計学 t値の計算方法 1 2022/11/29 18:37
- Excel(エクセル) Excel2007での条件付き書式について 6 2023/05/02 10:56
- その他(ソフトウェア) OMRON PLC CP2Eのプログラム 1 2022/11/24 10:57
- C言語・C++・C# numpyスライス機能を使った数値計算 2 2023/05/08 16:01
- 工学 ちなみになぜv=(v・e1)e1+(v・e2)e2はe1やe2が、正規直交基底でないと成り立たないと 2 2022/12/22 17:22
- JavaScript vertical sliderをautoplayしたい 2 2022/08/25 14:47
- 工学 以前、線形代数からフーリエ級数展開を導く上で 式v=(v, e1)e1+(v, e2)e2+…+(v 6 2022/06/29 17:24
関連するカテゴリからQ&Aを探す
おすすめ情報
デイリーランキングこのカテゴリの人気デイリーQ&Aランキング
-
文字列を変数名として扱う方法
-
動的なハッシュの配列を作成したい
-
ハッシュ検索はなぜ速い
-
まったく同じファイルのハッシ...
-
短いハッシュの作り方
-
ハッシュリストって単にハッシ...
-
データベースでユーザーのパス...
-
静的ハッシュの配列のキーに対...
-
画面を強制的に再描画させる方法
-
DoEventsが必要な理由について
-
VBのReturnの使い方
-
VBAのautofilter、criteriaの配...
-
VBAでの一時停止と再開の方法
-
アクティブセルから、A列最終行...
-
エクセル関数で1〜12の数字がル...
-
Do whileでExitせず、ループの...
-
vbscriptでIE自動入力(途中で...
-
「VC++6」ウィンドウの再描画
-
CSVファイルの特定の行だけを読...
-
delphi の 標準偏差のプログラ...
マンスリーランキングこのカテゴリの人気マンスリーQ&Aランキング
-
ハッシュ検索はなぜ速い
-
文字列を変数名として扱う方法
-
チェックデジットについて
-
ハッシュのハッシュを実現したい。
-
まったく同じファイルのハッシ...
-
列挙型と連想配列の違いを教え...
-
重複ファイルを削除したいので...
-
*(アスタリスク)の意味
-
短いハッシュの作り方
-
英語でのシャープとコメの呼び...
-
ハッシュマーク以降のアドレス取得
-
一意(ユニーク)かつ、ソート...
-
Perlは戻り値で、ハッシュや配...
-
ハッシュリストって単にハッシ...
-
ActivePerl がハングアップ
-
多次元配列から重複を削除
-
mapのポインタ
-
Perlのハッシュ変数のソートに...
-
Perlのサブルーチンの引数に配...
-
文字数の短いユニークなID生成
おすすめ情報