CodeJP 2013にて出題されたコードゴルフを頑張ってみた #codejp

イベントに参加したことよりまずこっちの話題をまとめとこうと思う。

2013/8/3(土),4(日)の2日間にて参加したイベントで、
3日に問題が出題されて、4日に3人1チームで組みコードをチューニングして提出するというもの。
いろんな人の考え方、切り崩し方(?)を垣間見ることが出来てとても楽しかったです。
てことで、以下コード話をつらつら


今回のお題はこちら→yoshiakist/codegolf · GitHub
デジタル時計を見栄えを変えず(末尾への空白挿入はOKとのこと)
clock.jsを如何に短くできるかというお題。

まず、イベント中2位になったウチ自身のコードはこちら(バイト数は「Minify Javascript Online / Online JavaScript Packer」で圧縮後のサイズ)

teamDのclock.js(363 byte)

t=0;
P=[a="1110",b="1010",b,b,a,
   c="0010",c,c,c,c,
   a,c,a,d="1000",a,
   a,c,a,c,a,
   b,b,a,c,c,
   a,d,a,c,a,
   a,d,a,b,a,
   a,c,c,c,c,
   a,b,a,b,a,
   a,b,a,c,a];
(F=function(){
 for(i=0,S="",m=t/60|0,s=t++%60;i<5;S+=P[i+5*(m/10|0)]+P[i+5*(m%10)]+(i%2?"0100":"0000")+P[i+5*(s/10|0)]+P[5*(s%10)+i++]+"<br>");
 document.body.innerHTML=S.replace(/1/g,"■").replace(/0/g," ");
 setTimeout(F,1e3)
})()

JSFiddle→ Edit this Fiddle - jsFiddle


あとあと出てくるソースコードと見比べるととても「無駄」の多いコードです。
方針といたしまして、デジタル部分を「■■■」「■□■」「■□□」「□□■」の4種類で、デジタル部分の0から9までを表現しているのがPの配列。
01は「□■」と対応させており、また一桁多い理由は空白挿入を省くためにすべての要素の末尾に0を混入。
1文字5要素で全部で50要素入っています。
この時点で100byte+変数初期化してて非常にもったいない(後述参照)。
経過時間を表すtから、分秒を算出(m=t/60|0,s=t++%60)。
インクリメントを埋め込んでるのはこれ以降tは使わないので、まとめて更新。
頭からi行目のデジタル表記をそれぞれ、mの10の位、mの1の位、「:」の部分、sの10の位、sの1の位の順で出力。
サイトのinnerHTMLに変換してから渡す(S.replace(/1/g,"■").replace(/0/g," "))。
innerHTMLに代入後、毎度SetTimeoutで関数を登録する。

以上が、うちの方針でした。
なんだかんだでまだ短くなる案もあったんですが、人様のコードを読んでると直感的に「あ、こっちのほうがスマートで短くなる」ってのがありました。
今回は id:nullpobug の書いたソースコードがまさにそんな感じでした。
イベント終了時は3位で終了したコードがこちら

teamCのclock.js(364 byte)

d = '■';
_ = ' ';
b = document.body;
f = Math.floor;
$ = 0 // sec
function x(bit){// x
  bit = bit*1;
  return ((bit&4)&&d||_)+((bit&2)&&d||_)+((bit&1)&&d||_);
}
function y(){ // showClock
    m = f( $ / 60 );
    s = $ % 60;
    o = ' '; // timeString
    c = '75557 11111 71747 71717 55711 74717 74757 71111 75757 75717'.split(o);
    for(i=0; i<5; i++){
        o += x(c[f( m / 10)][i]) + _ + x(c[m % 10][i]) + _ + x(i%2*2) + _ + x(c[f( s / 10)][i]) + _ + x(c[s % 10][i]) + '<br>';
    }
    b.innerHTML = o;
    $++;
}
y();
setInterval(y, 1000);

JSFiddleはこちら→ Edit this Fiddle - jsFiddle

一番感動したのがx関数です。基本的に数値→文字列にする際replaceしか思いついてなかった自分。
+関数作ることによって、最低16byteのコストを生むという懸念がありまして、関数化するというのを最初から除外してました。
それを見事カバーする形で綺麗にまとめたものだと思います。


