Fool Pool

ハマった記

Perlでカッコの対応をチェックする

新しい言語を学ぶ上で、最も学習効率が高い方法は、失敗しながら学ぶこと。そのためには、自分で実際にコーディング課題を設定して、それをクリアしていくことがベストだ。今回は初級プログラミング定番のカッコ対応チェックを実装する。

個人的なこだわりとしては、処理を一貫してリスト操作関数を使って書くことにしている。つまり、for, while ではなく、 match や grep 、時々 List::Util の reduce を使う。これらを適切使うと、処理の流れが分かりやすくなり、スパゲティーコードになりにくい。(その代わり、リスト操作を一度にやらずに、分割したりするので、処理速度はしばしば犠牲にすることがある。)

map, grep については、参考文献[1]に解説が載っている。

概要

入力ファイル中のカッコの対応をチェックする。

仕様

  • プログラムの第一引数にチェックするコード(テキストファイル)を指定する
  • コードのカッコの対応がとれている場合は、「Hey, your code is perfect!!」と表示する
  • 開きカッコが閉じカッコより多い場合は、「Oops, too many open parens (xx)!」と表示する(カッコ内には超過したカッコの数を表示する。)
  • 閉じカッコが多い場合も上記と同様とする

コード

#!/usr/bin/env perl

use List::Util qw(sum);

my @lines = <>;

my $total = sum map {
  my $line = $_;
  $line  =~ s/[^()]//g;              # カッコ以外の文字を除去
  my $n_open   = ($line  =~ s/\(//g); # 開き括弧の数を数える
  my $n_closed = ($line  =~ s/\)//g); # 閉じ括弧の数を数える

  $n_open - $n_closed;
} @lines;

print "Hey, your code is perfect!!\n" if $total == 0;
print "Oops, too many open parens! (" . $total . ")\n" if $total > 0;
print "Oops, too many closed parens! (". -$total .")\n" if $total < 0;

テストケース1 (matched.in):

((1+2))+(3)+(4+5)+6+((7+8))
((9)+((10+11)+(12)))

テストケース1の出力結果:

Hey, your code is perfect!!

テストケース2 (too_many_op.in):

((a)(b)(c)(
((hogehoge)(foo)(baa))
(buzz)

テストケース2の出力結果:

Oops, too many open parens! (2)

解説

流れは、
1. 1行ずつ処理する
2. 括弧以外の余計な文字を削除
3. 開き括弧、および閉じ括弧の数を数える
4. すべての行について、「開き括弧の数 - 閉じ括弧の数」を合計する
5. 合計が 0 なら、括弧対応は OK. 負なら、閉じ括弧が多く、正なら、開き括弧が多い。

ここで、3について、Perl で文字列中に含まれる特定の文字の個数を数えるには、いくつかやり方がある。

例1: パターン置換(s///g)を使う方法

    my $n_open = ($line  =~ s/\)//g);

例2: パターンマッチ(//g)を使う方法

    my $n_open = () = ($line =~ /\(/g);

例3: キャラクタ置換(tr//)を使う方法

    my $n_open = ($line =~ tr/\)//);

例4: grep を使う方法

    my $n_open = grep { $_ eq '(' } split(//, $line);

注意すべきは、置換を行った場合(例1, 例3)は、元の文字列 $line が破壊されてしまうこと。
元の文字列を壊したくない場合は、あらかじめ他の変数に退避させておく。

    my $n_open = (my $tmp = $line) =~ s/\)//g);

上記の場合、実際に置換されるのは変数 $tmp なので、元の文字列 $line は無傷。