GOTO M.

趣味のコーディングとか、勉強とか、読書とか

『ロング・ロング・ストリング』答案

挑戦した問題

codeiq.jp

問題の要約

自然数 m(2 ≦ m ≦$ 10^{10} $)を受け取り、$ n^{n} $ の10進桁数がmとなるような自然数nを出力する。(そのようなnが存在しない場合は-1を出力する)

提出コード(Ruby(73))

n=m=gets.to_i
f=->{Math.log10(n)}
$".map{n=(m/f[]).to_i}
p n*f[]<m-1?-1:n

※問のテストケースに特化して短縮しているので、m=2,3,4,5,7,10,200などでバグります。(後述)

考え方

問の言い換え

$$ \begin{align} n^{n} が十進m桁 &\Leftrightarrow 10^{m - 1} \leq n^{n} < 10^{m} \\ &\Leftrightarrow m - 1 \leq n\log_{10}(n) < m \\ \end{align} $$

のように言い換えられますので、以下の2ステップで問題を解けば良さそうです。

  1. $ f(x) = x\log_{10}(x) $ としたときの、$ f^{-1}(m) $ を求める。
  2. $ m - 1 \leq f(\lfloor f^{-1}(m) \rfloor) < m $ を満たすかを調べる。
    ※満たさなかったら-1を出力、満たせば $ \lfloor f^{-1}(m) \rfloor $を出力。
    ※f(x)は問の領域(x≧2)であれば単調増加なので、 <m を満たさないのは$ f^{-1}(m) $が整数の場合のみです。

$ f^{-1}(m) $の求め方

上記方針では$ f^{-1}(m) $を求める必要がありますが、$ f(x) = x\log_{10}(x) $ の逆関数は簡単な形では表せません。そこで以下のような方法で近似値を求めることになります。

  • $ g(x)=x\log_{10}(x)-m $ とおき、$ g(x)=0 $の解をニュートン法で求める。
  • $ n = 10^{m/n} $ を満たすnを二分探索等で求める。
  • $ y=\frac{m}{\log_{10}(x)} $ と $ y=x $ のグラフの交点を漸化式で求める。(採用案)

今回はコードゴルフでの短縮化も狙っていたので、ちょっと書いてみて最も短そうな最後の方針を採りました。具体的には、以下のような数列を計算していきます。

$$ \begin{eqnarray} a_k = \begin{cases} m & ( k = 0 ) \\ \frac{ m }{ \log_{10}( a_{k -1} )} & ( k > 0 ) \end{cases} \end{eqnarray} $$

細かい証明は省きますが、$ y=\frac{m}{\log_{10}(x)} $ は殆どの領域で傾きの緩い(-1<傾き<0の)単調減少になっているので、下図のようにこの数列は収束していきます。

f:id:g_m_k:20160227133612p:plain

コーディング

ちゃんとした実装

上記を踏まえ、誤差が閾値以下となるまで数列の計算を繰返すアルゴリズムを実装すると以下のようになります。

require "bigdecimal"
def blog10(a)
    return BigMath.log(a, 50)/BigMath.log(10, 50)
end

s=m=gets.to_i
EPS = 1e-08   #閾値

begin
    prev_s,s = s,n/blog10(s)
end while (prev_s-s).abs > EPS
s=s.floor
p (m-1...m).cover?(s*blog10(s))?s:-1
  • 大きなmで色々試したかったのでbigdecimalを使っていますが、設問の入力範囲であれば不要です。
  • 「$ f^{-1}(m) $の求め方」の図の通り、数列は両側から収束していきます。ループが奇数回で終わると $ f^{-1}(m) $ が整数値だった場合(★)には、実際より1少ない値がs.floorで計算されてしまいます。ただし、★のような場合には$ n^{n} $の10進桁数がmとなるような自然数nが存在しないため、いずれにせよ-1を出力して正答となります。(少し手抜きですね)

