カメヲラボ

主にプログラミングとお勉強全般について書いてます

CodeIQ過去問集44:最大の攻撃を繰り出せ!

本稿はCodeIQで出題された 第1回プロコン:【鬼】最大値をたたき出す攻撃を繰り出して鬼を倒そう という問題を再編集したものです。 ※出題時と記述が一部異なる場合がありますがご了承ください。

第1問がまだの方はこちら

第2問がまだの方はこちら

第3問がまだの方はこちら

第4問:最大の攻撃を繰り出せ!

【ストーリー】

熱々のラーメンからメンマを取り出す作業で軽く手に火傷を負ったあなたですが無事偏食の猿も仲間にすることができました。
犬、雉、猿を仲間にしたあなたは、ついに姫を救い出すために鬼が島に乗り込みます!!!

f:id:Ozy:20150605125910j:plain

鬼  「私をここまで追い込んだやつは今までいなかったぞ。やるな犬、雉、猿よ」
元々運動が得意でないあなたは前線で戦う犬、雉、猿を見ながら本当に3匹を仲間にしてよかったと思っています。
鬼  「だが、ここまでだ。私を倒すには力足らずだったな。」
先ほどまで鬼を押し込んでいた犬、雉、猿でしたが、鬼に攻撃が通らず、徐々に押し返されてきています。
犬  「桃太郎さん。ボク達がバラバラに攻撃していてもあの鬼には通じないみたいだ」
雉  「桃太郎さんも加わって、いちかばちか総攻撃を仕掛けよう」
猿  「桃太郎さんなら、私たちができる攻撃の中から最大値をたたき出す攻撃を考えられますよね?」
今まで岩の影に隠れていたあなたですが、ついに戦いの場に引きずりだされます。
お供の言うとおり、最大値をたたきださなければ全滅です。
でも、きっとここまできたあなたなら大丈夫ですよね。
鬼を倒して、あなただけの桃太郎を完結させてください。

※今さら補足

桃太郎+お供3匹が2×2の二手に分かれて、鬼の左右から大きな岩を同時にぶつけよう!というシチュエーションを想定して作った問題でした。

【問題】

4つのブロックを立方体と考えます。 一辺の長さをそれぞれa, b, c, dとして、

X = a3 + b3 Y = c3 + d3

とすると、X = Yを満たすX(=Y)の最大値を求めてください。

たとえば、1辺の長さが1~20までのとき、

(1) 13 + 123 = 93 + 103

(2) 23 + 163 = 93 + 153

のような場合が考えられますが、(1)を計算すると1729、(2)を計算すると4104となりますので、4104が条件を満たす最大値となります。

【入力】

標準入力から、整数値(20~1000)が与えられます。

【出力】

標準出力に、解となる整数値のみ出力してください。出力後の改行の有無は問いません。

【入出力例】

Case 1: Input

20

Case 1: Output

4104

Case 2: Input

50

Case 2: Output

110808

【解答例】

CodeIQ過去問集43:「MENMA」を探そう

本稿はCodeIQで出題された 第1回プロコン第3問:【猿】偏食の猿の好き嫌いを見極めよう という問題を再編集したものです。 ※出題時と記述が一部異なる場合がありますがご了承ください。

第1問がまだの方はこちら 第2問がまだの方はこちら

第3問:「MENMA」を探そう

【ストーリー】

くそ生意気な雉に頭の良さをみせつけたあなた。
今やウザいくらいあなたにべったりの雉を無事に仲間にできました。
そして、ついに最後の仲間、猿に出会います。

物語のように猿にキビ団子を渡すあなた。
猿  「おいら団子なんて食わねえよ」
あなた「…(てか、この世界でキビ団子全然役に立たねえな。)」
猿  「あ、そうだ。おいらラーメンっていう食い物を一度でいいから食ってみてえんだ」
あなた「まあ、インスタントでいいなら作ってやるぞ」
猿  「ほんとかい?ラーメンくれたら鬼でもなんでも倒してやる」

f:id:Ozy:20150605125848j:plain