さて、方針としてはほぼ一緒で、パターン=2進数で表せれるよねってのがcの変数代入部の数値です。
その後配列利用するためにsplit(' ')で分割してcに代入します。
setTimeoutの代わりにsetIntervalを採用。
この時特に違和感なく「setIntervalのほうがいいっていってたなぁ。」ってことでスルーしてました。


イベント期間中、以下の点を修正しました。

  • x関数の「bit=bit*1」の削除。文字列を数値に直す意図があったらしいのですが、「bit&4」でも同様なことができるので削除
  • f(Math.floor)関数の削除。複数回利用ってことで、「f(m/10)」ってやってましたが、「(m/10^0)」でも同様なことができるので変更
  • 「$++;」を「s=$%60」中に埋め込むことで「$++;」の削除
  • x関数の最適化その1。ショートサーキット→三項演算子
  • 変数oの初期化をsplit関数の引数受け渡し時に行うように変更
  • for文を減算利用するために、各数値の文字列順を逆に変更


にした結果、イベント中は以下の様になりました。

きんぎょばちセッション中短くなったコード(318 byte)

d = '■';
_ = ' ';
$ = 0 // sec
function x(bit){// x
  return (bit&4?d:_)+(bit&2?d:_)+(bit&1?d:_) + _;
}
function y(){ // showClock
    m = $/60^0;
    s = $++ % 60;
    c = '75557 11111 74717 71717 11755 71747 75747 11117 75757 71757'.split(o=' ');
    for(i=5; i--;)
        o += x(c[m/10^0][i]) + x(c[m % 10][i]) + x(i%2*2) + x(c[s/10^0][i]) + x(c[s % 10][i]) + '<br>';
    document.body.innerHTML = o;
}
y();
setInterval(y, 1000);

JSFiddle→ Edit this Fiddle - jsFiddle


日曜月曜と実家にいた時に以下の点を修正しました。

splitしないで文字列として扱い、digitの数値をIndex計算するように変更(309 byte)

d = '■';
_ = ' ';
$ = 0 // sec
function x(bit){// x
  return (bit&4?d:_)+(bit&2?d:_)+(bit&1?d:_) + _;
}
function y(){ // showClock
    m = $/60^0;
    s = $++ % 60;
    c = '75557111117471771717117557174775747111177575771757';
    for(i=5,o=''; i--;)
        o += x(c[i+5*(m/10^0)]) + x(c[m % 10 * 5 + i]) + x(i%2*2) + x(c[i+5*(s/10^0)]) + x(c[s % 10 * 5 +i]) + '<br>';
    document.body.innerHTML = o;
}
y();
setInterval(y, 1000);

JSFiddle→ Edit this Fiddle - jsFiddle

2桁のdigit表示の数値nの5-i行目を表示するw関数の作成、それに伴い変数m,s削除、$のインクリメントする行設置(306 byte)

d = '■';
_ = ' ';
$ = 0 // sec
function x(bit){// x
  return (bit&4?d:_)+(bit&2?d:_)+(bit&1?d:_) + _;
}
function w(n,i) {
    c = '75557111117471771717117557174775747111177575771757';
    return x(c[i+5*(n/10^0)]) + x(c[n%10*5+i]);
}
function y(){ // showClock
    for(i=5,o=''; i--;)
        o +=  w($/60^0,i)+x(i%2*2)+w($%60,i)+'<br>';
    $++;
    document.body.innerHTML = o;
}
y();
setInterval(y, 1000);

JSFiddle→ Edit this Fiddle - jsFiddle

@ さんの手によって短くなったw関数!iはグローバル変数なので、w関数の引数消しました(300 byte)

d="■";
_=" ";
$=0;
function x(e) {
    return(e&4?d:_)+(e&2?d:_)+(e&1?d:_)+_;
}
function w(e) {
    c="75557111117471771717117557174775747111177575771757";
    return x(c[i+5*(e/10^0)])+x(c[e%10*5+i]);
}
function y(){
    for(i=5,o="";i--;)
        o+=w($/60^0)+x(i%2*2)+w($%60)+"<br>";
    $++;
    document.body.innerHTML=o;
}
y();
setInterval(y,1e3);