ゴルフ版の実装(再掲)

n=m=gets.to_i
f=->{Math.log10(n)}
$".map{n=(m/f[]).to_i}
p n*f[]<m-1?-1:n
  • log10を何回も同じ引数で呼ぶため、lambda式(->記法)で短く宣言しています。
    メソッドの別名まわり詳しく無いので、もっと縮まるのかも)
  • $" はrequireで読み込まれたファイルの配列です。
    環境によりますが、$".mapとすることで19回くらいループします。設問の範囲であればこれくらいで十分に収束します。(mが大きくなると、収束までのループ数はゆっくりとですが発散すると思います)
  • 「問の言い換え」で示したステップ2の <m チェックを省いているため、$ f^{-1}(m) $ が整数だった場合(m=10,200,3000,...)には、-1を出力すべきところ解ありとして誤答します。
  • to_iを変なタイミングで行っているため、小さな値(m=2,3,4,5,7)では誤答となります。

Code golf 反省メモ(Advent of Code Not Quite Lisp)(Ruby)

挑戦した問題

Advent of Code Not Quite Lisp (anarchy golf)

http://golf.shinh.org/p.rb?Advent+of+Code+Not+Quite+Lispgolf.shinh.org

要約

開き括弧"("と閉じ括弧")"が並んだ文字列が与えられます。"("を+1、")"を-1として左から順に加算していき、合計値が-1となるまでに読込んだ文字数を出力します。

言い換えると、開き括弧と対応付かない閉じ括弧のうち最左のもののindexを1-originで求める問題です。

(()()))()(()

例えば↑のような入力の場合、(()())までは括弧の対応が取れていますが次の)には対応する開き括弧が無いため、7を出力することとなります。

Ruby1.8.7とRuby2.2.3でチャレンジし、いずれも2位でした。進歩してはいるものの、1位が段違いだなぁと感じた挑戦でした。

ソースコード(Ruby 1.8.7)

自分のコード(44)
#!ruby -n
c=1;$_["()"]=""*c+=2while/^\(/;p c
  • 「対応付く括弧の対"()"」を順に取り除いていくことを、先頭が)になるまで繰返すという方針です。
  • 変数cの宣言などが重たい感じがします。
1位の方(mitchs様)のコード(39)の吟味
def f
getc>40?1:f-~f
end
loop{p f
gets}
  • loopで回しているうちにgetcがエラーとなるので停止する?(anarchy golfのエラー出力の扱いがよく分からないので、次の挑戦時に色々試してみます)
  • golf的にはよく使う書き方ですが、f-~ff+f+1と同値です。
  • 関数fについては色々な解釈が考えられますが、開き括弧"("※を読んだ直後に使用することで、※より後ろで、※に対応する閉じ括弧までの文字数を返すという関数だと解釈しました。
  • f:id:g_m_k:20160104220302p:plain

ソースコード(Ruby 2.2.3)