ラーメンを作って、猿に出そうとしたその瞬間でした。
猿  「あ、まさかとは思うがメンマは入れてないよな。入ってたら鬼の味方するからな」
メンマ入りのラーメンを作ってしまったあなたは、大慌てでメンマをラーメンから取り除くのでした。

※この画像は私が用意したものではないので、問題が発生した場合はすぐに差し替えるか消します

【問題】

n×n(5≦n≦100)の文字列の中から、「MENMA」と連続する部分があるかどうかを判定してください。

f:id:Ozy:20180430171438j:plain

のように一直線に並んでいる場合だけでなく、

f:id:Ozy:20180430171435j:plain

f:id:Ozy:20180430171433j:plain

のように途中で折れ曲がっていても構いません。

【入力】

標準入力から、n×n(5≦n≦100)の文字列が与えられます。 文字列に含まれる文字は「A, E, M, N」のいずれかで、改行コードはLFです。

【出力】

標準出力に、文字列に連続する「MENMA」が含まれる場合は「yes」、含まれない場合は「no」と出力してください。出力にはyes/no以外の文字は出力しないでください。行末の改行については有無を問いません。

【入出力例】

Case 1: Input

MENME
NMENM
ENMEN
MENMA
NMENM

Case 1: Output

yes

Case 2: Input

EMNME
MNANE
EAENN
MEEEE
EMNEM

Case 2: Output

no

【解答例】

CodeIQ過去問集42:少ない重りで重さを量ろう

本稿はCodeIQで出題された 第1回プロコン:【雉】くそ生意気な雉を黙らせよう という問題を再編集したものです。 ※出題時と記述が一部異なる場合がありますがご了承ください。

第1問がまだの方はこちら

第2問:少ない重りで重さを量ろう

【ストーリー】

じゃんけんにわざと負けてあげることで自信を取り戻した犬を仲間にしたあなた。
鬼が島にむかって旅路を急いでいると、後ろから声が。

雉  「桃太郎さん桃太郎さん、お腰につけたキビ団子ひとつ私にくださいな」
あなた「(お、これは確か仲間になるフラグのあの有名なセリフだな)
    では、これをやるから私と一緒に鬼退治に行こうではないか」

f:id:Ozy:20150605125833j:plain

雉  「…。このキビ団子って何グラムですかね?
    重さによっては鬼退治に同行するより、自分で働いて買った方が早いんで」
あなた「…(雉以外の鳥じゃだめかな)」
とはいえ、犬、雉、猿をそろえなければ鬼には勝てません(と、童話に書いてあった)。
雉の前でキビ団子の重さを計って、大きな団子であることを証明しましょう。
雉  「あ、あと自分頭悪いやつにはついていきたくないんで、最少のおもりの数で計ってくださいね。」
あなた「…(そこにいる雀でなんとか鬼退治できねえかな)」

※この画像は私が用意したものではないので、問題が発生した場合はすぐに差し替えるか消します

【問題】

てんびんが1つあります。これを1回だけ使い、1グラムからnグラムまで、1グラム刻みの重さn通りをすべて量ることができるようにします。使用するおもりの個数をできるだけ少なくなるようにすると、おもりは最低いくつ必要になるかを計算してください。

例えば4グラムまですべて量るには、1グラムのおもり(A)と3グラムのおもり(B)を用意すると、次のように1~4グラムすべて量ることができます。

  • 1グラム…(A)をてんびんの片側に置く

  • 2グラム…(A)をてんびんの片側に、(B)をその反対側に置く(3-1=2グラムを量ることができる)

  • 3グラム…(B)をてんびんの片側に置く

  • 4グラム…(A)と(B)の両方をてんびんの片側に置く(3+1=4グラムを量ることができる)

【入力】

標準入力から、整数n(1≦n≦300)が与えられます。

【出力】

標準出力に、入力nに対するおもりの個数の最小値のみを出力してください。行末の改行コードは有無を問いません。

【入出力例】

Case 1: Input

4

Case 1: Output

2

Case 2: Input

10

Case 2: Output

3

【解答例】

CodeIQ過去問集41:接待じゃんけん

本稿はCodeIQで出題された 第1回プロコン:【犬】自信を無くした犬に自信を取り戻させよう という問題を再編集したものです。 ※出題時と記述が一部異なる場合がありますがご了承ください。

