プロが教える店舗&オフィスのセキュリティ対策術

perlでパターンマッチを使ったifの条件が必ずTRUEになってしまいます。

以下は条件です。二つのファイルがあって、片方のファイルのある列の文字列と別のファイルのある列の文字列が一致したら一致した行の1列目を出力するというプログラムです。

perl v5.12.3

『oct_gene.csv』は以下のような2列のファイルで、2列目であるalphabetの群は空白で区切られています。文字コードはASCIIです。
1zinc finger protein of the cerebellum 3
0stathmin-like 2
.
.
.
.


『RNA.csv』は以下のような3列のファイルで、3列目のalphabet群は同じく空白で区切られています。文字コードはUTF-8です。

1NM_324891 sin3 associated polypeptide
2 NM_53344 Nanog homeobox
.
.
.



open (WRITE, ">RNAseq_Oct.csv");
open(FILE, "RNA.csv");
while($line = <FILE>){
chomp $line;
@fact = split /\t/, $line;
open(OCT, "oct_gene.csv");

while($octline = <OCT>){
chomp $octline;
@oct = split /\t/, $octline;

if($fact[2] =~ /$oct[1]/){
print WRITE $fact[2] . "\t" . $oct[1] ."\n";
last;
}
}

close (OCT);

}

close (FILE);
close (WRITE);


この中のif文がうまく働かず、whileで繰り返すまでもなく必ず if($fact[2] =~ /$oct[1]/) が成り立ってしまいます。


どなたか詳しい方、どうかご教示願います。
それではよろしくお願いします。

A 回答 (3件)

あるべき動作仕様やデータフォーマットの詳細、動作環境などがわかりませんので、


スクリプトを動作させた場合に考えられる問題を列挙します。


● 各データファイルのフォーマットはタブ区切りになっていますか?

仕様として言及されていませんが、split /\t/ で処理している以上は
ファイルの列がタブ区切りになっていないとデータ列を正しく読み取れません。
その結果、$fact[2]と$oct[1]がいずれも未定義値になっている場合には
if($fact[2] =~ /$oct[1]/) { ... }
は常に真と判定されます。

$fact[2]と$oct[1]に正しい値が入ることを保証するために、
列数が異なる場合にはエラーとなるような例外処理が必要かもしれません。
@fact = split /\t/, $line;
if(@fact != 2) { die("Bad csv format at RNA.csv, line $.\n"); }

@oct = split /\t/, $octline;
if(@oct != 3) { die("Bad csv format at oct_gene.csv, line $.\n"); }

なお上記のケースでは、Perl5ではuse warningsプラグマを有効にしていれば
実行時に未定義値に関する警告を出してくれます。
use strictやmyによる変数宣言と合わせてチェック機能を有効活用することを
強くお勧めします。


●各データファイルの改行コードは実行環境の改行コードと一致していますか?

Perlを実行するプラットフォームOSとデータファイルの改行コードが
一致していない場合にもデータ読み込み時の破損が起きる可能性があります。

例として、UNIX/Linux系プラットフォーム(改行コード=LF)のPerlにおいて
Windows環境で作られたテキストファイル(改行コード=CR+LF)を読ませた場合、
chompでは末尾のLFのみが削られ、CRが残ってしまってデータが化けることになります。

この場合、Perl5.8以降であれば、3引数のopen()やbinmode()を使って
open(FILE, ":crlf", "RNA.csv");
または
open(OCT, "oct_gene.csv");
binmode(OCT, ":crlf");
とすることで入力時にテキスト中のCR+LFの改行をLFに統一することができます。

Mac(改行コード=CR)で実行する場合も同様の注意が必要になる場合があります。
Windowsでも、Cygwin環境で実行した場合はUNIX系と同じ挙動になるはずです。
バッドノウハウとして、chompを複数回実行するという裏技?もありますが、
場当たり的対処方法なのでお勧めしません。入力フォーマットの方を厳格にすべきです。


● 文字列の比較方法は意図したものになっていますか?

「alphabetの群は空白で区切られています」とあるにもかかわらず、
空白を一切考慮しないロジックになっているのが気になります。
上記のサンプルでは、「Nanog homeobox」に対しては「Nano」「g h」「home」「box」なども
マッチすることになります。
これが意図する仕様かどうか確認した上で、もし必要であれば比較元と
対象文字列のそれぞれについて、空白でsplitしてから個別比較する処理を追加してください。

また、/$oct[1]/とした場合、$oct[1]は一般文字列ではなく正規表現として解釈されます。
単純に文字列を含んでいるかをチェックしたいのなら、\Q~\Eを使って
if($fact[2] =~ /\Q$oct[1]\E/) {
とするか、あるいは正規表現を使わずに
if(index($fact[2], $oct[1]) >= 0) {# 文字列を含まない場合は-1が返る
でチェックしなければいけません。
でないと$oct[1]に正規表現のメタ文字が含まれている場合に意図しない結果になります。
$oct[1] = 'a.c' のときに 'abc' にもマッチしてしまうということです。


● 比較対象およびファイル出力する内容の、列の選択は正しいですか?

配列の添え字は1ではなく0から始まることに注意してください。
print WRITE $fact[2] . "\t" . $oct[1] ."\n";
で出力されるのは、
「RNA.csvの3列目とoct_gene.csvの2列目をタブ記号で連結したもの」
です。提示仕様にある「一致した行の1列目を出力する」とは異なるように
読み取れます。1行目ならわかるんですが・・。
    • good
    • 0
この回答へのお礼

回答ありがとうございます。問題は解決しました。入力の行列が完璧でなくところどころに項目が存在していなかったのでパターンマッチするところで未定義値が設定されていたのが原因でした。
また、色々な可能性に対する提案がありとても役に立ちました。今回のこと以外でも参考になりそうな部分が多いので非常に有り難いです。

お礼日時:2012/10/29 17:34

「whileで繰り返すまでもなく必ず if($fact[2] =~ /$oct[1]/) が成り立ってしまいます」って~ときの,

各変数の値はどうなっているんですか?
    • good
    • 0
この回答へのお礼

回答ありがとうございます。上にも書きましたが、パターンマッチの片方に未定義値が格納されているのが原因でした。

お礼日時:2012/10/29 17:35

> 『oct_gene.csv』は以下のような2列のファイルで、2列目であるalphabetの群は空白で区切られています。

文字コードはASCIIです。

> 『RNA.csv』は以下のような3列のファイルで、3列目のalphabet群は同じく空白で区切られています。

とありますが、

@fact = split /\t/, $line;
@oct = split /\t/, $octline;

これではタブで区切ってます。


@fact = split / /, $line;
@oct = split / /, $octline;

とかじゃないですかね。
    • good
    • 0
この回答へのお礼

回答ありがとうございます。
私が意図していたのは空白を含む列は一つとして、タブ区切りで分けるということです。

お礼日時:2012/10/28 20:37

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