自分のコード(35)
#!ruby -n
p$_[/(\(\g<0>.)*/].size+1
  • Ruby2は正規表現エンジンとして「鬼車」を搭載しているので、部分式呼出し\g<n>が使用できます。特に\g<0>でその正規表現全体と再帰的にマッチさせることができます。
  • つまり/(\(\g<0>.)*/は、ちゃんと対応する開き括弧"("と閉じ括弧")"の連続(ディック言語)にマッチします。欲しいのはその後の閉じ括弧")"なので+1をしています。
  • +1をなんとか削りたかったけれど、うまい案が浮かびませんでした。
1位の方(mitchs様)のコード(32)の吟味
loop{p gets[/(\(\g<0>)*./].size}
  • /(\(\g<0>)*./は、そのものずばり、対応する開き括弧"("と閉じ括弧")"の連続(ディック言語)とその直後の閉じ括弧")"にマッチします。(本問の場合.\)と読み替えて差し支えありません)

今後の Code golf に向けた反省

  • まとめると「うまい言い換え」と「単純なパターンからの成長」が大切だと感じました。
  • 「うまい言い換え」について。問題を以下のように読み替えることで、最短コードに一歩近付けたと思います。
    • 入力の先頭に仮想の開き括弧"("を想定することで「対応する閉じ括弧")"までの文字数を計算する」という問題に帰着する。(Ruby 1.8.7の解答)
    • 「開き括弧"("と対応付かない最初の閉じ括弧")"の位置を特定する」という問題に帰着する。(Ruby 2.2.3の解答)
  • 「単純なパターンからの成長」について。上の「うまい言い換え」が出来たとしても、自分のレベルではまだ一発で解答コードには至れません。最初の一歩として一番シンプルなパターンを、次の一歩として次にシンプルなパターンを……と少しずつ考えて行くことが大切そうです。
    • Ruby1.8.7/2.2.3のいずれの場合も、まず)という最もシンプルなパターンを考え、次に())というパターンを考えることで一歩一歩進める…のかなぁ。
  • 上記とは別に、個々の語彙レベルでも力不足を感じました。
    • loop{ ~ }という書き方
    • 再帰のためにdef ~ endを用いるという選択肢。(Ruby1.8.7ではアロー演算子も使えないこともあり、ついつい脳内で枝切りしてしまう)

Code golf 反省メモ(Square root of 1 in mod 1e300)(Ruby(というか数学))

挑戦した問題

http://golf.shinh.org/p.rb?Square+root+of+1+in+mod+1e300golf.shinh.org

要約

標準入力から与えられる3桁の数を下3桁に持つ自然数で、その平方数の10300での剰余が1となるようなものを求める。

ソースコード

自分の解答(62)

a=5;b=10**n=300;n.times{a=a*a%b};p [b-1,b-a=a*2-1,a][getc%3%3]
  • A224473の数列の一般項を求める式をそのまま実装しています。(Rubyでは冪剰余を効率良く求める短いメソッドが無さそう(opensslをrequireする必要がありそう)なので、冪乗を求めつつ剰余を都度求める方針を採っています)

Ruby1位の方(mitchs様)の解答(41)の吟味

p (eval'n='+gets+'n-=n*n/2*n;'*8)%10**300
上記コードで問の解答が求まることについて
  • nは常に奇数になる(自明では無い)ので、n-=n*n/2*nn=(3*n-n**3)/2と等しくなります。
  • n=(3*n-n**3)/2を繰返しても、nの平方数について常に10k(k>3)での剰余が1となっているので、n%1000がgetsの値となっていることは簡単に示せます。
  • キモはn=(3*n-n**3)/2を繰返すごとに、nの平方数の10mでの剰余が1となるようなmが増加していき、8回の繰り返しでm>300となる点です。
    ヌルい証明ですが、以下のように大体mは倍々で増加していくようです。 f:id:g_m_k:20150927001500j:plain
  • 問の条件を満たす数は複数(2つ)ありますが、上述のように10m(m>300)での剰余が1となる前提であればそのうち1つに定まるので特に面倒な処理は必要ありません。
そもそも上記のコードに至る発想について
  • 上記のコードで解答が得られることは納得できたのですが、そもそもn=(3*n-n**3)/2の漸化式をどのように発想できるのかはまだよく分かっていません。
    出題者のtails様の解説エントリの中では「ちょっと試行錯誤」と表現されていますが、なかなか「ちょっと」でここに至れません。。。
  • 問の背景には「無限小数」というものが有るようなので、tails様のエントリを改めてじっくり読んでみようと思います。上記で「nの平方数の10mでの剰余が1となるようなmが増加して」いくとまどろっこしく表現している部分が、すっきりと「収束」の概念で説明されています。

今後の Code golf に向けた反省

  • より単純な漸化式に帰着できないか考えてみる。(言うは易し行うは難し……)
    • 今回で言うとA224473の第300項を求める必要は無かった(300より大きいmについて第m項を求めればよい)ため、一般項を求めずに済む方法を考えてもよかった。
  • 出題者の方がtwitterをやっている場合、直近ログを漁って出題背景を妄想すると、問題についてより深い考察ができるかも。

Code golf 反省メモ(海のダンジョン)(JavaScript)

挑戦した問題

codeiq.jp

要約

以下のような形式で、3艘の”船”の性能が与えられる。3艘のうち性能÷定員の値が最大のもののindex(0~2)を求める。ただし、性能・定員のいずれかが3以下のものは最低値として扱う。

ほぼ文字制限の無いレベル1と、いくつかの文字列が禁止されたレベル2が有るが、今回はレベル1の方の反省。

q=[
    [ 3, 19],  // 0艘目の船 [船の性能, 定員]
    [ 5, 17],  // 1艘目の船 [船の性能, 定員]
    [13,  7]   // 2艘目の船 [船の性能, 定員]
  ];

なお、こちらで実際にコードを試してみることもできます。

ソースコード

自分の解答(61)(※1位の方との比較のため変数名は変えています)

j=i=0;for(t of q)t[0]>3&t[1]>3&(k=t[0]/t[1])>i&&(r=j,i=k),j++
  • 3艘の船についてループ処理を行う都度、性能÷定員が最大の船のindexをrに、その船の性能÷定員をiに格納しています。この2つの変数の宣言などが無駄に感じます。

1位の方(rotary-o様)の解答(53)の吟味

for(j=3;a=q[--j];3%a%b<3|a/b<i||(i=a/b,r=j))b=a.pop()
配列要素の取り出し方

まず気が付くのは、私のコードでいt[0],t[1]とかなりの文字数を使っている部分が、a,bと、すっきりした表現にできている点です。単純にやると逆に長くなってしまうこの辺りの処理が、JavaScriptの特性(意外と色々なものが数値として扱える)を活用してかなり短縮して書かれています。

a=t[0],b=t[1]  //単純に代入するケース
b=a.pop()      //rotary-o様の方法
[a,b]=t        //蛇足ですが、EcmaScript6(Firefoxなど)だとこう書けてかなり楽です。
暫定トップを保持する変数(i)の宣言

次に目に付くのは、私のコードでのc=0に相当する処理がrotary-o様のコードには無いことです。
今回の問題では1艘目の船を確定暫定トップにしてしまって良いことと、ユーザ入力部分の外で変数iが宣言のみされていることを考えると、iがnullの場合には暫定トップの更新処理(i=a/b,r=j部分)を行うような処理を行ってしまってよいことが利用できます。
i=nullの場合に更新処理を行いたいがために、rotary-o様のコードでは各種の条件が&や&&でなく|や||で接続されています。

ループの方法

ぱっと見一番大きいのがループの方法の違いです。私はfor ~ ofを使用しているのに対し、rotary-o様は通常のfor文を使用されています。なんとなく前者の方が大体短く出来る気がしており、ほぼ盲目的にこちらを使っていました。
現に、私のコードを単純に通常のfor文に置き換えると1文字増えてしまいます。

// for .. of 文
j=i=0;for(t of q)t[0]>3&t[1]>3&(k=t[0]/t[1])>i&&(r=j,i=k),j++
// 通常のfor文(こっちが1文字長い)
i=0;for(j=3;t=q[--j];)t[0]>3&t[1]>3&(a=t[0]/t[1])>i&&(r=j,i=a)

一方で、rotary-o様のコードの場合は逆にfor ~ ofの方がコードが長くなってしまうようです。

// for .. of 文
j=0;for(a of q)b=a.pop(),3%a%b<3|a/b<i||(i=a/b,r=j),j++
// 通常のfor文(こっちが2文字短い)
for(j=3;a=q[--j];3%a%b<3|a/b<i||(i=a/b,r=j))b=a.pop()

この3文字の差(-1文字vs+2文字)は以下から来ています。

  • 私コードでは、ループごとに実行される処理が1つのみなのに対し、rotary-o様版は2処理に分割できるがために、final-expression(for(xx;xx;ここ))に一方を入れることで1文字節約されています。(逆に言うと、for ~ ofだと余計なカンマが増えてしまいます)
  • 私のコードではfor文の外で変数cを初期化せざるを得ないがために、逆にi=0をfor文の外に出すコストはi=の2文字に過ぎません。一方で、rotary-o様版だとj=0;の4文字が必要となってしまいます。これで2文字の差です。
その他
  • 3%a%b<3部分の書き方も、(文字数は減らないものの)うまい書き方なので吸収したいです。

今後の Code golf に向けた反省

  • 変数の宣言を減らせないか検討すること。(今回でいうi)この際、null変数などが利用出来ないか吟味すること。
  • JavaScriptについては、意外なものが数値(や他の基本型)として扱えることが有るので、入念に検討すること。
  • 処理の形を変える度に、for ~ of通常のfor文のどちらを用いるのが適切か検討すること。
    • ループ内の処理が2式以上の場合や、ループ変数の宣言が高価な場合は通常のfor文を用いた方が良い場合がある。

Code golf 反省メモ(デスマコロシアム12)(Falcon)

挑戦した問題

codeiq.jp

要約

解説記事から引用します。

はじめは、aからzの文字でcodeiqに一致する文字のみを大文字に変換します。 次にaからzの文字でdpefjr(codeiqの次の文字)に一致する文字のみを大文字に変換します。 という風に、ループする度に大文字に置換する位置をずらす処理をaからzに対して22回繰り返します。

ソースコード

自分のFalcon(57)
//0.upto(571,{i=>print(chr(97-32*(82204>>(i%26-int(i/26))&&1)+i%26))})
//times(572,{i=>print(chr(97-32*(82204>>(i%26-int(i/26))&&1)+i%26))})
//times(572,{i=>>>""%(97-32*(82204>>(i%26-int(i/26))&&1)+i%26)})
//times(22,{i=>times(26,{j=>>>(c='A'/j)/-i in"CODEIQ"?c:c/32})})
  times(572,{i=>>>(c='A'/(i%26))/-(i/26)in"CODEIQ"?c:c/32})
  • char変数についてASCIIコードの加減算に使える"/"演算子(int->string)が独特でした。(といいつつi/26は小数なので、引数はintじゃなくてもいいのかな)
  • 下記のangel様の使用されている"/="のリテラルへの適用なども試していましたが、一重ループのこの形ではコード短縮に至りませんでした。
1位の方(angel様)のFalcon(53)の吟味
  for i=7 to 28:for j=0 to 25:>>"a"/=j-=32&&20551<<i>>j
  • デスマコロシアム(第12回・最終回?)に参加しました - ange1のブログ より。
  • 二重ループを使っている点、"CODEIQ"の包含判定に「in 文字列」でなく「十進bit列のシフト演算」を使っている点が私のコードと異なっています。
  • 他に気付くのは、ループにtimes関数でなく通常のfor文を使われている点です。確かに下記のように、ループ部分を抜き出すとangel様の方法の方が短くなっています(回す範囲は微妙に違いますが。)Rubyに慣れてきたことで先入観を持ってしまっていました。
times(22,{i=>times(26,{j=>doSomething})})
for i=7 to 28:for j=0 to 25:doSomething
  • 更に、-=32&&20551<<i>>jの部分も目から鱗の処理でした。下記①②だけ考えており、③は思い付けませんでした。演算子の優先順位を意識しつつ括弧を減らすのに便利そうなテクニックです。
j + 32*【0/1となる計算処理】                    //①
j + 【t/fとなる計算処理】?0:32                  //②
j + 32&&【右から6ビット目が0/1となる計算処理】  //③(※&&はビットAND)
  • また細かい点ですが、以下の1バイト差も大きいです。
n<<i>>j
n<<(i-j)

今後の Code golf に向けた反省

  • timesループが常にforループより安価だとは限らない。慣れない言語に触る時は一度両方試みること。(その他の構文にも言える)
  • 演算子の優先順位のせいで余計なカッコが付くときは、angel様コード吟味の4,5点目のように他演算子による代替案を考えること。
  • 二重ループ+シフト演算というパターンが検討されていなかった。複数の選択肢の組み合わせについて、もっと網羅的に試すようにすること。
  • 精神論。「1位は自分の知らない命令を見つけているのでは」という、ある意味楽観的な観測にすがりがち。それでgithubのテストケース全部読むことに時間を費やすよりは、もう少し頭を使う方向で縮める努力をすること。

『第9回デスマコロシアム』答案

CodeIQ『第9回デスマコロシアム』(※)に参戦しました。
※以下を特徴とするコードゴルフ大会です。

  • ideoneで使用可能な言語なら何でもOK。
  • 有利な言語に偏りすぎないよう、参戦人数の多い言語にはハンディあり。
  • バイト数でなく、文字数で競う。
  • import文は文字数カウント対象外、ideoneの自動生成部分もカウント対象外。(本稿のコードも、自動生成部分は略記しています)

第9回出題テーマは、以下の文字マトリクスの標準出力への出力です。
(新年ということでテーマは「末広がり」、a~zが8回繰り返されつつ、8文字ごとに大文字になっています)

AbcdefghIjklmnopQrstuvwxYzabcdefGhijklmnOpqrstuvWxyzabcdEfghijklMnopqrstUvwxyzabCdefghijKlmnopqrStuvwxyzAbcdefghIjklmnopQrstuvwxYzabcdefGhijklmnOpqrstuvWxyzabcdEfghijklMnopqrstUvwxyzabCdefghijKlmnopqrStuvwxyz

今回もこれまで自分がデスマコロシアムでは使わなかった言語縛りで挑戦してみました。最終結果の最短とタイor短縮できているもののみ以下に挙げます。

brainf**k(114)

++++++++[>+++>+>+>+<[+++++++>]<++++<<<<-]>++>[<[>>+>+>>+<[<.>->-]>[<<<.>>+++++++>->]<<<<<<<+>-]<[>+>>->-<<<<-]>>-]
  • やはりちょっとだけ癖が有る言語なので、縮め方にもコツが要るのかなと思いました。自分は以下のような手順でした。
    1. 大枠のアルゴリズムを考え、ワーク領域の目星を付ける。
    2. 細かいアルゴリズムを考える。
    3. アルゴリズムに合わせ、ワーク領域の配置、準備部分のコードを書く。
  • 大枠のアルゴリズムを考え、ワーク領域の目星を付ける。
    • brainf**kには当然剰余演算子などは存在しないため、26ごとの循環、8ごとの循環を自前で作り込む必要が有ります。ということで、まずは以下の領域が必要かなと考えました。(……さらっと書いていますが、前回のAzicore様の解答を印刷して勉強しながら頑張りました)
W:26の待避用
Z:26回ループ用
S:8回ループ用
A:大文字のASCIIコード
a:小文字のASCIIコード
P:8文字ごとの大文字化判定用
Q:Pによるif-else制御用1
R:Pによるif-else制御用2
S[
  Z[
    Q+P[
         a.
         P-
       P>-]>
       [<
         A.
         P+++++++
       P>->]<<
    a+A+
    W+
    Z-
  ]
  W[Z+A-a-W-]
  S-
]
  • アルゴリズムに合わせ、ワーク領域の配置、準備部分のコードを書く。
    • Brainf**kはポインタの移動にも都度1バイト必要なので、近く参照する変数同士は近い領域に置いておくのが定石のようです。ということで、変数間の遷移を以下のようにざっくり整理して変数の配置を検討しました。

f:id:g_m_k:20150202234719p:plain
(できればこの辺も自動化できると格好良いのですが、いまは手動です)
結果 ++++++++[>+++>+>+>+<[+++++++>]<++++<<<<-]>++>>+>+<< 部分のコードになりました。(ゼロ値領域を利用した短縮テクもAzicoreさんの前回コードからパクっています……)

Whitespace(109)

  			 	    

  
 
  
    		 	 
	 		 
	  		   
	 		
	 	
   	     
	   
  	
   	     	
	   	
     	
	    
 
		
  • 分り易く可視化すると以下の通りです。(S:Space, T:Tab, N:Newline)
SS TTTSTSSSS N	#   PUSH -208
NSS  N		# null:
SNS		#   DUP
SNS		#   DUP
SS STTSTS N	#   PUSH 26	
TSTT		#   MOD
SNT		#   SWAP
SS TTSSS N	#   PUSH -8
TSTT		#   MOD
NTS T N		#   JZ T
SS STSSSSS N	#   PUSH 32
TSSS		#   ADD
NSS T N		# T:
SS STSSSSST N	#   PUSH 65
TSSS		#   ADD
TNSS		#   PUTCHAR
SS ST N TSSS	#   PUSH 1,ADD(カウンタ++)
SNS		#   DUP
NTT N		#   JN null

Java7/Java(53)(※これだけ、新挑戦言語ではない)

import static java.lang.System.out; #出題規則によりこの行は文字数のカウント外
for(int i=207;;)out.write(i<0?0:122-i%8/7*32-i--%26);
  • out.writeメソッドは、out.printf("%c",...)と書くよりも文字数を大きく節約できるのですが、バッファの内容を書き出すにはout.flush()を呼び出す必要があり、トータルでは効率が悪いです。(第8回デスマコロシアムでは奇跡的にflushが不要でした)
  • そこで、0(null)をエンドレスに送り続けて意図的にエラー終了させることで、バッファを無理矢理吐き出すとともに、for文も省略することとしました。

Icon(49)

every i:=-207to 0&writes(char(i%8/7*32+122+i%26))
  • 基本的な考え方はjavaと同じですが、こちらはループのインクリメントをevery文に任せているため、ループの1サイクル内で添え字をインクリメントする書き方が適しません。そこから素直に書くと「添え字が8の倍数の時に何かする(32を引くとか)」という処理が必要になるのですが、短く書くのはちょっと難しくなります。そこで、マイナススタートで添字を回すことで、上記を「添え字が7mod8の時に何かする」と書くことができ、難易度はグッと下がります。

『第8回デスマコロシアム』答案

CodeIQ『第8回デスマコロシアム』(※)に参戦しました。
※以下を特徴とするコードゴルフ大会です。

  • ideoneで使用可能な言語なら何でもOK。
  • 有利な言語に偏りすぎないよう、参戦人数の多い言語にはハンディあり。
  • バイト数でなく、文字数で競う。
  • import文は文字数カウント対象外、ideoneの自動生成部分もカウント対象外。(本稿のコードも、自動生成部分は略記しています)

第7回出題テーマは、以下の文字マトリクスの標準出力への出力です。(最終行後の改行は有っても無くても可)

BAAAAAAAAAAAAAAAAAAAAAAAA
ACAAAAAAAAAAAAAAAAAAAAAAA
AADAAAAAAAAAAAAAAAAAAAAAA
AAAEAAAAAAAAAAAAAAAAAAAAA
        ~中略~
AAAAAAAAAAAAAAAAAAAAAAAYA
AAAAAAAAAAAAAAAAAAAAAAAAZ

せっかくなので、これまで自分がデスマコロシアムでは使わなかった言語縛りで挑戦してみました。最終結果の最短とタイor短縮できているもののみ以下に挙げます。
前回(第7回)も色々な言語で挑戦しましたが、まだまだ残っていますね…第9回はどうしよう)