第1問:接待じゃんけん

【背景】

本問はCodeIQ第1回プログラミングコンテストで出題された問題の1つで、プログラミング初心者の方でも参加しやすいように『簡単』かつ『親しみやすい内容』ということで作成しました。「接待じゃんけん」というのは当初私が作成した問題案のタイトルです。桃太郎のストーリーはCodeIQの中の人が一生懸命考えたものですので、その後本問が【犬】問題となったのです。

【ストーリー】

おじいさんとおばあさんにきびだんごをもらい、さっそく鬼退治に出かけたあなた。
とりあえず、あなたは昔読んだ物語「桃太郎」を思い出してみます。
あなた「確か、動物を仲間にするんだっけな。まずは、犬を探そう!」
そして見つけた桃太郎の最強の仲間、犬。

f:id:Ozy:20150605125816j:plain

あなた「犬よ。きびだんごをやるから私と一緒に鬼退治に向かおう」
犬  「いや、ボクなんかが鬼退治なんて無理ですよ」
あなた「…(話が違うぞ)」
犬  「私は何をやってもダメなんです。さっきも子供たちに<じゃんけん>という遊びで一回も勝てなくて。。そりゃ、鬼に勝てるって自信があればいくらでもついていきますよ」
あなた「(これはまずは自信をつけてやらないと鬼退治なんて無理だな‥)よし、ではそのじゃんけんとやらを私とやろう」

※この画像は私が用意したものではないので、問題が発生した場合はすぐに差し替えるか消します

【問題】

犬が出そうとしている手を読み取って、負けてあげるように手を出してください。 グーを「G」チョキを「C」パーを「P」とすると、たとえば犬が「G」を出そうとしているときは「C」を出してあげてください。

【入力】

標準入力から、「G, C, P」のいずれかの1文字が与えられます。

【出力】

標準出力に、「G, C, P」のいずれか1文字を適切に選んで出力してください。出力後の改行の有無は問いません。

【入出力例】

Case 1: Input

G

Case 1: Output

C

【解答例】

CodeIQ過去問集50:「2人組を作って!〜欠席多くても頑張る編〜」

本稿はCodeIQで2014年9月18日~2014年9月24日に出題された プログラミング言語★総選挙 本選挙「2人組を作って!〜欠席多くても頑張る編〜」 という問題を再編集したものです。 ※出題時と記述が一部異なる場合がありますがご了承ください。

プログラミング言語★総選挙 本選挙

【2人組を作って!〜欠席多くても頑張る編〜】

先生「はーい、それじゃあ今から2人組になってくださーい」 と言われて、辛い思いをしなくても済むように、隣り合った人同士で、ペアを作りたいと思います。

f:id:Ozy:20180428154738p:plain

このように、横に3人、縦に2人の合計6人いる場合、

f:id:Ozy:20180428154735p:plain

f:id:Ozy:20180428154732p:plain

f:id:Ozy:20180428154728p:plain

のように、ペアを作ることができます。 ただし、

f:id:Ozy:20180428154724p:plain

のようなペアの作り方では、左下の人と右上の人が離れてしまいますので、このようなペアの作り方は避けます。 そうすると、合計の人数が偶数であるかぎり、あぶれる人が出ることなく、かならずペアを作ることができます。

…が、現実はそれほど優しくはありません。欠席者がいるときもあります。

f:id:Ozy:20180428154832p:plain

人数が奇数になってしまうと、どう頑張っても1人残ってしまいます。 逆に、元々奇数人の場合はうまくペアを作ることができる場合もあります。

f:id:Ozy:20180428154829p:plain

ただし、欠席者の場所によってはうまくペアが作れない場合もあります。

f:id:Ozy:20180428154826p:plain

欠席者が多数いた場合でも、とにかく出席者だけでペアを作ります。

f:id:Ozy:20180430153054p:plain

f:id:Ozy:20180430153051p:plain

上記の場合はうまくペアを作ることができますが、次の場合はペアを作ることができません。

f:id:Ozy:20180430153048p:plain

【問題】

