Perl

Perlチャレンジ

コードゴルフといえばPerlですしおすし

ずぶの素人

拙者、Perlは完全にはじめてでござる。

お題

会社の人が紹介してた問題を解くことにする。

内容としては、以下のような感じ。

  • カンマ区切りの文字列を受け取る
  • 各文字列を3桁の数字・3桁かつゾロ目の数字・それ以外で場合分け
  • 場合に応じて文字列にprefixを付けて改行区切りで出力
  • 言語は自由

まずはRubyで解く

まずはテキトーにRubyで解いた。

puts gets.split(?,).map{ |s| /^\d{3}$/ !~ s ? "unmatch pattern: #{s}" : /^(\d)\1\1$/ =~ s ? "zorome: #{s}" : s }

1週間くらい前にRubyを始めたばかりなので、そんなに短く書けなかった。もっと短くできるんだろうとは思う。

Perlを学ぶ

入力

Perlは記号による組み込み変数がいくつもあるようで、入力の区切り文字は$/に入っている(デフォルトは改行)。

で、C++でいうところのcinみたいなことをやるのが以下。いきなりすごい。

<>

出力

入力の<>に対して、出力は普通にprint

文字列のくくりにはシングルクォートとダブルクォート両方が使えるが、ダブルクォートの場合は変数展開などが有効になるらしい。

変数は宣言なしでグローバルスコープとして使用でき、先頭に$をつけることで変数になる。

ちなみに、行末には;が必要っぽい。

$str = 'hoge';
print $str

split

文字列のsplitは区切り文字を正規表現で指定できる。引数を渡す際に括弧()はつけなくてよい。

Perlでは配列変数は@を付けて表現する。

@a = split(/ +/, 'hoge   fuga  piyo');
# @a = ('hoge', 'fuga', 'piyo')

@b = split / +/, 'hoge   fuga  piyo'
# @b = ('hoge', 'fuga', 'piyo')

カンマ区切りを受け取って改行区切りで出力

とりあえず、加工する前段階として入出力にチャレンジ。

出力の区切り文字は$,という変数で指定する。

したがって、先ほどの入力の区切り文字を使い、$,=$/のようにして改行を代入できる。

$,=$/;print split/,/,<>

# input
#>> 123,abc,3333,555,321

# output
#>> 123
#>> abc
#>> 3333
#>> 555
#>> 321

map

次に、各要素を加工するためのmapがあるか調べるとちゃんとあった。

基本構文は以下。

@mapped = map BLOCK @array;
@mapped = map EXPR,@array;

とりあえず全部hogeに置き換えてみるなら、こんな感じになる。

@mapped = map 'hoge', @array;
@mapped = map {'hoge'} @array;

デフォルト引数が、$_で表現される。

Rubyのブロック{|i|i+2}におけるiだとか、Pythonのラムダ式lambda x: x+2におけるxに相当する感じだと思っておけばいいはず。

@array = (1, 2, 3, 4);
@mapped = map { $_ + 10 } @array;
# @mapped = (11, 12, 13, 14)

# 以下も可
@array = (1, 2, 3, 4);
sub f{$_+10};
@mapped = map f, @array;

Perlでは関数を古風にサブルーチンと呼び、sub funcName{}のように定義できる。VBとかFortranを思い出す。

正規表現

いろいろあるようなので詳細は省くが、置換した文字列を返すのであれば末尾にrの修飾子をつける。

# 3桁の数字をhogeに置換
s/^\d{3}$/hoge/r

単純にマッチしたかを真偽値で返すのならば以下。

/^\d{3}$/  # true/false

三項演算子

Perlには三項演算子?:がある。普通のif文はCライク。正確にはifっぽいが。

if (BOOL) {
  # true case
}else{
  # false case
}

まとめ

ということで、これまで学んだ要素を組み合わせると以下のコードが出来上がる。

$input = <>;
@array = split /,/, $input;
sub convert{
  if (/^(\d{3})$/) {
    if (/^(\d)\1\1$/) {
      "zorome: $_"
    }else{
      $_
    }
  }else{
    "unmatch pattern: $_"
  }
}
@mapped = map convert, @array;
$, = $/;
print @mapped;

無駄な要素を省くと以下。

$,=$/;print map{!/^\d{3}$/?"unmatch pattern: $_":/(.)\1\1/?"zorome: $_":$_}split/,/,<>

Rubyよりは短くなった。

puts gets.split(?,).map{ |s| /^\d{3}$/ !~ s ? "unmatch pattern: #{s}" : /^(\d)\1\1$/ =~ s ? "zorome: #{s}" : s }

Rubyをもう少し頑張ってみたけどPerlには敵わず。

puts gets.split(?,).map{(/^\d{3}$/!~_1 ? "unmatch pattern: ":_1[0]*3==_1 ? "zorome: ":"")<<_1}