プロが教えるわが家の防犯対策術!

先の質問「プログラムのヒントを下さい」でも扱ったのですが、

apple
best
apple
END
apple
beer
beer
END
zero
child
death
zero

のようなテキストから、ENDとENDの間か、ENDと最初もしくは最後の間の重複した文字列を取り除くスクリプトを書こうと思います。出力例は以下のようになってもらいたいです。

apple
best
END
apple
beer
END
zero
child
death

自分で頑張って下のコードまで書きましたが動きませんでした。特に、ENDと最初もしくは最後の間
の取り扱いがわかりません。どなたかご教授お願いいたします。

open(IN, "datafile");
@xx = <IN>;

@zz = ();

foreach $yy (@xx) {
if ($yy eq "end"){
@uniq = uniqArray(\@zz);
foreach my $value ( @uniq ){
print "$value\n";
}
@zz = ();
}else{
push(@zz,$yy);
}
}
close(IN);

sub uniqArray{
my $array = shift;
my %hash = ();

foreach my $value ( @$array ){
$hash{$value} = 1;
}

return(
keys %hash
);
}

A 回答 (4件)

@ARGV = ('datafile');


my %h;
print grep { ! exists $h{$_} && ++$h{$_} && !(/^END$/ && undef %h) } <>;
    • good
    • 0
この回答へのお礼

回答ありがとございます。とてもスマートですね。perlは、テキスト操作に関しては本当にCより便利だと感じました。

お礼日時:2010/12/10 09:39

「うまく動かない」というなら, 「何がどう『うまく動かない』のか」くらい書けない? 前のやつも「同一の単語が出現しているとエラーが起きてしまいました」で終わらせてるけど, 「どんなエラーが起きるのか」は書けるはずだよね (そもそも普通に考える限り「エラーが起きる」ことがありえないと思うのだが).


それくらいやってもバチはあたらないと思うよ.

本題に入ると, #1 で言われている「@zzには最後のEND以降の部分が残ってます。」というのは, 実際にやってみればすぐわかります. そのままでは「最後の END より後ろの部分」が出力されないはずです.

ただ, このプログラムは不自然. 自分だったら
sub uniqArray {
my %hash;
my @result;
foreach my $element (@_) {
unless ($hash{$element}) {
push @result, $element;
$hash{$element} = 1;
}
}
@result;
}

open my $fh, '<', 'datafile';
my @tmp;
while (my $line = <$fh>) {
chomp($line);
push @tmp, $line;
if ($line eq 'END') {
foreach my $element (uniqArray(@tmp)) {
print "$element\n";
}
@tmp = ();
}
}
foreach my $element (uniqArray(@tmp)) {
print "$element\n";
}
くらいかなぁ. #1 で言われるように, 表示するところまでサブルーチンにしそうだけど.

「動かない」とか「エラーが出る」とか答えてくれるのは全くかまわないけど, そういうときは最初に書いたように「どういう入力に対してどんな結果になることを期待したが実際に得られた結果はそれと違ってこんなふうになっている」とか, あるいはエラーが出るというならどんなエラーなのか, メッセージを完全に書いてほしい. それくらいの手間は惜しむものじゃないと思う.
    • good
    • 0
この回答へのお礼

回答ありがとうございます。以前にもエラーの内容もかくようにおっしゃられていたのを覚えています。
これからは、きちんとエラーの結果の内容も書こうとおもいます。アドバイスありがとうございました。

お礼日時:2010/12/10 00:47

こんな感じですかね。


こういう問題はハッシュを使うと簡単ですよ。
インデントは全角スペースになっています。

#!/usr/bin/perl
use strict;

my %h;

while(<DATA>){
 chomp;

 if(/^END$/){
  undef %h;
  print "END\n";
 }else{
  if(not exists $h{$_}){
   print "$_\n";
   $h{$_} = 1;
  }
 }
}

__DATA__
apple
best
apple
END
apple
beer
beer
END
zero
child
death
zero
    • good
    • 0
この回答へのお礼

あまりにも簡潔なコードにびっくりしました!上級者のコードがみれて参考になりました。ありがとうございます。

お礼日時:2010/12/10 00:49

> open(IN, "datafile");


> @xx = <IN>;
INはここまでしか使わない(使えない)ので
> close(IN);

> if ($yy eq "end"){

「eq」は正しく「等しい文字列」の判定です。大文字小文字は区別されます。
'END' ne 'end' です

> foreach $yy (@xx) {
...
> }
このループが終わった段階で @zzには最後のEND以降の部分が残ってます。
ここでもう一度一連の処理(↓)が必要です。これもsubにしておいてもいいでしょう

@uniq = uniqArray(\@zz);
foreach my $value ( @uniq ){
print "$value";
# ← あと、 @xx=<IN>で読み込んだ文字列は改行コードまで含まれます。
#ここで\nを入れていると、改行が2つになります
#あるいは、\nはそのままにして、 foreach $yy (@xx) {の後で chomp $yy 等で改行コードを取り除くか。
}
    • good
    • 0
この回答へのお礼

回答ありがとうございます。残念ながら説明がよくわかりませんでした。

open(IN, "datafile");
@xx = <IN>;
close(IN);

@zz = ();

foreach $yy (@xx) {
chomp $yy;
if ($yy eq 'END'){
@uniq = uniqArray(\@zz);
foreach my $value ( @uniq ){
print "$value\n";
}
@zz = ();
}else{
push(@zz,$yy);
}
}


sub uniqArray{
my $array = shift;
my %hash = ();

foreach my $value ( @$array ){
$hash{$value} = 1;
}

return(
keys %hash
);
}

と直すところまではわかりました。しかし

> foreach $yy (@xx) {
...
> }
このループが終わった段階で @zzには最後のEND以降の部分が残ってます。
ここでもう一度一連の処理(↓)が必要です。これもsubにしておいてもいいでしょう

の意味がわかりません。

@zz = ();

で初期化しているのではありませんか?

# ← あと、 @xx=<IN>で読み込んだ文字列は改行コードまで含まれます。
#ここで\nを入れていると、改行が2つになります
#あるいは、\nはそのままにして、 foreach $yy (@xx) {の後で chomp $yy 等で改行コードを取り除くか。

とのことですが、

foreach $yy (@xx) {
chomp $yy;
if ($yy eq 'END'){
@uniq = uniqArray(\@zz);
foreach my $value ( @uniq ){
print "$value\n";
}
@zz = ();
}else{
push(@zz,$yy);
}
}

でよかったのでしょうか?

いずれにせよ、直してもうまくうごきませんでした。

どなたかアドバイスお願いします。

お礼日時:2010/12/09 23:31

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