perl の構文に関する質問です。

このコードが何をやっているのか教えてください。

$_=3x(1E3-1),s/3/++$p%3*$p%5?7:5x$p/ge,print s/5//g

以下のコードと同じで3または5で割り切れる1000までの数を足した結果が得られることは知っていまが、それぞれの記号が何を意味するのか分かりません。

$sum=0;
map {$sum += $_ if(!($_%3) || !($_%5))} 1..1000;
print $sum;

最近 perl を使い始めたので良くわかっていません。
よろしくお願いします。

回答の条件
  • 1人2回まで
  • 登録:2009/06/29 14:12:17
  • 終了:2009/07/01 14:38:16

ベストアンサー

id:y-kawaz No.2

y-kawaz回答回数1421ベストアンサー獲得回数2262009/06/29 18:45:52

ポイント59pt

まず一番の大外だけを見ると以下のようになっており、カンマ演算子が使われています。

A, B, C;

カンマ演算子は、左辺の値を捨てて右辺の値を返す演算子です。上記は、Aを評価して結果を捨てて、Bを評価して結果を捨てて、Cを評価してその値を返していおり、この例では以下のように解釈して構わないと思います。

$_=3x(1E3-1);
s/3/++$p%3*$p%5?7:5x$p/ge;
print s/5//g;

次に、$_=3x(1E3-1) の説明です。

  • 1E3、これは指数表記の数値リテラルで、1*10の3乗、すなわち 1000 を表しています。(例、3E2 は300で、.123E-3 なら 0.000123 になります)
  • (1E3-1)は括弧演算子でまずこれを計算します。1000-1、つまり999です。
  • 次に x 演算子です。x演算子は左辺の値を右辺の回数繰り返します。すなわち 3x(1E3-1) → 3x999 → 333333333...(3が999個連結された文字列)
  • ここでは最終的に $_ に "3333333..." という3が999個連続した文字列が代入されます。

次に、s/3/++$p%3*$p%5?7:5x$p/ge の説明です。

  • s/正規表現/置換結果/ は正規表現による文字列の置換を行います。$foo =~ s/xxx/yyy/ という風にすると $foo の値を置換しますが、=~ を省略すると暗黙的に $_ という変数に対する置換が行われます。なのでこれは最初に $_ に代入した 3333... という文字列に対して正規表現置換を行いその結果を $_ に代入しています。
  • s/正規表現/置換結果/ge の最後の ge ですがこれは正規表現に対するスイッチです。
    • g は繰り返しマッチを行うことを意味しています。gが無い場合は最初にマッチした部分を置換した時点で処理が終了して残りの 3 はそのまま残ります。
    • e は置換結果が文字列ではなく Perl の式であることを意味し、マッチする度に「Perlの式」が実行されてその式の結果が置換後の値になります。

つまり上記は $_ で 3 を見つける度に ++$p%3*$p%5?7:5x$p というPerlの式を実行することになります。$_ は3が999個並んだ文字列なので ++$p%3*$p%5?7:5x$p が実行されるので以下のように解釈が可能です。

$new = "";
for($i=0, $i<999, $i++) {
  $new .= ++$p%3*$p%5?7:5x$p;
}
$_ = $new;

ここで、

  • ++は変数に1を足して返す演算子で、++$p は {$p = $p + 1; return $p} のような感じに解釈すればよいです。
  • %演算子は左辺を右辺で割り算した余りを返します。1%3==1, 2%3==2, 3%3==0, 4%3==1 という感じになります。
  • *は掛け算をする演算子です。
  • ? と : は3項演算子というもので、x ? y : z とあった場合、x が真ならyを返し、xが偽ならzを返すif文のような動作をする演算子です。
  • 演算子の優先順位は ++ がもっとも優先度が高く、次に*と%、?: が一番優先度が低いです。ちなみに * と % は優先度が同じなので左から順に処理します。

これらをふまえると上記は以下のように解釈できます。

$new = "";
for($i=0, $i<999, $i++) {
  $p = $p + 1;
  #↓ここでは計算結果の値に余り意味はなく剰余計算を行う際に$pが3の倍数か5の倍数の時にゼロが掛け算されてゼロ(偽値)になります
  if($p%3*$p%5) {
    #↓$pが3の倍数か5の倍数のときに実行される
    $new .= 7;
  } else {
    #↓$pが3の倍数でなく、5の倍数でもないときに実行される
    $new .= 5x$p; #5を$p回繰り返した文字列
  }
}
$_ = $new;

さて、頭のいくつかの3を置換してみましょう

3が置換される値 コメント
1回目 7 $p=1なので7
2回目 7 $p=2なので7
3回目 555 $p=3で3の倍数なので5x3で555
4回目 7 $p=4なので
5回目 55555 $p=5で5の倍数なので5x5で55555
6回目 555555 $p=6で3の倍数なので5x6で555555
7回目 7 $p=7なので
8回目 7 $p=8なので
9回目 555555555 $p=9で3の倍数なので5x9で555555555

最終的に $_ は 7755575555555555577555555555... という文字列になります。