JSFiddle→ Edit this Fiddle - jsFiddle

変数d、_の初期化をx関数で行う(てかx関数でしか使ってないよね!?)(298 byte)

$ = 0 // sec
function x(bit){// x
  return (bit&4?d='■':_)+(bit&2?d:_)+(bit&1?d:_)+(_=' ');
}
function w(n) {
    c = '75557111117471771717117557174775747111177575771757';
    return x(c[i+5*(n/10^0)]) + x(c[n%10*5+i]);
}
function y(){ // showClock
    for(o='',i=5; i--;)
        o +=  w($/60^0)+x(i%2*2)+w($%60)+'<br>';
    $++;
    document.body.innerHTML = o;
}
y();
setInterval(y, 1000);

JSFiddle→ Edit this Fiddle - jsFiddle

関数とコメントを整理してもらったあとにまた短くなりました(主にx関数)(294 byte)

$ = 0; //sec
c = '75557111117471771717117557174775747111177575771757'; 
function x(b){
  l=" ■";return l[b/4&1]+l[b/2&1]+l[b&1]+l[0];
  //return(b&4?d='■':_)+(b&2?d:_)+(b&1?d:_)+(_=' ');
}
function y(n) {
    return x(c[i+5*(n/10^0)]) + x(c[n%10*5+i]);
}
function z(){
    for(o='',i=5; i--;)
        o +=  y($/60^0)+x(i%2*2)+y($%60)+'<br>';
    $++;
    document.body.innerHTML = o;
}
z();
setInterval(z, 1000);

JSFiddle→ Edit this Fiddle - jsFiddle

この記事書いてたら「この場合setTimeoutのほうが短くなるじゃん!!」(293 byte)

$ = 0; //sec
c = '75557111117471771717117557174775747111177575771757'; 
function x(b){
  l=" ■";return l[b/4&1]+l[b/2&1]+l[b&1]+l[0];
}
function y(n) {
    return x(c[i+5*(n/10^0)]) + x(c[n%10*5+i]);
}
function z(){
    for(o='',i=5; i--;)
        o +=  y($/60^0)+x(i%2*2)+y($%60)+'<br>';
    $++;
    document.body.innerHTML = o;
    setTimeout(z, 1000);
}
z();

JSFiddle→ Edit this Fiddle - jsFiddle

ついにIEさんだと動かないコードを!$さんがローカル変数に!(291 byte)

c = '75557111117471771717117557174775747111177575771757'; 
function x(b){
  l=" ■";return l[b/4&1]+l[b/2&1]+l[b&1]+l[0];
  //return(b&4?d='■':_)+(b&2?d:_)+(b&1?d:_)+(_=' ');
}
function y(n) {
    return x(c[i+5*(n/10^0)]) + x(c[n%10*5+i]);
}
function z($){
    for(o='',i=5; i--;)
        o += y($/60^0)+x(i%2*2)+y($%60)+'<br>';
    document.body.innerHTML = o;
    setTimeout(z, 1000, $+1);
}
z(0);

JSFiddle→ Edit this Fiddle - jsFiddle

document.body.innerHTMLをお行儀よく代入するのはやめた!(289 byte)

c = '75557111117471771717117557174775747111177575771757'; 
function x(b){
  l=" ■";return l[b/4&1]+l[b/2&1]+l[b&1]+l[0];
}
function y(n) {
    return x(c[i+5*(n/10^0)]) + x(c[n%10*5+i]);
}
function z($){
    for(o='',i=5; i--;)
        document.body.innerHTML=o+=y($/60^0)+x(i%2*2)+y($%60)+'<br>';
    setTimeout(z, 1000, $+1); //1秒後にzを呼び出す
}
z(0); // 初期画面表示

JSFiddle→ Edit this Fiddle - jsFiddle

evalさんを使っての再帰(283 byte)