出欠情報が、標準入力からアルファベットの文字列で与えられます。 隣り合う人同士でペアを作り、出席者全員をペアにできる場合は「yes」、できない場合は「no」と出力するプログラムを書いてください。

  • ◎出欠を表すアルファベットはすべて大文字で、出席の場合は”O”、欠席の場合は”X”とします。

  • ◎アルファベットの並びは、必ず矩形(各行の文字数が等しい)で与えられます。また、矩形のタテの長さをH、ヨコの長さをWとすると、入力データの範囲は1≦H≦10、1≦W≦10とします。

  • ◎欠席者の数には制限がありません。つまり、全員欠席の場合もあり得ます。 全員欠席の場合は、「no」と出力してください。

【入出力例】

◎sample(1)

[input]

OOO
OOX

[output]

no

◎sample(2)

[input]

OOO
OOO
OOX

[output]

yes

◎sample(3)

[input]

OOO
OOO
OXO

[output]

no

◎sample(4)

[input]

XXX
XOO
XXX

[output]

yes

【提出方法】

本問はすでに終了した問題ですので、提出することはできません。個人で楽しみましょう。

honsen.zipにテストデータがまとめてあります。

[【解説】]

CodeIQ過去問集49:「2人組作って!」

本稿はCodeIQで2014年9月4日~2014年9月16日に出題された プログラミング言語★総選挙 予備選挙「2人組作って!」 という問題を再編集したものです。 ※出題時と記述が一部異なる場合がありますがご了承ください。

プログラミング言語★総選挙 予備選挙

【2人組作って!】

先生「はーい、それじゃあ今から2人組になってくださーい。」 と言われて、辛い思いをしなくても済むように、隣り合った人同士で、ペアを作りたいと思います。

f:id:Ozy:20180428154738p:plain

このように、横に3人、縦に2人の合計6人いる場合、

f:id:Ozy:20180428154735p:plain

f:id:Ozy:20180428154732p:plain

f:id:Ozy:20180428154728p:plain

のように、ペアを作ることができます。 ただし、

f:id:Ozy:20180428154724p:plain

のようなペアの作り方では、左下の人と右上の人が離れてしまいますので、このようなペアの作り方は避けます。 そうすると、合計の人数が偶数であるかぎり、あぶれる人が出ることなく、かならずペアを作ることができます。

…が、現実はそれほど優しくはありません。欠席者がいるときもあります。

f:id:Ozy:20180428154832p:plain

人数が奇数になってしまうと、どう頑張っても1人残ってしまいます。 逆に、元々奇数人の場合はうまくペアを作ることができる場合もあります。

f:id:Ozy:20180428154829p:plain

ただし、欠席者の場所によってはうまくペアが作れない場合もあります。

f:id:Ozy:20180428154826p:plain

【問題】

出欠情報が、標準入力からアルファベットの文字列で与えられます。 隣り合う人同士でペアを作り、出席者全員をペアにできる場合は「yes」、できない場合は「no」と出力するプログラムを書いてください。

  • ◎出欠を表すアルファベットはすべて大文字で、出席の場合は"O"、欠席の場合は"X"とします。

  • ◎アルファベットの並びは、必ず矩形(各行の文字数が等しい)で与えられます。また、矩形のタテの長さをH(≧1)、ヨコの長さをW(≧1)とすると、入力データは2≦H×W≦30の条件を満たしています。

  • ◎欠席者は、0人または1人のみです。つまり、"X"は多くて1か所のみということになります。

【入出力例】

◎sample(1)

[input]

OOO
OOX

[output]

no

◎sample(2)

[input]

OOO
OOO
OOX

[output]

yes

◎sample(3)

[input]

OOO
OOO
OXO

[output]

no

【提出方法】

本問はすでに終了した問題ですので、提出することはできません。個人で楽しみましょう。

yosen.zipにテストデータがまとめてあります。

【解説】

プログラミング言語★裏選挙―愛の風車【解説】

本稿はCodeIQで2014年10月2日~2014年10月23日に出題されたプログラミング言語★裏選挙―愛の風車」 という問題の結果・解説です。

問題文はこちら