Factor(107)

USING: sequences.repeating sequences strings math.ranges grouping ; !出題規則によりこの行は文字数のカウント外
66 90 [a,b] [ "" 1sequence ] map [ 65 ] 25 repeated join 25 group [ 10 ] join [ 1string ] map "" join print
  • 「連鎖性プログラミング言語」と呼ばれるやや特殊な言語です。関数の入出力は全てスタックに格納され、全体的にいわゆる逆ポーランド記法っぽいプログラミングとなります。
  • ざっくりコードの説明をすると以下の通りです。
    1. 66 90 [a,b] までで、66~90('B'~'Z'のASCIIコード)の配列を準備します。
    2. [ "" 1sequence ] map で、上記配列の各要素を、1要素ずつ更に配列に包みます。(こうしないとstep.4でエラーとなるので手探りで…きっともっと良い方法が有るのだと思います)
    3. 一方 [ 65 ] 25 repeated で65('A'のASCIIコード)を25回繰り返した配列をスタックに積んでおきます。
    4. 現在スタックに 2と3の結果が乗っているので join により、2の配列の各要素の間に3の配列を挟み込みます。(Pythonなどの普通の"join"のイメージ)
    5. 25 group で、配列を25要素ずつに区切ったうえで更に [ 10 ] join 10(改行のASCIIコード)を挟み込みます。
    6. [ 1string ] map が他の言語で言う"char()"のような操作です。整数値の配列をそれぞれASCIIコードとして解釈して、文字の配列に変換します。
    7. 最後にまた "" join で文字の配列を1つの文字列に結合し、 print で標準出力に吐き出します。
  • "matrices"というボキャブラリも存在するようなのですが、普通に検索で出てくくるリファレンスとideoneのバージョンが違うのかうまく動かず断念。格好悪いコードになってしまいました。。。