c="75557111117471771717117557174775747111177575771757";$=0;eval(z="function x(b){l=' ■';return l[b/4&1]+l[b/2&1]+l[b&1]+l[0]}function y(n){return x(c[i+5*(n/10^0)])+x(c[n%10*5+i])}for(o='',i=5;i--;)document.body.innerHTML=o+=y($/60^0)+x(i%2*2)+y($%60)+'<br>';$++;setTimeout(z,1e3)")

文字列をminifyしてくれないのでほぼminify済みという・・・。


という、変遷。

ちなみにこれをminifyかけると…

function x(e){l=" ■";return l[e/4&1]+l[e/2&1]+l[e&1]+l[0]}function y(e){return x(c[i+5*(e/10^0)])+x(c[e%10*5+i])}function z(){for(o="",i=5;i--;)o+=y($/60^0)+x(i%2*2)+y($%60)+"<br>";$++;document.body.innerHTML=o;setTimeout(z,1e3)}$=0;c="75557111117471771717117557174775747111177575771757";z()

JSFiddle→ Edit this Fiddle - jsFiddle

function x(e){l=" ■";return l[e/4&1]+l[e/2&1]+l[e&1]+l[0]}function y(e){return x(c[i+5*(e/10^0)])+x(c[e%10*5+i])}function z(e){for(o="",i=5;i--;)o+=y(e/60^0)+x(i%2*2)+y(e%60)+"<br>";document.body.innerHTML=o;setTimeout(z,1e3,e+1)}c="75557111117471771717117557174775747111177575771757";z(0)

JSFiddle→ Edit this Fiddle - jsFiddle

function x(e){l=" ■";return l[e/4&1]+l[e/2&1]+l[e&1]+l[0]}function y(e){return x(c[i+5*(e/10^0)])+x(c[e%10*5+i])}function z(e){for(o="",i=5;i--;)document.body.innerHTML=o+=y(e/60^0)+x(i%2*2)+y(e%60)+"<br>";setTimeout(z,1e3,e+1)}c="75557111117471771717117557174775747111177575771757";z(0)

JSFiddle→ Edit this Fiddle - jsFiddle

    • 8月9日更新 --

偉大な方々

呼ばれてないけど、ボクもコードゴルフしてみたよ - latest log
最終的に273文字(297 byte)という結果。
短時間でここまで短くできる事がすごい。
evalを使った発想に至ったのはこのお方のソースを参考に実装しました。

B=0;
D="75557111117471771717117557174775747111177575771757";
P="    ,  ■ , ■  ,,■   ,■ ■ ,,■■■ ".split(","),
setInterval('r="",m=B/60|0,s=B++%60;for(i=5;i--;r+=Z(m/10|0)+Z(m%10)+P[i%2*2]+Z(s/10|0)+Z(s%10)+"<br>")function Z(n){return P[D[n*5+i]]}document.body.innerHTML=r',1e3)

JSFiddle→ Edit this Fiddle - jsFiddle


そして、多分現時点で最も短いであろうコードを書いた @ さん。
ぱっと見わからないところも素敵(褒め言葉)。


t=0;f=[0xee2ee2ee2e,4579142019114,0xee2eeeee2a,5128871092778,0xeeeeeaee2e];(function a(){c="";m=t/60;s=t%60;n=[s%10,s/10,10,m%10,m/10];for(m=5;m--;){for(s=20;s--;)c+=f[m]/Math.pow(2,s%4+4*~~n[s>>2])&1?"■":" ";c+="<br>"}document.body.innerHTML=c;t++;setTimeout(a,1E3)})()

JSFiddle→ code-golf - jsFiddle


そして、うちが短くしてみました(ぁ

t=0;f=[0xee2ee2ee2e,4579142019114,0xee2eeeee2a,5128871092778,0xeeeeeaee2e];eval(a="c='';s=t%60;n=[s%10,s/10,10,t/60%10,t++/600];for(m=5;m--;document.body.innerHTML=c+='<br>')for(s=20;s--;)c+=' ■'[f[m]/Math.pow(2,s%4+4*~~n[s>>2])&1];setTimeout(a,1E3)

JSFiddle→ Edit this Fiddle - jsFiddle