プログラミング言語★裏選挙―愛の風車【解説】

f:id:Ozy:20180428145747j:plain

順位表

最終的な順位は、次のようになりました。

順位 有効得票数 言語
1位 17 JavaScript
2位 16 PHP
3位 14 Perl
4位 12 Python
5位 9 Scala
6位 7 Bash, C++11
8位 6 AWK
9位 4 Go, VB.NET
11位 3 D
12位 2 Clojure, Erlang, Octave, R, Scheme, SQL, Whitespace
19位 1 A, Brainfuck, Common Lisp, Egison, F#, Fortran, Groovy, Lua, Objective-C, Pascal, なでしこ

僅差でJavaScriptが1位となりました! PHPは惜しかったですね。 Perlは最近挑戦者数が減ってきているように感じていましたが、まだまだ人気ですね。 Pythonは本選に残る人気でしたから、今回はあえて使用しなかった方が多かったのかもしれませんが、なんとか4位に滑り込みました。 5位以下では、Scalaが健闘しました。 総選挙のときも、関数型言語ではHaskellに次ぐ人気で、今後が楽しみですね。 BashAWKが結構上位に来たのも興味深いです。 C++11は意外に伸びませんでした。 問題の内容が簡単でしたから、わざわざ使おうという気になりにくかったのかもしれませんね。 総選挙のときと違いideone縛りを無くしたこともあってか、言語の種類は29種と非常に多かったです。

解説と優秀・おもしろ解答

素敵な風車の描き方(1)

風車なんだから、くるくる回転してナンボでしょ!ということで、羽の左上だけ生成し、それを回転させることで風車全体を作るようにするという方法が考えられます。

【Muさんのコード(Octave)】

for i = 5:7
  a = char(tril(ones(i))*('*'-' ')+' ');
  b = horzcat(a, rot90(a,-1));
  disp( vertcat(b,rot90(b,2)));
endfor

Octaveという言語は行列の操作が簡単にできるのが特徴で、まずa = …の部分で三角行列を作ることによって羽の左上を生成しています。 次の行で、最初に作った羽を右に90度回転させ、最初に作った羽と連結しています。 rot90関数の第2引数は回転させる回数で、-1ということは右に1回回転させるという意味になります。 これで風車の上半分ができました。これをさらに180度回転させると下半分もできるので、簡単に風車が完成しました。美しいですね。

素敵な風車の描き方(2)

羽を1枚だけ生成し、それを回転させるためには配列を用意しなければなりません。 配列を使わずに、2重ループでまとめて描いてしまうという方法もあります。 羽が4枚ありますから、xy平面上を4分割し、各領域で’*’を表示するか’ ‘を表示するかの条件分岐をすると、8つも分岐しなければなりません。 それはそれで何をやっているのかわかりやすいという利点もありますが、if文をたくさん並べることなく、簡潔でカッコよく書きたい!と思うのも人情です。 というわけで、「コイツはカッコいいぜ!!」と私が感じたコードを紹介しておきます。 【ぶんさんのコード(VB.NET)】

Imports System

Public Class Test

    Public Shared Sub Main() 

        Dim n,x,y As Integer

        For n = 5 To 7

            For x= -2*n+1 To 2*n-1 Step 2

                For y= -2*n+1 To 2*n-1 Step 2

                    System.Console.Write(IIF((x/y<=y/x),"*"," "))

                Next y

                System.Console.Writeline("")

            Next x

        Next n

        End

    End Sub

End Class

【haruyaさんのコード(Python)】

import sys
for a in [9, 11, 13]:
    for y in range(a, -a - 2, -2):
        for x in range(-a, a + 2, 2):
            sys.stdout.write(' ' if ((x + y * 1j) ** 4).imag > 0 else '*')
        print

両者の共通点として、羽のサイズNの風車を書く際、単純に0..2N-1の範囲でループせずに、-2N+1..2N+1の範囲で2ステップしているコードになっています。 これは、xy平面を4象限に分割しつつ、座標の絶対値を対称にするためです。 座標が0になってしまう箇所を避けることもでき、余計な分岐を無くすことができます。 わかりやすいように、N=5の場合を図にしてみましょう。 ぶんさんのコードで、(x/y<=y/x)となっている部分を調べるために、各座標に対応する(x/y – y/x)を計算してみます。

