-
Notifications
You must be signed in to change notification settings - Fork 15
第7回Esolang Codegolf Contest Writeup
このページ中にあるプログラムは CC0-1.0 とします。
将棋盤
入力には 16 個のテストケースが含まれている。 各ケースでは、文字 0, 1 からなる長さ 3 の文字列が 3 個、改行区切りで与えられる。 各ケースの最後には改行が与えられる。
各ケースについて、次の問題を解け。 入力を 3 行 3 列の将棋盤とみなす。 文字 1 は対応するマスに歩が 1 個あることを、文字 0 は対応するマスに駒がないことを表す。 二歩であるとは、2 個以上の歩が置かれている列が将棋盤に存在することとする。 3 個の歩が置かれている列でも二歩になることに注意せよ。 二歩であれば 1 を、そうでなければ 0 を出力せよ。 文字 0, 1 はちょうど 16 個出力せよ。 出力された文字列に含まれる空白文字(改行含む)は無視される。 文字 0, 1 および空白文字以外の文字を出力してはいけない。 提出するプログラムは、入力されうるすべてのテストケースに正しく出力できるものでなければならない。つまり確率解を禁止する。
入力には、テストケースとして以下に列挙される 16 パターンが 1 度ずつ出現する。 各パターンの行同士を並べ替え、列同士を並べ替えたものが実際に入力される。 どのパターンも 1 の個数は 2 以上 4 以下である。 入力されうるテストケースの集合は、1 の個数が 2 以上 4 以下である盤面の集合と一致する。 1 の個数が 1 以下または 5 以上である盤面に対しては、プログラムは正しく動作しなくてもよい。 パターンが出現する順番は固定されていない。
110
000
000
100
010
000
100
100
000
111
000
000
110
001
000
110
100
000
100
010
001
100
100
100
100
100
010
111
100
000
110
110
000
110
101
000
110
100
100
110
100
010
110
100
001
110
001
001
赤チーム https://hackmd.io/Z7BDACIWR5KuudmYyPsJ5g 青チーム https://hackmd.io/WKuqh7EuSuO7C4Qkh2zBwA 緑チーム https://hackmd.io/KWu-vK4aS7yCOXghStXQ5A
05AB1E (@dnek, 12 bytes)
16FIIIOà2@?
16F
: 16回ループ
IIIO
: 3行の入力の和
à
: 文字列としてみた和のmax(=和の各桁の数字の最大値)
2@
: max >= '2'
?
: print
#gs2もそうだが、golflangでは各桁の数字を配列として取れる命令が強かった。
ABC (@n4o847, 90 bytes)
WHILEe>0:
READaEG 0
READbEG 0
READcEG 0
PUT 0INs
IFmax "`a+b+c`">"1":PUT 1INs
WRITEs
攻め込まれなかったのであまりゴルフしていない。
3行を数値として読み込み、それらの和を文字列化したときに、最大の文字が 1
より大きければ (2
か 3
を含んでいれば) 1 そうでなければ 0。
無限ループだが入力で落ちる。
Ada (GNU GNAT) (@shell_mug, 164 bytes)
With Ada.Integer_Text_IO;Use Ada.Integer_Text_IO;procedure t is X,Y,Z:Integer;begin loop Get(X);Get(Y);Get(Z);Put(Boolean'Pos((X+Y+Z)**3*35 mod 37>2));end loop;end;
Integer型でビット単位の積/和を計算できないので少々不便。EOFを読み込むと標準エラー出力にエラーを吐いて終了するので、無限ループを回せばよい。
Aheui (@settyan117, 92 bytes)
밥밣따북처흐뭉저두벅
뿌떠벍벌뽀터어볻러포
방방방다다빠빠따따또
母音(ㅏㅗㅜㅓなど)で進行方向・量を、子音(上にある方のㅂㄱㄹ)などが処理内容を、パッチム(下にある子音)で追加情報を表す2次元・スタック型言語。読むと「 ぱっばるたっぶっちょふむんじょどぅぼっ ぷっとぼっぼるぽっとおぼっろぷ ぱんばんばんだだっぱっぱったったっと 」みたいになる(Google翻訳さんによる朗読)。 処理内容は
for(i=32;i>0;i-=2){
print((input()+input()+input())**3*35%37>=3)
}
移動方向の設定の自由度が高いので、設計したコードがわりと任意の長方形に入る。なので、同じマス数でもより平べったい長方形を選ぶとちょっとだけ縮む(5×6を3×10にして-2bytes)。
終了後存在に気づいた2マス移動の母音を使ったらさらに縮みそう。
apache2_mod_rewrite (実質@paradigm_9, 提出@dnek, 184 bytes)
Listen 80
Include m*d/*d
Include m*d/*
Include m*/r*
RewriteEngine on
RewriteRule (.*)x(.*(1)..1|.*(0)).*y(.*) $1$3$4$5 [N,R]
RewriteRule (.*)(...).(...).(...)\n(.*) $1x$2$3$4$2y$5 [N]
Apacheサーバーはesolangです(は?)。Apacheの設定ファイルを書くとサーバーが起動し仮想クライアントが http://127.0.0.1/<stdin>
にアクセスするので、Apacheで http://127.0.0.1/<stdout>
にリダイレクトさせるという置換系のプログラミング言語となっている。
この言語は、Apacheサーバーを最小コードで起動させるゴルフと、正規表現のゴルフの二段構えとなっている。Apacheを起動させるために本当は mods-enabled/*.load
, mods-enabled/*.conf
, mods-available/rewrite.load
だけのIncludeで良いが、ワイルドカードで必要以上に読み込ませている。サーバーは起動すればええんや!
RewriteRuleの方はオーソドックスな置換。まず目印(x,y)をつけてブロック分けしたあと、その中で二歩があるかどうかをFilterする。一番下の行は途中処理のみで最終出力には影響しないのでRedirectの[R]は不要。
APL (@kotatsugame_t, 47 bytes)
{+/'23'∈⍕ω}¨+/16 3⍴⍎⍕⎕ARG[6]
)OFF
数値として足して/2|3/
をしている。右から順に
⎕ARG[6]
:入力
⍕
:to_stringのはずだがよくわかっていない。ないと動かない。
⍎
:eval
16 3⍴
:16行3列に成形
+/
:sum(行ごと)
¨
:行ごとに左の関数を適用
{}
が無名関数で、引数はω
に入る。
⍕
:to_string
'23'∈
:elem。[2を含むか 3を含むか]という列が得られる。
+/
:sum
最終的に01の列になって、変数に入れたりしていないため出力される。
AsciiDots (@settyan117, 53 bytes)
%$A
.->*#?{+}{+}-{x}-{%}{>}$#A
A-/\#?-*#?*#34*#35*#7/
線に沿って、値を持ったドットが進んでいく言語。/,\は鏡。一番左の*
でドットを2つに分割、上のラインのドットに下のラインで処理を施していく。行った処理は、入力を
A
B
C
として、print ((A+B+C)+34)^34%35>7
。
出力後、ワープポイントAを通ってはじめに戻る(はじめワープの存在に気づかず愚直に繋げて72 bytesになっており、「あと1byte...!」になってた)。入力が尽きるとstderrにAborted!
と吐いて落ちる。
GNU awk (@n4o847, 28 bytes)
a+=$1{$0=a~/2|3/}NR%3?0:a=0_
行ごとに実行され、truthy な式があれば $0
の中身が出力される。詳しくは こたつがめ先生の ABC 最短解説記事 を読むと良い。
a+=$1{$0=a~/2|3/}
: 総和を a
に集める。$0
には「和に 2
か 3
が含まれるか ? 1 : 0」を入れておく。(これは a
が truthy のときのみ実行されるが、制約上、$0
が必要なときに a
は 0
ではないので良い。)
NR%3?0:a=0_
: 行番号が3で割り切れなければ 0
(falsy) を置き、割り切れれば a
をリセットして truthy を置く。変数の初期値は空文字なので、0
を _
と文字列結合させることで文字列としての "0"
(truthy) にしている。
Backhand (@ten986, 26 bytes)
WI:]!^@_vII++'B%'%a%1LO1j
↓のXの位置にascii codeで17を表す文字があります。
WI:]!^@_vII++'B%'X%a%1LO1j
1次元上を動くスタックベース言語です。
(A+B+C)%66%17%10>1
を使います。
W 例によって初期値では3stepずつ動くので、-2して1step動くようにする
I:]!^@_v 入力をとって-1なら終了
II++ 入力を全部加算
'B%'X%a%1LO 計算して出力
1j Wの次のIにjumpしてループ
Ballerina (@kotatsugame_t, 172 bytes)
import ballerina/io;public function main(){string s="";int i=0;while i<3{s+=io:readln("");i+=1;}int c=0;while i<12{c=s[i-3]=="1"&&s[i%9]=="1"?1:c;i+=1;}io:print(c);main();}
3行を結合して/1...1/
を愚直に探している。
for文はforeachなどと書かなければならないようで、whileループの方が短い。
int i=0
とint c=0
をまとめようとしたらエラーが出てびっくりした。
main再帰によるループは第6回と同様。
Bash (busybox) (@kotatsugame_t, 26 bytes)
sed 'N
N
h
G
/1...1/c1
c0'
sedと同じ。
Bash (pure) (@drafear, 48 bytes)
read a
read b
read c&&echo $[!!(a+b&c|a&b)];. $0
A&&B;C
は C
も実行されるので, 無限ループするが, いずれ Stack Overflow で落ち, 余分な出力はしないので通る。
Befunge-98 (@ten986, 23 bytes)
#@&:1+!_&&++'B%'%a%1`.
本来、↓のX
の位置に ascii code で17を表す文字が入っています
#@&:1+!_&&++'B%'X%a%1`.
(A+B+C)%66%17%10>1
が強すぎるので短くなります
#@ @で終了する。普段は#によって飛び越える
&:1+!_ 入力+1==0なら、左に行って、なんやかんやで@を踏んで終了
&&++ 入力を全部加算
'B%'X%a%1`. %66%17%10>1して出力
BiBTeX (@wass80, 275 bytes)
文書情報をLaTeXのコードに変換するやつ。 BiBTeXって中でスタック言語動いてたんですね。
コードゴルフ的には duplicate$
が破滅的に長いのがミソ。
グローバル変数を活用したほうが短くなることがある。
短いわけでは特にない。
ENTRY{input}{}{}STRINGS{i}FUNCTION{e}{i swap$ #1 substring$ "1" =}FUNCTION{s}{input 'i :=
#16 {duplicate$}{#1 -
#1 e #5 e #9 e + + #1 >
#2 e #6 e #10 e + + #1 >
#3 e #7 e #11 e + + #1 >
+ + #0 > {"1"}{"0"}if$ write$
i
#13
#200
substring$
'i :=}while$
newline$}READ
ITERATE{s}
Bots (@drafear, 90 bytes)
f(x){ic+1?id@+x}m(x,y){-y x}M(x,y){/x y*y m x}s(){id f f M 66 M 17 M 10/2?/-1 1 od ic?s@}s
何この言語面白すぎる。x % y
を計算する関数 M
を定義して, (A+B+C)%66%17%10>1
のロジックをベースに実装した。
id
のあとに ic
を呼ばないと次の行を取れないため, 少し長くなっている。
無限ループしているが, EOF到達で ic
が -1
を返すようになるので, ic+1?id@
で終了する。
私も面白いと思ったので書かせてください。
青チームでは(sum/2-5)*(sum/2-50)*(sum/2-55)!=0
で実装していた。記号まわりのスペースが要らないことに気づかず大会中は負けてしまったが、不要なスペースを消すと以下のような83Bのコードになる。((x-5)-50)*(((x-5)-50)+5)*(x-5)
として計算することで関数の数を減らしていることが工夫点の一つ。複数回演算結果を使いたいときや、スタックの後ろのほうに演算結果を送り込みたいときは関数を使うことになるが、関数定義で5B以上使ってしまうので、関数を増やさずに済む計算式にできるとコードが短縮できる。なおic
で読んだ改行コードを捨てずに足しているが、ここは @drafear さんのコードが圧倒的にスマートで、EOF判定と同時に捨てられていて美しい。
s(v){+v 5*v}
q(x){+x 22?-@x 50 s*x?+*1 0 od}
m(){id n+}
n(){ic+}
f(){m 0 m m/2-20 q f}
f
コードを短くするポイントの一つは、関数っぽいものはあくまでもスタックの書き換えなので、関数の末尾でしか参照しない引数はそもそも渡さなくてもよいということ。部分適用した関数を返すと考えてもよいかもしれない。上の @drafear さんのコードのf(x){ic+1?id@+x}
はf(){ic+1?id@+}
でよくて2B縮まる。引数を減らすという観点だともう一つmodをとるm(x,y){-y x}M(x,y){/x y*y m x}
のm
の引数も気になるところで、これは0-(x/y*y-x)
と式変形することでm(){-0}M(x,y){/x y*y-x m}
というように5B縮めることができる。余計な減算が増えているのに記号まわりのスペースが省略できることでM
の長さが変わっていないというのはゴルフを難しくする要素が感じられる。
一方で入力の読み方に関しては @drafear さんのコードのほうが短い。これはfoldの初期値を書かずに済むこと、関数が1つで済んでいることが大きいように思う。改行コードを捨てる処理も含めて拝借すると、以下のような71Bのコードになる。青チームのアルゴリズムでは入力3行目の改行コードをic*5
の形で元々あった50と置き換えて読み捨てることができるというのもポイント。
s(v){-v 5*v}
q(x){ic*5-x s*x?+*1 0 od}
n(){ic+1?id@+}
f(){id n n/2-5 q f}
f
Brainfuck (esotope) (@angel_p_57, 99 bytes)
+[>>+>>+>+++[-<[,[-<+>]+<<]>>>>>>,>]<+[[-<[>[->>]<[>++++>]<<-]>[-[>>>>+<]]<<]>>-[----->+<]>---.<]>]
抜かれるほどにアイデアが湧く不思議言語。 単純には、各桁の文字コード値を合計して mod を取る ( 今回は mod 5 を採用 ) という計算ですが、処理のオーバーヘッドを極限まで切り詰めた積りです。 なお、出力文字の生成は普通に -1(255)÷5-3=48をベースにしてます。
解説付きコード
** 1文字先読み無しでループ **
+[
** ループフラグも併せ、3文字分のマークをつけておく **
>>+>>+>
** 3行分のループ **
+++[-
** マークのポイントで1文字読んで隣接マスに合計蓄積 **
<[
,[-<+>]+ ** 忘れずにマークを付け直す
<<]
** 戻る際に最初のマークを改行文字に置き換えておく
>>>>>>,>]
** 改行文字をキーにして出力処理始動、EOF後はNOPになる
<+[
** 残っているマークが合計の位置を表す
[-
** 変則mod5処理、改行文字の残りは余りの処理の中で吸収できる
<[>[->>]<[>++++>]<<-]
** 余りを見て2歩を検知したら、出力調整用の1をセットしてループを抜ける
>[-[>>>>+<]]
<<]
** 48を作って出力(2歩なら先にセットした1が足されている)
>>-[----->+<]>---.
<]
** 出力文字がそのままループ継続&次の最初のマークとして働く
>]
後日更新版(97B)
+[>>+>>+>+++[-<[,[-<->]+<<]>>>>>>,>]<+[++[[<[->>]>[<+++>>>]<<+]<[-[>>>+<]]<]>-[----->+<]>---.<]>]
基本路線は変わりませんが、modを取る部分を変更したバージョン。 元の99Bでは、smsmsm← (sが桁合計、mがマーク1or11)で、sを被除数、mを結果として変則mod5を計算していました。これを、mを被除数とみなして ( 例えば 1 なら -255 とみなして ) あたかも負の数でmod4を計算する用に変更しています。( なお、sの蓄積も符号を反転して行います ) 部分的には、mの1,11のmod4での違いを埋める2Bと、modの処理変更での1Bの計3B分損なのですが、その他の移動距離がもろもろ最適化されて計2Bの短縮につながりました。
braintwist (@primenumber, 163 bytes)
brainfuckの104byte解
,+[->,>,<<<<<,---[->,[->[-]>[>>>>+<<<<<+>-]<[>+<-]<]<[>+<-]>]>>>>[>+<-]>[>+<-]>[<+>[-]],++[<++++>-]<.,+]
に、負番地アクセスを避けるために先頭に >>>
と、末尾に終了するための +]
, -]
or ,]
を付け足したうえで、ビームサーチで探索した
コードを見る
894
9
15
5
80
63
0
941
7
58
101
24
2
24
9
2
0
2
95
466
85
29
1
1
3
85
3
8
9
174
1
1
542
4
257
630
C (GCC) (@nu4nu, 64 bytes)
d,x;main(i){for(;gets(&d);x=i++%3?x:puts(x&'\x02\x02\x02'?"1":"0"))x+=d;}
gets
で文字コードの256進数として読むのが短い(このコードは環境がリトルエンディアンであることに依存している)。\x02のところはバイナリで埋め込む。puts
が0を返すというのが驚きだった(musl libcらしい。glibcでは書き出した文字数が返るので!
が必要で1B長くなる)。
大会後 @n4o847 さんによって3行に1回出力する部分が改善された。ループカウンタは不要で、x
を見れば何回足したかがわかる。同じアイデアでさらに縮めたものがこちらの61B。
x;main(d){for(;gets(&d);x=x&32?x:puts(x&'\x02\x02\x02'?"1":"0"))x+=d;}
CJam (@drafear, 15 bytes)
q~]3/{_:+\:|>}%
ロジックは A+B+C>A|B|C
。gsにはないquick foldで短くしている。
e.g. [1 2 3]:+
-> 6
古典派音楽理論 (@nu4nu, 1802 bytes)
コードを見る
|
R
E4 D4
G3
C3 B2
|
R
E4
T
C3
|
R
T D4
T
T B2
|
R
E4 D4
T
C3 B2
|
R
E4 D4
T
C3 B2
|
R
E4 D4 E4 T
T
C3 B2 C3 T
|
R
E4 D4
T
C3 B2
|
R
E4 F4
T A3
C3
|
R
E4 D4
G3
T B2
|
R
E4 F4 E4 T
G3 A3 G3 T
C3
|
R
T F4
T A3
T
|
R
E4 F4 E4 T
G3 A3 G3 T
C3
|
R
T D4 E4 D4
T
T B2 C3 B2
|
R
E4 D4
T
C3 B2
|:
R
E4 D4 E4 D4
T
C3 B2 C3 B2
|
R
E4 F4
T A3
C3
|
R
E4 D4 E4 D4
G3
T B2 C3 B2
|
R
E4 D4 E4 T
T
C3 B2 C3 T
|
R
E4 D4 E4 D4
T
C3 B2 C3 B2
|
R
E4
T
C3
|
R
T D4
T
T B2
|
R
E4 F4
T A3 T T
C3
|
R
E4 F4
G3 A3
T
|
R
E4 F4
G3 A3 T T
T
|
R
E4 F4
G3 A3
T
|
R
E4 F4
G3 A3 T T
T
|:
R
E4 D4 E4 D4
G3
T B2 C3 B2
|
R
E4
T
C3
|
R
T D4
T A3 G3 T
T B2
|
R
E4 D4
T
C3 B2
|
R
E4
T
C3
|
R
T D4 E4 D4
T
T B2 C3 B2
|
R
E4
T
C3
|
R
T D4
T A3 G3 T
T B2
|
R
E4 F4
T A3
C3
|
R
E4
G3
T
|
R
T D4 E4 D4
T
T B2 C3 B2
|
R
E4
T
C3
|
R
T D4
T A3 G3 T
T B2
|
R
E4 D4 E4 T
T
C3 B2 C3 T
|
R
T
T
T
|
R
T F4 T T
T A3 D3 T
T T A2 B2
|
R
E4 F4
G3 A3 T T
C3
|
R
E4 F4 E4 T
G3 A3 G3 T
T
|
R
T D4 E4 D4
T
T B2 C3 B2
|
R
E4
T
C3
:|
R
T F4 D4 T
T A3 G3 T
T B2
|
R
E4 F4
T A3
C3
|
R
E4 D4
G3
T B2
|
R
E4 F4 D4 T
T A3 G3 T
C3 B2
|
R
E4 F4
T A3
C3
|
R
E4 D4 E4 T
G3
T B2 C3 T
|
R
T D4
T
T B2
|
R
E4 D4
T
C3 B2
|
R
E4 F4 D4 T
T A3 G3 T
C3 B2
|
R
E4 D4
T
C3 B2
|
R
E4
T
C3
|
R
T F4 T T
T A3 D3 T
T T A2 B2
|
R
E4 D4
G3
C3 B2
|
R
E4 F4 E4 T
T A3 G3 T
C3
|
R
T F4
T A3
T
|
R
E4
G3
T
|
R
T F4
T A3
T
|
R
E4
G3
T
|
R
T F4 D4 T
T A3 G3 T
T B2
|
R
E4
T
C3
|
R
T
T
T
|
R
T F4
T A3 D3 T
T C3 A2 B2
|
R
E4 F4
G3 A3
C3
|
R
E4 D4
G3
T B2
|
R
E4 F4 D4 T
T A3 G3 T
C3 B2
|
R
E4
T
C3
|
R
T
T
T
|:
R
T D4
T
T B2
|
R
E4 D4
T
C3 B2
|
R
E4 F4
T A3
C3
|
R
E4
G3
T
:|
R
T F4 E4 D4
T A3 G3 T
T T T B2
|
R
E4
T
C3
|
R
T D4 E4 D4
T
T B2 C3 B2
|
R
E4 D4
T
C3 B2
|
R
E4 F4
T A3
C3
|
R
E4 D4
G3
T B2
|
R
E4 F4 T T
T A3 D3 T
C3 T A2 B2
|
R
E4 D4
G3
C3 B2
|
R
E4 F4 E4 T
T A3 G3 T
C3
:|
R
T
T
T
|
疑似コードを見る
d = 0
a = 0
c = 0
b = 0
i = 0
++i
a = getchar()
{
b = getchar()
c = getchar()
d = getchar()
e = 0
++e
++e
{
d = getchar()
a *= d
d = getchar()
b *= d
d = getchar()
c *= d
e -= i
d = getchar() // newline
}
b += a
b += c
a = 0
a += d
a -= i // a = 9
++d
++d
d += d
b %= a
d += d // d = 48
{
a = 0 // dummy to exit loop
++d
}
putchar(d)
a = getchar()
++a
a -= 1
}
参考資料: 第6回のwrite up
- 変数名の候補を作る。「chordの禁則」の進行先から、
I
から始まってI
に帰れる短いシーケンスを構成する。今回はI
,I IV
,I V
,I IV I
,I V I
,I VI IV
の6つを使用した - コマンドと変数にtoneを割り当てる。最大で4 chordsなので全て1小節に収まる
- わかりやすさのためにソプラノはずっとお休み
- 残りのパートは狭い範囲のtoneを動くようにする。具体的には、アルトは
D4 E4 F4
、テノールはD3 G3 A3
、バスはA2 B2 C3
。これで「音域」「声部と音高」「不自然な音程の移動」の禁則はクリア -
II
→V
は第6回write upにあるとおりV7 omit1
を使うと1音の変更で済む。結局I
に移動するときに3音変更することになるのであまり得はしないように思えるが、I VI II V
(%=)のアルトが短くなる -
IV
→V
はV7 omit5
を使うと1音の変更で済む。こちらはI
に移動するコストが変わらないので単純にお得 -
V
(V7 omit1
,V7 omit5
含む)の後続は必ずI
になるようにしたので、I
への移動時にだけ「限定進行音」の制約が発生する。上記各パートのtoneの選択から、自然にアルトとバスで「限定進行音」の禁則をクリアできる - 小節内のゴルフ(Tを使う、1音・2音にできるものはする)はこの時点で済ませた(手でやったので提出したコードではまだ改善できるところが残っていた)
- 疑似コードに合わせてコマンドと変数を貼りつける。小節始めの
I
を適宜T
を使う形に書き直す-
T
を使っていると「連続五度、連続八度」のカウント外になるという仕様が非常に重要。元々のtoneの割り当て方から同じchordが連続している場所以外では禁則をクリアしていて、同じchordが連続している場所(変数末尾のI
→変数のI
→コマンド/変数先頭のI
)についてもT
に書き換えることで禁則をクリアできる(逆にいうと連続五度のエラーが出るときはT
に書き換えられる場所が残っている。ゴルフをサポートする親切機能!)
-
- デバッグ。出力がワイド文字のputcharしかないので、16bit以上の値を出すことができない&UCS-2がUTF-8になって出てきて直感的ではない
- 特殊小節線の「直前に実行した命令」が「特殊小節線の直前から記述が開始された命令の1つ前の命令」であるという仕様。第6回write upに書いていなかったら長時間ハマっていた
COBOL (@kotatsugame_t, @nu4nu, 229 bytes)
program-id.a.data division.local-storage section. 1 x pic 9(3).
1 y pic 9(3). procedure division.a.accept x.accept y.add y to x.
accept y.compute x=(x+y)/2.if x=0 stop run.if x=55 or x=50 or
x=5 display 0 else display 1.go a.
3行を数値として読み、総和/2が5,50,55のどれかであるかで分岐する。(@nu4nuさんによる方針。) 変数宣言がありえないくらい長いので、読みつつ足している。足すだけならcomputeよりもaddの方が短くなる。 行頭8文字にはコードを書けないためタブで埋め、1行80桁の制限内でギリギリまで改行を減らす。 procedureの直前などよくわからない箇所に空白が必要になった。
CompileTime TypeScript (@n4o847, 154 bytes)
type M<I>=I extends`${infer A}
${infer B}
${infer C}
${infer Z}`?`${`${A}${B}${C}${A}`extends`${any}1${0|1}${0|1}1${any}`?1:0}${M<Z>}`:``
export default M
文字列マッチで3行 (A
, B
, C
) と残り (Z
) に分ける。A + B + C + A
に 1(0|1)(0|1)1
というパターンが含まれれば 1、そうでなければ 0 とし、再帰する。
再帰回数制限が厳しいので、このアルゴリズムに至るまでは苦戦していた。
奪い合いが発生して縮んでいったので作者としては嬉しい。(相手もほぼ同じ手法だった。)
運用前 は I extends string
が必要だと思っていたがそうではなかったらしい。
Coq (@satos___jp, 515 bytes)
Require Import Io.System.All List ListString.All Ascii String.
Import ListNotations C.Notations.
Definition l c:=match c with Ascii b _ _ _ _ _ _ _=>b end.
Definition m a b c:=orb(orb(l a&&l b)(l b&&l c))(l c&&l a).
Fixpoint q n:=match n with
0=>ret tt|S o=>let! x:=read_line in
let! y:=read_line in
let! z:=read_line in
do! match x,y,z with
Some[a;b;c],Some[d;e;f],Some[g;h;i]=>log(LString.s(if orb(orb(m a d g)(m b e h))(m c f i)then"1"else"0"))|_,_,_=>ret tt
end
in q o
end.
Definition main:=launch(fun _=>q 16).
前回のCoil氏のCoqのコード( https://esolang.hakatashi.com/contests/6/submissions/5eb03453858a3144057ce98e )を元にした。入力はAsciiコンストラクタで包まれた8個のbool値の組のリストとして与えられるので、パターンマッチングして各文字をa-iに取得。文字のラスト1bitが立っているかを関数lで取得し、関数mでbool値3つのうち2つがtrueかどうかを判定する。
C++11 constexpr (@azaika_, 320 bytes)
using S=const char*;struct A{char v[17];operator S()const{return v;}};constexpr char c(S s){return(*s+s[4]+s[8]>145|s[1]+s[5]+s[9]>145|s[2]+s[6]+s[10]>145)+48;}constexpr A f(S s){return{c(s),c(s+12),c(s+24),c(s+36),c(s+48),c(s+60),c(s+72),c(s+84),c(s+96),c(s+108),c(s+120),c(s+132),c(s+144),c(s+156),c(s+168),c(s+180)};}
普段 C++ のメタプロしかしとらんので取れて良かった。C++11 constexpr といえば再帰計算だが、esolang-box の仕様上、再帰を行うのが難しいので今回はループが必要な場所は全部手で展開している。また関数外部に持ち出せる constexpr なストレージを用意するために、配列型を構造体でラップしてコピーを可能にする手法を取った。あとはreturn
の後の空白を可能な限り消したり改行を消したりで詰めるだけ。
Crystal (@hakatashi, 41 bytes)
loop{p read_line('z',12)*2=~/1...1/m?1:0}
博多市の十八番の言語なのでちゃんと縮められてよかった。青チームのRubyの最短コードと全く同じコードを事前に書けていたので、それをCrystalで動くよう修正して提出。read_line
関数の第1引数には改行文字を渡すのだが、出現しない文字を渡すことでread_lineと言いながら複数行を一気に読むことができる。
うら氏が提出していたテンプレートリテラルでnilを吸収するテクニックめちゃくちゃ賢い。動いてたら取られてましたね。こわい
C# (.NET Core) (@paradigm_9, 109 bytes)
using C=System.Console;int A()=>int.Parse(C.ReadLine());for(;;)C.Write("11101".Contains(A()+A()+A()+"")?0:1);
今までの大会では interface A{ static void Main() {}}
が必要だったが、C#のバージョンアップでそれは不要となった。+""
で文字列にできちゃうのは実用ではちょっと困るがゴルフとしては最高ですね。
Csound (@ikubaku10, 343 bytes)
<CsoundSynthesizer>
<CsInstruments>
sr=1
kr=1
instr 1
aA=-20480
aB=-20224
iA tab_i 0+p4,1
iB tab_i 1+p4,1
iC tab_i 2+p4,1
kR=floor((iA+iB+iC)/2)
if(kR==5)then
out aA
elseif(kR==50)then
out aA
elseif(kR==55)then
out aA
else
out aB
endif
event "i",1,0,16,p4+3
turnoff
endin
</CsInstruments>
<CsScore>
f 1 0 0 -23 "input.in"
i 1 0 16 0
</CsScore>
Csoundは本来音源を作って演奏をするためのツールですが,音を出せるということはプログラムだって組めるはず(??????).というわけでCsoundのesolang-boxを追加させていただきました(こいつが犯人).
方針はまず各テストケースごとに各行を10進数の整数値とみなしてその合計を計算,その後結果を2で割ってそれが5, 50, 55のいずれかであるかどうかを調べています.当てはまるならば二歩ではない(すべての桁が0か1)です.
次に実装について説明します.まずスコアで入力をGEN23ファンクションテーブル生成ルーチンのパラメータとして使うことですべての行の数値をファンクションテーブルの中に入れます. そして実際に判定処理を行うinstrument(Csoundにおける信号処理単位です)をテストケースの数と同じ16秒間演奏します. 秒数とテストケースの数が対応しているのはサンプルレートを1に設定しているので,16回だけ出力が行われることになるからです. instrumentの中ではファンクションテーブルの内容を取得して判定処理を行っています.判定自体は一般的なプログラミング言語で描く場合と大差がないと思います.
出力は文字を音声信号として出力しなければならないので少し工夫が必要です.
Csoundは入出力時に音声サンプルを16bit符号あり整数に変換します.実際の信号処理はすべて浮動小数点数として扱われます.
このボックスでは入出力どちらとも8bit符号なし整数としてCsoundに渡されるので,最終的にASCII文字コードに対応するような音声サンプルが出てくるようにします.
そのためには次の式を用いて音声サンプル値を計算します. c
を出力したい文字, **
をべき乗演算子, ord
を文字コードを得る関数として:
-2**15 + 256 * ord(c)
これによって計算された値がコードの aA
と aB
に格納されています.それぞれ文字0と1を表しています.
instrumentを単に16秒間演奏するだけでは同じ結果が出力され続けてしまいます. そこでinstrumentの処理の最後にファンクションテーブルの要素インデックスを変えるためのパラメータを変えた上でinstrumentをもう一度鳴らすスコアイベントを発行しています. またそれだけでは今演奏しているinstrumentの信号もかぶってしまうのでturnoffを実行することでこれ以上音を鳴らさないようにしています.
ちなみに処理系のバリデーションがバグってるっぽくてUnified Source CodeのCsSynthesizerタグは閉じなくても実行できます(???).
(間違った)Csoundプログラミングについて更に知りたい方はぜひこちらをご覧ください: https://github.com/ikubaku/csound-as-programming-language
(@nakario大会後追記) 空白を除去したりロジックを変更したりarrayを利用することでさらに100B以上縮めることが出来る。また、何故か外部コマンド実行も出来てしまったのでsedを使うと大幅に短縮できる。
まじめなコード (213 bytes)
Csoundが出力する音声データは "output.out" に保存される。そのことを利用し、このファイルに直接printすることで音楽的要素を考慮する必要がなくなり、サンプリングレートを指定したり音声出力をturnoffしたり文字コードを音声サンプル値に変換する処理を省略できる。Csoundの醍醐味をぶっ壊しているので、次回以降Csoundが採用された際に使えるかどうかは微妙である。
tab2array
はCsScoreで定義されたFunction-tableの1番をarrayとして読み出すために使っている。受け取る変数はi-rate型のarrayとk-rate型のarrayがある?と解釈しているが、k-rate型だとprintするタイミングまでに値が更新されない?のかnullのような値が入っている?のか、とにかくよくわからない動作(fprints "output.out","%d",kA[0]==0?3:7
が0をprintするなど)を引き起こす。とにかくi-rate型の変数を使っておけば良い。
ループするにはwhileやuntilの他、値を一定値ずつ増やし(減らし)ていき、しきい値との大小で判別するloop_{ge,gt,le,lt}が利用できるが、今回はarrayの範囲外アクセスで異常終了できるのでgotoで無限ループさせる。
<CsoundSynthesizer>
<CsInstruments>
instr 1
iA[]tab2array 1
l:
fprints "output.out",(iA[p3]+iA[p3+1]+iA[p3+2])#34%35>7?"1":"0"
p3+=3
goto l
endin
</CsInstruments>
<CsScore>
f 1 0 0 -23"input.in"
i 1 0 0
</CsScore>
外部コマンド実行 (79 bytes)
<CsoundSynthesizer>
<CsScore bin="sh">
sed 'N
N
h
G
/1...1/c1
c0' i*
</CsScore>
Cubically (@nu4nu, @kotatsugame_t, 78 bytes)
⇒~+7+8!&-2-4
⇒*2BR*4«0»3R3B3f1
⇒f2f2f2~
F(D-6f1«0f1«0f1~f3f3»8D3|4>4%)
疑似コードを見る
f1:
getchar
add input
add 1 // unsolved
jz return
sub 19 // FD[2]
sub 30 // FD[4]
f2:
mul 19 // FD[2]
B
R
mul 27 // FDBR[4]
lshift 17 // FDBR[0]
rshift 23 // FDBR[3]
R3
B3
call f1
f3:
call f2
call f2
call f2
getchar // skip newline
main:
F
loop{
D
sub self
call f1
lshift 3 // FD[0]
call f1
lshift 3 // FD[0]
call f1
getchar // skip newline
call f3
call f3
rshift 1 // unsolved
D3
or 36 // F[4]
gt 36 // F[4]
print
}
色が0から5で表されたルービックキューブを回す言語。ルービックキューブの面は読み出し専用で、面に書かれた数の合計値がその面の値となる。つまり原理的に負数や46以上の値を表すことができない。面の他には、notepadと呼ばれる値の読み書きができるアキュムレータが1つ、getcharした結果が1文字入る読み出し専用の領域が1つ、キューブが初期状態が否かを0/1で表す読み出し専用の領域が1つ。アキュムレータの値に応じてキューブを回すことで間接的にキューブに何らかの演算結果を代入することができるが、さすがにそれをやり始めると地獄なので、アキュムレータに書き込む演算を繰り返す形で解く必要がある。
判定アルゴリズムは4以上の2ベキ進数で3要素を並べたものを加算して222と論理積をとるもの。それをアキュムレータ1つで行う方法は @kotatsugame_t さんの着想による。2ベキ進数でa b c
と並んでいるところから、乗算でa b c a b c
にして、アキュムレータのビット数が有限(Cのint型)なのを利用して左シフトと右シフトで上下を落とせば、b c a
が得られる。ここに読み込んだ文字コードを足し48を引く(48は表せないので2回に分ける)。これを繰り返せば列ごとの和を得ることができる。これを4進数でやると、最後に42と論理積をとることになり、無事に使える即値の範囲に収まってめでたしめでたし。
……のはずが、右シフトが算術シフトのため、途中で左シフトした段階でMSBが立ち、負数として扱われてしまうケースがあり正しく動かないことがわかった。そこで以下のような変更を施した。
- 8進数にして必ず0になるビットを作ることで、左シフト後にMSBが0にならないようにする
- 8進数だと最後に146と論理積をとることになるが、それはできないので、2で割った後36との論理和をとり36と比較することで代替する
キューブの回転については十分にゴルフができておらず今後の課題となっている。まず前提として19,27,17,23ができるだけ簡単なキューブの操作で同時に手に入る必要がある。これは今はFDBRという4操作で実現しているが、3操作以下で実現できる可能性もある。なおシフト演算子はUTF-8で2B食うので、シフトを2回に分けることで必要な即値の制約を緩めるのは、キューブを1回回して戻すのと同じコストとなり得ではない。シフト演算子が長いという意味では、3や1の代わりに8や2が簡単に用意できるなら乗除算に置き換えたほうがよい。また36を得るためにFの状態まで戻るのももったいない。
Cubix (@kotatsugame_t, 21 bytes)
pa^<IIaqbI/biO<@..?:,
pa
^<
IIaqbI/b
iO<@..?:
,.
..
スタック型の言語だが、ほとんどの演算がスタックの中身を消費しないのは特徴的か。例えば加算をすると、popしたオペランドを再度pushしてから演算結果をさらにpushする。
上のような配置の2x2x2のキューブ上をIPが動いていく。スタートは3行1列目のI
で、方向は右向きだ。
3x3x3に詰め込んで満足していたが、ふと2x2x2を考えてみたら詰め込めてしまってびっくりした。
アルゴリズムは、3行を数値として読んで(x&y|(x|y)&z)!=0
である。
スタックの中身を消費しないことを利用してIIaqbIapb:,O
と書ける。
x&y
を計算してから、いったんq
でスタックの底に退避させ、後でp
で持ってきている。
!=0
の代わりに、:
でdupして,
で切り捨て除算している。実装上0/0
は0らしい。
次に、どのようにIPが動くのか見ていこう。
まず3行1列目のI
から右方向にIIaqbI/
と進む。
/
で進行方向が変わる。右方向に進んでいたので、次は上方向になり、立方体上面、1行4列目のa
に上から進入する。
上面では<
と^
によってUターンのようなことをしながらap
を実行する。1行4列目のp
から上に進むと、今度は3行8列目のb
に上から進入することになる。
そのままb:
と進み、今度は6行3列目に下から進入する。.
はnopで、途中<
で進行方向を変えつつ,O
と進む。
これでケース1つぶんの入出力が完了した。無限ループになってはいけないので、実行を継続するか判定する必要がある。
i
で1文字読んで、改行コードか-1(EOF)かで判定できる。
先ほどの続きから、4行1列目のi
、さらに左に行くと今度は4行8列目に回り込んで:?
と進む。:
はdupなので関係ない。?
が条件分岐になる。
?
にたどり着いたとき、スタックのトップが0より小さいなら左に、0より大きいなら右に曲がる。
先ほど読んだ文字の文字コードが見られ、改行コードなら右に、EOFなら左に曲がることになる。
左向きに進んでいるので、右に曲がると上方向に進むことになる。また/
で反射して右方向に進み、そのまま3行1列目のI
に左から進入することになる。確かにループしている。
逆に、左に曲がると下方向に進むことになる。今度は6行4列目に下から進入し、そのまま上がって@
にたどり着き、実行が終了する。
コードゴルフとしては、2x2x2に収めたほかにも、下面にnopが多くあらわれるように工夫している。 2x2x6文字に足りない分は勝手にnopで埋められるので、下面の3つのnopはコードに書かなくてもよい。
Cy (@nu4nu, 41 bytes)
"\\#{loop{p gets(p,12)*2=~/1...1/m?1:0}}"
Rubyのコードを埋め込んだだけで私は何もしていません。
D (GDC) (@kotatsugame_t, 99 bytes)
import std.stdio;void main(){for(int a,b,c,i=16;i--;write=1-!(a+b+c&546))readf(" %x %x %x",a,b,c);}
見た通り、16進数として読んで足し、546とbitwise andを取って出力する方針。
1引数の関数呼び出しがwrite()
ではなくwrite=
でよいのは、第6回大会のWriteUpから。
ループをmain再帰にしてゼロ除算で落そうとすると、出力してくれなかったので、仕方なくfor文を書いた。
せめてもとfor文にいろいろ詰め込んでおいた。
文責 @paradigm_9
3日あったが結局誰にも解かれなかった言語。この言語は難しすぎるMalbolgeをある程度簡単にしたよ!という言語で、ロード時の謎復号処理も無く、データが印字可能文字である必要もなく、そういう不自由さは無いため取り掛かりやすい。三進数ベースな点・命令セット・メモリと実行コードが共用な点など、Malbolgeの理不尽でないエッセンスだけを抜き出した言語となっている。
実際、HelloWorldサンプルを見ると分かるが、 ^!________________________________!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*
から書き始めることで34番地メモリから50個分くらいのメモリを割と自由に扱える。HelloWorldサンプルでは34から37番地メモリをぐるぐる回って目的のことをしている。
この言語はありえないことが一つだけあって、唯一の演算 tritwise sub が非常に弱いという問題がある...。合成しても27種類しか関数を作れない。全ての関数を作れるMalbolgeのCrazy演算をそのまま使えればよかったのに...。というわけで、疑似命令で自由に書けるようにして、あとは関数合成だけだ!というところまで取り掛かったリポジトリを作ったが、そこで終了してしまった...。なお、Disは悪魔という意味らしい。地獄(Malbolge)よりはマシ?
GNU ed (@hiromi-mi, 53 bytes)
,j
s/.\{9\}/&\
/g
,s/.*1...\{3\}\?1.*/1
,s/...*/0
wq
-
,j
の二文字 で全連結可能なのが強い - 正規表現中で改行をマッチさせられないい
後日短縮版45B by @angel_p_57
コードを見る
,j
s/.\{9\}/&& 0\
/g
,s/1..1.*/ 1
,s/.* /
wq
hiromi-mi氏のを元にしていますが、文字列を2度繰り返して 1..1
のマッチで済ませた点と、お尻に最終的な出力となる0,1をつけておくことで、置換の文字数をちょっと値切った点が短縮ポイントです。
Egison (@drafear, 154 bytes)
def i ():=io(readLine())
def main x:=let s:=appendString(appendString(i())(i()))(i())in
do
write(if regex"1..1"(appendString s s)=[]then"0"else"1")
main 1
Element (@eto_nagisa, 20 bytes)
16'[3'[_+][(2<&]!"`]
一次元スタック指向言語だが、入出力や算術演算などを行うmain stackと条件分岐や論理演算などを行うcontrol stackの二つを持つことが特徴。
全体のアルゴリズムとしては、3つの整数を10進数として足した和の各桁が1以下か?で判定している
3'[_+]で3つの整数をとって足す 空のスタックから何かを取り出しているが、これは整数で評価すると0になるのでok
次はcontrol stackに3が積まれたままなのでそのままforループできる 和を文字列とみて先頭を切り取り、2未満か判定してandをとるという操作を3回行う control stackには3が積まれているが、3は0/1でのandを行う上では単位元なので問題なし
めっちゃ綺麗に書けてこの問題のために作られた言語かと思いました
Elixir (@kotatsugame_t, 65 bytes)
f=fn f,s->t=IO.getn s,12
f.(f,t<>t=~~r/1...1/s&&1||0)end
f.(f,"")
文字列を2倍して/1...1/
を探している。s
は.
を改行にマッチさせるオプション。
<>
は文字列連結の演算子で、~r//
は正規表現リテラル。
String.match?
を使用せずとも=~
演算子でマッチングできる。
以前の大会のWriteUpを参考にしてループを再帰で書いており、IO.getn
を実行しつつ前回の答えを出力したりなどちょっと工夫しているが、普通にループを書いたほうが短かったらしい。
↓61B
for x<-0..15,do: IO.puts IO.read(12)=~~r/1...(....)?1/s&&1||0
Emojicode (@dnek, 209 bytes)
🏁🍇🔂i🆕⏩0 16❗️🍇0➡️🖍🆕s🔂j🆕⏩0 3❗️🍇s⬅️➕🍺🔢🆕🔡▶️👂🏼❗️4❗️🍉s⭕42➡️🖍s↪️s▶0🍇1➡️🖍s🍉😀🔡s❗️❗️🍉🍉
整形
🏁🍇
🔂i🆕⏩0 16❗️🍇
0➡️🖍🆕s
🔂j🆕⏩0 3❗️🍇
s⬅️➕🍺🔢🆕🔡▶️👂🏼❗️4❗️
🍉
s⭕42➡️🖍s
↪️s▶0🍇
1➡️🖍s
🍉
😀🔡s❗️❗️
🍉
🍉
擬似コード
for i in 0..<16:
s = 0
for j in 0..<3:
s += parse_int(read_line(), base=4)!
s = s & 42
if s > 0:
s = 1
print(str(s))
emojifunge (@ten986, 113 bytes)
🎥🔟🔟7️⃣➕🅱️ℹ️ℹ️ℹ️➕➕🈹🈹🈹1️⃣📉🔢🎥5️⃣🔟➕🕰📽️🔚
作者です。
絵文字を使った2次元スタックベース言語です。 本来、2次元上を進み、壁があれば時計回りに回転したり、スタックをネストしてスタック同士の演算をしたりできるのですが、そんな機能は使いません。
(A+B+C)%66%17%10>1
を使います。
66が🅱️
という1コマンドで手に入る最高言語です。
🎥 ~~~ 🎥
によってそこまでのコマンドを保存し、📽️
で再生できます。
さらに、🕰
で直後のコマンドを複数回実行できるので、📽️
を15回実行して終了します。
余談ですが、この言語には📅
というコマンドがあり、日付の年月日時分秒をスタックに積みます。このせいで、(確率解を禁止するのと同様の理由で)以下のような注意書きが為されました。
一部期間でのみ通る解答も禁止とします。(例えば、2021年でのみ正答する回答)
すいませんでした。
Erlang (@kotatsugame_t, 121 bytes)
main(_)->f(0).
f(I)->{_,[A,B,C]}=io:fread("","~d~d~d"),io:format("~p",[1-(A bor B bor C)div(A+B+C)]),I>14 orelse f(I+1).
1-(a|b|c)/(a+b+c)
。ループの代わりに再帰関数を書いた。
入力が尽きると「標準出力」にエラーを吐いて落ちるため、どうにかして終了させる必要がある。
前回大会ではリスト内包表記を使ってちょうどの回数分呼び出していて、今回も最初はそれで書いていたが、引数をインクリメントしつつ短絡評価のorelse
を使う方が1B短くなった。
freadは入力された数値のリストとokのステータスを返してくるため、両方受け取らなくてはならない。
-export([main/1]).
は必要なく、先頭で1回改行しておけばよい。
赤チームの共有メモを見て知ったが、io:format
ではなくio:write
を使うとよいらしい。113B
main(_)->f(0).
f(I)->{_,[A,B,C]}=io:fread("","~d~d~d"),io:write(1-(A bor B bor C)div(A+B+C)),I>14 orelse f(I+1).
Evil (@kuromunori, 156 bytes)
ccxzaeeknaeekhjxnryoryoryrromulalsbromulalsbromulalsbrromulalsbromulalsbromulalsbrgnmululsblsuskohgnmululsblsuskohgnmululsblsuskzaeeeaeevtfvavmvwzkhhgutfkxb
そんなにゴルフしていない 可変リストと固定長リスト,1時変数があるので, 結構自由に書ける(さほど邪悪でない) ループが大変で, 直前or直後のマーカーまでジャンプで, マーカーが2種類しかないため,2重以上のループを書くのが結構しんどそう インクリメントとデクリメントしかないため, 和や差を書くのにもループを1つ使うため余計しんどい 全体を16回繰り返す処理と, 和差のループだけループを書き, 残りはあきらめてn回繰り返しべた書きをしているため,そこそこ長い
ExchangeIF (@angel_p_57, 6219 bytes)
ロジック部分(作成支援スクリプト)
作った画像は、ウソ画像化用rubyスクリプトでウソ画像にし、ファイル名を1文字に縮めた上で zip -X コマンドでアーカイブします。画像生成スクリプト
#!/bin/bash
# base.jpg はフツーの1x1 jpegファイル
for i in {00..28}; do cp ../base.jpg $i.jpg; done
# mkexif.py は pyexiv2を使う、自作Exif操作スクリプト
# 引数の数字は、最初が「処理タイミング」以降が対象の変数(番号)を指します。例外的にgotoのみ、最後の引数がgoto先タイミングです。
docmd () { i=$1; shift; python3 ../mkexif.py $(printf "%02d" $i).jpg $i "$@"; }
docmd 0 stor 0 73x2
docmd 1 stor 1 2x1
rm 02.jpg
docmd 3 stor 3 2x1
docmd 4 get 4
docmd 5 get 5
docmd 6 get 6
docmd 7 get 2
rm 08.jpg
docmd 9 get 7
docmd 10 get 8
docmd 11 get 9
docmd 12 get 2
docmd 13 add 7 4 # src dst
docmd 14 add 8 5
docmd 15 add 9 6
docmd 16 sub 1 3
docmd 17 goto 3 8 # jump if not negative
docmd 18 sub 0 4
docmd 19 sub 0 5
docmd 20 sub 0 6
docmd 21 stor 10 7x7
docmd 22 goto 4 26 # jump if not negative
docmd 23 goto 5 26 # jump if not negative
docmd 24 goto 6 26 # jump if not negative
docmd 25 stor 10 8x6
rm 26.jpg
docmd 27 put 10
docmd 28 goto 0 2
#timeline
# 0: q=145
# 1: b=2
# 3: z=2
# 4: s1=getc
# 5: s2=getc
# 6: s3=getc
# 7: d=getc
# 9: i1=getc
#10: i2=getc
#11: i3=getc
#12: d=getc
#13: s1+=i1
#14: s2+=i2
#15: s3+=i3
#16: z-=b
#17: goto 8 if z>=0
#18: s1-=q
#19: s2-=q
#20: s3-=q
#21: c=49
#22: goto 26 if s1>=0
#23: goto 26 if s2>=0
#24: goto 26 if s3>=0
#25: c=48
#27: putc(c)
#28: goto 2 if q>=0
#
# variable list
#var: occurrence assigned-number
#q : 4+1 0
#b : 2 1
#d : 2 2
#z : 3 3
#s1,s2,s3: 3 4,5,6
#i1,i2,i3: 2 7,8,9
#c : 3 10
Exif編集スクリプト
import sys
import pyexiv2
basedate='2021:3:7 0:0:'
cmd2iso={
'stor': '0',
'get' : '100',
'put' : '200',
'add' : '300',
'sub' : '400',
'mul' : '500',
'div' : '600',
'mod' : '700',
}
def a2tude(a):
v1=a//10
v2=a%10
return str(v1)+'/1 '+str(v2)+'/1'
img=pyexiv2.Image(sys.argv[1])
img.clear_exif()
order=sys.argv[2]
cmd=sys.argv[3]
datestr1=basedate+order
src=0
dst=0
isize=''
if cmd=='goto':
iso='0'
src=int(sys.argv[4])
datestr2=basedate+sys.argv[5]
else:
iso=cmd2iso[cmd]
datestr2=datestr1
if cmd=='get':
dst=int(sys.argv[4])
elif cmd=='stor':
dst=int(sys.argv[4])
isize=sys.argv[5]
else:
src=int(sys.argv[4])
if cmd!='put':
dst=int(sys.argv[5])
img.modify_exif({'Exif.Image.DateTimeOriginal':datestr1, 'Exif.Image.DateTime': datestr2, 'Exif.Photo.ISOSpeedRatings': iso})
if src>0 or dst>0:
longtitude=a2tude(src)
latitude=a2tude(dst)
img.modify_exif({'Exif.GPSInfo.GPSLongitudeRef': 'E', 'Exif.GPSInfo.GPSLongitude': longtitude, 'Exif.GPSInfo.GPSLatitudeRef': 'N', 'Exif.GPSInfo.GPSLatitude': latitude })
if isize!='':
sizex,sizey=isize.split('x')
img.modify_exif({'Exif.Photo.PixelXDimension': sizex, 'Exif.Photo.PixelYDimension': sizey})
img.close()
嘘画像化スクリプト
g=->{STDIN.getbyte}
w=->a{STDOUT.write a.pack 'C*'}
rw=->n{w[n.times.map{g[]}]}
rw[4]
s1,s2=g[],g[]
w[[s1,s2]]
s=s1*256+s2-2
rw[s+2]
s1,s2=g[],g[]
w[[s1,s2]]
s=s1*256+s2-2
rw[s]
loop{
h1,h2,s1,s2=g[],g[],g[],g[]
s=s1*256+s2-2
if h2==0xc0 || h2==0xc2
w[[h1,h2,s1,s2]]
s=s1*256+s2-2
rw[s]
break
end
s.times{g[]}
}
STDOUT.write "\xff\xdb\x00\x02\xff\xc4\x00\x02\xff\xda\xff\xd9"
何気に最終日を捧げてしまった言語。 zipファイルに含まれる画像1つ1つがコマンドで、かつその内容がExifの内容に依るという…。 Exifの仕様・操作方法の調査を頑張り、容量オーバ対策でウソ画像ファイルで容量削減を目指し、 最後に処理系のバグで掛け算が使えないことに気付き、なんというか地雷を全力で踏み抜いた感が。 通ってほっとしたというのが素直な感想です。 ※おかげで周辺ツールの充実っぷりが。
後日短縮版4016B ソースはこちら
更新点
ExifのMetringModeという属性で「関節参照が使える」というのをあまり把握していなくて、その部分の対応により命令数∝zipアーカイブ容量を大幅に減らすことができました。( 以下のスクリプト中 @6 とかしているのがそう )ロジックについては、以下の画像生成スクリプトの下部 timeline としているところでなんとなく感じてください。
ただ、ループが1段深くなったことで、jumpによる移動ステップが増えており、実行時間的には11秒程度とかなりTLEに近いところまで来ています。 ※言語仕様上、jumpで1ステップ分飛ぶだけで、1/150秒のwaitが入るため。
画像生成スクリプト
#!/bin/bash
# base.jpg はフツーの1x1 jpegファイル
for i in {00..16}; do cp ../base.jpg $i.jpg; done
# mkexif.py は pyexiv2を使う、自作Exif操作スクリプト
# 引数の数字は、最初が「処理タイミング」以降が対象の変数(番号)を指します。例外的にgotoのみ、最後の引数がgoto先タイミングです。
docmd () { i=$1; shift; python3 ../mkexif.py $(printf "%02d" $i).jpg $i "$@"; }
docmd 0 stor 2 2x1
docmd 1 stor 0 8x6
docmd 2 stor 3 29x5
docmd 3 stor 5 29x5
docmd 4 stor 7 29x5
docmd 5 stor 4 5x1
docmd 6 stor 6 7x1
docmd 7 get 1
docmd 8 sub 1 @6 # src dst
docmd 9 goto @6 10 # jump if not negative
docmd 10 stor 0 7x7
docmd 11 sub 2 6
docmd 12 goto 6 6 # jump if not negative
docmd 13 sub 2 4
docmd 14 goto 4 5 # jump if not negative
docmd 15 put 0
docmd 16 goto 0 0 # jump if not negative
#timeline
# 0: v2=2
#L1:
# 1: v0=48
# 2: v3=145
# 3: v5=145
# 4: v7=145
# 5: v4=5
#L2:
# 6: v6=7
#L3:
# 7: v1=getc
# 8: @v6-=v1
# 9: goto L4 if @v6>=0
#10: v0=49
#L4:
#11: v6-=v2
#12: goto L3 if v6>=0
#13: v4-=v2
#14: goto L2 if v4>=0
#15: putc(v0)
#16: goto L1 if v0>=0
関節参照対応Exif生成スクリプト
import sys
import re
import pyexiv2
basedate='2021:3:7 0:0:'
cmd2iso={
'stor': '0',
'get' : '100',
'put' : '200',
'add' : '300',
'sub' : '400',
'mul' : '500',
'div' : '600',
'mod' : '700',
}
def argv2pos(s):
m=re.search('^(@?)(\d+)',s)
if not m:
raise ValueError(s+' is not a valid position')
ref=m.group(1)=='@'
pos=int(m.group(2))
return (ref,pos)
def a2tude(a):
v1=a
v2=0
return str(v1)+'/1 '+str(v2)+'/1'
img=pyexiv2.Image(sys.argv[1])
img.clear_exif()
order=sys.argv[2]
cmd=sys.argv[3]
datestr1=basedate+order
src=0
dst=0
met=0
isize=''
if cmd=='goto':
iso='0'
ref,src=argv2pos(sys.argv[4])
if ref:
met=1
datestr2=basedate+sys.argv[5]
else:
iso=cmd2iso[cmd]
datestr2=datestr1
if cmd=='get':
ref,dst=argv2pos(sys.argv[4])
if ref:
met=2
elif cmd=='stor':
ref,dst=argv2pos(sys.argv[4])
if ref:
met=2
isize=sys.argv[5]
else:
ref,src=argv2pos(sys.argv[4])
if ref:
met=1
if cmd!='put':
ref,dst=argv2pos(sys.argv[5])
if ref:
met+=2
img.modify_exif({'Exif.Image.DateTimeOriginal':datestr1, 'Exif.Image.DateTime': datestr2, 'Exif.Photo.ISOSpeedRatings': iso})
if src>0 or dst>0:
longtitude=a2tude(src)
latitude=a2tude(dst)
img.modify_exif({'Exif.GPSInfo.GPSLongitudeRef': 'E', 'Exif.GPSInfo.GPSLongitude': longtitude, 'Exif.GPSInfo.GPSLatitudeRef': 'N', 'Exif.GPSInfo.GPSLatitude': latitude })
if isize!='':
sizex,sizey=isize.split('x')
img.modify_exif({'Exif.Photo.PixelXDimension': sizex, 'Exif.Photo.PixelYDimension': sizey})
if met!=0:
img.modify_exif({'Exif.Image.MeteringMode': str(met)})
img.close()
さらに短縮版3857B ソースはこちら
解説を見る
2重ループを1重化できることに気付き、1命令の削減(=アーカイブ中のファイル数1減少)に成功しました。 デクリメントするカウント値をmod計算に利用することで、4種類の値を周期的に作ることができ、それで関節参照を行います。
以下更新版の画像生成スクリプト
#!/bin/bash
# base.jpg はフツーの1x1 jpegファイル
for i in {00..15}; do cp ../base.jpg $i.jpg; done
# mkexif.py は pyexiv2を使う、自作Exif操作スクリプト
# 引数の数字は、最初が「処理タイミング」以降が対象の変数(番号)を指します。例外的にgotoのみ、最後の引数がgoto先タイミングです。
docmd () { i=$1; shift; python3 ../mkexif.py $(printf "%02d" $i).jpg $i "$@"; }
docmd 0 stor 3 3x1
docmd 1 stor 4 8x6
docmd 2 stor 1 29x5
docmd 3 stor 2 29x5
docmd 4 stor 7 29x5
docmd 5 stor 5 7x5
docmd 6 sub 3 5
docmd 7 get 0
docmd 8 stor 6 89837x10051
docmd 9 mod 5 6
docmd 10 sub 0 @6 # src dst
docmd 11 goto @6 12 # jump if not negative
docmd 12 stor 4 7x7
docmd 13 goto 5 5 # jump if not negative
docmd 14 put 4
docmd 15 goto 0 0 # jump if not negative
#timeline
# 0: v3=3
#L1:
# 1: v4=48
# 2: v1=145 # 902951687%2 or 14 or 26=1
# 3: v2=145 # 902951687%5 or 17 or 29=2
# 4: v7=145 # 902951687%8 or 20 or 32=7
# 5: v5=35
#L2:
# 6: v5-=v3
# 7: v0=getc # 902951687%-1 or 11 or 23=0
# 8: v6=902951687
# 9: v6%=v5
#10: @v6-=v0
#11: goto L3 if @v6>=0
#12: v4=49
#L3:
#13: goto L2 if v5>=0
#14: putc(v4)
#15: goto L1 if v0>=0
எழில் (実質@u6606u5e03, 提出@dnek, 91 bytes)
@(i=16,i,i=i-1)ஆக
printf(str((eval("0"+"+int(raw_input(\"\"),4)"*3)&42)>0))முடி
Pythonで実装されている、タミル語圏のプログラミング言語。構文のキーワードはタミル語だが、組み込み関数はタミル語名でも英語名でも呼び出せる。タミル文字はバイト数が多いので、英語名を使った方が良い。組み込み関数のラインナップはPythonとだいたい同じ。 ロジックはPython3の最短コードと同じである。ただし以下のような相違点がある。
- エラーは標準出力に吐かれるのでவரை文(while文に相当)は使えない。代わりにஆக文(C++などのfor文に相当)を使う。
-
input
は小数型の値(?)を返し不便なので、raw_input
を使う。 - boolean型がなく
0
と1
で真理値が表現されるため、and 1
の代わりに>0
とする。
標準出力はபதிப்பி hoge
を使うのが普通だが、printf(str(hoge))
の方が短い。
当初@u6606u5e03はfor文の中身をi=0,i<16,i=i+1
と書いていたが、@dnekさんがi=16,i,i=i-1
に改良した。
FerNANDo (@nu4nu, 211 bytes)
コードを見る
r
s 0
t 0
u 0
v 0
w 0
x 0
q
l
r _ _ _ _ l _ R a
l r
l l
l
r _ _ _ _ _ _ _ b
r _ _ _ _ _ _ _ c
a a
S s v
s a
s S
v a
v v
b b
T t w
t b
t T
w b
w w
c c
U u x
u c
u U
x c
x x
p q
q p
q
t s
t t
u t
0 0 r r R R 0 u
r
イメージとしては列ごとに2bitのカウンタで1の数を数えている。s,t,uがそれぞれの列に2個以上1があれば0、v,w,xがそれぞれの列に1個以上1があれば0に落ちる。負論理の全加算器のようで微妙に違うロジック。1から0に落ちるようにしたのは、初期化を短く書くため。(1にするのはs 0
で済む。0にするのはs 1 1
)
a a
S s v
s a
s S # s = s & (v | ~a) = (s & v) | (s & ~a) = ~nand(s,v) | ~nand(s,~a) = nand(nand(s,v),nand(s,~a))
v a
v v # v = v & ~a = ~nand(v,~a)
改行文字を読み飛ばす以下のロジックは @eto_nagisa さんのコードをベースにした。基本的には文字コードの2^3の位が0になるまで繰り返し読めばよいが、入力の最後が改行文字なので、単純にそのビットだけでループを作ると無限ループしてしまう。読めたかどうかを表すビットも一緒に見てやればよい。これでも改行文字を読む行を書くよりは短い。
l
r _ _ _ _ l _ R a
l r
l l
l
ferNANDoゴルフで頻出の、入力が尽きたことを検出した最後のループでは空白文字を出力する処理は以下のようにした。直前のループで読んだ入力値を使って計算するためLSBは0にも1にもなりうるはずなので、どちらになってもよいようにしている。(\fか\rになる)
0 0 r r R R 0 u
><> (@ten986, 27 bytes)
222f\~**1(n
0:i}/?=9:-%4;?(
2次元スタックベース言語です。
入力を
a1 a2 a3
b1 b2 b3
c1 c2 c3
としたときに、(2-a1%4-b1%4-c1%4)*(2-a2%4-b2%4-c2%4)*(2-a3%4-b3%4-c3%4) < 1
を計算しました。ascii code で 0
は 48
、1
は 49
です。%2
としてないのは、改行(=10
)でも同様の計算をしたいためです。
スタックに2,2,2,15
を積みます。それぞれ*1%4
*2%4
*3%4
改行%4=2
を引くためです(1と3は逆かも)。
}
でスタックをシフトしながら入力をとり、入力が-1
なら終了します。
入力%4
を引いていき、これが9
になるまで続けます。/\
は反射で、?
のConditional trampolineで飛び越えます。
あとは計算して出力するだけです。
大会終了後のロスタイムで @kuromunori さんが、以下の26bytesのコードでACしました。
「fishのEOF(-1)チェックは入力が0以下か判定して正常終了させるより,+1してmodを取って強制終了させる方が1コマンド短い」とのことです。
222f\~**1(n
1:i}/?=9:-%4%+
Fortran 2018 (@nu4nu (実質 @kotatsugame_t), 56 bytes)
1 read*,i,j,k
print*,1-ior(i,ior(j,k))/(i+j+k)
goto1
end
bit-wise ORの記述が長いので盲点だが、10進数で読めることもありこれが短い。
F# (.NET Core) (@shell_mug (実質@nakario), 86 bytes)
let rec f r=printf"%d"(Seq.min[(r()+r()+r())%66%17%10;2]/2);f r
f(stdin.ReadLine>>int)
基本方針は@nakarioが示したが決め手となったのは@drafearがヒントをくれたif~then 1 else 0
のSeq.min
による代替。また、@nakarioが嘘解法(Seq.min[(r()+r()+r())%66%17%10-1;1]
)を通してしまったところを@shell_mug氏が救ってくれた。
Functional() (@angel_p_57, 326 bytes)
0,1,=,:,V,F,R,W,E,V(L,F(u)(V(t,=(E(),0))(L)(u,t(u)()))),V(D,F()(1(R(),R(),R(),R(),R(),R(),R(),R()))),V(G,F(P,T,o,2,3,4,5,6,7,8,9,a,b,c,d)(P(T(o(2,6,a),o(3,7,b),o(4,8,c))))),L(F()(G(F(x)(W(x),W(0),W(0),W(0),W(1),W(1),W(0),W(0)),F(x,y,z)(x(1,y(1,z))),F(x,y,z)(x(y(1,z),y(z,0))),D(),D(),D(),D(),D(),D(),D(),D(),D(),D(),D(),D())))
書いてみると意外と分かり易く見えるので、慣れって恐ろしい…。 取り敢えず、関数型ってことしか把握してません。最初の0~Eはデフォルトの機能を呼び出す文字の選定で、自分好みに決めることができます。 ( zero,one,equal-to,assign,define-variable,function,read,write,eof ) 処理の繰り返しは高階関数Lなんですが、サンプルをアレンジしただけなので挙動はよく分かってません。 ※単純に書くと、遅延評価にならなくてTLE必至です。 面白いのが、0,1 に割り当てた zero,one で、ビットとしての 0,1 のみならず、Lisp的な cdr,car のような役割も持ちます。 なので、x+y+z>=2 が x(y(1,z),y(z,0))のように定義できたりします。 なお、read,writeはbit単位です。最下位のbitだけ気にすれば良いところ、この仕様は逆にありがたかったところ。
後日提出版220B
0,1,=,:,V,F,R,W,E,V(L,F(D)(E(W(F(2,3,4,5,6,7,8,9,a)(2(5(1,8),5(8))(1,3(6(1,9),6(9))(1,4(7(1,a),7(a)))))(D(),D(),D(),D(D()),D(),D(),D(D()),D(),D())),W(W(W(D()))),W(W(1)),W(W()))(0,L)(D)))(F()(R()(R(R(R(R(R())))),R(R()))))
色々Functional()のことが分かったのでRefineしました。
解説
まず、適度に改行を入れたのが次のコードです。
0,1,=,:,V,F,R,W,E,
V(L,F(D)(
E(
W(
F(2,3,4,5,6,7,8,9,a)(
2(5(1,8),5(8))(1,3(6(1,9),6(9))(1,4(7(1,a),7(a))))
)( D(),D(),D(),D(D()),D(),D(),D(D()),D(),D() )
),W(W(W(D()))),W(W(1)),W(W())
)(0,L)(D)
))(
F()( R()(R(R(R(R(R())))),R(R())) )
)
全体の構成としては、L という関数を V によって定義し、それを実行するようになっています。 これは、Rubyで言うと、ちょうど次のようにlamdbaを使った再帰に相当します。
(fL=->fReadByte{
fReadByteを使う処理本体;
(STDIN.eof ? nullfunc : fL )[ fReadByte ]
})[ 1バイト読んで最下位bitを返すlambda ]
そして処理本体は、改行を抜いた9文字の各最下位bit(取得にはDを使う)に2歩判定ロジックより0,1を決定しwrite-bitする処理と、残りの7bitを順次write-bitする処理で構成されます。( ちょっと12文字目用のD()
が変な位置にありますが気にしないでください )
短縮のコツが完全には掴めず、かなり何回も試行錯誤していたのですが、次のことを心がけるといいような気がします。
- 変数定義は敗北
今回のLのような自己再帰のケースを除いて、変数定義はしないこと。名前が必要なら関数の引数として処理しましょう。 - 関数分割は甘え
関数分割すると処理的には見やすいですが、1度しか評価しない部分は積極的にインライン化しましょう。 - シーケンシャルな処理は関数の引数にする
a(),b(),c()
のような順番の処理は、なるべくc(b(a()))
と書きましょう。引数の扱いがおおらかなFunctional()ならではかと思います。 - 0はなるべく省略する
関数の引数が足りない場合は0埋めされて実行されます。なので、0を書くのはできるだけ省略しましょう。
gnuplot (@kotatsugame_t, 84 bytes)
se pr"-"
do for[i=1:192:12]{pr!!sum[j=i:i+2]ARG1[j:j]+ARG1[j+4:j+4]+ARG1[j+8:j+8]>1}
縦に足して2以上か判定する。2行目の前半までは前回大会のWriteUpからコピペした。
いろいろ検索してみたところループっぽくsum
が書けることが判明した。かなり強い。
インデックスはスライスの形でなければならず、そのくせstepに相当するものが書けないので、ARG1
を参照しているあたりは冗長に感じられる。
Go (@kotatsugame_t, 96 bytes)
package main
import."fmt"
func main(){for{a,b,c:=0,0,0;Scan(&a,&b,&c);Print(1-(a|b|c)/(a+b+c))}}
1-(a|b|c)/(a+b+c)
。無限ループしてゼロ除算で落とす。
import
を一工夫して関数呼び出しのパッケージ名を省略するのはいつものテク。
golfish (@n4o847, 14 bytes)
c7 04 cc c7 10 10 33 30 32 7c 33 f3 27 06 |......302|3.'.|
自作言語。単数値入力がバグっていて使えない。作ったの誰だよという気持ちになる。
c7
: スタックを反転
04
: 入力をすべて読み込み、空白で区切って各要素を数値化した配列を積む
cc
: 配列をスタックに展開
c7
: スタックを反転
10
: 加算
10
: 加算
33
: 文字列化
30
: シーケンス開始
32 7c 33
: 2|3
f3
: シーケンス終了(正規表現)
27
: マッチする ? 1
: 0
06
: 出力
c7 04 cc c7
で、ここを何回通ってもいいようにして1行にすることで縮んだ。
無限ループだが、スタックが空になると nil
を加算して落ちる。
GolfScript (@kotatsugame_t, 15 bytes)
12/{.~++\~||>}%
(a+b+c)>(a|b|c)
。つい~]
してしまいそうになるが、配列にしたところで結局展開することになる。
ここでぐっとこらえて12文字ごとに区切ると短くなる。チームメイトのCJamを見て気づいた。
gs2 (@dnek, 9 bytes)
57 13 33 fe 64 24 29 2b 52 |W.3.d$)+R|
解釈
57 read-nums
13 (int)3
33 split
fe map
64 sum
24 digits
29 max
2b half
52 show
3行ずつ(和の各桁の数字の最大値)/2 結果の数値はそのまま残すとchrで解釈されてしまうのでshowを使う。
Hack (@syobon_hinata, 91 bytes)
<<__EntryPoint>>
function a(){$r="readline";while($a=$r()+$r()+$r()){echo$a**3%37&11?1:0;}}
しとお先生のPHPをパクっただけです。長い関数名を変数にぶち込んで短縮なんて、Hackでもそんなハック通じるんだ……(ここ笑うポイントですよ) Hackが提出できない状態だったので書いて寝たら直った後shell_mugさんが提出してくれました 感謝 (by @syobon_hinata ふぁぼん)
Haskell (@henkma, 67 bytes)
m@main=mapM(\_->readLn)"...">>=print.fromEnum.any(>'1').show.sum>>m
Husk (@nakario, 24 bytes)
m(s±n20%212*34Σmr)C3¶
右から関数が適用されるので、解説では下から読むと良いかも。 コード実行終了時のスタックの内容が出力される。
m( 以下の関数を適用
s int to str
± sign関数で01に変換
n20 bit-wise and 20
%212
*34
Σ リストのsum
mr 3つの文字列をそれぞれ10進数としてintに変換
)
C3 リストを3つずつに分割
¶ 入力を改行で分割
ImageMagick (@kotatsugame_t, 123 bytes)
convert -size 1x1 -depth 8 gray:- \( -fx 'for(f=48;d=i*12,d%4<3,f|=u[d]+u[d+4]+u[d+8]>73/128;d++);f/256' -resize 16x1! \) -
ABSを解いてみた記事があるので、これを見て頑張る。
方針としては、列ごとに足して2以上のものが存在するかを見ている。判定結果を48で初期化したf
にbitwise orすることで、ループ後にf
が出力すべき値になっている。
73/128=146/256
は48/256+49/256+49/256
。本来は「以上」なのでイコールが必要になるべきところだが、浮動小数点数で演算しているのでなくても正しい値が出る。
1文字変数を使おうとするとエラーが出てびっくりするが、公式ドキュメントをよく読んで使われていない変数名を探せば問題ない。
for文の第1項から第3項にはセミコロンで区切って複数の式が書ける。インクリメントを普通の式に混ぜるとよくわからないことになる。
ABSを解いてみた記事と比べると、オプションのクォートやかっこがいくつか省略できた。
IRC (@nu4nu, @yamerarenaku, 716 bytes)
コードを見る
* main has joined #code
* c has joined #code
* x has joined #code
* y has joined #code
* s has joined #code
* t has joined #code
* main sets mode: +v c
* main sets mode: +v x
* main sets mode: +v y
* main sets mode: +v t
* main changes topic to 'f'
<x> I'm -7008.
<y> I'm 0.
* main changes topic to 'g'
<input> Hey, t.
<input> Hey, s.
<t> I'm t times 8.
<t> I'm t plus s.
<input> Hey, s.
<t> I'm t times 8.
<t> I'm t plus s.
<input> Hey, s.
<x> I'm x plus t.
<y> I'm y or t.
<if> x, are you less than 999?
<jump> Let's talk about g.
<t> I'm 1.
<if> x, are you equal to y?
<t> I'm 0.
<output> What's your value, t?
<c> I'm c plus 1.
<if> c, are you equal to 16?
* main has quit IRC (Quit: )
<jump> Let's talk about f.
アルゴリズムは和と論理和の一致を見る方針。文字コードのまま8進数っぽく読んで、余分に足した48はまとめて引く。これは引き算を書くより初期値を入れたほうが短い。変数宣言が非常に長い言語なので、3行読むループカウンタは作らず和の値で分岐している。write upを書いているときに気づいたが、入力が尽きるとHey
で0が出てくるので(第6回write upに書いてあった)、16回カウントするループカウンタも要らなかった。
Java (本質@angel_p_57, 提出@naan4UGen, 159 bytes)
interface A{static void main(String[]a)throws Exception{for(int i=0,j,s;i++<16;System.out.print((s&42)>0?1:0))for(j=s=0;j<24;j+=2)s+=System.in.read()%2<<j%8;}}
angel_p_57氏によるものをシュッと1byte縮めて提出した。
本質は入力を4進で読んで&42
すること。
by angel_p_57: naan4UGen氏の説明が全てです。なお、Java苦手なんですが、Brainfuckに隣接する重要拠点だったので、気合で奪取しました。後日、%2
での入力文字コード最下位bitを抽出をs
の初期値調整で省略する、以下の158B版に気付きました。
interface A{static void main(String[]a)throws Exception{for(int i=0,j,s;i++<16;System.out.print((s&42)>0?1:0))for(j=s=48;j<72;j+=2)s+=System.in.read()<<j%8;}}
Jelly (@kotatsugame_t, 24 bytes)
ƈƈFпOs12µs4PS%9>1)
アルゴリズムは、1ケース12文字(改行を含む)の文字コードを順にabcdefghijkl
としたとき(aei+bfj+cgk+dhl)%9>1
である。
左から読んでいこう。
ƈƈFп
:入力を全部読むおまじない。第5回大会のWriteUpから持ってきたものが1B改善できた。元はƈƈ¹Ð¿
であるが、この¹
はただの恒等関数のくせに2Bも使っている。適当に1Bのmonadを試してみたところ、F
でうまく動いてくれた。F
の意味はリストのFlattenである。
O
:ord。
s12
:リストを12個ごとに区切り、リストのリストにする。
µ
:関数定義を区切る。ここから右は左とは独立した関数になる。
区切られた関数はmonad
である。s12
で作った12要素のリスト[a,b,c,d,e,f,g,h,i,j,k,l]
が引数となることを考えよう。
s4
:4要素ごとに区切ったリストのリストにする。[[a,b,c,d],[e,f,g,h],[i,j,k,l]]
。
P
:総積。積は、リストに対しては内積になるらしい。[aei,bfj,cgk,dhl]
。
S
:総和。aei+bfj+cgk+dhl
。
%9
:9で割ったあまり。
>1
:1より大きいか否か。
)
:µ€
のエイリアス。関数定義を区切り、€
で左の要素に対してmappingしている。
つまり、先ほどのµ
より左にあるリストのリストに対して、今見たmonad
が要素ごとに適用されている。
)
が信じられないくらい偉い。µ
という2B文字と€
という3B文字が合わさって1B文字になっていて笑いが止まらない。
ところで、赤チームのコードは見た目的に全然違うものだった。そちらを参考に21Bまで縮めたので、後学のために読んでおこう。
ɠṭɠ,ɠ¤OPS%9>0¢
アルゴリズムは変わっていない。
ɠ
で1行読み込んでいるが、このとき改行が消えるため、OPS%9
したあと>1
ではなく>0
になっている。
ɠṭɠ,ɠ¤
を読もう。先頭のɠ
はnilad
なので、以下これに対して関数が適用されていく。
ṭ
は左オペランドを右オペランドにリストとして足す。左オペランドはɠ
だが、右オペランドについて少し注意する必要がある。
順当に読めばすぐ右にあるɠ
だが、少し先の¤
でパーサの挙動が変わり、実際にはɠ,ɠ
が右オペランドになっている。
¤
は、直前にパースしたコマンド列の後ろからいくつか切り取って、nilad
と1つ以上のlink
が繋がったパターンを1つのnilad
としてまとめる役割を持っている。
今回はɠ
というnilad
と,ɠ
というlink
がまとめられている。複数の切り取り方がある場合は、最も短いものが採用されるので、ここで切られる。
ɠ,ɠ
は2行読んで2要素のリストにしたnilad
なので、それに最初のɠ
が足されて、全体として["efg","ijk","abc"]
のようになる。
あとは先ほどと同様OPS%9>0
で答えが得られる。
以上で、ɠṭɠ,ɠ¤OPS%9>0
が1ケース分の入力を読んで計算するnilad
であることが分かった。
最後に¢
で直前のlink
が(nilad
として)参照されている。直前のリンクというものは存在しないため、この場合現在見ているlink
が参照されるようだ。
結果的に、いわばmain再帰のような形のコードとなって無限ループが実現されている。(これは僕の解釈なので、より良い説明があればぜひ教えてほしい。)
最終的にEOFを読んで落ちる。
Jellyfish (@ten986, 1583 bytes)
コードを見る
{p<|||++j
1 17 j
10 j
66 1
1
1
{ p<|||++j
1 17 j
10 j
66 1
1
1
{ p<|||++j
1 17 j
10 j
66 1
1
1
{ p<|||++j
1 17 j
10 j
66 1
1
1
{ p<|||++j
1 17 j
10 j
66 1
1
1
{ p<|||++j
1 17 j
10 j
66 1
1
1
{ p<|||++j
1 17 j
10 j
66 1
1
1
{ p<|||++j
1 17 j
10 j
66 1
1
1
{ p<|||++j
1 17 j
10 j
66 1
1
1
{ p<|||++j
1 17 j
10 j
66 1
1
1
{ p<|||++j
1 17 j
10 j
66 1
1
1
{ p<|||++j
1 17 j
10 j
66 1
1
1
{ p<|||++j
1 17 j
10 j
66 1
1
1
{ p<|||++j
1 17 j
10 j
66 1
1
1
{ p<|||++j
1 17 j
10 j
66 1
1
1
{ p<|||++j
1 17 j
10 j
66 1
1
1
ゴルフしてません。
2次元上にvalue、function、operatorを起き、左上のものを実行します。 各functionは1,2変数を取ります。1変数の場合は右に進んで一番近いもの、なければ下のものを取ります。2変数の場合は右と下を取ります。(それゆえ、左上のものを実行することになります)(というかちゃんとドキュメント読んでないので正しいかわからん)
p<|||++j
1 17 j
10 j
66 1
1
1
↑により、(A+B+C)%66%17%10>1
を計算します。
|
がmodであること(ドキュメントを読むとわかりづらい)、j
はinputだが、1変数を取る関数(そのために1が余分にある)、p
はoutputだが、1変数なので下に何もない必要があることに注意します。
{
は1,2変数を取り、片方を返します。これを用いて、↑を16個並べます。やったね。
ところで、j
もp
も下から実行されてる可能性があります(は?)。
後日短縮版38B by @angel_p_57
P
*
|408
x109
@ ++S
*3vvLjr48
rBEN0
16
解説を見る
なんか気になって調べて組んでみたところ、意外なほどに短くなりました。 実は、Jellyfishの演算の多くはベクトルにも対応しており、つまりmapのようにリストの全要素一括演算できるのです。 ※matrix-printにより、結果もそのまま空白区切りで出力することができます。その上で、新たなロジックを考え出して適用しました。 十進数としての3行分の合計 S に対して、sign(mod(408,S xor 109)) がピッタリなのです。 ※signは、正→1,0→0,負→-1と符号に対応する値を返す演算、xor は bit-xor です ( なぜか Jellyfishはbit-and/or/shiftがないのにbit-xorだけある )。
これはルール上、入力に現れる 1 の個数に制限があることから気付いたロジックです。 この制限により、出力 0 となる S は 11, 101, 110, 111 の4通りしかありません。そのため、各数を109とxorした数のLCMが408というとても小さな数に収まります。結果として mod 演算で 0 とそれ以外というように分離することができました。
なお、16個のSの準備は、これまたベクトル演算で次の疑似コードのように行います。
vorg = (48行分の入力数値のリスト)
v1 = (vorgの先頭を抜いたリスト)
v2 = (v1の先頭を抜いたリスト)
vsum = vorg+v1+v2 // 要素数は一番短いものに合わせる
vi = (0~15の連番)*3
S = (viの各要素をインデクスとしてvsumの要素を抽出したもの)
Jellyfishの2次元的な組み方については、まだ完全に把握できているわけではないのですが、 機会があればどこかで解説を書きたいと思います。 → zennの記事として公開しました。
jq (@drafear, 36 bytes)
.+input+input+.|(match("1..1")|1)//0
JavaScript (Rhino) (@laft_kmc, 154 bytes)
importPackage(java.io);r=new BufferedReader(new InputStreamReader(java.lang.System.in));for(i=x=0;i<48;)x+=+r.readLine(),x=++i%3?x:print(+/2|3/.test(x))|0
Kotlin (@kotatsugame_t, 92 bytes)
fun main(){var s=0
repeat(3){s+=readLine()!!.toInt(4)}
print(if(s and 42>0)1 else 0)
main()}
4進数で読んで足し、42とbitwise orする。repeat
というのがあって、短そう。
ifが式なので、これで出力する。ケースの処理はmain再帰で行う。
LLVM 10 IR (@nu4nu, 396 bytes)
define i8@main(i8%0,i32*%1,i32*%2){br label%4br label%5phi i32[%10,%a],[0,%4]phi i8[%11,%a],[0,%4]call i32(...)@gets(i32*%1)load i32,i32*%1add i32%6,%9add i8%7,1switch i32%8,label%a[i32 0,label%17]a:switch i8%7,label%5[i8 2,label%12]and i32%10,131586icmp eq i32%13,0select i1%14,i32 48,i32 49store i32%15,i32*%2call i8(...)@puts(i32*%2)br label%4ret i8 0}declare i32@gets(...)declare i8@puts(...)
C言語と同じくgets
で読んだものを足して0x20202と論理積をとる方針。Cで書くと以下のようなコードになっている。
main(_,v,p)int*v,*p{
while(1){
int x = 0;
int i = 0;
do{
if(gets(v) == 0)return;
x += *v;
}while(i++ != 2);
*p = x & 0x20202 ? 49 : 48;
puts(p);
}
}
C言語の提出物と違っているところは以下。
- グローバル変数や第1引数が0や1であることは使わない。使おうとすると結局それらと途中の演算結果の
phi
が入るので、それぐらいなら直接変数の初期値を書いたほうが短い - 文字列リテラルを使うと長いので、一度動的に文字列を作ってから
puts
する方針 -
gets(&d)
のようなことをやろうとするとalloca
が必要そうだったので、書き込み可能なアドレスとして第2引数と第3引数を使う
C言語の大会後の短縮(3行読んだことの判定のループカウンタを用意しない)はここでも有効で、378Bとなっておおよそphi
が1つ消えるぐらいの短縮となる。
define i8@main(i8%0,i32*%1,i32*%2){br label%4br label%5phi i32[%9,%10],[0,%4]call i32(...)@gets(i32*%1)load i32,i32*%1add i32%6,%8switch i32%7,label%10[i32 0,label%17]and i32%9,32switch i32%11,label%5[i32 0,label%12]and i32%9,131586icmp eq i32%13,0select i1%14,i32 48,i32 49store i32%15,i32*%2call i8(...)@puts(i32*%2)br label%4ret i8 0}declare i32@gets(...)declare i8@puts(...)
Lua (@platypus999, 65 bytes)
::a::r=io.read X=r()+r()+r()io.write(X^3*68%74>6 and 1or 0)goto a
1
とor
だけはなぜかくっついててもよかった。ロジックは3つの入力$A,B,C$を十進数で受け取ったあと$(A+B+C)^3 \times 68 \ \mathrm{mod}\ 74 > 6$を利用した。
m4 (@RikuAotsuki, 34 bytes)
syscmd(sed "h;N;N;G;/1...1/c1
c0")
sed使ってしまいました:bow:
Make (@nakario, 165 bytes)
b:;$(foreach s,$(STDIN),$(if $(filter $(words $a),2),$(word 1,$(foreach p,00 01 10 11,$(if $(findstring 1$p1,$s$(subst $% ,,$a)$s),1)) 0)$(eval a=),$(eval a=$a $s)))
今回のお題は3行ごとに文字列を処理する必要がある。そこでforeach
でのループ中に$(eval a=$a $s)
で変数a
に追加していき、$(filter $(words $a),2)
で長さが2+現在見ている文字列で3つになったときにfindstring
によって1..1
へのマッチを探す。処理後は$(eval a=)
で変数a
を初期化する。$(word 1,...)
による複数パターンがマッチする場合の処理は前回も使用した。
追記 by @kotatsugame_t
substring
ではなくforeach
に対してif
を使うことで1B縮んだ。そのままだと空白が入って常にtruthyになってしまうので、strip
をかませる。
$(if $(strip $(foreach p,00 01 10 11,$(findstring 1$p1,$s$(subst $% ,,$a)$s))),1,0)
MAO (@n4o847, 83 bytes)
|||: 0
|0:0|
|1:1|
\n:|
1001:
1011:
1101:
1111:
000:
010:
011:
0 :
1 :
0:1
一時期盛り上がったあの Markov Algorithm Online がなんと esolang として登場!
本家と異なる点として、行数ではなく文字数を縮めること、改行や空白の扱いが変わることなどがあり、これが割と重要になってくる。
上のコードは実は、空白を _
、タブを \t
で表すと
|||:_0\t
|0:0|
|1:1|
\n:|
1001:_
1011:_
1101:_
1111:_
000:
010:
011:
0_:_
1_:_
__0:1
となっている。
1~4行目: まず、\n
は先に1文字に変えたほうが良い。ここでは |
に変えて、3つ集まったら退避させることで3行ごとのブロックにしている。
5~8行目: さてここで、各ブロックは xxxxxxxxx_0\t
のようになる。xxxxxxxxx
の中に 1xx1
のパターンがあれば _
にしておく。
9~11行目: では 1xx0xx1
のパターンはどうするのかというと、この xx0xx
の部分を3文字消せれば 1xx1
に帰着できる。制約を満たすパターンを全探索すると、この部分は必ず 000
, 010
, 011
のどれかを含むことがわかる。これにより、パターンを全列挙しているにも関わらず大幅な文字数削減となった。
12~13行目: 1番右の _
の右の 0
は残しておいて、それ以外の 0
や 1
は消す。
14行目: 5~8行目でヒットしたブロックには _
が2つか3つ含まれるはずである。出力中の _
は無視されるので、2つだけ見ればよい。ということでヒットしたブロックは 1
になる。
(追記): 消すのは 000
と 010
だけで良かった。これで 78 bytes になる。
MATL (@nakario, 22 bytes)
16:"0 3:"j8ZA+]146Z&0>
利用しているロジックは8進数A,B,Cに対して(A+B+C)&146>0?1:0
。
16:" ; 16回繰り返す
0
3:" ; 3回繰り返す
j ; 入力を1行とる
8ZA ; strを8進数として解釈
+ ; stackのtopに加算
]
146
Z& ; bit-wise and
0>
Mines (@dnek, 205 bytes)
*********
........*
.*.******
1,1
0,2
0,1
4,1
1,1
2,2
0;1
0;1
0;1
0,1
0,1
0;0
4,1
2,2
2,1
1,1
0;2
0;0
2,2
3,1
2,2
2,1
2,2
0,1
1,1
0;2
0;0
2,2
7,1
2,2
2,1
5,1
0,1
1,1
0;2
0;0
0,2
7;1
6,1
0,1
0,1
0;2
2;1
1,0
解釈(1lは数字1のマスを左クリック)
012345678
0 *********
1 34556667*
2 1*2******
1,1 4l push 4
0,2 1l push 1
0,1 3l push 3
4,1 6l push 6
1,1 4l sub
2,2 4l push 2 # [4, 1, -3, 2]
0;1 3r in(n)
0;1 3r in(n)
0;1 3r in(n)
0,1 3l add
0,1 3l add
0;0 9r swap
4,1 6l div # 3行の和/2(>=1)を作った(入力が無い場合、(2+1+(-3))/4=0になる)
2,2 2l dup
2,1 5l push 5
1,1 4l sub
0;2 1r not
0;0 9r swap # 5と比較した(swapで後ろに置いておく、以降も同じ)
2,2 2l dup
3,1 5l push 5
2,2 2l dup
2,1 5l mul
2,2 2l dup
0,1 3l add # (5 * 5) * 2
1,1 4l sub
0;2 1r not
0;0 9r swap # 50と比較した
2,2 2l dup
7,1 7l push 7
2,2 2l dup
2,1 5l mul
5,1 6l push 6
0,1 3l add # (7 * 7) + 6
1,1 4l sub
0;2 1r not
0;0 9r swap # 55と比較した
0,2 1l positive # 入力があれば1行スキップ
7;1 7r skip
6,1 6l push 6 # スキップされなければゲームクリアで終了
0,1 3l add
0,1 3l add
0;2 1r not
2;1 5r out(n) # 比較結果の和(一致回数)の否定を出力
1,0 9l reset(l) # ゲームオーバー(盤面をリセットして続行)
大きい数を扱う時は、push用の「5」や「6」を多めに作っておくと良い。
moo (@satos___jp, 39 bytes)
10{))i*(i*(i*i}0{iiiicct++d9s/9*<n0c}0c
(a*b*c+d*e*f+g*h*i)%9==0
で判定する。各ループにおいては、最初の4つのiで3文字+改行コード10を得たあと、cで関数10に飛ぶ。関数10では、スタックを回して積を計算し、最後にまた改行コード10がスタックトップに残るので繰り返しcで関数10を呼び出す。
Nim (@nakario, 76 bytes)
import re
while 1>0:(var A,B,C=stdin.readLine;echo int(A&B&C&A=~re".*1..1"))
コード自体は自明。強いて言えばstdin.readLine
を1回書くだけで3回分使うのはnimでは必須のテクニック。
余談だがnimは大会途中までimport strutils
やimport re
すると何故かExec禁止規定に引っかかってINVALID判定を食らっていたので、途中まで下のような愚直な文字列処理で書いていた。
while 1>0:(var A,B,C=stdin.readLine;A&=B&C&A;var x='0';for i in..8:x=max(x,min(A[i],A[i+3]));echo x)
Node.js (@__dAi00, 87 bytes)
require('fs').readFileSync(x=i=0).map(v=>++i%12?x+=v%2<<i%4*2:console.log(x=x&168?1:0))
本質は入力を4進で読んで&42することで、空白も0とみなして読んでしまいあとから/4している。ここでx/4&42 == x&42*4 == x&168
(by @drafear) またconsole.log(x=x&168?1:0)
はもともとx=console.log(x&168?1:0)||0
だったがxは出力時に&168
するので1ずれてても問題ないのでまとめてしまう
Octave (@kotatsugame_t, @higucheese_, 43 bytes)
disp(bitand(sum(scanf("%x",[3,16])),546)>1)
16進数で読む。scanf
の引数でサイズを与えることで、3行16列の行列として返ってくる。各列が各テストケースに対応する。
sum
はオプションなしだと列ごとに足してくれる。
これと546とのbitwise andを取って非ゼロか判定し、出力する。すべて1行16列のベクトルに対する要素ごとの演算に拡張される。
最初は10進数で読んで足し、num2str
をして/2|3/
の方針で解いていた。@higucheese_さんによってこちらのほうが短くなることがわかり、for文を消して完成。
O (@drafear, 20 bytes)
F){jjj++3^)Z2+%1>p}d
条件マイニングすると ((A+B+C)**3+1)%37>1
が掘れたのでこれで解きました。
osecpu (@hiromi-mi, 44 bytes)
実際は OSECPU-ASKA であり、以下のソースコードをコンパイルしたもの。
#include "osecpu_ask.h"
Int32s fsiz:R01, j:R02;
Int32s c1: R03, tmp: R06, val: R07, i: R08, k: R09, sum: R10;
VPtr p:P01;
junkApi_fileRead(fsiz, p, 1); // argv[1] のファイルを読み込み配列p に.
for (;tmp <fsiz;j += 12) {
val = '0';
for (i=0;i !=3;i++) {
tmp = j+i;
sum = 1;
for (k=0;k != 3;k++) {
PALMEM0(32, c1, T_UINT8, p, tmp); // c1 = p[tmp];
sum = sum + c1;
tmp = tmp+4;
}
k = sum / 3;
val = val | k;
}
api_putcharRxx(val); // レジスタ val を出力
}
0
のアスキーコードは 0x30 であり、 1
のそれは0x31 であることを使うと floor(1 + a[i] + a[i+4] + a[i+8]) が 0
か 1
かに対応して if 文がなくせる。
Perl (@n4o847, 28 bytes)
print/2|3/+0while$_=<>+<>+<>
3行読み込んで数値として足して $_
に入れる。2
か 3
が含まれているか見る(対象は $_
となる)。true が 1
、false が ''
なので(なんで???)数値化して出力する。
Perl 6 (@kotatsugame_t, 30 bytes)
say +?/2|3/for map *+*+*,lines
lines
で入力を行ごとに読む。
*
はWhatever Codeという名前で、ラムダ式がめちゃくちゃ簡単に書ける。例えば*+1
は\x.x+1
に相当する。
今回は*+*+*
なので、\xyz.x+y+z
のように解釈される。引数を3つ取って和を返す関数である。
引数を3つ取る関数をmapに渡すと、気を利かせて要素を3つずつイテレートしてくれるので、これで3行ごとの和のリストが得られた。後置forでループする。
ループ内では/2|3/
にマッチするか試す。結果はNil
またはマッチした文字列(を含む専用の型)で、これを直接数値として評価しようとすると1ではなく2や3になって困ったことになる。
そこで、いったん前置?
で真偽値に直し、改めて前置+
で数値にしている。2 or 3 -> True -> 1
という変換がなされる。
PHP 7.0 (@settyan117, 62 bytes)
<?php
$r=readline;while($a=$r()+$r()+$r())echo$a**3%37&11?1:0;
自分の最初の提出 (73 bytes)
<?php
while($a=(int)readline()+readline()+readline())echo$a**3%37&11?1:0;
を縮めた。入力には1個以上(2個以上)の1があるので、入力がある間は$a>0
である。整数へのキャスト(int)
は0+
に、その後+
と縮めていき、最終的にいらないことがわかった。自分のブログがウイルスに感染した際に得た知見で、関数名の文字列を直接呼び出せるというものがあったのを思い出し、$r="readline"
として大きく縮んだ。その後、$r=readline
で良いことに気づいた。
PicFunge (@ten986, 134 bytes)
↑提出した画像
第5回writeupの通り、だいたいBefunge-93です。
しかし、&
の仕様が数字を1文字だけ受け取る、に変更されているため、最悪な気持ちになります。それぞれ*100
、*10
、*1
して元の数値に変換します。
((A+B+C)**3+1)%37>1
を計算します。
↓が、変換元のコードです。
>#@&:1+!_"d"*&&\~*&"d"*&&\~*&"d"*&&\~*++++++++::**1+"%"%1`.
ちなみに、半分のとこで折り返してBの次元に行くことで、画像サイズ自体は半分にできますが、容量は増えました。
Piet (@nu4nu, 101 bytes)
疑似コードを見る
out(number) // 最初はnop
(pop)
push 1 // 出力用。0を出すときはnotする
push 2 // 方向転換用。
// 入力が尽きたときはここの1と2が入力値として処理されスタックが空になり、
// 入力が読めたときはスタックに残るので、pointer命令を呼ぶと180度逆方向に分岐させることができる
in(number)
in(number)
in(number)
add
add
dup
push 10
dup
push 3
push 1
roll
mod
push 1
greater
switch // sum%10 > 1なら2行目へ
div
dup
push 10
dup
push 3
push 1
roll
mod
push 1
greater
switch // sum/10%10 > 1なら2行目へ
div
push 1
greater
switch // sum/100 > 1なら2行目へ
[3行目]
pop
not // スタックに0が残った状態で左上に合流
(roll)
[2行目]
push 3
add
pointer // EOF時は下向きになってL字領域に落ちる
// それ以外のときは上向きになってCCの辻褄が合う
// スタックに1が残った状態で左上に合流
プログラミング言語としてのPietの基礎についてはKMCの方々が勉強会資料を公開されているのでそちらを参照されたい。
(ピクセル数ではなく)画像ファイルのサイズを競うタイプのPietショートコーディングでは、画像フォーマットの選択(npietが対応しているのはPPM, GIF, PNG)を考える必要がある。色数をc(GIFのときは2ベキに切り上げ)、ピクセル列をPとすると、ファイルサイズは、PPMは 10+3len(P)
、GIFは 25+3c+len(lzw(P))
、PNGは 69+3c+len(deflate(P))
となる(npietが読み飛ばす空白文字やフッタのサイズを除いたサイズであることに注意。PNGは4BのCRC32が複数個入ること、フッタ(IEND)が省略できないこともあり長い!)。今回は、ピクセル数がそこそこあるのでPPMは論外として、LZWとdeflateで圧縮後サイズに44Bも差がつくほどピクセル数が大きくないと想定されることからGIFを選択する。以下GIFを前提として説明する。
GIFで使われるLZW(やPNGのdeflateで使われるLZSS)は繰り返しが多いと圧縮率がよくなる。たとえばPietで10をスタックに積むコードを考えると、ピクセル数でいえば push(5) dup add
や push(3) dup mul push(1) add
が短くなるが、LZWによる圧縮を考えると push(10)
のほうが短くなる。もちろんこれは10を積むコード単体の圧縮の話で、他の箇所で似たようなコードが出てくる場合はその限りではない。色を並べる際にはこのような圧縮のことも頭に入れておく必要がある。
GIFファイルのヘッダサイズは、使う色の種類が4色以下(ごく簡単なPietプログラミングでしか登場しない)、8色以下、16色以下、32色以下(Pietでは最大20色)で段階的に増えていく。8色から16色に増えるとヘッダが24B大きくなるので、できれば8色で済ませたい。16色を超えるとさらに48B大きくなってしまうので、少なくとも16色には抑えたい。色数を抑えるには、途中で意味のない命令や白色を使うことになる。今回提出した上記の8色のコードでも、1行目と3行目にぽつぽつ白色が混ざっているのがわかる(2行目はプログラムの制御的な理由で広大な白が広がっている)。8色と16色ではもちろん8色のほうが色数を抑えるための無駄なピクセルが増えるため、ピクセル数では16色のほうが小さくなる。ただし8色ではヘッダサイズが小さくなる上に、圧縮率も高くなる。たとえば上記コードの1行目真ん中あたりの青白が連続している箇所は青白(+α)の2,3ピクセルが1ピクセルぶんぐらいに圧縮される。今回はヘッダサイズが小さくなる効果が絶大と判断し、8色を選択した。
Pietプログラムの終端は、黒色または壁に尖った部分が接したL字状の領域とするのが典型となる(npiet-1.3e以前はゼロ剰余で落とせたが修正されてしまった)。今回のコードでいうと右下の領域がそれにあたる。ここで、npiet特有のショートコーディングテクニックとして、「GIF伸長時に足りないピクセルはGIFのパレット0番の色で埋められる」という仕様(経験則)がある(少し詳しいことは昔書いた記事にある)。それを利用すると、今回の画像では一番下の行(とその上の行の右端2ピクセル)はGIFファイルに含めなくてもよいことになる。なお右下ピクセルまで真面目に圧縮したとすると6B長くなっていた。なお今回は圧縮を省略して大きく得をしそうな同色連続ブロックが終端ブロックしかなかったので、終端ブロックを右辺と下辺に接する位置に置いているが、他に大きな同色連続ブロックがある場合はそちらを下辺に置いたほうが短くなる場合もある。
ブロックの配置に関しては勘でやっているところがあり、全く最適ではないかもしれない。今回は、ピクセル数がある程度減るように、終端ブロックの手前で折り返してから0/1出力の判定を行い、1を出力するときは2行目に逃がす、という構造にした。そのため、疑似コード上は全く同じコードを2回繰り返すようになっていていかにも圧縮上有利なように見えるが、実は1回目と2回目でピクセルの並びが左右逆になっているため繰り返しの恩恵を受けられていない(後述するが、そもそも離れた位置にあり、途中でLZW圧縮用の辞書をリセットするので、仮に回文的な並びになっていても恩恵はない)。たとえば0を出すか1を出すか決めてから折り返すようにすると、ピクセル数は増えるが繰り返し構造が利用できて得だった可能性がある。
ピクセルの並びを決めたら最後にLZW圧縮する。@nu4nu はどのピクセルをまとめてエンコードするかを手で指定するような方式でやっているが、競技プログラミングが得意な人々はもう少し自動化できるように思う。注意点は以下の3点。
- greedyにエンコードするのが得でない場合がある。たとえばWikipediaのLZW記事の
TOKYOTOKKYOKYOKAKYOKU#
の圧縮では、greedyにエンコードし16コードとなっているが、OK
YO
とまとめずにO
KYO
としてやることで、最後にKYOK
を使えて15コードにできる - Clearコードを途中に挟んで辞書をリセットしたほうが短い場合がある。LZWは放っておくとどんどん辞書が膨らんでいき、それに伴いエンコードしたときのビット数も増えてしまう。今回のPietプログラムはそこまで繰り返しが多いわけでもないので、エンコードして6ビットになる手前のキリのよいところでClearコードを挟んでいる
- End of Informationコードを途中に挟んだほうが短い場合がある。このコードはGIF仕様によれば画像の終わりを指す(名前そのまま)ということになっているが、npietでは行ごとにGIFLIBでデコードしている関係か、単純に行の終わりとしてしか認識されない。このコードを置くと右側ピクセルが例によってパレット0番の色で埋められるので、今回のプログラムの2行目のように右端に複数ピクセルにわたってパレット0番の色が並んでいるときはこれを利用したほうが短い
なお第6回write upで紹介されていたgifsicle --batch -O3
を試したが、End of Informationコードはnpiet依存なのでともかくとして、残りの2点についても考慮されていないようで、117BのGIFが出てきた(終端ブロックの圧縮を省略してtrailerを削っても108Bにしかならない)。
(補足)上記のコードは sum%10>1 || sum/10%10>1 || sum/100>1
というロジックで判定しているが、(sum**3+1)%37>1
だと以下のような75Bのプログラムにできた。(最初書いた78Bは詰めが甘く @dnek さんに縮められてしまった!)
プロデル (@dnek, 70 bytes)
元はShift JIS
『1-(「11101」がf+f+fを含)を報告』を16回繰り返
f手順
コンソールから受け取を返
意味は書いてある通り(?)で、3行の和が文字列として「11101」に含まれているかを判定している。 今回の主な発見は、
- 鉤括弧や片仮名を半角で書ける
- 末尾の「終わり」は省略できる
- 手順は前方から呼び出せる
ちなみに、赤チームの@shell_mugさんの74bytesを修正すると、以下の68bytesになる。
16回繰り返
a=
『a=コンソールから受け取*8+a』を3回繰り返
+(a^3%74>6)を報告
修正点は、
- 「コンソール」を半角にする
-
a=0
の0
を省略
Pure Folders (@kuromunori, 665600 bytes)
コードは長すぎるため上の提出を参照 フォルダーの階層構造を使ってプログラムをする言語。 フォルダを手で作るのは正気じゃないので、pythonで生成用のコードを書いて生成した。 適宜関数を作りながら書くと意外とすんなり書ける pure foldersのpython実装だと、入力をstringとしてしか扱えないので、実質使える演算が不等号と等号しかない 3桁の各行を比較して小さい順にソート、その後は二歩じゃない条件をごり押しで不等号・等号で作るとできる 疑似コードは以下
var_13 = 16
var_14 = 0
var_15 = 1
while ((var_13) > (var_14)):
var_14 = (var_14) + (var_15)
var_0 = input()
var_1 = input()
var_2 = input()
if ((var_0) > (var_1)):
var_3 = var_0
var_0 = var_1
var_1 = var_3
if ((var_0) > (var_2)):
var_3 = var_0
var_0 = var_2
var_2 = var_3
if ((var_1) > (var_2)):
var_3 = var_1
var_1 = var_2
var_2 = var_3
var_3 = '0'
var_4 = '1'
var_11 = (var_4) + (var_3)
var_12 = (var_3) + (var_3)
var_5 = (var_3) + (var_12)
var_6 = (var_11) + (var_3)
var_7 = (var_12) + (var_4)
var_8 = (var_3) + (var_11)
var_9 = (var_4) + (var_11)
var_10 = (var_11) + (var_4)
if ((var_0) == (var_5)):
if ((var_1) == (var_5)):
var_4 = '0'
if ((var_2) == (var_6)):
if ((var_2) > (var_1)):
var_4 = '0'
if ((var_1) == (var_7)):
if ((var_2) == (var_8)):
var_4 = '0'
if ((var_2) == (var_9)):
var_4 = '0'
if ((var_2) == (var_10)):
if ((var_1) == (var_8)):
var_4 = '0'
if ((var_0) == (var_7)):
if ((var_1) == (var_8)):
if ((var_2) == (var_6)):
var_4 = '0'
print(var_4, end='', flush=True)
Pxem (@sio_tet, 76 bytes)
._1.w._.+._.+0.t.cLA.-.z.ce.z.cn.z.co.z1.tOO.aNN.aEE.aLL.a.m.o.s._.c12.-.+.a
擬似コード
A=geti()
do{
B=geti(),A+=B
B=geti(),A+=B
reg='0'
if(A!=11) if(A!=101) if(A!=110) if(A!=111)
reg='1'
putc(reg)
A=geti()
}while(A+1)
while(pop0!=pop1) の中の末尾にpush(X),push(X)を置いて必ず1回で終わるようにしてif文として使うことができるのが案外気づきにくかった これ2で割って5,50,55でやる方が2バイトくらい縮む気もしますが周りが自チームに染まっていたのであまり深く考慮しませんでした
Python 3 (@n4o847, 48 bytes)
while 1:print(eval('+int(input(),4)'*3)&42and 1)
「1行を4進数として読む」を eval
で3回繰り返す。その和と 42
(0b101010
) とのビット積が 0
ならそのまま、そうでなければ 1
を出力する。無限ループだが入力で落ちる。
Racket (本質@shell_mug, 提出@naan4UGen, 81 bytes)
#!racket
(let m()(print(if(>(modulo(expt(*(+(read)(read)(read))8)3)74)6)1 0))(m))
shell_mug氏の提出をシュッと1byte縮めて提出。
本質は10進で読んだ総和X
について(8X)^3%74>6
第6回を参考に、名前付きletによる無限ループを書いた。真理値から整数への変換は (if(expr)1 0)
の方が(and(or(expr)0)1)
より短い。(@shell_mug)
Rail (@kotatsugame_t, 81 bytes)
$'main'
-{i}{i}{i}aa(!!)1()2d()8d()4d8dmm2rso{main}
$'i'
-1is4m1isa4m1isai(!!)#
方針としては、0と1を反転させつつ4進数で足して、各桁/2の積を取り、さらに0と1を反転して出力している。つまり!(a+d+g<2&&b+e+h<2&&c+f+i<2)
相当。
0と1を反転させることで条件式をlogical andにし、積によって表現している。
最初はlogical orのまま条件分岐で書いていたが、謎のエラーが出てよくわからなかったのでこの方針を取った。結果的にコードも短くなったらしい。
main関数のほかに、入力用の関数i
を用意した。呼び出すと、0と1を反転させつつ3文字読んで4進数にし、さらに1文字読み捨ててくれる。
テストケースの処理は、今言った関数i
を3回呼び出した後頑張って割ったり余りをとったりする。
ループはmain再帰で行う。
ReScript (@n4o847, 92 bytes)
Js.log(%raw(`(""+require("fs").readFileSync(0)).replace(/.{12}/gs,a=>+/1...1/s.test(a+a))`))
青に Node.js と ReScript を取られ、その後追いついたが Node.js は同率で出せず、ReScript は出せた。
文は %%raw
で囲むが、式は %raw
で囲める。JS で console.log
をするより ReScript 側で Js.log
をした方が短い。多言語埋め込みされるの可哀想だしもっと ReScript 自体の機能使ってあげたい……という気持ちになる。
reversed-c (@nu4nu, 92 bytes)
{؛p=+x((„0„:„1„¿,\x02\x02\x02,⅋x)sʇnd:x¿Ɛ%++q=x؛(p⅋)sʇǝƃ؛)ɹoɟ}(q)uıɐɯ؛x'p
\x02の部分はバイナリを埋め込む。Cのコードをそのまま変換しただけ。
write upを書きながら気づいたが、UTF-8でエンコードされるので、"
&
4
7
_
と大文字の一部は3Bになってコストが大きい(大会中は全部1文字1Bか2Bだろうと思っていた)。大会後に縮まった以下のC言語の61Bのコードを使ってreversed-c特有の短縮について考察する。
x;main(d){for(;gets(&d);x=x&32?x:puts(x&'...'?"1":"0"))x+=d;}
3行目を読んだことを検出するx&32
の部分は、&
と3
がマルチバイト文字になってしまう上に&
のコストが大きいので、実はx>>23
やx>9e6
のほうが短くなる(ANDをとる値を工夫してx&96
にしても同点)。ここは判定とd
の加算の順番を逆にすればx>>22
にできて1B短くできる。
x&'...'?"1":"0"
の部分は"
のコストが高いのをなんとかしたい。まずputchar
を使うと3バイト文字の数を2つ減らすことができる(4
が3Bになることに注意)。さらに48,49の代わりに560,561を使うことで2B短くできる。'0'
でも同じ長さ。ただ関数名がputs
より7B長くなること、返り値が非零になるので!
が必要なことから、残念ながらトータルでは短くならない。別のやり方としてワイド文字を使う方法がある。puts(L"10"+!(x&'xxx'))
とすれば1B減らすことができる。
ということで大会後に縮めた88B。
{؛(p+x:((,\x02\x02\x02,⅋p+x)¡+„01„⅂)sʇnd¿22<<x=x؛(p⅋)sʇǝƃ؛)ɹoɟ}(p)uıɐɯ؛x
Ring (@nakario (実質@shell_mug), 49 bytes)
for x in 1:16{give a give b give c?a&c|b&c|a&b>0}
基本方針は@shell_mug氏が示してくれていたので、see
を?
にするなど細かい修正をしただけ。
Ruby 2.7.1 (@kotatsugame_t, 34 bytes)
loop{p gets(p,12)*2=~/1...1/m?1:0}
12文字読んで2倍し、/1...1/
にマッチするかどうかで出力を分ける。全体をloop
で囲んで無限ループにする。
gets
の第1引数にnil
を与えると、改行で終わらず一気に読んでくれるようになる。nil
は無引数のp
で得られる。
第2引数に読み込む文字数を指定すると、それだけ読んだところでストップしてくれる。
Rust (@ishitatsuyuki, 118 bytes)
use std::io::*;fn main(){loop{let b=&mut[0;12];stdin().read(b);print!("{}",(4..15).fold(0,|s,i|b[i-4]&b[i%12]|s)-58)}}
最初にsatosさんが121 bytesを書いてくれた。終点のセミコロンを消して-1byte, (i+4)%12
をかっこが不要なように(rangeを含めて)書き換えて-2byte.
動作原理は/1...1/にマッチするかどうかを計算している。
Seclusion (@drafear, 4687 bytes)
コードを見る
.176
3.10 0
7.10 0
11.10 0
15.10 0
0.48/{0 500+0 300}0
4.48/{0 500+0 300}0
8.48/{0 500+0 300}0
500/{0 300}0
500-{0 503+0 300}0
1.48/{0 501+0 300}0
5.48/{0 501+0 300}0
9.48/{0 501+0 300}0
501/{0 300}0
501-{0 503+0 300}0
2.48/{0 502+0 300}0
6.48/{0 502+0 300}0
10.48/{0 502+0 300}0
502/{0 300}0
502-{0 503+0 300}0
503-{0 0+0 300}0
0.1.49 0
12.48/{0 504+0 300}0
16/{0 504+0 300}0
20/{0 504+0 300}0
504/{0 300}0
504-{0 507+0 300}0
13.48/{0 505+0 300}0
17/{0 505+0 300}0
21/{0 505+0 300}0
505/{0 300}0
505-{0 507+0 300}0
14.48/{0 506+0 300}0
18/{0 506+0 300}0
22/{0 506+0 300}0
506/{0 300}0
506-{0 507+0 300}0
507-{0 1+0 300}0
1.1.49 0
24/{0 508+0 300}0
28/{0 508+0 300}0
32/{0 508+0 300}0
508/{0 300}0
508-{0 511+0 300}0
25/{0 509+0 300}0
29/{0 509+0 300}0
33/{0 509+0 300}0
509/{0 300}0
509-{0 511+0 300}0
26/{0 510+0 300}0
30/{0 510+0 300}0
34/{0 510+0 300}0
510/{0 300}0
510-{0 511+0 300}0
511-{0 2+0 300}0
2.1.49 0
36/{0 512+0 300}0
40/{0 512+0 300}0
44/{0 512+0 300}0
512/{0 300}0
512-{0 515+0 300}0
37/{0 513+0 300}0
41/{0 513+0 300}0
45/{0 513+0 300}0
513/{0 300}0
513-{0 515+0 300}0
38/{0 514+0 300}0
42/{0 514+0 300}0
46/{0 514+0 300}0
514/{0 300}0
514-{0 515+0 300}0
515-{0 3+0 300}0
3.1.49 0
48/{0 516+0 300}0
52/{0 516+0 300}0
56/{0 516+0 300}0
516/{0 300}0
516-{0 519+0 300}0
49/{0 517+0 300}0
53/{0 517+0 300}0
57/{0 517+0 300}0
517/{0 300}0
517-{0 519+0 300}0
50/{0 518+0 300}0
54/{0 518+0 300}0
58/{0 518+0 300}0
518/{0 300}0
518-{0 519+0 300}0
519-{0 4+0 300}0
4.1.49 0
60/{0 520+0 300}0
64/{0 520+0 300}0
68/{0 520+0 300}0
520/{0 300}0
520-{0 523+0 300}0
61/{0 521+0 300}0
65/{0 521+0 300}0
69/{0 521+0 300}0
521/{0 300}0
521-{0 523+0 300}0
62/{0 522+0 300}0
66/{0 522+0 300}0
70/{0 522+0 300}0
522/{0 300}0
522-{0 523+0 300}0
523-{0 5+0 300}0
5.1.49 0
72/{0 524+0 300}0
76/{0 524+0 300}0
80/{0 524+0 300}0
524/{0 300}0
524-{0 527+0 300}0
73/{0 525+0 300}0
77/{0 525+0 300}0
81/{0 525+0 300}0
525/{0 300}0
525-{0 527+0 300}0
74/{0 526+0 300}0
78/{0 526+0 300}0
82/{0 526+0 300}0
526/{0 300}0
526-{0 527+0 300}0
527-{0 6+0 300}0
6.1.49 0
84/{0 528+0 300}0
88/{0 528+0 300}0
92/{0 528+0 300}0
528/{0 300}0
528-{0 531+0 300}0
85/{0 529+0 300}0
89/{0 529+0 300}0
93/{0 529+0 300}0
529/{0 300}0
529-{0 531+0 300}0
86/{0 530+0 300}0
90/{0 530+0 300}0
94/{0 530+0 300}0
530/{0 300}0
530-{0 531+0 300}0
531-{0 7+0 300}0
7.1.49 0
96/{0 532+0 300}0
100/{0 532+0 300}0
104/{0 532+0 300}0
532/{0 300}0
532-{0 535+0 300}0
97/{0 533+0 300}0
101/{0 533+0 300}0
105/{0 533+0 300}0
533/{0 300}0
533-{0 535+0 300}0
98/{0 534+0 300}0
102/{0 534+0 300}0
106/{0 534+0 300}0
534/{0 300}0
534-{0 535+0 300}0
535-{0 8+0 300}0
8.1.49 0
108/{0 536+0 300}0
112/{0 536+0 300}0
116/{0 536+0 300}0
536/{0 300}0
536-{0 539+0 300}0
109/{0 537+0 300}0
113/{0 537+0 300}0
117/{0 537+0 300}0
537/{0 300}0
537-{0 539+0 300}0
110/{0 538+0 300}0
114/{0 538+0 300}0
118/{0 538+0 300}0
538/{0 300}0
538-{0 539+0 300}0
539-{0 9+0 300}0
9.1.49 0
120/{0 540+0 300}0
124/{0 540+0 300}0
128/{0 540+0 300}0
540/{0 300}0
540-{0 543+0 300}0
121/{0 541+0 300}0
125/{0 541+0 300}0
129/{0 541+0 300}0
541/{0 300}0
541-{0 543+0 300}0
122/{0 542+0 300}0
126/{0 542+0 300}0
130/{0 542+0 300}0
542/{0 300}0
542-{0 543+0 300}0
543-{0 10+0 300}0
10.1.49 0
132/{0 544+0 300}0
136/{0 544+0 300}0
140/{0 544+0 300}0
544/{0 300}0
544-{0 547+0 300}0
133/{0 545+0 300}0
137/{0 545+0 300}0
141/{0 545+0 300}0
545/{0 300}0
545-{0 547+0 300}0
134/{0 546+0 300}0
138/{0 546+0 300}0
142/{0 546+0 300}0
546/{0 300}0
546-{0 547+0 300}0
547-{0 11+0 300}0
11.1.49 0
144/{0 548+0 300}0
148/{0 548+0 300}0
152/{0 548+0 300}0
548/{0 300}0
548-{0 551+0 300}0
145/{0 549+0 300}0
149/{0 549+0 300}0
153/{0 549+0 300}0
549/{0 300}0
549-{0 551+0 300}0
146/{0 550+0 300}0
150/{0 550+0 300}0
154/{0 550+0 300}0
550/{0 300}0
550-{0 551+0 300}0
551-{0 12+0 300}0
12.1.49 0
156/{0 552+0 300}0
160/{0 552+0 300}0
164/{0 552+0 300}0
552/{0 300}0
552-{0 555+0 300}0
157/{0 553+0 300}0
161/{0 553+0 300}0
165/{0 553+0 300}0
553/{0 300}0
553-{0 555+0 300}0
158/{0 554+0 300}0
162/{0 554+0 300}0
166/{0 554+0 300}0
554/{0 300}0
554-{0 555+0 300}0
555-{0 13+0 300}0
13.1.49 0
168/{0 556+0 300}0
172/{0 556+0 300}0
176/{0 556+0 300}0
556/{0 300}0
556-{0 559+0 300}0
169/{0 557+0 300}0
173/{0 557+0 300}0
177/{0 557+0 300}0
557/{0 300}0
557-{0 559+0 300}0
170/{0 558+0 300}0
174/{0 558+0 300}0
178/{0 558+0 300}0
558/{0 300}0
558-{0 559+0 300}0
559-{0 14+0 300}0
14.1.49 0
180/{0 560+0 300}0
184/{0 560+0 300}0
188/{0 560+0 300}0
560/{0 300}0
560-{0 563+0 300}0
181/{0 561+0 300}0
185/{0 561+0 300}0
189/{0 561+0 300}0
561/{0 300}0
561-{0 563+0 300}0
182/{0 562+0 300}0
186/{0 562+0 300}0
190/{0 562+0 300}0
562/{0 300}0
562-{0 563+0 300}0
563-{0 15+0 300}0
15.1.49 0
ジェネレータのコードを見る
#!/usr/bin/env python3
zero = 300
n_out_bits = 192 - 16
print(f'.{n_out_bits}')
# clean \n
for t in range(16):
if t % 4 == 3:
print(f'{t}.10 0')
for t in range(16):
in_base = t * 12
group_base = 500 + t * 4
buf_addr = group_base+3
out_addr = t
for group in range(3):
group_addr = group_base + group
for line in range(3):
in_addr = in_base + line * 4 + group
clear = ".48" if in_addr < 16 else ""
print(f'{in_addr}{clear}/{{0 {group_addr}+0 {zero}}}0')
print(f'{group_addr}/{{0 {zero}}}0')
print(f'{group_addr}-{{0 {buf_addr}+0 {zero}}}0')
print(f'{buf_addr}-{{0 {out_addr}+0 {zero}}}0')
print(f'{out_addr}.1.49 0')
ゴルフしていなくて16回繰り返しているので長くなっています。 ポイントだけ説明すると
- decrement がなかったので,
-{}
で代用しています。{}の終わりで value=0 の頂点に行くことで1回しかループしないようにしています。 - 唯一ゴルフした
.1.49
ですが 1 <=> 0 (LogicalNot) の変換をしています
sed (@kotatsugame_t, 20 bytes)
N
N
h
G
/1...1/c1
c0
3行を連結し、2倍して/1...1/
を探している。
N
:次の行を読む。2回実行するので、合計で3行、つまりケースごとに以下の処理を行うことになる。
h
:上書きホールド。以前のホールドスペースの内容を削除し、現在のパターンスペースの内容を入れる。
G
:ホールドスペースの内容をパターンスペースに追加する。これで文字列が2倍されたことになる。
/1...1/c1
:/1...1/
にマッチしたら1を出力して(残りの処理をせず)次の行の処理に進む。
c0
:前の行のマッチに失敗した場合のみこの行に到達する。0を出力して次の行に進む。
Serenity (@paradigm_9, 230 bytes)
{insts:[a in setv
f: a getv popa disc a getv popa 16 mul a getv popa 4 mul a getv popa add add
i i getv inc setvk 48 div :f jz
g: add add 16 sub 42 and 0 gt 48 add a getv 0 1 swap pusha
i i getv inc setvk 64 div :g jz
a getv out]}
青チームの完全陣地にあり、そして結局誰にも解かれなかった言語。なので上記は後日の参考記録です。解かれなかった原因が何をするのも難しいからというDisと異なり、こちらは何でもできるスタック指向言語で、更にラベルへのジャンプ・スタックのswap・配列・変数も使用できます。ただ、out命令を使って出力した瞬間プログラムが強制終了する仕様、in命令で取った入力文字列をpopaすると後ろから取得する(つまり一回目のpopaは改行コードが手に入る)という仕様には注意が必要です。
Simula (@repyt-margorp, 152 bytes)
begin integer i,s,t;s:=0;while i<16do begin s:=s+InInt+InInt+InInt;t:=s**3*35;t:=t-(t//37)*37;if t>2then OutInt(1,1)else OutInt(0,1);s:=0;i:=i+1;end;end
SNOBOL4 (@shell_mug, 82 bytes)
S B = 0
A = INPUT + INPUT + INPUT :F(END)
A ANY(23) . B
OUTPUT = B / 2 :(S)
END
SNOBOL4は数を文字列として扱える(逆も可能)。3行分を足した数に'2'か'3'が含まれていれば(ANY(23)
)、マッチ結果がBに代入される。マッチしなければBへの代入は行われない。
ところでB =
だけでBが0に初期化されるので、2B縮んで80Bになる。
Snowman (@drafear, 39 bytes)
~:vgvgaCvgaCduaC"1..1"sMaL0nG2nBsP;16bR
line1+line2+line3 + line1+line2+line3 を /1..1/
でtestするロジックです。
Shakespeare (@nu4nu, @yamerarenaku, 791 bytes)
コードを見る
.
Ajax,.
Sly,.
Ford,.
Page,.
John,.
Act I:.
Scene I:.
[Enter Ford and Sly]
Ford:
You is square big old joy.
Sly:
You is sum a you a sum a old red joy and red joy.
[Exeunt]
[Enter John and Page]
John:
Listentothyheart.Is you worse a Sly?If so, you is sum a you a quotient you a big old joy.
Page:
You is sum a you a I.Is Ford worse a Sly?If so, let us return to Act I.You is quotient you a big hog.
John:
You is sum a I a sum a Sly a sum a twice Sly a red joy.You is product a you a sum a you a sum a big red joy and joy.You is product a you a sum a I a sum a big red joy and joy.
Page:
You is zero.Am I as bad as you?
John:
If not, you is joy.Openthyheart.
[Exeunt]
[Enter Ajax and Ford]
Ford:
You is sum a you a joy.
Ajax:
You is hog.Is Sly nicer a I?If so, let us return to Act I.
[Exeunt]
- 同時に2変数しかステージ上に存在できず、ステージ上にいない変数の内容を読むことはできるが代入はできないという言語。変数の入れ替えに25Bぐらい使うので、できるだけ入れ替えなしでプログラムを記述する必要がある。ステージ上にいる2変数の間でも、代入対象を変えると話者の切り替えで5Bぐらいロスする。結局変数の入れ替えは2変数同時に
[Exeunt]
で退場させて別の2変数を呼んでくるのが短い - 即値が書けるが0と正負の2のベキ乗限定で、それ以外の数値は和の形で表す必要があって長くなる。アルゴリズムは
(5-sum/2)*(50-sum/2)*(55-sum/2)==0
だが、これを(sum/-2+4+1)*(sum/-2+16+16*2+2)*((sum/-2+16+16*2+2)+4+1)==0
のような形で書いている(16は全体のループ回数として変数に入っている) - 今回の問題と微妙に相性が悪いポイントとして、数値の読み込みがC言語の
scanf("%lli")
で行われるので、0から始まる入力は8進数として解釈され、1から始まる入力は10進数として解釈されるという問題があった。これは読み込んだ値が9以下(プログラム中では便宜上16以下)のときは4で割った値を足すことで全て10進数で解釈した値となるように補正している - 正確には
scanf
でなくfgets
→sscanf
の2段構えで数値入力を取っているというのもゴルフ的には困るところだった。入力終端でfgets
先のバッファが更新されないので、最後の行を延々読むような挙動になって終端の検出が不可能(scanf
だけならscanf
する前に変数にゴミを入れておけば終端を区別できる)。fgets
が改行文字も食べてしまうので余分にgetchar
を呼んでEOFかどうか見ることもできない。仕方なくループカウンタを用意している - パーサーの作りが結構いいかげんで、チーム内では @yamerarenaku さんが大抵の前置詞や接続詞を
a
と書いてよいことを発見して最初にかなり縮めていた。ただ、大会後に赤チームからa
すらも要らないこと、you
のあとのbe動詞も要らないことが指摘されて驚いた - 大会後に気づいたことだが、式に複数の即値を足す場合、たとえばyou+(2+1)だと
sum you sum big joy and joy
のように即値の間にand
が必要だが、2+(you+1)の形にするとsum big joy sum you joy
のようにand
を書かなくて済む。即値の後ろはsum
だけでなくtwice
やcube
などもOKだがyou
やSly
のような名前はダメだった。演算子が区切りとして働くと考えればよいらしい。何かと即値を演算する場合でなくても、たとえば5を代入したいときはYou sum big big joy and joy.
より2B短くYou sum joy twice big joy.
と書ける - 会話形式でプログラムする言語はIRCも解いたが、Shakespeareのほうが工夫しがいがあって圧倒的に楽しかった
ループを1つにしたり(sum**3+1)%37>1を使ったりして縮めた548B
.
Sly,.
Ford,.
Page,.
John,.
Act I:.
Scene I:.
[Enter Ford and Sly]
Ford:
You sum joy square sum big joy twice big joy.
Sly:
You sum you joy.
[Exeunt]
[Enter John and Page]
John:
Listentothyheart.Is you worse Sly?If so, you sum you quotient you big old joy.
Page:
You sum you I.
John:
Is remainder of the quotient sum joy cube I Sly nicer joy?You zero.If so, you joy.Is remainder of the quotient Ford sum joy twice joy worse joy?If so, openthyheart.
Page:
If so, you zero.Is Sly nicer square square root Ford?If so, let us return to Act I.
[Exeunt]
SQLite3 (@utgwkk, 124 bytes)
with f(x)as(select""union select x+12 from f limit 16)select(select substr(v,x,12)||substr(v,x,12)like"%1___1%"from i)from f
標準入力は i
テーブルの v
カラムにまるごと入っています。ちなみにesolang-boxをよく見ると o
テーブルにINSERTしてもよい ことが分かりますが、たぶんSELECTした方が短いと思います。
IN
を使って書く方法もありましたが、最終的に /1...1/
とマッチする解法が短くなったのでこっちを使いました。正規表現マッチがないので LIKE
演算子で代用します。
WITH
句で数列を生成するのに ""
から始める手法はバイト数を削減できて便利ですね。
あとは https://sqlite.org/lang.html を読んでください。
Starry (@drafear, 103 bytes)
`,,,** + + + * + + * + + * + + *** + * ' + * `.'
ロジックは s=A+B+C,(s//200 + s//20 + s//2) % 5 != 0
を改良した以下のようなものです
a := (A+B+C)//2
b := a//10
c := b//10
x := (a+b+c)%5
疑似コードを見る
a:
readi
readi
readi
add
add
dup
dup
dup
div
swap
2
div
dup
10
div
dup
10
div
add
add
5
mod
jn b
dup
sub
b:
printi
jn a
追記 (@nu4nu)
(s**3+1)%37>1
の判定で実装すると以下のような82Bにできる。x>1
はx/2!=0
よりx-x*x!=0
が短い。
`,,,** + + * * + + + * + +* + + * +* * + + * * ' + * `.'
Streem (@n4o847, 138 bytes)
stdin|flatmap{x->chars(x)}|slice(9)|map{case[a,b,c,d,e,f,g,h,i]->median([median([a,d,g]),median([b,e,h]),median([c,f,i]),"1","1"])}|stdout
Ruby の開発者が作った、ドキュメントが無くて過去の大会やソースコードからエスパーするしかないマジで謎の言語。(『まつもとゆきひろ 言語のしくみ』読めば使い方書いてあるんだろうか? 持ってないのでわからん)
array の max を使いたかったが使い方がわからないのでしょうがなく回りくどいことをしている。
Stuck (@n4o847, 20 bytes)
"sss++_6KYjz$]]p"16V
ドキュメントに書いていない機能をふんだんに使っている。
"~"16V
: これは前回も使ったが、文字列を16回実行する。(undocumented)
↓ sss++
: 文字列のまま3行読み込み、足す。
"abcdefghi"
↓ _
: 複製する。
"abcdefghi" "abcdefghi"
↓ 6K
: 6文字ごとに区切って配列にする。(undocumented)
"abcdefghi" ["abcdef", "ghi"]
↓ Y
: リバースする。(undocumented)
"abcdefghi" ["ghi", "abcdef"]
↓ j
: 文字列結合だが、区切り文字を省略できるのは undocumented。
"abcdefghi" "ghiabcdef"
↓ z
: zip
[(a, g), (b, h), (c, i), (d, a), (e, b), (f, c), (g, d), (h, e), (i, f)]
↓ $
: ソートする。
[('0, '0), ..., ('0, '1), ..., ('1, '0), ..., ('1, '1)]
↓ ]
: 配列をスタックに展開
(略)
↓ ]
: 配列をスタックに展開
(略)
↓ p
: 出力 (undocumented)
(略)
ソート時、('0, '1)
が含まれていれば必ず ('1, '0)
も含まれるので、最後は ('1, '0)
か ('1, '1)
のどちらかになる。これのさらに最後で判断できる。
各ループでスタックに不要物が残るが、実は「ループに p
が含まれているときはスタックの残りが出力されない」という実装になっている。もちろんドキュメントには書かれていない。
Swift (@drafear (実質@nakario), 78 bytes)
let f={Int(readLine()!)!}
for _ in 0..<16{print((f()+f()+f()+32)&90%5>0 ?1:0)}
文字列の取得やIntへの変換は失敗する恐れがあるためOptionalが帰ってくるが、全て無視して!
でunwrapする。unwrapに失敗すると何故かそれまでに処理したはずの出力もどこかに消えてしまうため、無限ループは使えない。三項演算子の?
の直前の空白がないと?
がOptional Chainingの演算子として解釈されるためここは縮められない。
下の同率別解はforではなくmapを使ったもの。$0
をどこかで使わないとクロージャの型が一致しないため仕方なく挿入している。
let f={Int(readLine()!)!}
(0..<16).map{print((f()+f()+f()+32)&90%5>0 ?1:0);$0}
Tcl (@angel_p_57, 118 bytes)
while {[gets stdin x]>0} {set t "set x \[expr 4\$x+1\[gets stdin\]\]"
eval $t
eval $t
puts [string match *\[23\]* $x]}
拠点拡大のためにサラっと書いたので、チューニングできてるか不明…。 単純に、10進数として3行分の合計を計算し、2,3の桁があるかどうかを文字列のマッチでみているものです。 ただ、処理を eval で短縮する際、8進数と見做されるおそれがあるので、4や1を余分に頭につけて対処しています。
Tetris (@ten986, 487 bytes)
コードを見る
10:
20:
I.<<<
Z:
Z.>>
Z.>>>>
T.<
S:AA<<<
Z:AA>
O.>>>
L.AA<<<
O.<
T.AA>>
L:B>>>>>
100:
I:A<<<<<
T:<<
S:A<
J:>>>>
O.>
O.<<<
J:>
Z:>>>
S.A>>>>>
146:
147:
Z.A<<<
T.AA<<<
S:B
S.A>>>
I.>
T.A>>>>
S:AA>>>
J.<<<
J:AA<<
O.
I.A>>>>
J:>
T:<<
J:B<<<
S:A<<
T:>>
O:>>>>
240:
241:
242:
243:
244:
245:
I.<
S:<<<
Z:>>
Z.>>>>
I:A<<<<<
T:<
T:A<<<
S:A
J.B>>>>>
Z:A>>>
J:A>>
30:
Z.A<<<
T.AA<<<
S:B
S.A>>>
I.>
T.A>>>>
S:AA>>>
J.<<<
J:AA<<
O.
I.A>>>>
J:B<<<
I.<
T:A>>
O:B>>>>
I.
50:
T.<
S:<<<
J:>>
Z:>>>>
-1:
O.
方針だけゴルフしました。 テトリミノを置く手順を工夫すると縮む可能性が大いにあります。
方針メモ
10:
20:
(Bomb)
R=300 MPB (P-=300)
100:
(Bomb)
[読み取って、(始点からの相対位置で)0,100,200,300に加算]
R=any I+SJ (Input '0''1''\n') (R = A[P] + '0''1''\n') (S:A[P]=R) (J:10,20,30,146,147,-1)
R=100 PJ (P+=100) (J:100)
146:
147:
(Bomb)
[1を出力する処理]
R=100 S (A[P]=100)
R=200 /S (R=2) (A[P]=2)
R=100 /-S/+O (/-: R=48) (A[P]=48) (R=1) (R=49) (出力)
240:
241:
242:
243:
244:
245:
[30になるまで読み飛ばす処理]
R=100 P (P+=100)
R=any I++SJ (Input '0''1''\n') (R = A[P] + A[P] + '0''1''\n') (S:A[P]=R) (J:240,241,242,243,244,245,40)
30:
(Bomb)
(0を出力する処理)
R=100 S (A[P]=100)
R=200 /S (R=2) (A[P]=2)
R=100 /-O (/-: R=48) (出力)
50:
[P+=いい感じの値]
[100: に戻る]
R=100 PJ (P+=100) (J:100)
-1:
(終了する)
まずインタプリタが強いです(が、めんどくさいので Execute 10 step、Execute 100 step、Execute to label を追加した改造インタプリタを作りました)。
第5回のwriteupを読むと、jump-if-canが強いことが分かります。
そのため、始点から+0,+100,+200,+300の位置に各列のasciiの和を格納します。始点は、各ループごとに被らないようにすればよいです。
和が146、147になれば1を出力し、以降を読み飛ばします。読み飛ばすには、+
を2回実行すればよく、別のループで使用していない値を格納できます。
値が30になれば0を出力します。
入力が-1になったとき、ちょうどよくjump-if-canに-1を組み込むことができるので、終了できます。ラベルの後に1命令もない場合、ラベルが認識されないため、適当な命令を置きます。
参考記録 (@sio_tet, 348 bytes)
コードを見る
48:
49:
I.>>>
S:>
S:<
S:<<<
Z:B>>>>>
S:AA>>>>
S:>>
S:AA
O.<<<
S:AA<
S:<<<
J.A<<<<
110:
120:
T:B<
S:AA<<
Z:B<<<
T:B>
I.B
S:A>
S:A>>>
Z:>>>>
148:
149:
196:
197:
198:
244:
245:
246:
247:
S:A<<<<
T:B>
I:<<
J:B>>>
S:A>
Z:>>>>
144:
145:
L:B<<<
T:B
S:<
S:B>>
Z:B>>>>>
J:>>>
T.<<
146:
147:
S:B<
Z.B<<<
S:>>
O:
Z:>>>>
Z:A<<
T:<<<
J:AA<
J:B>>>
O:>
99:
Z:>>>>
実装メモ(コメント)入り
48:
49:
I.>>>
S:>
S:<
S:<<<
#(100)PPP
Z:B>>>>>
S:AA>>>>
S:>>
S:AA
O.<<<
S:AA<
S:<<<
#(100)PSPSP #P=500, A[400]=100, A[500]=100
J.A<<<<
#(100)SPSB #A[600]=100, A[700]=100
110:
120:
T:B<
S:AA<<
Z:B<<<
T:B>
I.B
S:A>
S:A>>>
Z:>>>>
#(100)ZS++SPB #A[0]=300, P=300
148:
149:
196:
197:
198:
244:
245:
246:
247:
S:A<<<<
T:B>
I:<<
J:B>>>
S:A>
Z:>>>>
#(100)P(=)I+SJB #A(400,500,600)=(sum(fst, snd, thd)+100)
#30:
144:
145:
L:B<<<
T:B
S:<
S:B>>
Z:B>>>>>
J:>>>
#(100)MP+SJB
#-100: #A[300]の値(最初の非2歩)
#-200:
#-300:
#-400:
#-500: #2歩にならないのは5回しかないためゴミはこの5つの値しか取らず、衝突しない
T.<<
#(100)P-prepare
146:
147:
S:B<
Z.B<<<
S:>>
O:
Z:>>>>
#(100)<P?>*SB
#この<P?(144,145)>で適切な位置({144, 145}, {146, 147}のどちらかを見ている)
Z:A<<
T:<<<
J:AA<
J:B>>>
O:>
99:
Z:>>>>
#(100)+Z/OJB #({14500, 14600}, {14700, 14800})に切り分けられる!
上記勝者であるten986さんが特徴を既に書いているので、違う点を。 違わないですが、jump-if-canはマジで他の言語にない最強の特色です。 ただ、弱点があって、100の倍数(100, 200, 300程度まで)以外の即値を製造するのが難しいです。 そのため今回は、出力のために即値48,49を作るのを避け、 (入力の各桁についての((和)*100+100)/300)の最大値を出力することを目標としました。 また、メモリの0初期化が難しいのは分かっていましたが、 100初期化はそこまで難しくない事に今回始めて気付いたので、 まずメモリの使いたい場所を100で埋めています。 配置の工夫としては、実装メモ中でP-prepareとしている上の行のTミノによって、 Pの文字を1段ずらし、3項演算子的な役割を果たしています 最後のラベルは、最後1個置いても大丈夫やろってことで下から2行目に雑に入れてます。
TeX (plain) (@syobon_hinata, 235 bytes)
\let\m=\multiply\let\v=\advance\let\n=\newcount\n\x\n\y\n\i\i=0\loop\ifnum\i<16\read0to\a\read0to\b\read0to\c\x=\a\v\x\b\v\x\c\y=\x\m\x\y\m\x\y\m\x35\y=\x\divide\y37\m\y37\v\x-\y\immediate\write16{\ifnum\x>2 1\else0\fi}\v\i1\repeat\end
TeXには「レジスタの値を増やす」「n倍する」「1/nにする」と代入・単項-
みたいな演算子しかなく、アセンブリ言語を書いている気分になりました。ビット演算もないんですよ……modはa%b=a-floor(a/b)*b
でいけますが。
使ったロジックは(A+B+C)**3*35%37>2
です。let定義を利用して短縮したため暗号みたいになってます。
(by @syobon_hinata ふぁぼん)
Transceternal (@hiromi-mi, 504 bytes)
コードを見る
0122234334Ϣϣ0Ϥϥt3s22u2v2w2wϞϟ0ϠϡV3W3X3Y3Z3!3"3#3$3%3&3'3(3)3*3+3,3-3.3/3:3;3<3=3>3?3@3[3\3]3^3_3tuϒϓ0ϔϕ>uώϏ0ϐϑN3O3P3Q3R3S3T3U3Vu~Σ0ΤΥ,u`{0|}F3G3H3I3J3K3L3M3Nuqb2a526273829329ry2xtuϮϴ2ϳϯ2ϰ2ϱ3ϲ2ϲh3i3j3k3l3m3n39ϵp2o9c3d3e3f3g3h϶pϷpϸpϹpϺpϻpϼpЅІ0ЇЈnuϽЁ2Ѐ9Ͼ2Ͽ2mЂЄ2Ѓ5u2Ϣφχ0ψωΦ3Χ3Ψ3Ω3Ϊ3Ϋ3ά3έ3ή3ί3ΰ3α3β3γ3δ3ε3ζ3η3θ3ι3κ3λ3μ3ν3ξ3ο3π3ρ3ς3σ3τ3υ3FuqzbAE2DtB2C29Ϯϊϋ0όύFuφzϖϗ0Ϙϙήu~zϚϛ0ϜϝNuϖzϦϧ0ϨϩζuϒzϪϫ0ϬϭVuϦz
a[0] == a[4] or a[0] == a[8] or a[4] == a[8]
を全て書き下している。
1進法以外を取り扱うのが大変なので他の方法は難しい。
Transceternal コードゴルフテクニックも参照。 あと50バイトほどは縮むと思うがやってない。
追記 298バイトになった
生成スクリプト
# Transceternal Esolang Codegolf Contest #7 生成Script by @_hiromi_mi
# SPDX-License-Identifier: CC0-1.0
from string import ascii_letters, digits, punctuation
import sys
from typing import Dict
def tob36(val):
_d36 = digits + ascii_letters + punctuation + "".join([chr(x) for x in range(0x3a3, 0x052f)]) + "".join([chr(x) for x in range(0x61e, 0x70d)])
#_d36 = digits + ascii_letters
_ld = len(_d36)
if (val < _ld):
return _d36[val]
else:
return tob36(val // _ld) + _d36[val%_ld]
def retain_node(graphs):
_id = tob36(len(graphs))
graphs[_id] = ['a' + str(-1*len(graphs)), 'x' + str(-1*len(graphs))]
return _id
def use_node(graphs, _id, left, right):
#_id = retain_node(graphs)
graphs[_id][0] = left
graphs[_id][1] = right
return _id
def new_node(graphs, left, right):
_id = retain_node(graphs)
graphs[_id][0] = left
graphs[_id][1] = right
return _id
def construct(graphs, node_str, B0, B1, const=True):
if node_str == '1'*9:
if '1'*6 not in construct.table:
construct.table['1'*6] = construct(graphs, '1'*6, B0, B1, const=False)
return construct.table['1'*6]
if const:
if "0" not in node_str:
return construct_ones(graphs, len(node_str), B0, B1)
# just other than last:
if "1" not in node_str[:-1] and node_str[-1] == "1":
return construct_oneszero(graphs, len(node_str), B0, B1)
if "0" not in node_str[:-1] and node_str[-1] == "0":
return construct_zeroones(graphs, len(node_str), B0, B1)
if node_str in construct.table:
return construct.table[node_str]
if node_str == '0011':
construct.table[node_str] = new_node(graphs, B0, new_node(graphs, B0, construct_ones(graphs, 2, B0, B1)))
return construct.table[node_str]
if node_str == '000':
construct.table[node_str] = new_node(graphs, B0, new_node(graphs, B0, construct_zeroones(graphs, 1, B0, B1)))
return construct.table[node_str]
node = ''
top = ''
lastnode = ''
print(f"NOT Reused {node_str}", file=sys.stderr)
for index, a in enumerate(node_str):
if const and len(node_str[index:]) > 0 and "0" not in node_str[index:]:
lastnode = construct_ones(graphs, len(node_str[index:]), B0, B1)
break
newnode = retain_node(graphs)
if node:
graphs[node][1] = newnode
else:
top = newnode
node = newnode
lastnode = newnode
graphs[node][0] = B0 if a == '0' else B1
# 残りが1だけのとき
graphs[node][1] = lastnode
if const:
construct.table[node_str] = top
return top
construct.table = {}
def construct_ones(graphs, num, B0, B1):
if construct_ones.num > num:
result = traverse(graphs, construct_ones.longest, "1" * (construct_ones.num - num), B0, B1)
#print(graphs[result], result, construct_ones.num, num)
return result
if construct_ones.num == num:
result = construct_ones.longest
return result
# construct_ones.num < num
node = ''
top = ''
print(f"NOT Reused ones {num} / {construct_ones.num}", file=sys.stderr)
for a in "1" * (num - construct_ones.num):
newnode = retain_node(graphs)
if node:
graphs[node][1] = newnode
else:
top = newnode
node = newnode
graphs[node][0] = B1 #B0 if a == '0' else B1
# ここが 0 のとき"" 空白文字がはいるからおかしくなった
#graphs[node][1] = node if construct_ones.num == 0 else construct_ones.longest
graphs[node][1] = B0 if construct_ones.num == 0 else construct_ones.longest
construct_ones.num = num
construct_ones.longest = top
return top
construct_ones.longest = ""
construct_ones.num = 0
def construct_zeroones(graphs, num, B0, B1):
# just create zero
if construct_zeroones.longest == "":
# share with construct_ones
construct_zeroones.longest = new_node(graphs, B0, B0)
construct_zeroones.num = 1
#construct_zeroones.longest = retain_node(graphs)
#graphs[construct_zeroones.longest][0] = B1
#graphs[construct_zeroones.longest][1] = construct_zeroones.longest
#construct_zeroones.num = 1
if construct_zeroones.num > num:
result = traverse(graphs, construct_zeroones.longest, "1" * (construct_zeroones.num - num), B0, B1)
#print(graphs[result], result, construct_zeroones.num, num)
return result
if construct_zeroones.num == num:
result = construct_zeroones.longest
return result
# construct_zeroones.num < num
node = ''
top = ''
print(f"NOT Reused zeroones {num} / {construct_zeroones.num}", file=sys.stderr)
for a in "1" * (num - construct_zeroones.num):
newnode = retain_node(graphs)
if node:
graphs[node][1] = newnode
else:
top = newnode
node = newnode
graphs[node][0] = B1 #B0 if a == '0' else B1
# ここが 1 のとき"" 空白文字がはいるからおかしくなった
graphs[node][1] = construct_zeroones.longest
construct_zeroones.num = num
construct_zeroones.longest = top
return top
construct_zeroones.num = 0
construct_zeroones.longest = ""
def construct_oneszero(graphs, num, B0, B1):
# just create zero
if construct_oneszero.longest == "":
# share with construct_ones
construct_oneszero.longest = construct_ones(graphs, 1, B0, B1)
construct_oneszero.num = 1
#construct_oneszero.longest = retain_node(graphs)
#graphs[construct_oneszero.longest][0] = B1
#graphs[construct_oneszero.longest][1] = construct_oneszero.longest
#construct_oneszero.num = 1
if construct_oneszero.num > num:
result = traverse(graphs, construct_oneszero.longest, "0" * (construct_oneszero.num - num), B0, B1)
#print(graphs[result], result, construct_oneszero.num, num)
return result
if construct_oneszero.num == num:
result = construct_oneszero.longest
return result
print(f"NOT Reused oneszero {num} / {construct_oneszero.num}", file=sys.stderr)
# construct_oneszero.num < num
node = ''
top = ''
for a in "0" * (num - construct_oneszero.num):
newnode = retain_node(graphs)
if node:
graphs[node][1] = newnode
else:
top = newnode
node = newnode
graphs[node][0] = B0 #B0 if a == '0' else B1
# ここが 0 のとき"" 空白文字がはいるからおかしくなった
graphs[node][1] = construct_oneszero.longest
construct_oneszero.num = num
construct_oneszero.longest = top
return top
construct_oneszero.num = 0
construct_oneszero.longest = ""
def new_state_set(graphs, left_str, right_str, B0, B1):
_id = retain_node(graphs)
graphs[_id][0] = new_node(graphs, B0, new_node(graphs,
construct(graphs, left_str, B0, B1), construct(graphs, right_str, B0, B1)))
return _id
def new_state_set_2(graphs, bottom_node, B0, B1):
_id = retain_node(graphs)
graphs[_id][0] = new_node(graphs, B0, bottom_node)
return _id
# instead of root
#def new_node_self(graphs):
# _id = retain_node(graphs)
# graphs[_id][0] = _id
# graphs[_id][1] = _id
# return _id
def new_state_if_nodes_base(graphs, base_node, then_node):
_id = retain_node(graphs)
top = retain_node(graphs)
graphs[_id][0] = top
graphs[top][0] = "0" # TODO: root
node = retain_node(graphs)
graphs[top][1] = node
graphs[node][0] = base_node
graphs[node][1] = then_node
return _id
def new_state_if_2(graphs, bookmark_node, bottom_node, then_node):
_id = retain_node(graphs)
top = retain_node(graphs)
graphs[_id][0] = top
graphs[top][0] = bookmark_node
node = retain_node(graphs)
graphs[top][1] = node
graphs[node][0] = bottom_node
graphs[node][1] = then_node
return _id
def new_state_if_3(graphs, bookmark_node, bottom_node):
_id = retain_node(graphs)
top = retain_node(graphs)
graphs[_id][0] = top
graphs[top][0] = bookmark_node
graphs[top][1] = bottom_node
return _id
def new_state_if_nodes(graphs, left, right, then_node):
_id = retain_node(graphs)
top = retain_node(graphs)
graphs[_id][0] = top
#graphs[top][0] = new_node_self(graphs)
graphs[top][0] = "0" # TODO: root
node = retain_node(graphs)
graphs[top][1] = node
graphs[node][0] = new_node(graphs, left, right)
graphs[node][1] = then_node
return _id
def new_state_if(graphs, left_str, right_str, then_node, B0, B1):
return new_state_if_nodes(graphs, construct(graphs, left_str, B0, B1), construct(graphs, right_str, B0, B1), then_node)
def next_state(graphs, prev_state, n):
graphs[prev_state][1] = n
def traverse(graphs: str, from_node: str, traverse_str: str, B0: str, B1: str) -> str:
node = from_node
for a in traverse_str:
if a == 1 and graphs[node][0] == B0:
print("Error", file=sys.stderr)
if a == 0 and graphs[node][0] != B0:
print("Error", file=sys.stderr)
node = graphs[node][1]
return node
def main():
graphs = {} # type: Dict[str, list]
root = retain_node(graphs)
admin = retain_node(graphs)
B0 = retain_node(graphs)
B1 = retain_node(graphs)
# bookmark が使える!
# もっといえば前半のif文も統一できる?
#graphs[B0][0] = new_node(graphs, new_node(graphs, new_node(graphs, B0, new_node(graphs, B0, B0)), new_node(graphs, B0, new_node(graphs, B0, B0))), new_node(graphs, B0, new_node(graphs, B0, B0)))
# 0000\-\-\-\-\-\-\-\-\-\... として traverse or get
#graphs[B0][0] = construct(graphs, '0'*9, B0, B1, const=False)
y = admin
z = [""] # placeholder ""
for x in range(8):
y = new_node(graphs, y, B0)
z.append(y)
graphs[B0][0] = y
graphs[B0][1] = B1
#rslttop_graph = retain_node(graphs)
#graphs[rslttop_graph][0] = B1
#graphs[rslttop_graph][1] = B1
#construct(graphs, "11", B0, B1)
# ここは変数置場
#graphs[B1][1] = B0
#graphs[B1][0] = rslttop_graph
graphs[B1][0] = B1
#graphs[B1][1] = rslttop_graph
graphs[B1][1] = B1
graphs[root][0] = admin
graphs[admin][0] = B0
graphs[admin][1] = B1
new_copy_state_base = graphs[construct(graphs, '0011', B0,B1)][1]
graphs[traverse(graphs, new_copy_state_base, '0', B0, B1)][0] = construct(graphs, '0011', B0, B1)
#new_copy_state_base = new_node(graphs, B0, new_node(graphs, construct(graphs, '0011', B0, B1), construct(graphs, '1', B0, B1)))
# update_state は8回参照されるので手前のほうがいい
base_update_state_base = new_node(graphs, B0, new_node(graphs, construct(graphs, '1', B0, B1), construct(graphs, '1'*8+'1',B0,B1)))
set_state_0 = retain_node(graphs)
graphs[set_state_0][0] = new_copy_state_base
set_state_01_bottom = new_node(graphs, construct(graphs, '10', B0, B1), construct(graphs, '000', B0, B1))
# 0
set_state_01 = new_state_set_2(graphs, set_state_01_bottom, B0, B1)
next_state(graphs, set_state_0, set_state_01)
set_state_1 = retain_node(graphs)
graphs[set_state_1][0] = new_copy_state_base
# 01001 にset を埋め込む
set_state_11_bottom = construct(graphs, '01001', B0, B1)
graphs[traverse(graphs, set_state_11_bottom, '0', B0, B1)][0] = construct(graphs, '10', B0, B1)
#set_state_11 = new_state_set_2(graphs, set_state_11_bottom, B0, B1)# 1
set_state_11 = retain_node(graphs)
graphs[set_state_11][0] = set_state_11_bottom
next_state(graphs, set_state_1, set_state_11)
# 01001 : 0100 sometimes become B0
ifs_bottom_node = new_node(graphs, construct(graphs, '01001', B0, B1), construct(graphs, '000', B0, B1))
ifs_bottom_node_state_0 = new_node(graphs, ifs_bottom_node, set_state_0)
# 0b11110 or 0b00011111
if00_state_3 = new_state_if_3(graphs, z[3], ifs_bottom_node_state_0)
if0_state_3 = new_state_if_2(graphs, z[6], ifs_bottom_node, if00_state_3)
next_state(graphs, root, if0_state_3)
if010_state_3 = new_state_if_2(graphs, root, set_state_01_bottom, set_state_0)
next_state(graphs, if00_state_3, if010_state_3)
next_state(graphs, if010_state_3, set_state_1)
if10_state_3 = new_state_if_2(graphs, z[3], ifs_bottom_node, if010_state_3)
next_state(graphs, if0_state_3, if10_state_3)
next_state(graphs, if10_state_3, set_state_1)
step3_if_state = if0_state_3
# 0b11110 or 0b00011111
ifs_bottom_node_step_3 = new_node(graphs, ifs_bottom_node, step3_if_state)
if00_state_2 = new_state_if_3(graphs, z[4], ifs_bottom_node_step_3)
if0_state_2 = new_state_if_2(graphs, z[1], ifs_bottom_node, if00_state_2)
next_state(graphs, root, if0_state_2)
if010_state_2 = new_state_if_3(graphs, z[7], ifs_bottom_node_step_3)
next_state(graphs, if00_state_2, if010_state_2)
next_state(graphs, if010_state_2, set_state_1)
if10_state_2 = new_state_if_2(graphs, z[4], ifs_bottom_node, if010_state_2)
next_state(graphs, if0_state_2, if10_state_2)
next_state(graphs, if10_state_2, set_state_1)
step2_if_state = if0_state_2
copy_if0_state = new_state_set(graphs, '0'*4+'1', '10', B0, B1)
copy_if0_state_after = retain_node(graphs)
graphs[copy_if0_state_after][0] = base_update_state_base
next_state(graphs, root, copy_if0_state)
next_state(graphs, copy_if0_state, copy_if0_state_after)
# 01010011
for x in ['0'*5+'1', '0'*6+'1', '', '0'*7+'1', '0'*8+'1', '0'*9+'1', '', '0'*10+'1', '0'*11+'1']:
if x == '': # return
copy_if0_state = copy_if0_state_after
else:
copy_if0_state = new_state_set(graphs, x, '10', B0, B1)
next_state(graphs, copy_if0_state_after, copy_if0_state)
copy_if0_state_after = retain_node(graphs)
graphs[copy_if0_state_after][0] = base_update_state_base
next_state(graphs, copy_if0_state, copy_if0_state_after)
ifs_bottom_node_step_2 = new_node(graphs, ifs_bottom_node, step2_if_state)
#
# 0b11110 or 0b00011111
if00_state = new_state_if_3(graphs, z[5], ifs_bottom_node_step_2)
if0_state = new_state_if_2(graphs, z[8], ifs_bottom_node, if00_state)
next_state(graphs, copy_if0_state_after, if0_state)
if010_state = new_state_if_3(graphs, z[2], ifs_bottom_node_step_2)
next_state(graphs, if00_state, if010_state)
next_state(graphs, if010_state, set_state_1)
if10_state = new_state_if_2(graphs, z[5], ifs_bottom_node, if010_state)
next_state(graphs, if0_state, if10_state)
next_state(graphs, if10_state, set_state_1)
# 複製した文字の「次」の文字をポインタが指すように変更
# そのとき 00111...1 ではなく 複製前の文字列で見たほうが 0010..0 というノードを作らずに済む
update_ptr_bottom = construct(graphs, '1'*9, B0, B1)
graphs[traverse(graphs, update_ptr_bottom, '', B0, B1)][0] = construct(graphs, '001', B0, B1)
#update_ptr = new_state_set(graphs, '001', '1'*8, B0, B1)
update_ptr = new_state_set_2(graphs, update_ptr_bottom, B0, B1)
# update_syayr will be modified
next_state(graphs, set_state_01, update_ptr)
next_state(graphs, set_state_11, update_ptr)
#update_state = new_state_set(graphs, '1', '1'*12*8+'1', B0, B1)
update_state = retain_node(graphs)
graphs[update_state][0] = base_update_state_base
next_state(graphs, update_ptr, update_state)
for x in range(12-1-10):
update_state_new = retain_node(graphs)
graphs[update_state_new][0] = base_update_state_base
graphs[update_state][1] = update_state_new
update_state = update_state_new
# to reuse 0011
node_00011 = new_node(graphs, B0, construct(graphs, '0011', B0, B1))
ending_state = new_state_set_2(graphs, new_node(graphs, construct(graphs, '1', B0, B1), node_00011), B0, B1)
isendable_state = new_state_if_nodes(graphs, construct(graphs, '1'*1, B0, B1), construct(graphs,'000', B0,B1), ending_state)
graphs[traverse(graphs, construct(graphs, '1'*9, B0, B1), '1'*5,B0,B1)][1] = graphs[isendable_state][0]
next_state(graphs, update_state_new, isendable_state)
next_state(graphs, isendable_state, graphs[root][1])
# 仮置場のものを出力に設定
next_state(graphs, ending_state, B0)
def get(graphs, start, astr):
node = start
for s in astr:
node = graphs[node][int(s)]
return node
# TODO base が l.329 からかわってないこと前提
#graphs[base][1] = graphs[graphs[graphs[graphs[graphs[graphs[graphs[graphs[graphs[graphs[graphs[genif_state][0]][1]][1]][0]][1]][1]][0]][1]][1]][0]][1]
# ようするに if 文のそこから loop で戻ってくるまでを `1` * 15 と解釈できることを利用
#graphs[base][1] = get(graphs, genif_state, '01101101101')
# そのノードのみ比較するには0 つける必要あり
# 11 は既に使っているので、B0[1] -> B0 なことを使って、if の右辺を11 にする
# そうすると空いている 111[0] に 000 を入れられる
output(graphs)
def output(graphs):
stack = [list(graphs.keys())[0]]
stack2 = [[]]
watched = set([])
#print(stack[0], end=' ')
print(stack[0], end='')
while len(stack) > 0:
last_id = stack[-1]
last_item = stack2[-1]
if last_id in watched:
stack.pop()
stack2.pop()
continue
# isNew なら 0番目の内容を出力してそっちに飛んでいく
# not isNew なら1番目の内容を出力してそっちに飛んでいく
if len(last_item) == 0:
node = graphs[last_id][0]
else:
node = graphs[last_id][1]
last_item.append(node)
#print(node, end=' ')
print(node, end='')
#print(stack)
#print(stack2)
while len(stack2[-1]) == 2:
watched.add(stack.pop())
stack2.pop()
if len(stack) == 0:
break
if node not in stack:
stack.append(node)
stack2.append([])
#print()
if set(graphs.keys()) != watched:
print(set(graphs.keys())^set(watched), file=sys.stderr)
print("Errors: not watched yet", file=sys.stderr)
main()
012ba9876541222222223333!'2&"2#2$2%2d32q3p22(n2mdg$h3i3j3k3l3z0υφds2r2pρς2πdο2f2e2cfd2),2+*2"q-n.;2:/2*q<n=n>[2@?2/q\n]`2_^2?q{n|Σ2~}2^qΤnΥnΦΩ2ΨΧ2}qΪnΫή2έά2ΧqίnγδbεCx2λqτ2A2dsαβ8uCST4UCQR7PCGH9ICEF6DCoeΰζ2tqsyμ2gνnξnσz!JK0LtoweBxyMN6OCJwVWaPwXY7ZCVwvη5uwθι8κCvw
TSP (@nakario, 681 bytes)
コードを見る
DDW$WWWWW$DDDDW$AAWWW$WWW$WAA$WWWDD$D$W$D$WWWWWAAAA$WWW$AASSSSSSS$AA$DDS$AA$DDS$AAAD$SSDA$DSS$SDA$SDADA$SDA$SDA$AAAAAAAAAAAAAAAAASS$DS$DDDD$DDD$S$AAA$AAAAAS$A$AAAAAAAS$DDD$DDDD$D$D$DDD$DDDDDDDD$D$W$DS$S$S$A$W$AS$WAA$S$AAAAAAAAAAAASSS$WWA$DDDDDDDDDDDDDDDDDDDSSSSSSSDA$SSSSSSSSSSSSSSD$SSSA$DDS$SSAA$DDW$WWWWWWWWWWAWSWSWSWSWSWSWS$DDDWWW$D$WA$DDDDDWW$AAAA$A$W$DW$W$D$D$WA$DDDDDDDDDDDDDDDDDDDDDWWW$WAAA$WWWAAAAAAAAAAAAAAAAAAAAAAADDDDDDDDDDDDDDDDDDDDDDD$DWS$DDD$WAAAAAAAAAAAAAAAAAAAAAAAADDDDDDDDDDDDDDDDDDDDDDD$SSA$AAAAWDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA$AAAAS$AAW$AAAAAAAAAAAAAAS$
TSPはスタックベースの言語だが、命令が8つしかないのでまずそこが大変。ただ足し算を行うだけでもdup, dup, sub, swap 2, sub, sub
の6命令必要。しかし逆に言うと出来ることが限られているため、ここで頭を使う必要はあまりない。愚直にやりたいことをコツコツと組み立てていく。
次に、必要な命令列が決まったらそれをxy平面の格子点上に配置していく。原点からのチェビシェフ距離max(abs(x), abs(y))
とマンハッタン距離abs(x)+abs(y)
が小さい順に命令が実行される。原点からの距離が同じ点が複数存在してはいけないので、以下のようにいったん三角形状に配置してから各々の命令に対応する座標に配置し直すのがよい。
i
pd
--s
pp>j
一度このピラミッドを作った後で命令列を改変しようとするとこのピラミッドが総崩れになるので、事前によく確認しておくか、命令順だけ決めればピラミッドが自動生成される環境を整えておくこと。
この配置に際して、push N
, swap N
, jump-zero N
の3命令は命令に到達するための歩数N
が重要になってくる。N
が小さいときに三角形の真ん中付近や斜辺側に配置してしまうと到達不可能になるため、x軸かy軸に接している側に近くなるよう、命令を配置しない空白地帯を設けることも重要である。
こうして命令をxy平面上に配置した後は、この言語の主題である最短経路の探索になる。巡回セールスマン問題はNP困難に分類される問題なので近似解を探索するのが良い。格子点上のマンハッタン距離によるグラフに絞ればある程度高速に解けると聞いたが詳細は不明。しかし、上記の3命令は歩数が重要になり、特定の順番で頂点をたどる必要がある場合があるため、そのように制約をつけた問題を解けるソルバを作るか、あとでnop命令を経由するよう経路を改変する必要がある。
今回は誰もTSPを解こうとする人がいなかったので、ソルバを作るのは諦めて人力で短くなりそうな経路を選んだ。その際は命令列の配置とxy平面の可視化を合わせたこのコードをひと晩かけて書いて使用した。これに食わせた元々の命令列と出力されたmapを下に置いておく。
元となったコード
コメントを処理するコードは書いてないので不要部分は手で消して食わせた。
p3
L1
- # reset stack
-
-
-
-
-
-
-
d
-
i
j L5
i
i
i
d
-
- 0 a1 a2 a3
i
s3 0 a1 b1 a3 a2
i
s3 0 a1 b1 b2 a2 a3
i
i
d
-
- 0 a1 b1 b2 a2 a3 b3
i
s5 0 a1 b1 c1 a2 a3 b3 b2
s3 0 a1 b1 c1 a2 b2 b3 a3
i
s3 0 a1 b1 c1 a2 b2 c2 a3 b3
i
i
d
-
- 0 a1 b1 c1 a2 b2 c2 a3 b3 c3
p3
d
-
L4 # ここでswap4が成功するかどうか確認
s4
j L2
s4
- 0 a1 b1 c1 a2 b2 c2 a3 b3 c3
d
d
-
s2
-
-
d
d
-
s2
-
- 0 a1 b1 c1 a2 b2 c2 a3+b3+c3
p145
> 0 a1 b1 c1 a2 b2 c2 a3+b3+c3>145
j L4
p49 # print 1
o
p3
d
-
j L1
L2 # print 0
p48
o
p3
d
-
j L1
L5
出力された命令の配置
歩数が必要な命令の場合はそばに歩数を付け足した。
| #
|
|
| #
|
|
|
2 # |
2 # | ##
4 # | ##
|
4 # |
| #
3 #| #
3 #|
5 #|
3 #| #
3 #|
|
# | #
# # # | #
# # |
## # |
# # ### # ### | 48
# ## | 3 3 145 3 #
# ### | # # # ## #
--------------------------#--------------------49---3
|
# | #
| #
|
|
| #
# | ###
28 | #
| #
| ## #
|
| #
| ##
|
|
25 #|
|
|
|
|
15 #|
|
|
4 # |
|
|
4 # |
Icarus Verilog (@syobon_hinata, 94 bytes)
module a;time a,b,c;initial
while($fscanf(1<<31,"%d%d%d",a,b,c))$write(a+b&c|a&b&&1);endmodule
ほとんど全部テンプレで削れるところがない。time型は名前に反してただの整数として使っています。
scanfの引数の形式を指定する部分と、あとwriteの中の判定ロジックが鍵。先に提出していたこたつがめさんのコードではa+b+c>(a|b|c)
を使っていたが、a+b&c|a&b
を使うことで短縮に成功した。もともとa+b&c|a&b?1:0
としていて「同じ長さになったけど短縮できねえ」と嘆いていたところ&&1
を思いつき試したら通ったという経緯があります。(95bytesのコードは複数書けました 94は私が思いつく限りこれだけです)
(by @syobon_hinata ふぁぼん)
Vim (@kotatsugame_t, 32 bytes)
qq3JYPJr0/1...1\|\%#
xVpjq15@qZZ
qq..q
でマクロを作り、15@q
で15回(実行しながら定義するため1回減っている)繰り返す。
以下マクロを詳しく見ていく。処理開始の時点では1ケース3行のうち1行目にいて、処理が終わったら次のケースの1行目にいる、という状態を保つ。
3J
:3行連結
YPJ
:ヤンクして直上に貼り付け、即座に下の行と連結する。これで行が2倍になったことになる。
r0
:カーソル下の1文字(J
の直後のため、必ず空白文字)を0に置き換える。
/1...1\|\%#
:/1...1/
を探す。マッチすれば、カーソルは最初の1の上に動く。マッチしなければ次の\%#
だが、これは現在のカーソル位置であるため、カーソルの移動は起こらない。マッチに失敗するとマクロの実行が止まってしまうので、必ず成功させるためのもの。末尾の/
は省略できる。
この時点でカーソル直下の文字が出力すべき値になっている。
xVp
:カーソル直下の1文字を削除しつつレジスタに入れ、ビジュアルモードで1行全体を選択してレジスタの中身で上書きする。
j
:1行下、つまり次のケースに進む。
最後のj
で最終行のさらに下に進もうとすると勝手にマクロが止まるので、qq..q15@q
ではなくqq..@qq@q
と再帰のようにしてもよい。これはループ回数が3桁以上になると縮む。
AtCoderにVimが追加されてからたった10か月の間に100問以上の最短コードが生まれていて、知見が溜まりまくり。
VB.NET Core (@drafear, 117 bytes)
Module P
Sub Main
Dim x=0,a=Sub()x+=Console.ReadLine
a
a
a
Console.Write(-(x^3*36Mod 37>x\x))
Main
End Sub
End Module
Main関数を末尾で呼び出すことで複数テストケースに対応した。入力終了後は、0/0の整数除算による異常終了で打ち切った。
ロジックは、3行の10進数入力の和$x$について$x^3 \times 36 \ \mathrm{mod}\ 37 > \frac{x}{x} (=1)$を利用した。
引数のない関数(サブプロシージャ)の呼び出しは()
を省略できることに気づき、とても縮んだ。
V (@paradigm_9, 49 bytes)
import os
os.system("sed 'h;N;N;G;/1...1/c1
c0'")
execできたのでexecしてしまった...ごめんちゃい!
wake (@kotatsugame_t, 50 bytes)
all:$<
s.*1...1.*:"1"
s.*:"0"
(.{12})(.*):s$1$1 $2
12文字ずつ切り出して2倍し、/1...1/
をしている。切り出したものは区別するためにsを文頭に付けた。最後は空文字列がマッチできずに落ちるが、問題ない。
WebAssembly WAT (@nu4nu, 358 bytes)
(module(memory(import"env""memory")1)(func(export"main")(param i32)(result i32)(loop
local.get 0
i32.const 12
i32.div_u
i32.const 49
local.get 0
i32.load
local.get 0
i32.load offset=4
local.get 0
i32.load offset=8
i32.add
i32.add
i32.const 131586
i32.and
i32.eqz
i32.sub
i32.store
local.get 0
i32.const 12
i32.add
local.tee 0
i32.load
br_if 0)i32.const 341))
esolang-boxのwasm-cli.jsの実装を読むと以下のことがわかる。
- アドレス0x1000から入力が置かれる
- 引数に0x1000が入ってくる
- 関数の返り値が出力文字列の格納されたアドレスとして扱われる
出力を書き込むアドレスを好きに選べるというのがポイント。最初はそれに気づいていなくてアドレス0から書く必要があると思っていた。
今回の問題では12文字読み出して1文字書くことになる。読み出しアドレスを12で割ったものを書き込みアドレスとしてやれば、アドレス関連の変数を1つにできる。終了条件は、入力終端まで来ると0クリアされた領域を読み出すようなのでそれを使っている。アドレスを比較するよりも短い。
言語としては、演算の種類は豊富なのにdupやswapがないというのが特徴的に思えた。
Whitespace (@angel_p_57, 134 bytes)
コードを見る
※空白・タブ・改行をs,t,nで置き換えた版
nsstnsssnnstnnstnnstnnstsnnstsnnstsnnssssntnstnsntnnssnsnssnstnttttttsssntnnsssnssststsnssstnsnsstssttnstssttntstttsstnttssnsnntstsntn
疑似アセンブリ(自作)コード
mark MAIN #(label:t) nss-t-n
push 0 # ss-s-n
call READ # nst--n
call READ # nst--n
call READ # nst--n
call DIGIT # nst-s-n
call DIGIT # nst-s-n
call DIGIT # nst-s-n
mark POST #(label:ss) nss-ss-n
puti # tnst
jump MAIN # nsn-t-n
mark READ #(label:NULL) nss--n
dup # sns
dup # sns
geti # tntt
retr # ttt
add # tsss
ret # ntn
mark DIGIT #(label:s) nsssn
push 10 # ss-ststs-n
push 1 # ss-st-n
dup # sns
copy 3 # sts-stt-n
copy 3 # sts-stt-n
mod # tstt
sub # tsst
jneg POST # ntt-ss-n
pop # snn
div # tsts
ret # ntn
この自作アセンブリコードでのテスト実行・Whitespaceコード生成は https://github.com/angel-p57/whitespace にあるツールでやってます。
作戦としては、3行分の合計の各桁に2以上の数がないかどうかを見るだけ。そのため、mod,divを繰り返します。 そのために関数的なジャンプ(call)を繰り返すだけなので、スタック型だと知っていれば非常に分かり易いと思います。 ただ、callしてもretせずジャンプして戻ってもいいのがちょっと邪悪。
後日短縮版98B
※空白・タブ・改行をs,t,nで置き換えた版
nsssnssstsststtsssnssttststtsnnstnnstnnstntsttsnsntstnssstnnsstntnstnsnsnnssnsssnsnstnttttttsssntn
詳細を見る
Jellyfishの短縮にインスパイアされ、600%(a+b+c-86)!=0 ? 1 : 0
というロジックを開発して短縮しました。そのため、コード上の工夫はほとんど不要になっています。
※Whitespaceはビット演算がないのでJellyfishと同じ手は使えない、逆にJellyfishは !=
等の演算がベクトル非対応なので、こちらのロジックでは縮まない。
以下、疑似アセンブリ(自作)コードです。
mark MAIN # (label:s) nss-s-n
push 600 # ss-stsststtsss-n
push -86 # ss-ttststts-n
call READ # nst--n
call READ # nst--n
call READ # nst--n
mod # tstt
dup # sns
jzero POST # nts-t-n
push 1 # ss-st-n
mark POST # (label:t) nss-t-n
puti # tnst
jump MAIN # nsn-s-n
mark READ # (label:NULL) nss--n
push 0 # ss-s-n
dup # sns
geti # tntt
retr # ttt
add # tsss
ret # ntn
Wren (@settyan117, 120 bytes)
import"io"for Stdin
while(1){
var a=0
for(j in 0..2)a=a+Num.fromString(Stdin.readLine())
System.print((a^34)%35>7?1:0)
}
仕様が貧相軽量な言語。正規表現もない。結構かっちりしていて、ほとんど縮まらなかった。入力が尽きると落ちるのを利用してwhile(1)にしたところで多分一番縮んだ。
WysiScript (@settyan117, 2810 bytes)
コードを見る
<q style="font:Hei;color:#001001;background:gold;text-decoration:underline"></q><q style="font:bold Hei;color:teal;background:red"><q style="font:bold Hei;color:#f0fff0;background:red"><q style="font:Hei;color:#001;background:pink;text-decoration:underline"></q><q style="font:Hei;color:#001;background:navy;text-decoration:underline"></q><q style="font:Hei;color:#001;background:tan;text-decoration:underline"></q><q style="font:Hei;color:#031;background:cyan;text-decoration:underline"></q><q style="font:bold Hei;color:teal;background:red"><q style="font:bold Hei;color:#f0fff0;background:red"><q style="font:bold Hei;color:#add;background:pink"><q style="font:Hei;color:pink;background:red"></q><q style="font:bold Hei;color:#d1ffe2;background:red"><q style="font:bold Hei;color:#6e7;background:red"></q><q style="font:Hei;color:#003001;background:red;text-decoration:underline"></q></q></q><q style="font:bold Hei;color:#add;background:navy"><q style="font:Hei;color:navy;background:red"></q><q style="font:bold Hei;color:#d1ffe2;background:red"><q style="font:bold Hei;color:#6e7;background:red"></q><q style="font:Hei;color:#003001;background:red;text-decoration:underline"></q></q></q><q style="font:bold Hei;color:#add;background:tan"><q style="font:Hei;color:tan;background:red"></q><q style="font:bold Hei;color:#d1ffe2;background:red"><q style="font:bold Hei;color:#6e7;background:red"></q><q style="font:Hei;color:#003001;background:red;text-decoration:underline"></q></q></q><q style="font:bold Hei;color:#6e7;background:red"></q><q style="font:bold Hei;color:#d1ffe2;background:cyan"><q style="font:Hei;color:cyan;background:red"></q><q style="font:Hei;color:#011;background:red;text-decoration:underline"></q></q></q><q style="font:bold Hei;color:#70661e;background:red"><q style="font:Hei;color:cyan;background:red"></q></q></q><q style="font:bold Hei;color:#facade;background:red"><q style="font:bold Hei;color:gold;background:red"><q style="font:bold Hei;color:#b166e2;background:red"><q style="font:Hei;color:pink;background:red"></q><q style="font:Hei;color:#011;background:red;text-decoration:underline"></q></q><q style="font:bold Hei;color:#b166e2;background:red"><q style="font:Hei;color:navy;background:red"></q><q style="font:Hei;color:#011;background:red;text-decoration:underline"></q></q><q style="font:bold Hei;color:#b166e2;background:red"><q style="font:Hei;color:tan;background:red"></q><q style="font:Hei;color:#011;background:red;text-decoration:underline"></q></q></q></q><q style="font:bold Hei;color:#d1ffe2;background:gold"><q style="font:Hei;color:gold;background:red"></q><q style="font:Hei;color:#011;background:red;text-decoration:underline"></q></q></q><q style="font:bold Hei;color:#70661e;background:red"><q style="font:Hei;color:gold;background:red"></q></q></q>
▲ 適当に見やすく(?)表示したもの
文字の色とサイズと書体(例えば、underlineはリテラル)と背景色にしか意味がない言語。WYSIWYGの究極系。…だとずっと思っていたんですが、サイズにも意味はなかった(DOMの構造にしか興味がない)。なんなら文字もいらないので、WYSIWYGの対極でもある。 公式サイトのドキュメントがえらくて、"SIGBOVIK paper"を読めばだいたい書ける。同じページにある公式のIDEはえらくないので、省略・略記をちょっとずつ試す地道な作業を行うとごっそり縮む(たぶんまだ縮む)。
XSLT (@utgwkk, 264 bytes)
<transform version="1.0" xmlns="http://www.w3.org/1999/XSL/Transform"><output method="text"/><template match="/"><value-of select="string-join(for$i in(0 to 15)return if(matches(substring(input,$i*12,12),'1...(....)?1','s'))then 1 else 0)"/></template></transform>
第6回東大京大コードゴルフ大会のWriteupがなかったら書けなかったと思う。12バイトずつ切り出して正規表現マッチする解法です。XML短縮テクは@k_hanazukiさんのWriteupを見てください。 XQuery書かずに済むならそのほうがよいと思う (なんかXQueryで書こうとして型が合わないみたいなエラーが出て無限にハマってしまった)。
Zig (@azaika_, 156 bytes)
本質は @ikubaku10 (@azaika_ は fork-exec の fork を消しただけ)
const o=@import("std").os;pub fn main()!void{var c="/bin/sed";var e=o.execveZ(c,&[_:null]?[*:0]u8{c,"h;N;N;G;/1...1/c1\nc0",null},&[_:null]?[*:0]u8{null});}
uglifyする前のコードを以下に示します:
const o = @import("std").os;
pub fn main() !void {
var c = "/bin/sed";
var e = o.execveZ(c, &[_:null] ?[*:0]u8 {c, "h;N;N;G;/1...1/c1\nc0", null}, &[_:null] ?[*:0]u8 {null});
}
この解の方針はexecシステムコールを利用してsedの解を用いて問題を解くことです. 4行目でtype annotationがごちゃごちゃしていますが,sentinel-terminated arrayとpointerのリテラルがこんなふうにしか書けなかったので仕方なさそうです. execveに与える引数の配列の最初の要素にも"/bin/sed"を入れていますが,実はここは空文字列でも大抵の環境で問題ありません. しかしZigのボックスは/bin/sedがbusyboxのアプレットなので第1引数にプログラム名をちゃんと指定しないと動きません. busyboxが使われていない環境では3行目の変数宣言をやめてexecveZの第1引数にそのまま文字列を指定し,引数の1番めの要素を空文字列にすることでちょっと短縮できます.
え?execを使っていない解も見たい?ではせっかくなのでそのバージョンも紹介します.execを使わない場合は次のようなコードで解けます:
const o=@import("std").os;pub fn main()!void{while(1>0){var b=[_]u8{99}**12;var f=[_]u8{0};var i=0*try o.read(0,&b);while(i<3){f[0]|=(1+b[i]+b[i+4]+b[i+8])/3;i+=1;}_=try o.write(1,&f);}}
uglify前はこんな感じ:
const o = @import("std").os;
pub fn main() !void {
while(1 > 0) {
var b = [_]u8 {99}**12;
var f = [_]u8 {0};
var i = 0 * try o.read(0, &b);
while(i < 3) {
f[0] |= (1 + b[i] + b[i+4] + b[i+8]) / 3;
i += 1;
}
_ = try o.write(1, &f);
}
}
この解は@moratorium08 さんの解答をベースに結果の計算方法の工夫と細かいcode-golfingにより短縮を図ったものです.
まず3行目の無限ループですが while(true)
と書くのではなく while(1>0)
と書くことで1バイト短縮しています.
4行目では入力を取得するための配列を宣言+初期化しています.なぜ99を入れているのかについては後で説明します.
6行目はインデックス用の変数を初期化して入力を取得しています.なぜこのような書き方にしているのかについて説明します.
まず標準入力の読み取りに os.read
を用いています.正攻法は std::io.getStdIn().reader.read
を使うことなのですが,直接readを叩いてやることでかなり短縮できます.
後の出力部分でも同じ手法を使っています.
それを踏まえてこの部分を愚直に実装すると次の様なコードになります:
var i: u8 = 0;
_ = try o.read(0, &b);
i
の宣言でtype-annotationが必要なのは整数リテラルがデフォルトでcomptime_int型として扱われるので可変変数にできないからです.
またZigには式の値を必ず使わなければならないというルールがあります.したがってreadの戻り値を使わないときは上のコードのように読み捨てる必要があります.
ここで os::read(buffer: []u8)
の戻り値の型に注目します.https://github.com/ziglang/zig/blob/783cb980ab137d6a99c20f007d70b4191d4a63c4/lib/std/os.zig#L328 によると ReadError!usize
になっています.これをうまく使って可変変数 i
を作ってみましょう.
i
の初期値は0にしたいのでまずtryでunwrapしたあとの戻り値に0を掛けます.そしてその値を i
の初期化に使うと以下のようなコードになります:
var i = 0 * try o.read(0, &b);
これで 0 *
を増やしただけでセミコロン1つと読み捨て,type-annotationを消すことができました.5バイト程度削減できたでしょうか?
7〜10行目で結果を計算しています.
与えられるテストケースのバイト列をぐっと睨むと(@hiromi_miさんによれば)「ある列の文字コードの合計に1を加えたものを3で割った値」がその列についての返すべき結果になることがわかります(??????).
0と1のASCIIコードの2進数表記はそれぞれ 0b00110000
と 0b00110001
ですからすべての列についてのこの値の論理和を取れば出力するべき文字の文字コードになります.
11行目では結果を1テストケース分出力しています.ここは普通ですね.
ところでこのままでは入力が終わってもずっと入力取得をし続け,プログラムが停止しません.EOFを検出したりreadのエラーハンドリングを実装したりするとコードが長くなってしまうのでどうにかしたいところです.
そこで入力がなくなったときの挙動について考えてみます.(詳しい理由は調べられていませんが)ボックスの環境でのreadは入力がなくなったときに0バイト与えられた配列に格納して成功します.
ここで配列 b
の全要素を99で初期化していたことを思い出してください.このまま計算を続けると8行目で結果がu8に収まらなくなりオーバーフローします.
Zigは実行時のオーバーフロー検出をやってくれるのでこの時点でプログラムが異常終了します.
esolang-battlerの判定ルールでは実行時エラーが発生してもジャッジが行われるのでこのようなプログラムの停止方法は合法です.これでちゃんと入力の最後で処理を終わることができます.
このようにexecする場合には及びませんがかなり短く書くことができます(187 bytes).
Zsh (pure) (@drafear, 50 bytes)
read a
read b
read c&&echo $[!!((a+b)&c|a&b)];. $0
bash (pure) と同じ