カメヲラボ

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

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");
  }
}

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

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

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

CodeIQ過去問集51:プログラミング言語★裏選挙 ~ 愛の風車

本稿はCodeIQで2014年10月2日~2014年10月23日に出題された プログラミング言語★裏選挙―愛の風車」 という問題を再編集したものです。 ※出題時と記述が一部異なる場合がありますがご了承ください。

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

【問題】

素敵な風車を、標準出力に出力してください。

f:id:Ozy:20180428143128p:plain

これは羽のサイズが4の風車です。 この形状で、サイズが5・6・7の風車を順に出力してください。 羽の部分は’*’で、空白部分は半角スペースとします。改行コードは問いません。

*    *****
**   **** 
***  ***  
**** **   
******    
    ******
   ** ****
  ***  ***
 ****   **
*****    *
*     ******
**    ***** 
***   ****  
****  ***   
***** **    
*******     
     *******
    ** *****
   ***  ****
  ****   ***
 *****    **
******     *
*      *******
**     ****** 
***    *****  
****   ****   
*****  ***    
****** **     
********      
      ********
     ** ******
    ***  *****
   ****   ****
  *****    ***
 ******     **
*******      *

見た目がこのようになっていればOKです(心配な方は、kazaguruma.txt をダウンロードしてdiffを取ってみてください)。 出力結果をそのままソースコード内に埋め込むような解答を禁止はしませんが、プログラミング愛があれば、そんなコードは書きませんよね?

【使用可能言語】

先日行われた『CodeIQ プログラミング言語★総選挙』で本選に残らなかった言語を対象とします。つまり、

C / C++
C#
Haskell
Java
Python3
Ruby

禁止(Python2とC++11は使用可能)です。 基本的に、Ideone.com(http://ideone.com/credits)でテストしたいと考えておりますが、ここに無い言語や、コンパイラのバージョンが気に入らない等、他の環境をご希望の方は、解答ファイルに実行環境を明記していただければ、可能な限り対応いたします。 フリーの処理系を使用する場合、導入方法をコメント内にご記入ください。 無料でない実行環境の場合、対応できない可能性がございますのでご了承ください。 また、Ideone.com上で使用可能な言語については以下のルールで集計します。

【投票】

ご提出いただいたコードを実行し、実行結果が正しく得られたものを有効票とします。 これを元にランキングを生成し、1位~4位の言語は、次のキャラクターに擬人化され、彼らのバッジが付与されます。 かわいい女の子は架空の存在です。現実を見ましょう。

f:id:Ozy:20180428144955p:plain

【提出方法】

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

【結果・解説】

作文ゴルフじゃなくてゴルフ作文

作文ゴルフじゃなくてゴルフ作文

今更感が半端ないですが、半年くらい前にこの動画↓(55秒くらい)に現れる小ネタの作文を書きました。

f:id:Ozy:20180427171133p:plain

<body style="font-family:monospace;"><script>for(i=228;i--;)for(j=4;j--;document.write(i%19|j?s:s+"<br>"))s="□■"["AAAAAAAAAAAAAAAAAPAHEPAMDIBBPAOBIHAMACEMIBDMADJBBDCGIBGAGEEABBIACBACAECABCAEEEAJAABMJPDPHCACBAIEEAJAABEJBCDEOACBAIEEAJAABCBBBCCAACBAIPEAJAABBBOAMBAACDAIEAAABIAABAAAACABCAAEAAADMAABAAAAMIBGAEAAAAMDAABAAAAIHAMADAAAAAAAABAAAAAAAAPB".charCodeAt(i)-65>>j&1]</script>

ブラウザに貼り付けて開くとこんな感じになります。 f:id:Ozy:20180427170631p:plain

400バイトもあると頑張らなくても収まってしまうので、作文として書きやすいように圧縮データ部分は全部アルファベットで済むようにしました。余裕があるので等倍フォントを指定してます。

…まさかコードゴルフの仕事依頼が来るとは!!

階段ピョンピョン1・2・3!【解説】

  • 本稿はCodeIQで2014年2月3日~2月24日・2015年1月19日~3月2日に出題された 「コード銀行:階段ピョンピョン1・2・3!」 という問題の解答例と簡単な解説です。

  • 本稿におけるコードは単なる解答の1例に過ぎません。「もっと良い解法があるよ!」とか、「こう書いた方がわかりやすいよ!」とか、「他の言語で書いてみたよ!」という場合は、自由にコメントに書いていただいても、個人のブログ等で公開していただいても結構です。

問題本文はこちら

階段ピョンピョン1・2・3!【解説】

◆基本的な考え方

n段目(n>3)に到達するとき、

[1] (n-1)段目まで上ったあと1段上がる
[2] (n-2)段目まで上ったあと2段上がる
[3] (n-3)段目まで上ったあと3段上がる

の3通りあるので、これらを再帰的に足し合わせるコードを書きます。

def count(n)
  return count(n-1) + count(n-2) + count(n-3)
end

という感じですね。もちろんnが負になってしまってはいけませんので、

if n < 0
  return 0

みたいな処理は必要です。nがちょうど0の場合は少し考える必要があります。

◆n = 0の場合の定義

たとえばnが2の場合を考えると、

[1] (2-1=1)段目まで上ったあと1段上がる
[2] (2-2=0)段目まで上ったあと2段上がる
[3] (2-3=-1)段目まで上ったあと3段上がる

[1]は明らかに1通りです。[3]は段数が負になるので0通りとしましょう。2段の上がり方は、実際には

・1段 + 1段
・一気に2段

の2通りです。 したがって、[2]は1通りと数えた方が都合が良くなりますので、count(0)は1と定義しておきましょう

◆メモ化

単純な再帰の場合は計算量がO(n3)でかなり遅いので、メモ化を行います。

def count(n)
  if memo[n]が存在
    return memo[n]
  endif
  
  memo[n] = count(n-1) + count(n-2) + count(n-3)
  return memo[n]
end

のような感じですね。memo[n]が存在するかどうかの判定には、memo[n]を0で初期化しておいて、0より大きいときは存在するということにしておきましょう。つまり、

memo = [1, 0, 0, 0, ... , 0 ]

のような初期値を持ったリストを用意するということです。先程count(0)は1と定義しましたので、memo[0]の値のみ1で初期化しておきます。

◆コード例

以上を踏まえて、Python3でコードの例を書いておきました。

import sys
from operator import add
from functools import reduce

n = int(sys.argv[1]) # 段数はARGVから取る
memo = [1] + [0] * n # memo[0]は1で初期化、memo[1..n]は0で初期化
step = [1, 2, 3] # ステップは3種類

def count(n):
    if memo[n] > 0: return memo[n]
    memo[n] = reduce(add, [count(n-k) for k in step if n-k >= 0])
    return memo[n]

print(count(n))

n=30で実行すると、

53798080

という出力が得られます。