f:id:Ozy:20180428150252p:plain

上記のように羽の部分(‘*’を表示する部分)の値は0以下になり、1つの不等式できちんと判別できています。 haruyaさんのコードでは、複素数平面を利用していて、複素数(x + yi)を4乗すると、虚部はxy(y2 – x2)という式になります。 これは、先程の(x/y – y/x)という式に(xy)2を掛けたものと一致するので、判定方法としては同じです(ただし、両者のコードはx, yの向きが異なっていますので注意してください)。 風車の左上と右下・右上と左下では羽の向きを判定する式が共通化でき、2領域の区別はx, y座標を乗除したときの正負で判定できるというところがポイントです。

おもしろ解答

個性的な解答が多数ありましたが、すべてをご紹介することはできませんので、130のコードのうち特に印象に残ったものをご紹介いたします。

【hcmiyaさんのコード(Bash)】

for i in {5..7}
do 
    hane1="$(head -c$i /dev/zero |tr '\0' ' ' |sed ':j;H;s/ /*/;t j;x'|tail -n+3)"
    hane2="$hane1
$(rev <<<"$hane1")"
    rev <<<"$hane2" |tac |paste -d '' <(cat <<<"$hane2") -
done

sedで羽を1枚作り、tacやrevで回転させるタイプのコードで、様々なコマンドを上手に使いこなしている感と、それによって非常に簡潔なコードに仕上がっている点に惹かれました。