Java7(60)(※これだけ、新挑戦言語ではない)

import static java.lang.System.out; #出題規則によりこの行は文字数のカウント外
for(int i=0;i<651;)out.write(i++%27<1?66+i/27:i%26<1?10:65);
  • 最短も60文字ということなので、同じようなコードに辿りついているのかな。気になります。

JavaScript(SpiderMonkey)(76)

r="";for(i=649;i;)r+=String.fromCharCode(i--%26?i%27?65:90-i/27:10);print(r)
  • 標準出力に吐くたびに改行が入ってしまうため変数に結果全体を格納することとなり、1文字ずつ出力のこの方針とは相性が悪い言語ですね。

Pike(55)

import Stdio.stdout; #出題規則によりこの行は文字数のカウント外
for(int i=649;i;)write("%c",i--%26?i%27?65:90-i/27:10);
  • コードは普通にコードゴルフっぽく、1文字1文字をうまいこと出力してやる方針です。
  • Java/javascriptと似た構文で少し短く書ける一方で、Scala/Groovyよりマイナーな言語なので重複ペナルティも避けられてよいかも。ただしjavaでいう"System.out.write"のように、整数値をそのまま文字として出力できるメソッドが無い(見付けられてないだけ?)点が惜しいです。

R(39)

cat(intToUtf8(rbind(diag(1:25)+65,10)))
  • ざっくりコードの説明をすると以下の通りです。
    1. diag(1:25)+65 までで、対角成分が'B'~'Z'、その他成分が'A'(のASCIIコード)となる配列を作成します。
    2. rbind( ... ,10) までで、上記の行列の各行に10(=改行のASCIIコード)を結合します。
    3. intToUtf8 によりASCIIコード行列を文字列に変換しますが、そのままだとクォーテーションなどまで表示されてしまうので、cat で表示しています。
  • Octaveと異なり行列の出力時に自動的に行ごとの改行が入らないので、手動で改行を入れてやる必要があります。またOctaveなら"char"で済むところ"intToUtf8"を使う必要があり歯痒い感じです。