最後に、print s/5//g を説明します。

  • s/5//g はこれまで説明したとおり $_ =~ s/5//g を意味します。
  • また正規表現置換自体を実行した戻り値は置換回数になります。
  • つまり print s/5//g は $_ が 5 にマッチした回数を print することになります。
  • 要は $_ に入っている 5 の数を数えています

さて、ここで上の 3 を 7 や 5x$p に置換したときの表を見てみます。

よく見ると$pが3の倍数の場合は5が$p個、$pが7の倍数の場合も5が$p個出力されており、$pは1から1000までの値をとります。

すなわち $_ に入っている5の数を数えることは、1から1000までの5の倍数と7の倍数の値を数えることと同じ意味になることが分かります。

以上です。


ここまでの説明で疲れたので、後者の $sum=0~ の説明は適当です。

  • 1..1000 は (1,2,3,...,1000) という配列を作る演算子です。
  • map は、第1引数の文を第2引数で与えられた配列の値全てにたいして実行します。つまり {$sum += $_ if(!($_%3) || !($_%5))} を999回実行します。
  • map の第1引数の文の中の $_ の値は1つずつ取り出された第2引数の値になります。つまり1回目は1、2回目は2が入っています。
  • A if(B) という構文は B が真であれば A を実行するという意味で、if(B) {A} すなわち if(!($_%3) || !($_%5)) { $sum += $_ } と同じです。
  • !($_%3) || !($_%5) は $_ が3の倍数か5の倍数なら真になり、そのときだけ $sum += $_ が実行されます。
  • 上記全てをふまえると、1から1000までの数字で3の倍数か5の倍数の合計が$sumに入ることが分かるんじゃないかと思います。
id:phji

詳細な回答ありがとうございます。

上のワンライナーのコードが私の質問でして、下のは私が書いたコードでした。

もう少し分かりやすく質問をするべきでした。

単純な演算子の説明までさせてしまってすみません。

三項演算子を展開して if 文で書いてくれているところのコメントが if と else で逆だと思います。

$p%3*$p%5 で論理演算子を使わなくても、「3または5で割りきれない」を表現できるんですね!

置換して、条件を満たす置換した箇所を数えるのが全体の流れだと分かりました。

また、それぞれの式の満たす意味も分かりました。

ありがとうございました。

2009/07/01 14:36:26

その他の回答(1件)

id:cubick No.1

cubick回答回数125ベストアンサー獲得回数362009/06/29 15:13:33

ポイント11pt
$sum=0;

変数 $sum に 0 を代入。

map {$sum += $_ if(!($_%3) || !($_%5))} 1..1000;

map { } 1..1000 で「1~1000まで、{}の中を繰り返し実行」します。

{}の中では実行されるたび、変数 $_ に1~1000が代入されます。そして

変数($_)が3で割り切れる、または変数($_)が5で割り切れるなら、合計($sum)に

仮変数($_)の値を足していきます。

print $sum;

最後に変数 $sum の値を表示。

id:phji

すみませんが、私が知りたかったのは上の方のコードです…

2009/07/01 13:49:01
id:y-kawaz No.2

y-kawaz回答回数1421ベストアンサー獲得回数2262009/06/29 18:45:52ここでベストアンサー

ポイント59pt

まず一番の大外だけを見ると以下のようになっており、カンマ演算子が使われています。

A, B, C;

カンマ演算子は、左辺の値を捨てて右辺の値を返す演算子です。上記は、Aを評価して結果を捨てて、Bを評価して結果を捨てて、Cを評価してその値を返していおり、この例では以下のように解釈して構わないと思います。

$_=3x(1E3-1);
s/3/++$p%3*$p%5?7:5x$p/ge;
print s/5//g;

次に、$_=3x(1E3-1) の説明です。

  • 1E3、これは指数表記の数値リテラルで、1*10の3乗、すなわち 1000 を表しています。(例、3E2 は300で、.123E-3 なら 0.000123 になります)
  • (1E3-1)は括弧演算子でまずこれを計算します。1000-1、つまり999です。
  • 次に x 演算子です。x演算子は左辺の値を右辺の回数繰り返します。すなわち 3x(1E3-1) → 3x999 → 333333333...(3が999個連結された文字列)
  • ここでは最終的に $_ に "3333333..." という3が999個連続した文字列が代入されます。

次に、s/3/++$p%3*$p%5?7:5x$p/ge の説明です。

  • s/正規表現/置換結果/ は正規表現による文字列の置換を行います。$foo =~ s/xxx/yyy/ という風にすると $foo の値を置換しますが、=~ を省略すると暗黙的に $_ という変数に対する置換が行われます。なのでこれは最初に $_ に代入した 3333... という文字列に対して正規表現置換を行いその結果を $_ に代入しています。
  • s/正規表現/置換結果/ge の最後の ge ですがこれは正規表現に対するスイッチです。
    • g は繰り返しマッチを行うことを意味しています。gが無い場合は最初にマッチした部分を置換した時点で処理が終了して残りの 3 はそのまま残ります。
    • e は置換結果が文字列ではなく Perl の式であることを意味し、マッチする度に「Perlの式」が実行されてその式の結果が置換後の値になります。