カニ戯(ryさんのコード(Bash)】

for x in 5 6 7
do
    echo $x | \
    sed \
     -e ':0;x;s/^.*$/&\*/;x;y/987654321/876543210/;/0/!b0' \
     -e 's/.*//;g;s/^./@/;y/@*/* /;G;s/\n//;h;s/.*//;x;h' \
     -e ':1;/^\** *$/!{;s/\* /\*\*/' \
     -e 's/^\(.*\)\(\*\*\)\( *\)$/\1\* \3/;H;b1}' \
     -e 's/\(^\**\)\( *$\)/\2\1/;H;s/ \*\*\*/\*\* \*/;H' \
     -e ':2;/^\*/!{;s/ \*/  /2;s/ \*/\*\*/;H;b2};g'
done

こちらはsedだけですべての羽を作っているコードで、パッと見た感じだけでお腹いっぱいなのですが、コードを順に追っていくとsedプログラミングがよくわかります。 sedゴルフに興味があるけどよくわからないという方は、これで勉強してみてはいかがですか? パターンスペース・ホールドスペースという2つのバッファをうまく使うのがポイントです。

【みけCATさんのコード(A言語)】

AtSnc:c\ArSnc:+*n++n-1-%cn/cn\AfSnc:+*n/cn++n-1-%cn\AgSnc:?>%cn/cnS \\:S*\\:\
Ri3PAn+5iRy*2nPRx*2nPAv%ynPAu%xnOFgnF?<xnt:r:nF?<ynt:f:n+*nvuOMc10

なんじゃこら!というか、A言語って何ですか!という感じですが、 https://github.com/snukent/a から処理系が手に入るようです。 素敵な風車の描き方(2)で解説したアルゴリズムを使って書くと、

Rn3PAk+n5Ri*k2PAy+1*2+i-kPRj*k2PAx+1*2+j-kO?</xy/yxS \:S*\:OMc10

のような感じで、かなり短く書くことができます。興味のある方は試してみてください。

【KTazakikさんのコード(Brainfuck)】

[-]>[-]+++++++[-<++++++>]<>[-]>[-]++++++++[-<++++>]<>[-]++++++++++<<
 .>....<.....>>.<<
 ..>...<....>.>.<<
 ...>..<...>..>.<<
 ....>.<..>...>.<<
 .....><.>....>.<<
>....<.><.....>>.<<
>...<..>.<....>>.<<
>..<...>..<...>>.<<
>.<....>...<..>>.<<
><.....>....<.>>.<<
 .>.....<......>>.<<
 ..>....<.....>.>.<<
 ...>...<....>..>.<<
 ....>..<...>...>.<<
 .....>.<..>....>.<<
 ......><.>.....>.<<
>.....<.><......>>.<<
>....<..>.<.....>>.<<
>...<...>..<....>>.<<
>..<....>...<...>>.<<
>.<.....>....<..>>.<<
><......>.....<.>>.<<
 .>......<.......>>.<<
 ..>.....<......>.>.<<
 ...>....<.....>..>.<<
 ....>...<....>...>.<<
 .....>..<...>....>.<<
 ......>.<..>.....>.<<
 .......><.>......>.<<
>......<.><.......>>.<<
>.....<..>.<......>>.<<
>....<...>..<.....>>.<<
>...<....>...<....>>.<<
>..<.....>....<...>>.<<
>.<......>.....<..>>.<<
><.......>......<.>>.<<

ソースコード自体が風車っぽくてステキですね。 ‘\n’, ‘*’, ‘ ‘をセットしただけでほぼベタ書きじゃないかー!という突込みはナシで^^; 他にも面白いコード(WhitespaceとかSQLとか!!)をたくさんいただきましたが、これくらいにしておきます。

最後に

単純な問題ではありましたがどれも個性的で、読んでいて楽しいコードがたくさんありました。 同時に、ものすごく似た感じのコードもあり、それらを大量に見た感想を書いておきます。 まず、次のようなコードをご覧ください。

void kazaguruma( int n )
{
  for (int y = 0; y < 2 * n; ++y) {
    for (int x = 0; x <  2 * n; ++x) {
      if ( y < n ) {
        if ( x < n ) {
          if ( y >= x ) {
            printf( "*" );
          } else {
            printf( " " );
          }
        } else {
          if ( y + x < 2 * n ) {
            printf( "*" );
          } else {
            printf( " " );
          }
        }
      } else {
        if ( x < n ) {
          if ( y + x >= 2 * n - 1 ) {
            printf( "*" );
          } else {
            printf( " " );
          }
        } else {
          if ( y <= x ) {
            printf( "*" );
          } else {
            printf( " " );
          }
        }
      }
    }
    printf( "\n" );
  }
}

羽のサイズnを受け取り、左上・右上・左下・右下の順で分岐して羽を書くコードです。 好き嫌いで言えば、いかにも冗長な感じで、私はあまり好きなタイプのコードではありません。 ただ、同じようなコードをたくさん見ていると、そんなに悪いコードでもないように思えてきます。 このような形が書かれていれば、コードの中身を読まずともその内容がすぐにわかるからです。 平面的に文字を描画する場合、“左から右に向かって”、“上から下に向かって”、という順序は最も素直で、よほど捻くれたプログラマでない限りはこの順序で条件分岐も書くでしょう。

つまり、このようなコードはシルエットを一目見ただけで、どのようなコードかが理解できます。 私は子どもの頃から視力が弱いので、ぼんやりとモノを見る癖がついてしまっていますし、そういうコードの見方をしている人にとってはこれ以上わかりやすい記述はありません。 それでは自分が普段このようなコードを書くのかというと、書きません。 一応、頭の中では上記のようなイメージ(文字通り画像のような“絵”)がパッと浮かびます。 次に何をするのかというと、その絵を見ながら、文句を言うわけです。 「printfが9個もあるじゃないか!!!」 それで、次のようなコードに書き替えてみたりします。

void draw( bool a )
{
  printf( a ? "*" : " " );
}

void kazaguruma( int n )
{
  for (int y = 0; y < 2 * n; ++y) {
    for (int x = 0; x < 2 * n; ++x) {
      if ( y < n ) {
        if ( x < n ) {
          draw( y >= x );
        } else {
          draw( y + x < 2 * n );
        }
      } else {
        if ( x < n ) {
          draw( y + x >= 2 * n - 1 );
        } else {
          draw( y <= x );
        }
      }
    }
    printf("\n");
  }
}

改行用のprintfは仕方なしということで、条件式を引数に、drawという関数を新たに作りました。 8つのprintf関数が1つになったことと、ifブロックのネストが減ってだいぶスッキリしました。 まあ、これくらいのコード量ならよいよね?
しかし、drawという短い記述の関数を作ってしまったが故に、短くしたはずのkazaguruma関数が比較的冗長に見えてきてしまいました。 それなら、ネストの一番内側にある、左右の条件式の部分をもうちょっとまとめておこうか。

void draw( bool a )
{
  printf( a ? "*" : " " );
}

void drawLR( int n, int x, bool l, bool r )
{
  draw( x < n ? l : r );
}

void kazaguruma(int n)
{
  for (int y = 0; y < 2 * n; ++y) {
    for (int x = 0; x < 2 * n; ++x) {
      if ( y < n ) {
        drawLR( n, x, y >= x, y + x < 2 * n );
      } else {
        drawLR( n, x, y + x >= 2 * n - 1, y <= x );
      }
    }
    printf("\n");
  }
}

なんだかコードもスッキリしたし、関数化して細かく切り分けとかしてコンパクトに書ければ「ステキ!抱いて!!」ってなるんじゃね? じゃあ上下段も思い切って、切り出そうかな。

void draw( bool a )
{
  printf( a ? "*" : " " );
}

void drawLR( int n, int x, bool l, bool r )
{
  draw( x < n ? l : r );
}

void drawUDLR( int n, int y, int x, bool ul, bool ur, bool dl, bool dr )
{
  y < n ? drawLR( n, x, ul, ur ) : drawLR( n, x, dl, dr );
}

void kazaguruma(int n)
{
  for (int y=0; y<2*n; ++y) {
    for (int x=0; x<2*n; ++x) {
      drawUDLR( n, y, x, y >= x, y + x < 2 * n, y + x >= 2 * n - 1, y <= x );
    }
    printf("\n");
  }
}

…なんだか横に伸びてきちゃった。ちょっと空白減らして、nもグローバルでいいかな?

int n;

void draw(bool a)
{
  printf(a ? "*" : " ");
}

void drawLR(int x, bool l, bool r)
{
  draw(x<n ? l : r);
}

void drawUDLR(int y, int x, bool ul, bool ur, bool dl, bool dr)
{
  y<n ? drawLR(x, ul, ur) : drawLR(x, dl, dr);
}

void kazaguruma()
{
  for(int y=0;y<2*n;++y){
    for(int x=0;x<2*n;++x){
      drawUDLR(y, x, y>=x, y+x<2*n, y+x>=2*n-1, y<=x);
    }
    printf("\n");
  }
}

このような思考がエスカレートするのは、いわゆる(コード)ゴルフ脳という状態で、自分でブレーキを掛けなければなりません。 この辺りまでで我慢ができれば、正常なプログラマではないかと思います。 上記のような関数への切り出しも、比較的短めのコードなので見栄えがしますが、何十万行もあるコードでこれをやっていくと、ものすごく読みにくくなってしまいます。 全体を見渡して、「まあこれくらいが自然かな」という感覚をある程度持っておくと、他人にコードを提供したりする場合に、相手にとって読みやすいコードを書くのが上手になります。

ベテランのプログラマと、若いプログラマ、もちろん十把一絡げに考えることはできないのですが、ひとつの傾向として、ベテランプログラマで「この人は仕事できるな」って感じるの人は、シチュエーションに合わせたコーディングができるという特長があるように思います。 どのように書くかは、良く言えば美学、悪く言えば自己満足で、趣味の世界ならどこまでもこだわってよいと思います。 しかし、他人と何らかのかかわりを持って、仕事として書くなら、自分より上級のプログラマに迷惑をかけることなく、なおかつ自分よりスキルの劣るプログラマを包み込めるようなバランス感覚を身に付けると、仕事もしやすくなるのではないでしょうか。

…と、一応転職向けのサイトということで無理やり仕事の話に結び付けましたが、ちょっと無理やり過ぎましたね。 兎も角も、ある程度コードが書けるようになったら、他人のコードをたくさん読んで、コードから書き手のスキルレベルを感じ取る経験は、きっと役に立ちます。 特に若い人は、いろんな人が書いた、いろんな言語のコードに触れてみてください。