つまり上記は $_ で 3 を見つける度に ++$p%3*$p%5?7:5x$p というPerlの式を実行することになります。$_ は3が999個並んだ文字列なので ++$p%3*$p%5?7:5x$p が実行されるので以下のように解釈が可能です。

$new = "";
for($i=0, $i<999, $i++) {
  $new .= ++$p%3*$p%5?7:5x$p;
}
$_ = $new;

ここで、

  • ++は変数に1を足して返す演算子で、++$p は {$p = $p + 1; return $p} のような感じに解釈すればよいです。
  • %演算子は左辺を右辺で割り算した余りを返します。1%3==1, 2%3==2, 3%3==0, 4%3==1 という感じになります。
  • *は掛け算をする演算子です。
  • ? と : は3項演算子というもので、x ? y : z とあった場合、x が真ならyを返し、xが偽ならzを返すif文のような動作をする演算子です。
  • 演算子の優先順位は ++ がもっとも優先度が高く、次に*と%、?: が一番優先度が低いです。ちなみに * と % は優先度が同じなので左から順に処理します。

これらをふまえると上記は以下のように解釈できます。

$new = "";
for($i=0, $i<999, $i++) {
  $p = $p + 1;
  #↓ここでは計算結果の値に余り意味はなく剰余計算を行う際に$pが3の倍数か5の倍数の時にゼロが掛け算されてゼロ(偽値)になります
  if($p%3*$p%5) {
    #↓$pが3の倍数か5の倍数のときに実行される
    $new .= 7;
  } else {
    #↓$pが3の倍数でなく、5の倍数でもないときに実行される
    $new .= 5x$p; #5を$p回繰り返した文字列
  }
}
$_ = $new;

さて、頭のいくつかの3を置換してみましょう

3が置換される値 コメント
1回目 7 $p=1なので7
2回目 7 $p=2なので7
3回目 555 $p=3で3の倍数なので5x3で555
4回目 7 $p=4なので
5回目 55555 $p=5で5の倍数なので5x5で55555
6回目 555555 $p=6で3の倍数なので5x6で555555
7回目 7 $p=7なので
8回目 7 $p=8なので
9回目 555555555 $p=9で3の倍数なので5x9で555555555

最終的に $_ は 7755575555555555577555555555... という文字列になります。


最後に、print s/5//g を説明します。

  • s/5//g はこれまで説明したとおり $_ =~ s/5//g を意味します。
  • また正規表現置換自体を実行した戻り値は置換回数になります。
  • つまり print s/5//g は $_ が 5 にマッチした回数を print することになります。
  • 要は $_ に入っている 5 の数を数えています

さて、ここで上の 3 を 7 や 5x$p に置換したときの表を見てみます。

よく見ると$pが3の倍数の場合は5が$p個、$pが7の倍数の場合も5が$p個出力されており、$pは1から1000までの値をとります。

すなわち $_ に入っている5の数を数えることは、1から1000までの5の倍数と7の倍数の値を数えることと同じ意味になることが分かります。

以上です。


ここまでの説明で疲れたので、後者の $sum=0~ の説明は適当です。

  • 1..1000 は (1,2,3,...,1000) という配列を作る演算子です。
  • map は、第1引数の文を第2引数で与えられた配列の値全てにたいして実行します。つまり {$sum += $_ if(!($_%3) || !($_%5))} を999回実行します。
  • map の第1引数の文の中の $_ の値は1つずつ取り出された第2引数の値になります。つまり1回目は1、2回目は2が入っています。
  • A if(B) という構文は B が真であれば A を実行するという意味で、if(B) {A} すなわち if(!($_%3) || !($_%5)) { $sum += $_ } と同じです。
  • !($_%3) || !($_%5) は $_ が3の倍数か5の倍数なら真になり、そのときだけ $sum += $_ が実行されます。
  • 上記全てをふまえると、1から1000までの数字で3の倍数か5の倍数の合計が$sumに入ることが分かるんじゃないかと思います。
id:phji

詳細な回答ありがとうございます。

上のワンライナーのコードが私の質問でして、下のは私が書いたコードでした。

もう少し分かりやすく質問をするべきでした。

単純な演算子の説明までさせてしまってすみません。

三項演算子を展開して if 文で書いてくれているところのコメントが if と else で逆だと思います。

$p%3*$p%5 で論理演算子を使わなくても、「3または5で割りきれない」を表現できるんですね!

置換して、条件を満たす置換した箇所を数えるのが全体の流れだと分かりました。

また、それぞれの式の満たす意味も分かりました。

ありがとうございました。

2009/07/01 14:36:26

コメントはまだありません

この質問への反応(ブックマークコメント)

トラックバック

  • 読み難いワンライナー はてブを眺めていたら以下のような質問を見付けたので、解説してみるよ! perl の構文に関する質問です。 このコードが何をやっているのか教えてください。 $_=3x(1E3-
「あの人に答えてほしい」「この質問はあの人が答えられそう」というときに、回答リクエストを送ってみてましょう。

これ以上回答リクエストを送信することはできません。制限について

絞り込み :
はてなココの「ともだち」を表示します。
回答リクエストを送信したユーザーはいません