Skip to content

Esolang Codegolf Contest 8 Writeup

hiromi-mi edited this page Aug 31, 2023 · 1 revision

問題

ミニポーカーの役判定をせよ

TSG王国とKMC王国では、ミニポーカーの交流戦が行われています。 50人の国民がそれぞれ5枚の手札を持ち、審判が役判定をしようとしますが、人が多くて大変です。 審判を助けるために、ミニポーカーの役判定をするプログラムを作成してください。 ミニポーカーでは、通常のポーカーの役のうち「ストレートフラッシュ」「フラッシュ」「ストレート」「ハイカード」のみが採用されています。

入力

  1. 各国民の手札が50ケース、改行区切りで与えられる
  2. 手札は5枚のカードからなり、各カードを表す文字列を連結したものとして与えられる
  3. 1枚のカードは2文字で構成され、1文字目はスート(A B C D のいずれか、それぞれスペード、ハート、ダイヤ、クローバーを表す)、2文字目は数字(1~5のいずれか)が与えられる

すなわち、各手札は10文字で表される。 入力の最後には改行が与えられる。

出力

50行の入力それぞれについて、以下の問題を解け。それぞれの出力の末尾 には改行を入れよ。

ミニポーカーの役判定をする。ミニポーカーには以下の役があり、より上にある役が優先される。対応する文字>列を出力せよ。

  1. ストレートフラッシュ:5枚すべてのスートが一致し、5枚の数字が連続している。SF または FS を出力する
  2. フラッシュ:5枚すべてのスートが一致する。Fを出力する
  3. ストレート:適切に並び替えると、5枚の数字が連続している。Sを出力する
  4. ハイカード:以上の条件を満たさない。何も出力しない

出力された文字のうち、SとF、改行以外は無視される

各チームごとの文章へのリンク・一般的なテクニック

  • 「ストレートフラッシュ」は「フラッシュ かつ ストレート」と同じなので、実質的には「フラッシュならばFを出力」「ストレートならばSを出力」を独立に行えば良い。

ループの終了条件

  • 50回
  • 入力が尽きるまで
  • 無限ループ or 無限再帰
    • 出力の51行目以降が無視されることを利用
    • EOFErrorみたいなのに任せる
    • 入力がなくなったときにゼロ除算を発生させる
  • 一括置換を使うのでループしない

入力

  • 1文字ずつ読む
    • 文字コードを32 or 64で割った余りが被らないので、出現した文字の集合をビットフラグで管理しやすい
    • 改行の判定
      • 文字コード == 10
      • 文字コード < 11
      • 文字コード & 8
  • 2文字ずつ読む
    • カード1枚が2文字なので
    • 改行は1文字なのでその処理が必要
    • 最初の1文字を基準のスートとしてまず読み込んで、数字→スートの組×5として処理することもできる
      ※その場合、最後の改行はどのスートとも一致しない特別なスートとして処理する
  • 1行ずつ読む
    • 長さが固定なので11バイトずつ読むこともできる
    • 行全体をソートする方針
    • 行全体に正規表現マッチ/置換を行う方針
  • 一気に全体を読む
    • 一括置換とか

ストレートの判定

  • 偶数番目の文字を整数とみなして
    • $\prod s_i = 1\cdot 2\cdot 3\cdot 4\cdot 5$ は不可(2*2*2*3*5 を誤判定する)
    • $\sum 2^{s_i} = 62$ は可
      • 和の代わりに bitwise OR にすれば、ストレート成立時の $62$ が最大値
  • 偶数番目の文字の文字コードを
    • $\prod s_i = 344362200$
    • $\sum {s_i}^2 = 13015$
  • 行内の文字を文字コード順にソート
    • ソートした結果が 12345 で始まっている
    • ソートして前半 5 文字を取り出して uniq したときの長さが $5$
    • ソートして uniq した結果 5 文字目が存在してそれが数字
  • 正規表現で重複する数字があるか調べる(なければストレート)
    • /(\d).*\1/
      • まず S を入力に追加して、↑にマッチしたら S ごと空文字列(or S を含まない何か)に置換する
    • ^(?!.*(\d).*\1)
      • 否定先読みで同様に置換する

フラッシュの判定

  • $f(x) = x&amp;(x-1)$ という演算が、$x$ の1が立ってる中で最下位ビットのみを0にするという性質があるため、$f(x)=0 \Leftrightarrow popcount(x)\le 1$ と、popcount(1が立ってるビットの数)の判定に活用できる。
    これにより、1<<文字コード の総 bitwise OR の popcount が 1以下なのを検出することで、フラッシュの判定になる。
  • 行内の文字を文字コード順にソート
    • ソートした結果の 6 文字目と 10 文字目の文字が同じ
    • ソートして後半 5 文字を取り出して uniq したときの長さが $1$
    • ソートして uniq した結果の後ろから 2 文字目が数字
  • 正規表現で /^(.).(\1.)*$/ または /^(.)(.\1){4}/
    • 単に /(.)(.\1){4}/ では同じ数字が5つあるときに誤マッチするので、本当はまずい。でもたくさんあるね…

出力

  • 51行目以降は無視される
    • エラーとかが出力されてもいい
    • ループの終了条件を真面目にやらなくてもいい?
  • S, F, 改行以外は無視される
    • Fのかわりに False を使える
    • けど FALSE は使えない(Sも入ってる)
    • 入力される文字は(改行を除き)無視されるので、そのまま出力できる
    • 文字コードに対する演算(和、 bitwise XOR など)
      • 'S' + (ストレートの場合に $0$ になる値)
      • 'F' + (フラッシュの場合に $0$ になる値)
      • 'S' と 'F' は偶奇が異なるので、加える値が偶数になるようにすると誤爆を回避できる

言語ごとの解説

3var

Red Team (@nu4nu, @angel-p57, @siotouto, 362 bytes)

'<[kkkkkk[kkkm+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>"<mk+>'<kkkkkkF]<-<+<kkkkkkkO#aa/>m/>m/>#aamamkmk[->|]#aaamaaamaaaaaa->x#aammkk*>PkkkkO@'<_]

A,Bの2つのアキュムレータと、演算結果や入力値が入るRレジスタの3つのレジスタしかない言語。inc,dec,2乗,abs(!)はA,B自身だけで完結するが、それ以外の演算(四則演算とpow)は結果がRに入る。Rを演算の入力オペランドとして使うことができないというのが厄介で、A,Bに移動してから演算する必要があり、他の言語での有力な解法の一つである「F判定用のアキュムレータとS判定用のアキュムレータを用意する」ができないという点で当初は無理ゲーと思われていた(少なくとも私は)。 これを

  • 入力を2乗してアキュムレータに足し込む。スートについては愚直にN(定数)回足し込むことで、F判定用の2乗和 * N + S判定用の2乗和を作る
  • Nで割るとF判定用の2乗和が得られる
  • 元の値をF判定用の2乗和(S判定用の2乗和の最大値より大きい)で割った余りがS判定用の2乗和
  • あとは適当にinc/decして片方を出力すれば楽になる

とすることで実装できそうという見解が @siotouto さんから出され、実際に @angel-p57 さんによりN=512として実装されたものがACしたことで、短縮が始められる段階となった。(なお、Nが256の倍数ならFとSがくっついたままでも"S"を出せるので、実装では↑のFとSの分離をやっていませんが、なるほどーと思ったので書いています)

362B版のコードは$(\sum d_i^2) \times 2 - 27$で"S"を、$|(\sum(s_i-9)^2) % 575 - 150| \times 14$で"F"を出すようなアルゴリズムになっている。(条件にマッチしないときは別の無害な文字が出る)

コードを整形すると以下のようになる。ループはインタプリタ実装上do/whileのように実装されているので、ループ条件指定子は]の手前にだけ書けばよいことに注意。

'<                ~ B = next char
[                 ~ do ... while(B > A(=0))
  kkkkkk          ~ B -= 6 (下と合わせる)
  [               ~ do ... while(B > loop(=4))
    kkkm          ~ B -= 3, B *= B
    +>+>...+>     ~ A += B を128回
    "<mk          ~ B = next int, B *= B, B -= 1
    +>            ~ A += B
    '<kkkkkk      ~ B = next char, B -= 6
  F]              ~ while(B > loop(=4))
  <-<+<kkkkkkkO   ~ B = 10, B = A - B, B = A + B, B -= 7, putchar(B)
  #aa/>m/>m/>     ~ A /= 128
  #aamamkmk       ~ B = 575
  [->|]           ~ while(A>B) A -= B
  #aaamaaamaaaaaa ~ B = 150
  ->x             ~ A = abs(A - B)
  #aammkk*>P      ~ B = 14, A *= B, putchar(A)
  kkkkO           ~ B -= 4, putchar(B)
  @'<             ~ A = 0, B = next char
_]                ~ while(B > A(=0))

128で割る部分以降を以下のように縮めると344Bになる。25を作ることで150を引く処理が短く書けるのがポイント。あと整数出力で改行文字が出せるのも面白い。

#aa/>m/>/>/>      ~ A /= 128 (1B増える代わりにB = 4になる)
am[->->]          ~ B = 25, A -= 2 * 25 * (25 + 1) = 1300 = 150 + 2 * 575
kk[[->F]->|]      ~ B = 23, while(A>B) A -= 23 * (23 + 1) + 23 = 575
sd+>+>P           ~ putchar(5**2 - 1 + 23 + 23 = 70)
@p'<              ~ 整数出力で改行文字が出る
_]

一つ触っていて気づいたバグ(?)として、]|]のように1文字以下の間隔で閉じカッコが並んでいると、外側の閉じカッコからジャンプするときに内側の開きカッコにジャンプしてしまうというものがある(閉じカッコから逆順にコードをなめていくところで無駄に戻っていて直近の閉じカッコを見落とす)。今回はちょうど間に挟むものがあって助かった。

Ada (GNU GNAT)

Blue Team (@ToE42, 387 bytes)

with Ada.Text_IO;use Ada.Text_IO;with Ada.Strings.Fixed;procedure m is a:String(1..10);c,d,e,f,g:Integer;begin Get(a);if a(1)=a(3)and a(1)=a(5)and a(1)=a(7)and a(1)=a(9)then Put("F");end if;c:=Integer'Value(a(2)&"")+2;d:=Integer'Value(a(4)&"")+2;e:=Integer'Value(a(6)&"")+2;f:=Integer'Value(a(8)&"")+2;g:=Integer'Value(a(10)&"")+2;if c*d*e*f*G=2520 then Put("S");end if;New_Line;m;end m;

(a1+2)*(a2+2)*(a3+2)*(a4+2)*(a5+2)=2520を満たすのが(1,2,3,4,5)の組しかないことを利用. ループには再帰を使用した. かなり冗長だが奪取されなかった.

Aheui

Green Team (@bx4ay, 168 bytes)

ハングルで書く2次元言語 (1次元のコードを書いてしまいすみません)。子音で命令、母音で読む方向を指定する。多数のスタックを行き来できるのが特徴。

밯빠바쟈헤차밯싹빠밯라파밯싹빠밯라파밯싹빠밯라파밯싹밯라삭밯따따따따발밦따밣따밝타라맣사다다다밣박다밠따다맣밯맣
  • Sの方針:数字5つの文字コードの積を233で割った余りを出力
  • Fの方針:2番目から5番目のアルファベットについて、最初のアルファベットを割った余りをとり(同じかどうかの判定)、それらの和に70(='F')を足して出力

AlphaBeta

Green Team (@kotatsugame, 59 bytes)

wJISHrEykiigvEFrErITHJtEykkiiigggvZWyJNYSSZiOLSGeddsLDLywJO

Red Team (@siotouto, 81 bytes)

SJCITJAtISZYUUUUUTZJyiQHsEmSEHrITTJAtEaHrISZYUZONZWyNYZTGkiirLSSyihhBtEaaHrLxcCLO
  • start(0-), getInput(10-), process(49-) の3箇所に分けてる
  • Fは'B'+(4回の比較の0, 1)で出力
  • Sはランク文字コードの2乗和+124で出力
  • goto先は100とかでもちゃんと終了する

APL

Green Team (@kotatsugame, 86 bytes)

⍪{' S'[1+×/4↓ω],' F'[1+5∈ω]}¨+/{'ABCD12345'∈ω}¨50 11⍴⍕⎕ARG[6]
)OFF

Arcyóu

Red Team (@siotouto, 111 bytes)

(%%(v(q)"\n")(F(i)(p(?(&(%(F(n)(=(_(v i(% "%d" n)))2))(_ 1 6)))"S" "")(%(F(d)(?(=(_(v i d))6)"F" ""))(' A B C D
  • 文字でイテレート出来ない・・・
  • 文字列(各文字)でスプリットして長さでチェック、
    • Sは5種類の文字で長さが2になってるか(&がall)
    • Fはどれかが達成すればFなので6=>Fにするmap
      • 今回は配列のまま出力して良いのでそのままpに
  • 末尾の)は省略出来る
    • 細かいポイントとして%%にしても省略する)が2個以上増えて得なことがある

Atlas

Blue Team (@saito-ta, 14 bytes)

!={[5++'S(}]+B

入力の各行を ! (sort) してから = (count) を取っている。 数字に該当する先頭から 5 文字分が [0,0,0,0,0] ならストレートなので、これの総和を 'S に加えている。 また、最後が 4 ならフラッシュなので、これを B に加えている。

Aubergine

Red Team (@angel-p57, 270 bytes)

=aa=aa=aa-aa=b1-BB+b1-BB+b1-BB+b1-BB+b1-BB=Ab+b1-BB+b1+b1=Bo=aa=bo=a1+a1+a1+aa+aa+aa+aa-ba=B1=ao=b1+b1+bb+bb-aB+b1+bi:ba=bi=aa-bi+b1+b1+b1=a1+a1+a1+aa-Ab+a1+a1+aa-a1+aa+aa-bb-B1:aB=b1+b1-ab+b1+bb+aB=oa-aB+ab+b1+ab+ab=b1+aB+b1+aB+b1+aB+b1+aB+b1+aB=bi=oa=a1+ai-ab=bi=oa-ib
  • 変数2つもあるし、ポインタ経由でメモリ扱えるし、命令は2オペランドRISCっぽいし、しかもIP(命令位置)とれるなんて、もはや汎用言語じゃん! (演算がロクに無いけど)と最終日に選んだ言語。
  • と言いつつ、データメモリがコードメモリと共用(!)であることに気付かず大苦戦。割と組むだけになってしまいました。
  • 戦略としては、F は不一致数をカウント ( 改行分の 1 が min )、S はメモリ上の5か所のフラグ ( 0 or 1 ) の総和で計算。

疑似コード

L0:
  m[1]=m[2]=m[3]=m[4]=m[5]=m[6]=0; m[0]=5; m[8]=getc();
  L1:
    m[getc()-48]=1;
    t=(fixed+3);  // fixed系はIPに依存した固定数値
    if ( getc()-m[8] ) goto L2;
    t=fixed;
    L2:
    m[6]-=t-(fixed+6)+3;
    m[0]-=1;
    if ( m[0] ) goto L1;
  putc(m[6]+58);
  putc(78+m[1]+m[2]+m[3]+m[4]+m[5]);
  putc(10);
  goto L0;
コメント入りコード整形版(そのままでは流せません)
# nop x3 で9個書き換え用の領域をallocate
=aa=aa=aa
# 全体ループ先頭
# メモリ1~6を0初期化、0を5で初期化、8にgetc()して最初のスート保存
-aa=b1-BB+b1-BB+b1-BB+b1-BB+b1-BB=Ab+b1-BB+b1+b1=Bo
# 行内ループ先頭(57 or 60へjump … 60の方が作り易いと思う)
# 数字入力し、対応したメモリ1~5に1をセット
=aa=bo=a1+a1+a1+aa+aa+aa+aa-ba=B1
# スート/改行入力し、メモリ8との差分を a にとる
=ao=b1+b1+bb+bb-aB
# スート違いならbに保存したIP+9先にjump
+b1+bi:ba
# else処理、IPでbを上書き
=bi=aa
# if/else合流、改めて b とIPの差を取り、スート一致ならメモリ6に3加算
-bi+b1+b1+b1=a1+a1+a1+aa-Ab
# ループ戻りのIP(60)をaに計算、メモリ0のカウンタを減らして非0ならjump
+a1+a1+aa-a1+aa+aa-bb-B1:aB
# ここから行末処理
# メモリ8の累積値(4回一致で+12)+58でF文字出力、その後aを78にセット
=b1+b1+b1+ab+bb+a1+aB=oa-aB+ab+b1+ab+ab
# 以降、メモリ1~5のフラグ値(0/1)をaに加算 ( 全部1で83 )
=b1+aB
+b1+aB
+b1+aB
+b1+aB
+b1+aB
# S文字・改行出力後、ループ先頭(6)へjump ( 10や6はIPの差分から作成 )
=bi=oa=a1+ai-ab=bi=oa-ib

後日更新版 (@angel-p57, 135 bytes)

=B1=bo-b1-bi=B1+B1:ab=aA=aA=b1=bB+ii__@CCCCCCCCCCC@CCCCCCCCCCCCCCC@oR-BA-AA-Ai+Ai-a1:bA=ab+a1+BA=oB-BB+a1=bA-aa-A1:bA+bi+b1=ii-bi=ob-ii

※コード中 @,C,R は実際は ^@(ASCII 0), ^C(ASCII 3), ^R(ASCII 18)

  • コード内の領域を変数・定数として活用できる点を突き詰め、組み直したバージョンです。
  • 疑似コード
    
    // 初期値:
    //  a=b=0 (仕様), *1=66, *2=49 (ダミー命令のコード)
    // *39~*49,*51~*65=全て3, *38=*50=*66=0, *67=111, *68=18
    *0=1;                        // =B1           ダミー命令
    L0:                          //               メインループ兼入力ループ先頭、この時 a=0
      b=getchar()-1-9;           // =bo-b1-bi     スート文字: 55~58, 数字: 39~43, 改行: 0
      *b=1+1;                    // =B1+B1
      if ( b!=0 ) goto L0;       // :ab           a=0を利用
      L18:                       //               S,F文字出力用ループ先頭、この時 a=0
        a=**a;                   // =aA=aA        2 or 1 → 49 or 66 
        b=*1;                    // =b1=bB        66
        goto L66;                // +ii           データエリア(38~68番地)のスキップ
        L66:                     //               出力用文字コード総和計算ループ先頭
          *b-=*a;                // -BA           2 or 3
          *a=3;                  // -AA-Ai+Ai     初期値3へリセット(39~49 or 51~66番地)
          a-=1;                  // -a1
          if ( *a!=0 ) goto L66; // :bA           b=66を利用
        a=b+1;                   // =ab+a1        67、この時 b=66
        *b+=*a;                  // +BA           +111
        putchar(*b);             // =oB           S文字 or F文字出力
        *b=0;                    // -BB           初期値0へリセット
        a+=1;                    // +a1           68
        b=*a;                    // =bA           18
        a=0;                     // -aa
        *a-=1;                   // -A1
        if ( *a!=0 ) goto L18;   // :bA           b=18を利用
      b-=8;                      // +bi+b1=ii-bi  18→10
      putchar(b);                // =ob           改行出力
      goto L0;                   // -ii
    
  • ストレート・フラッシュの判定はいずれも「現れた文字種の数」で賄います。そのため、「その文字が現れた」ことをフラグ(初期値3→2に変化)としてメモリ内に保持します。( 39~43,55~58番地 )
  • 改行が来た場合は、同様に 0番地に 2が保存されます。これは S,F 出力用のループカウンタとして使います。
  • S,Fそれぞれ出力用の文字は、番兵(値0のメモリ)に辿り着くまでメモリ範囲を降順sweepして総和計算を行います。
    • 結果の保存には 66番地 (初期値0)を使います。
    • また、sweepしつつ各番地での値を初期値3にリセットしていきます。
    • S文字: 49番地開始、38番地番兵、c=5, 0-3×(11-c)-2×c+111=83 で S になります。
    • F文字: 66番地開始、50番地番兵、開始位置が結果保存場所と被るため「初期値3、65番地開始」と同じ状況になり、c=1, 3-3×(15-c)-2×c+111=70 で F になります。
  • iレジスタは実行中のコード位置を操作できるので、ジャンプ命令的にも定数参照的にも使えます。
    • 例えば、33番地の +ii により i が66に変化、これが疑似コード中 goto L66 に相当します。( 実際には更に+3されるので69番地の命令に移ります )
    • iの値は+3ずつ変化するため、差分を計算することで小さな3の倍数を作るのに役立ちます。例えば最後の方の +bi******-bi のところでは、iの差分 9 の変化を b に与える効果があります。

Bash (pure)

Blue Team (@saito-ta, 68 bytes)

read a&&(F ${a//[1-5${a:8}]}
f=$_
12345
S ${_//[$a]}
echo $_$f
. $0)

$_ は、直前のコマンドの最後の引数(引数が無い場合はコマンド名)に展開される変数です。これを 3 箇所で使っています。 $0 は現在のスクリプト名を格納する変数です。これを . (source) することでループしています。

bed

Blue Team (@hatsusato, 70 bytes)

qawm,r2c-i*lr+iwm,rojr=*iqqbitj,rhx5$arohr=i46*iw.mlro[!i53*iw.m.q32$b

Befunge-98

Green Team (@ten986, 55 bytes)

v#S',+F'*3%y'_jd,a,
4
<-1_v#:p0:+1':
~@#*<3p0~ '+_v#-a:

青チームに3byte負けたものの、Befungeを活かした面白いコードができたので満足しています。勝ちたかったけど。 p命令を用いて、0行目の49~53文字目に文字を設置することでストレート判定をすることが1番の特徴です。 Befungeはスタックの入れ替えが難しい言語(topと1個下のswapしかできない)なので、ストレートの判定を盤面で行う方針にし、スタックから外してやることで、フラッシュの判定でスタックをフルに使えるため嬉しいです。 (ただ青チームの方針であれば、それぞれ2乗和を取ればいいので、swapだけで済むのですが・・・)

  • v 4
    • 初期化。スタックに4を積む
  • <-1_v#:p0:+1':
    • 初期化。0行目の49~53文字目にそれぞれ1 2 3 4 5 を設置する
    • 設置した文字は、最初から盤面にある文字同様踏むと対応する命令が実行される
    • 数字は対応する数字をスタックに積む命令
      • この数字をストレート判定時にそのまま踏むと、ゴミをスタックに積むためSを出力せずに済む
  • ~@#*<3p0~ '+_v#-a:
    • 入力と判定の前処理
    • ~@# で各行奇数文字目の入力、EOFは反射する
      • 前回のコードゴルフの時と仕様変わっててびっくりしました。前は-1を受け取ってたはず
    • _v#-a: で改行判定、10なら次の処理へ
    • 奇数文字目はフラッシュ判定に使う
      • ~ * 3 + が該当
      • 各桁x<-(x+ord)*3をしていきx%121*3+70を出力すればOKなので、その前処理
      • 初期値の0は、この行に来る際の_v#で生成されてる
    • 偶数文字目はストレート判定に使う
      • 1~5 をordで受け取り、「 ': 空白」「~:1~5のord」「0:0」の順に積みpすると、0行目の49~53文字目に空白を設置できる
      • 初期化時に設置した1~5を削除できる
      • ストレートの場合は1~5が1回ずつ出現するため、初期化時に設置した1~5すべてを削除できる
      • ストレート以外なら、どれか1つは削除できないものがある
  • #S',+F'*3%y'_jd,a,
    • 出力
      • ,+F'*3%y'_%121*3+70 をする
      • < でなく _ なのは、スタックにトップにあるゴミのpopを兼ねるため
      • S' で S を積む
      • 0行目の49~53文字目を通ることで、ストレート以外であれば1~5がスタックに積まれる
      • ,a, でスタックのトップと改行を出力

Blue Team (@shinh, 52 bytes)

<|-a:~@#
^>$'_%'#%'[\x19]%'F\-,++++"_[\x89]"*%'S+,a,
^>:*+~:*\
  • [\x19][\x89] は表示がつらいので置換しました。定数を作った感じです
  • F も S も自乗和を mod して 0 になったら OK というやつです
  • Befunge 好きなんですが Befunge-98 始めて触ったので色々便利なのがあるなぁという感想でした

(@ten986追記) 感想戦にて、++++4k+とできるため1byte減りました。

Bots

Red Team (@satos---jp, 162 bytes)

i(n,x,y){*n y j x}j(){ic r}r(x){-10 x?i s x}s(a,x,y){-x 344362200?od oc 83 f x y}m(x,y){/x y*y-x}f(x,y){m y 3604+20 m 55?od oc 70 oc 10}l(t){?t i@1 1 1-t 1 l}l 50

以下は読みやすくしたもの。

i(n,x,y){* n y j x}
j(){ic r}
r(x){- 10 x ? i s x}
s(a,x,y){- x 344362200 ? od oc 83 f x y}
m(x,y){/ x y * y - x}
f(x,y){m y 3604 + 20 m 55 ? od oc 70 oc 10}
l(t){? t i @ 1 1 1 - t 1 l}
l 50

アルゴルズムは積によるS判定と積とmodによるF判定。lがメイン関数、r,i,jで入力読み込み、s,fでS,F判定。 Botsを書くコツとして、基本演算ではスタックの2番目にしか返り値を入れられないので、より複雑な操作(例えばスタックの上2個を保存する)をしたい場合は関数を定義するとよい。

(nu4nu追記) 第7回でこの言語のファンになってしまったのでゴルフしてみました。 ↑をベースにS判定をmod 72345、プログラム終了条件を-1が読めたときにする等の修正したものが以下の138B(改行なしで)。

i(n,x,y){+n 1?*@n y j x}
j(){ic r}
r(x){-10 x?i s x}
m(x,y){/x y*y-x}
s(a,x,y){m x 72345?od oc 83 m y 3604+20 m 55?od oc 70 oc a}
l(){j 1 1 l}
l

mod(正確には-(x%y))の定義を嫌ってS判定とF判定を分けたものが以下の132B(改行なしで)。256以上の文字コードをocすると死んでしまうので、S側は数値として読んだ2乗和の2倍-27を出力している。EOF時idは0を返すので@判定がicより短くなるというのもポイント。

j(){ic r}
r(x){-10 x?i s x}
i(c,y,z){-c z?*+z 0 e y}
e(){id m}
m(d,z,y){?d*@d d+y j z}
s(a,y,z){+y y-27 oc?z oc od 70 oc a}
l(){ic e 0 l}
l

Brainfuck (esomer)

Blue Team (@soup-soup605, 346 bytes)

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

Red Team (@angel-p57, 114 bytes)

,+[-<<<<<+++++[->>>,-[-[-<->>+<]>[-<+>]<]>,>[-<-<+>>]<[[-]<<<+>>>]<[->>+<<]<<<<++>]-[------->+>+<<]>>[----.<]<.,+]
  • 各行、スート・数字を5回のインナーループで処理 ( 改行文字もスート扱いする )
  • S=Σ( -(s-1)(s-2)/2 )%256、F=不一致スート数 ( 改行分あるため最小 1 ) で、共に +69 で該当する文字に直せる
  • +69 は、-1/7 = 73 ( (256*2-1)/7=73 ) を適用、あとで -4 補正 (この処理が一括でできるのがウリ)
  • 改行文字 N=10 は、インナーループで +2 ずつして作っておく
  • 以下の整形版コード中、n が5組分のカウンタ、N が改行文字、F,S がF文字、S文字用の累積数値、s,d がスート・数字の入力文字、bが基準になる最初のスート
整形版コード
,+[-
 ** memory layout: NnFSsdb **
 b<<<<<n+++++[-
  >>>s,-[-[-<->>+<]>[-<+>]<]
  >d,>b[-<-<+>>]<d[[-]<<<F+>>>]<[->>+<<]
 <<<<N++>]
 n-[------->F+>S+<<]
 >>[----.<]
 <N.
,+]

braintwist

Red Team (@angel-p57, 219 bytes)

  • Brainfuck (esomer)の114B版、負番地に行かないように <,> を入れ替えて最後に +] を追加した116文字をエンコードして、100万未満のシードでできるMT乱数列と睨めっこして、6~7文字ずつ合うものを探しては残り xor してまた乱数列を探して…で作成したコード。( シード間の行数が乱数列の一致する長さに対応 )
  • 乱数列の探し方でそんなに差が出るとは思えないので、元のBrainfuckコードがほぼ全てという気も。
コード提出版

164041





18647






257156





197671





126871





756886





585318






47728





131165





94414





204275





356164





307763





760067





110911





24396





92514





403957





305571

BubbleSortLanguage

C (GCC)

Blue Team (@saito-ta, 63 bytes)

main(s,a){main(10/a?s=s&s-2^'m G',!puts(&s):s|1<<a,getchar());}

いわゆる main 再帰。いずれスタックがオーバフローすることによってプロセスが終了する。その際に出力のフラッシュが行われないことが問題になるが、出力の 51 行目以降が判定に影響しないことを利用し、ゴミを出力し続けることによってフラッシュを惹起している。(ここまで赤青共通) puts() が常に 0 を返すことを仮定し、これを利用して s を 1 にリセットしている。 起動時の main() の第二引数 a の値によっては正しく動かないのでは?という確率解疑惑があった。これについては、

  • Performance Checker で実験したところ、起動時の a の下位 4 ビットの値は必ず 8 になるようだ。
  • そうすると、 1<<a の値は 1<<0x08 又は 1<<0x18 ということになる。
  • 1<<0x08 の場合は、出力の 2 文字目に当たるので、問題ない。
  • 1<<0x18 の場合は、出力の 4 文字目に当たり、文字列として終端されないことになる。しかし、実験した限り、 1 行目が 4 文字を超えて出力されることはなかったため、問題ないと判断した。
  • ところで、起動時の a が確率的に 0x00000008 になることってあるんですかね… その場合、 10/a が確率的となって、まずいのかもしれない。

Red Team (@nu4nu, 75 bytes)

b;main(c){main(b=c&8?!printf("%c%c\n",b/375,70^b&b-1):b|1<<c,c=getchar());}

^'m G'を思いつけなかったのが全て。割り算で短くなって嬉しくなっていた。赤チームの「ビットマスクのLSBを常に立たせることでmain再帰の引数に持ってこられるようにする」というのも、一番下の1を消すイディオムb&b-1にとらわれていて思いつけなかった。

せっかくなのでスタックのASLRについてメモを残しておく。 今回のジャッジサーバーはx86_64のUbuntu Linuxが動いていて、カーネルバージョンは5.15.0-76-genericとなっている。(Performance Checkerにuname(2)を呼ぶCのコードを投げればわかる) スタックの仮想アドレス配置に関連するLinuxのソース(あまり昔から変わっていないようだが、一応v5.15を参照)はSTACK_RND_MASK, randomize_stack_top(), arch_align_stack(), およびそれらの呼び出し元のfs/binfmt_elf.cやfs/exec.cあたり。randomize_stack_top()がページサイズ(4KiB)単位のrandomizeで、arch_align_stack()が16B単位のrandomizeとなっている。STACK_RND_MASKは64bitでは0x3fffffで、乱数をこれでマスクした数のページ数分だけstack_topが移動するので、スタックの仮想アドレスはざっくり16GiBの振れ幅があることになる(コメントには1GBと書いてあるがどう見てもfが1つ多い)。Performance Checkerにmainの第2引数の値を出すようなコードを5回投げた結果がこんな感じで、アドレス下位34bit(ただし最下位4bitを除く)が振れている。

00007ffeb46cbc18 00007ffc2f800208 00007fff30d20f48 00007ffc96bc66e8 00007ffd6d240af8

ということで、mainの第2引数をintとして解釈したときに8になりうるか、という問いに関しては、ごくごく稀になりうる、というのが答えだと思われる。

cmd.exe

Green Team (@EtoNagisa, 1288 bytes)

コードを見る
set/pq=
call :f %q:~0,10%
call :f %q:~11,10%
call :f %q:~22,10%
call :f %q:~33,10%
call :f %q:~44,10%
call :f %q:~55,10%
call :f %q:~66,10%
call :f %q:~77,10%
call :f %q:~88,10%
call :f %q:~99,10%
call :f %q:~110,10%
call :f %q:~121,10%
call :f %q:~132,10%
call :f %q:~143,10%
call :f %q:~154,10%
call :f %q:~165,10%
call :f %q:~176,10%
call :f %q:~187,10%
call :f %q:~198,10%
call :f %q:~209,10%
call :f %q:~220,10%
call :f %q:~231,10%
call :f %q:~242,10%
call :f %q:~253,10%
call :f %q:~264,10%
call :f %q:~275,10%
call :f %q:~286,10%
call :f %q:~297,10%
call :f %q:~308,10%
call :f %q:~319,10%
call :f %q:~330,10%
call :f %q:~341,10%
call :f %q:~352,10%
call :f %q:~363,10%
call :f %q:~374,10%
call :f %q:~385,10%
call :f %q:~396,10%
call :f %q:~407,10%
call :f %q:~418,10%
call :f %q:~429,10%
call :f %q:~440,10%
call :f %q:~451,10%
call :f %q:~462,10%
call :f %q:~473,10%
call :f %q:~484,10%
call :f %q:~495,10%
call :f %q:~506,10%
call :f %q:~517,10%
call :f %q:~528,10%
call :f %q:~539,10%
exit /b
:f
set t=%1
if %t:~0,1% == %t:~2,1% if %t:~2,1% == %t:~4,1% if %t:~4,1% == %t:~6,1% if %t:~6,1% == %t:~8,1% set /px=F<nul
set /as=%t:~1,1%+%t:~3,1%+%t:~5,1%+%t:~7,1%+%t:~9,1%
set /ap=%t:~1,1%*%t:~3,1%*%t:~5,1%*%t:~7,1%*%t:~9,1%
if %s% == 15 if %p% == 120 set /px=S<nul
echo 1
exit /b

書いただけ.forループが難しすぎる. はじめは下の方にある:fの下の部分を50回コピペしていたが,コード長制限で怒られたため関数に切り出して呼び出しを50回コピペした.なぜかforループ中でcallをするとsyntaxerrorと言われて険しい気持ちに 実際はjumpだからダメとかなのか・・?

(nu4nu追記) forループは試行錯誤の結果以下のような記述でうまくできた。

set/pq=
for /l %%i in (0,11,539) do call :f %q% %%i
exit /b
:f
set t=%1
call set t=%%t:~%2%%
(以下上の1288Bと同じtに関する判定式)

substringを作る記述をサブルーチンの中に追い出すのが一つのポイント。forループ側でいろいろやろうとするとsubstringを作ったつもりが元の文字列になっていたり謎が多い。またsubstringのindexを変数にするにはcall setというおまじないが必要らしい。このあたりのテクはSS64.comに詳しく書いてある。

ただ、forループでがんばるよりも、入力を削っていって空になったら終わりになるようなgotoループのほうが圧倒的に短い。その他の短縮込みで214Bになった。(2行目は末尾にスペースが入っていることに注意)

set/pt=
set t=%t% 
:l
set/ap=1%t:~1,1%*1%t:~3,1%*1%t:~5,1%*1%t:~7,1%*1%t:~9,1%
if %p%==360360 set/px=S<nul
set s=%t:~,1%%t:~2,1%%t:~4,1%%t:~6,1%%t:~8,1%
if %s:~,4%==%s:~1% set s=
echo %s%
set t=%t:~11%
goto%t:~-1%l

S側の判定は数字に10を足したものの積の比較としている。文字列と数値の境界が曖昧な言語ならでは。==360360%58==6で判定したほうが短くなるように思えるが、%をエスケープするために^%%58と書く必要があり短くならない。 F側はスート文字だけ拾ってきたものを一つずらして比較。s=Fでもよいが、echoの引数が空だとEcho is OFFが出て、Fがダブって出てもACというジャッジ仕様で1B短くできている。 終了は%t%が空になったときにgotoしなくなるようにして実現している。ちょうどset/pの仕様か何かで入力最後のLFが消えていて、何かを補わないと空になってくれない(%t:~11%%t%が10文字以下のとき何もしない!)ので、そこにスペースを埋めてgoto時に参照している。最初はexit%t%と書いて空になったときに抜けるようにしていたが、その行とスペースが消せるだけこちらのほうが短い。

今回は使わなくてもなんとかなったが、forのデリミタや文字列置換にLFを指定する方法がさっぱりわからなかった。問題によっては苦労することがあるかもしれない。

(さらに追記) 文字列置換を駆使すると166B。F側は先頭文字と同じ文字を消して6文字目をechoすることで、フラッシュのときはLFかスペースに展開されてEcho is OFFが出るようにしている。出力を2回に分けているのがもったいなく思えるが、Echo is OFFに依存しないようにするにはFを仕込む必要がありこれより短くできなかった。

set/pt=
set t=%t% 
:l
set/ap=1%t:~1,1%*1%t:~3,1%*1%t:~5,1%*1%t:~7,1%*1%t:~9,1%
set/px=%p:603=S%<nul
call set s=%%t:%t:~,1%=%%
echo %s:~5,1%
set t=%t:~11%
goto%t:~-1%l

Compile-time TypeScript

Blue Team (@n4o847, 203 bytes)

type Y<P,Q=any>=`${any}${P}${Q}`
type M<I,T=``>=I extends`${infer P}${infer Q}
${infer R}`?M<R,`${T}${Q extends Y<1>&Y<2>&Y<3>&Y<4>&Y<5>?"S":0}${Q extends Y<P,Y<P,Y<P,Y<P>>>>?"F":0}
`>:T
export default M

末尾再帰。スートに交差型、数字に合併型を使ってうまくやりたかったができず、赤チームがきれいにやってくれていたので完敗。

Red Team (@kurgm, 188 bytes)

type M<X,O=0,P={},Q=0>=X extends`
${infer R}`?M<R,`${O}${`${1|2|3|4|5}`extends Q?'S':0}${[P]extends[0]?0:'F'}
`>:X extends`${infer A}${infer B}${infer R}`?M<R,O,P&A,Q|B>:O
export default M

整形 & 読みづらいので改行を \n にした

type M<X, O = 0, P = {}, Q = 0> =
X extends `\n${infer R}` // 先頭が改行文字 = 行末まで処理した
  ? M<R,
      `${O}${
        `${1 | 2 | 3 | 4 | 5}` extends Q ? "S" : 0
      }${
        [P] extends [0] ? 0 : "F"
      }\n`>
  :
X extends `${infer A}${infer B}${infer R}`
  ? M<R, O, P & A, Q | B>
  : O; // 終端まで処理したので結果を返す
export default M;

TS 4.5 から入った末尾再帰最適化を活かして再帰回数の上限に引っかからないようにしている。

  • 2文字ずつ読んで、スートは交差型 &、数字は合併型 | で reduce する
    • スートが全部同じ(F条件)ならreduce結果はそのアルファベットになる。違うのがあれば never になる
    • 数字はS条件のとき "1"|"2"|"3"|"4"|"5" になる
  • 初期値の P=0 は本来は交差型の単位元 unknown だが、文字数節約のため {} (= null, undefined 以外) にしている
  • 初期値の Q=0 は本来は合併型の単位元 never だが、文字数節約のため 0 にしている
  • Pnever かの判定について。 union distribution の関係で extends の左側を P だけにはできない。extends の右側は本来 [never] だが文字数の削減を図って [0] にしている

`${O}${ の部分はエディタでは赤線が引かれるのだが普通に通るらしい(finalさんの気付きにより判明)

Convex

Green Team (@kotatsugame, 25 bytes)

qN/{$5/:Å:,~69+c\78+cN}%

Crystal

Blue Team (@shinh, 59 bytes)

while l=gets;p [l=~/^(.)(.\1)*.$/&&:F,l=~/(\d).*\1/||:S]end

Red のチームとの差分を見ると、 Blue は A1B1C1D1A1 に F と出るので使ってなかった正規表現を使ってそう→と思ったが、僕自身も他のところで使っていた……

Red Team (@rotary-o, 58 bytes)

while s=gets
p [s=~/(\d).*\1/||:S,s=~/(.)(.\1){4}/&&:F]end
  • S、Fともに正規表現で判定(同じ数字が5つでもFになってしまう…)
  • while s=getsだとString?からStringになるらしい( https://kotatsugame.hatenablog.com/entry/2020/09/21/230023 )
  • 余計な文字を出力してもよいことを利用して、pで出力
  • ほぼRuby 3と同じ

C# (.NET Core)

Blue Team (@saito-ta, 本質@drafear, 116 bytes)

using C=System.Console;for(int s=0,a;(a=C.Read())>0;s|=1<<a)if(a<11)C.WriteLine(((s&s-2&30)>0,(s>>=12)>991?"S":""));

解法は Ruby 0.49 と似たような考えなので、C#特有のことについて:

  • 型を指定すると未初期化宣言OK
  • 1<<651<<1, 1<<491<<17 と同じ
    • s>>=122,4,6,8,16 のためにギリギリを攻めていて、ギリギリを攻めることによって 991 とギリギリ3桁でおさまった
  • EOF に到達しても C.Read() はエラーにならず、0を返す
  • WriteLine("書式", "引数1", "引数2", ...) の書式に (Bool,int) のtupleを渡すとエラーになるのに、(Bool,String) だとなぜか通る

Red Team (@rotary-o, 120 bytes)

using C=System.Console;for(int b,i=0;(b=C.Read())>0;i|=1<<b)if(b<11)C.WriteLine((i>31<<17?"S":"")+" F"[1>>(i&i-1)],i=0);
  • 1文字ずつ読んで、ビット列に保存
  • S、Fともにビット列で判定(nu さんのCがベース)
  • 片方は三項演算子ではなく" F"[...]でcharで取得(両方charにすると文字列にするのに""+が必要になるので片方だけ)
  • Console.WriteLine()で第1引数がstringの場合は、第2引数以降にフォーマット用のオブジェクトを指定するが、使われないものを指定してもよいので、if(...){}を省略するために、i=0を第2引数にしている
  • Blue TeamのTupleにして出力は思い付かなかった

Csound

Red Team (@ikubaku, 334 bytes)

<CsoundSynthesizer>
<CsInstruments>
instr 1
l:
iA[]tab2array 1,p3,p3+11
iA sorta iA
fprints"output.out",(iA[1]*iA[2]>>16)*(iA[3]*iA[4]>>16)*(iA[5]>>8)%72345==0?"S":" "
fprints"output.out",iA[6]==iA[10]?"F":" "
fprints"output.out","\\n"
loop_lt p3,11,550,l
endin
</CsInstruments>
<CsScore>
f 1 0 0 -1"input.in"0 -1 0
i 1 0 0
</CsScore>

まずデータ入力部分について説明します。 CSound esolang-boxでは標準入出力の操作が困難なため、入力データはcwdの"input.in"、出力データは"output.out"に保持することになっています。

データの入出力はこれまでの大会での結果から、入力をfunction tableの生成ルーチンのパラメータとして扱い、出力をfprintsによって行うことがコード長の観点から最適であるとわかっているので、これらの手法を使います。 しかしこれまでの大会では入力データが整数のみであったのに対し、今回は'A'-'D'の英字を含んでいます。CSoundのfunction table生成ルーチンGEN23では文字列や16進整数をパースすることができないので、function tableに入力データを読み込むに当たってGEN23を使うことはできません。

そこでここでは生成ルーチンとしてGEN01を使います。 GEN01は読み込み元ファイルを生の音声データとして扱い、その音声データをfunction tableに保存します。 GEN01にはいくつかのパラメータがありますが、今回重要なのは次の2つのパラメータです。

  • iskip: 音声ファイルの読み込みオフセット(先頭何秒を読み飛ばすか)を指定します。
  • iformat: 生音声データのエンコード方式を指定します。

ここでは最初からデータを読み出したいので iskip を0にしておきます。 iformat は-1の"8-bit signed character"を指定します。このようにすると1byteずつ16bit符号付き整数の音声サンプルと解釈されてfunction tableに格納されます。入力データのあるbyteを x とすると、function tableに格納されるサンプル y は次のC言語プログラムで表されます。

int8_t x; int16_t y;
y = (int16_t)x * 256

したがってfunction table 1に入力データを格納するためのf文は次のようになります。

f 1 0 0 -1"input.in"0 -1 0

次にinstrument定義の部分を説明します。 このコードで採用した判定アルゴリズムは、入力データ s を昇順にソートして s[1]*s[2]*s[3]*s[4]*s[5] % 72345 == 0 ならストレート、 s[5]==s[9] ならフラッシュとする方法です。

まずfunction table 1を11サンプルずつなぞるループを書きます。 function tableのサンプルを特定のウィンドウを使ってなぞるには tab2array opcodeのオプション引数 istartiend を使います。 それぞれ開始サンプルのインデックスと終了サンプルのインデックス+1を指定すればOKです。 こうすることで返り値の配列には指定した範囲のサンプルが格納されます。 またウィンドウ位置指定+ループカウンタとして余っていたinstrumentのパラメータ p3 を使っています。 CSoundのinstrument定義内ではscoreからinstrumentに与えられるパラメータ pn をi型の変数としてあつかうことができます。 これらを使うことでグローバル変数定義のための文字数を稼ぐことができます。 function tableを特定のウィンドウで区切らずなぞる方法もありますが、添字部分が長くなるので採用しませんでした。

sorta opcodeは配列を昇順にソートするopcodeです。元の入力データに対して下駄がついている+符号付きであるという状態ですが、この時点でサンプルが負になるようなケースがないことと、各データに下駄がついていてもソート結果には影響がないことからこの処理だけで要求を満たします。

その後はデータについている下駄とオーバーフローに注意しながら愚直に判定し、結果を出力します。 判定結果が偽であった場合もスペースを出力しているのは、fprintsで空白文字を出力するような制御パスがある場合に起こる問題を回避するためです。(以前出力した結果がくっついてしまう場合がある。処理系のバグ?)

Cubix

Green Team (@kotatsugame, 74 bytes)

.o..>F...'.@^^!->i?>1&%S/?\..>Noivq^'p<;-p..^<>.*i>&%o^;S>&q;;^.>v^!'2;<i

GNU ed

Red Team (@angel-p57, 48 bytes)

,s/^\(.\)\(.\1\)*.$/&F
v/\([1-5]\).*\1/s/./S
wq

  • 全行対象のフラッシュ対象行 F 付与置換、否定マッチのアドレス指定での一文字 S 置き換え、共に正規表現系の言語と共通と思います。
  • 実は sed 45B の実装が把握できず最初は短縮できてなかったのですが、rotary-o さんが実装を見つけられたため ed に適用できました。

Blue Team (@hatsusato, 48 bytes)

,s/^\(.\).\(\1.\)*$/&F
v/\([1-5]\).*\1/s/$/S
w
q

Egison

Green Team (@ten986, 368 bytes)

def v z:=match z as multiset char with
|#['1','2','3','4','5']->"S\n"
|_->"\n"
def w z:=match z as list char with
|[$s,#s,#s,#s,#s]->"F"
|_->""
def k z:=match z as list char with
|[$a,$b,$c,$d,$e,$f,$g,$h,$i,$j,_]->appendString(w[a,c,e,g,i])(v[b,d,f,h,j])
def i ():=io(readChar())
def main x:=let s:=[i(),i(),i(),i(),i(),i(),i(),i(),i(),i(),i()]in
do
write(k s)
main 1

Blue Team (@drafear, 151 bytes)

def u l:=length(unique(l))+69
def main n:=do
write(pack(map itoc((\l->[u(take 5 l)+9,u(drop 5 l),10])(sort(map ctoi(unpack(io(readLine()))))))))
main 1

Red Team (@satos---jp, 142 bytes)

def main x:=let s:=unique(unpack(io(readLine())))in
let d q c:=itoc(length(union s(unpack q))+c)in
do
print(pack[d"ABCD"74,d"12345"64])
main 1

uniqがあるので文字種数を手軽に計算できS,F判定ができる。Stringライブラリは貧弱なのでpack/unpackによるstring<->list char変換とitoc/ctoiによるchar<->int変換が便利。printを使うと勝手に改行してくれる。

Element

Green Team (@EtoNagisa, 44 bytes)

50'[_5'[(,+.(2+"*']"12600=[S`]271%'![F`]\
`]

通常のスタックと,条件分岐に用いるコントロールスタックという2つのスタックをもつ言語 Sのほうは2を足した積が2520になればOKという方針 Fのほうはordを結合して271で割り切ればOKという方針 入力の文字列の先頭から一文字ずつ切り取り,結果を入力文字列の後ろにくっつけていくことでスタック操作の回数を減らせた コントロールスタックをうまく使うと,rotateが減らせる.rotateは4byteかかるので高すぎる forループは,コントロールスタックの先頭を見てその回数だけ繰り返すという仕様なので,ループの中に入ったあとはコントロールスタックの先頭は捨ててもいい.今回は,ループスタックの先頭を積を保存するために使っている.

型がガバガバなので,いろいろなことができる.スタックから無を取り出しても怒られないし,無と演算をしても大丈夫.

Blue Team (@drafear, 58 bytes)

50'[_10'[(,2^4 0@+0 2@#]0 2@13015%83+,#`95%35%25%-70+,#``]

Emojicode

Red Team (@kurgm, 298 bytes)

🏁🍇🔁👍🍇🎶🆕🔡▶️👂🏼❗️❗️➡️🖍🆕s🦁s🍇a🔡b🔡➡️🔢↩️↔a b❗️🍉❗️↪️🎼🆕🔡s🔤🔤❗️🔤12345🔤❗️🍇👄🔤S🔤❗️🍉↪️🐽s 5❗️🙌🐽s 9❗️🍇👄🔤F🔤❗️🍉😀🔤🔤❗️🍉🍉

整形

🏁 🍇
  🔁 👍 🍇
    🎶 🆕🔡▶️👂🏼 ❗️ ❗️ ➡️ 🖍🆕 s
    🦁 s 🍇 a🔡 b🔡 ➡️🔢
     ↩️ ↔ a b ❗️
    🍉 ❗️
    ↪️ 🎼 🆕🔡 s 🔤🔤 ❗️ 🔤12345🔤 ❗️ 🍇
      👄 🔤S🔤 ❗️
    🍉
    ↪️ 🐽 s 5 ❗️ 🙌 🐽 s 9 ❗️ 🍇
      👄 🔤F🔤 ❗️
    🍉
    😀 🔤🔤 ❗️
  🍉
🍉

擬似コード

main {
    while true {
        let mut s := String.new_readline().toGraphemesArray();
        s.sort({|a: String, b: String| -> Int
            return a.compareTo(b);
        });
        if String.new(s, "").beginsWith("12345") {
            print("S");
        }
        if s.getAt(5) == s.getAt(9) {
            print("F");
        }
        println("");
    }
}

Emojicodeは構文とかリファレンスがちゃんと整備されている神言語です(個人の感想)

あまり狙われていなかったっぽいのでゴルフはしてないです

  • 文字列から直接インデックスアクセスはできない。いったん書記素(文字列)の配列に変換する必要がある
  • 無限ループにしているが、最後は s.getAt(5) のところでエラーになる。エラー内容が stdout に出るけど51行目以降が無視される仕様に救われる

emojifunge

Green Team (@ten986, 512 bytes)

コードを見る
⬜️🔤💕❕⤵️🙃ℹ️🙃🔤🙃ℹ️🙃🔤🙃ℹ️🙃🔤🙃ℹ️🙃🔤🙃ℹ️🙃📥📤💕📥📏📤✖️📥📤💕📥📏📤✖️📥📤💕📥📏📤✖️📥📤💕📥📏📤✖️📥📤❕❕6️⃣➕🔟✖️🔡2️⃣➕5️⃣🏗2️⃣➕5️⃣🏗2️⃣➕5️⃣🏗2️⃣➕5️⃣🏗2️⃣➕5️⃣🏗✖️✖️✖️✖️4️⃣7️⃣9️⃣🔟✖️✖️✖️📏®️➕🔡🔤🔡0️⃣0️⃣✴️
⬜️⬜️⬜️⬜️🔚

盤面上争う必要性が薄かったため、ゴルフしていない。 第7回の方針をパクって計算を辻褄合わせたくらい。

Foobar and Foobaz and Barbaz, oh my!

(hiromi_miよりコメント) 言語機能が不足し回答不能と判断しました。差し替えました

Fish (pure)

Blue Team (@nakario, 82 bytes)

set b string replace -r
$b 'S(.)(.\1){4}' 'F$0' S(read -z)|$b 'S(.*(\d).*\2)' '$1'

readの-zオプションを使うと区切りがnull文字になるので全入力を一気にとってこれる。その呼び出し側にSをつけると最初の行だけでなくすべての行の頭にSがつく。これはコマンド呼び出し結果 (read -z)改行区切りで配列として解釈され、配列は展開時に前後の隣接する文字を伴って展開されるため。 replaceは-qオプションを付けない限り出力もしてくれるのでechoも不要。 RedチームのXSLTの手法を取り入れると77bytesまで縮んだ。

set b string replace -r
$b '(.)(.\1){4}.*' 'F$0' (read -z)S|$b '(\d).*\1.*' x

Green Team (@kotatsugame, 91 bytes)

set f string match -r
while read s
echo ($f '(\d).*\1|S' "$s"S) ($f 'F(.)(.\1){4}' F$s)
end

F# (.NET Core)

Blue Team (@n4o847, 111 bytes)

let rec f t=let s=Set(stdin.ReadLine())in printfn"%A"("_S"[(s-t).Count/5],"_F"[(t-s).Count/3]);f t
f(set"ABCD")

F# において stringchar seq として扱えることを利用した集合演算で、OCaml のように各文字コードを見ていく当初の方針よりも短くなった。

s を入力行の文字集合、tset ['A'; 'B'; 'C'; 'D'] とし、s - t (数字) が 5 種類なら 'S't - s (スート) が 1 種類なら 'F' を出力する。

C# 同様、タプルで出力すると少し短い。

Red Team (@sh-mug, 115 bytes)

let rec($)a b=let d=stdin.Read()-2 in d<0||d=8&&(printf"%c%c
"(char(a%41%26*5))(char(b%89+2));1$1)||b$a*d
let _=1$1

Go

Red Team (@rotary-o, 135 bytes)

package main
import."fmt"
func main(){for{s,i:="",0;Scan(&s)
for _,b:=range s{i|=2<<(b%32)}
Printf("%c%c\n",i>>16^47,i&(i-1%i)&60|70)}}
  • 1行ずつ処理で、1文字ずつビット列に保存
  • S、Fともにビット列で判定(nu さんのCがベース)
  • 終了条件を書かずに、%iでのゼロ除算例外で終了
  • ビット演算の優先順位が高いようなので括弧をつけている
  • 初めて書くのでよく分かっていない(Javaから移植しただけ)

Blue Team (@drafear, 140 bytes)

package main
import."fmt"
func main(){s:=0;for{a:=0;Scanf("%c",&a);if a==10{s>>=39;Printf(`%c%c
`,114-s%32,70-(s-32)&s>>9);}
s|=1<<(a-10);}}

golfish

Blue Team (@shinh, 57 bytes)

Green Team (@bx4ay, 50 bytes)

(実際はバイナリ)

01 c0 ed ec ff c0 c0 c0  01 c2 01 21 c3 01 12 c3
01 21 18 c3 01 12 c3 01  21 18 c3 01 12 c3 01 21
18 c2 01 12 df dd 11 d7  11 14 05 d7 da 12 10 05
01 05
  • Sの方針:数字5つの文字コードの積を233で割った余りを出力
  • Fの方針:2番目から5番目のアルファベットについて、最初のアルファベットと異なるものがあればG、そうでなければFを出力

GolfScript

Blue Team (@saito-ta, 22 bytes)

n%{$5/{.&,78+}/9-]n+}%

入力の行ごとに、ソートして、 5 文字ずつに分けて、それぞれ、文字の種類を数えて、それに 78 を足す。 後のほうからはさらに 9 を引く。するとあら不思議。

gs2

Blue Team (@saito-ta, 13 bytes)

ソース:

line-mode
sort
5
/
    group
    length
m2
"NE"
    +
z1

やっていることは、すぐ上にある GolfScript (22) とほとんど同じです。

Hanoi_Stack

Blue Team (@n4o847, 2812 bytes)

以前作った生成コード を使用。とりあえず通すことが重要なため、最初は 50 回のループを愚直に生成したら、バイト数制限に引っかかった。インタプリタにデバッグ機能をつけて確認しつつ、外側のループだけちゃんと書いて提出。その後赤チームに周囲を取られ、アクセスできなくなった。

2 bytes 以上の演算をしようとすると結果の上の方のバイトが失われるというバグ がある。

Red Team (@satos---jp, 204 bytes)

最初に入力が反転してstackに積まれるのでひっくり返す必要がある。byte長が2以上の演算がバグっているという噂を耳にしたが、1byte演算で事足りるアルゴリズムを使った結果それには出くわさなかった。インタプリタに現在のスタックと次の命令を出力する機能が欲しい。

Haskell

Green Team (@bx4ay, 76 bytes)

m@main=do f<-filter.flip notElem<$>getLine;print[f"12345S"!!0,f['A'..]!!4];m

main再帰で各行ごとに処理を行った。

  • "12345S"と各行の差集合をとって0番目の要素を出力(12345が全て含まれるときSを出力)
  • "ABCDEF..."と各行の差集合をとって4番目の要素を出力(ABCDのうち1つだけが含まれるときFを出力)

Blue Team (@n4o847, 78 bytes)

m@main=do h:x<-getLine;print$['S'|all(`elem`x)"12345"]:drop 3["F"|c<-x,c==h];m

ベースは shinh さんが書いたものを縮めた。緑チームと競い合っていたが、結果完敗……。行を複数回使う場合ポイントフリーにしづらく、do 記法でバインドするのがなんだかんだ短い。

ImageMagick

Red Team (@Muratam, 278 bytes)

convert ( -size 1x1 -depth 8 gray:- ) ( -resize '541x1!' -fx 'sc=0;xa=u[i];xb=u[i+2];xc=u[i+4];xd=u[i+6];xe=u[i+8];rr=u[i];rr=i%11==1&&xa!=xb&&xb!=xc&&xc!=xd&&xd!=xe&&xa!=xc&&xa!=xd&&xa!=xe&&xb!=xd&&xb!=xe&&xc!=xe?.324:rr;i%11==0?(xa==xb&&xb==xc&&xc==xd&&xd==xe?sc:.273):rr' ) -

二年前に書いた記事のやつが全然動かなくなってた...。 記事や過去は -resize を後に書いても動いていたが、前に書かないと u[i] が意図通り動いてくれなくなっていた。なぜかこのバージョンのImageMagickはいたるところがバグっていて、三項演算子(?:)の分岐が逆になることがあったり(は?)、変数宣言をするかどうかで結果が変わったり(は?)、三項演算子の中で三項演算子を使うと結果が宇宙的に変わったりする。例えばi%11==0?(xa==xb&&xb==xc&&xc==xd&&xd==xe?sc:.273):rrはフラッシュ判定ですが、全部同じなら無効文字を出すとなってミスってるように見えるが、実はなぜかバグって三項演算子の逆転が発生するので逆になって通ります。sc=0;の定数値の変数の利用も不要に見えますが、省略して直接値を書くと何故か結果が変わるので消せない...。条件式もだいたい&&で結合するとバグってうまく動かなくなるので無限に言い換えを試すと通るやつがあって、それがこのコードです。

なんでこんなバグるのか考えたけどきっとみんなImageMagickを画像処理にしか使ってなくて文字列処理用途としてのImageMagickはまったく保守されていないんだろうな...。うーん、まともに動いていた2年前のImageMagickにバージョン固定しておいたほうがいいですねこれは...。

hiromi_mi追記: 今回は v7.1.1-12 が使われました

Version: ImageMagick 7.1.1-12 Q16-HDRI x86_64 21239 https://imagemagick.org
Copyright: (C) 1999 ImageMagick Studio LLC
License: https://imagemagick.org/script/license.php
Features: Cipher DPC HDRI OpenMP(4.5) 
Delegates (built-in): 
Compiler: gcc (9.4)

IRC

Green Team (@ten986, 689 bytes)

コードを見る
* main has joined #code
* x has joined #code
* y has joined #code
* s has joined #code
* main sets mode: +v x
* main sets mode: +v y
* main sets mode: +v s
* main changes topic to 'a'
<input> Hey, x.
<if> x, are you equal to 0?
* main has quit
<s> I'm 1.
* main changes topic to 'b'
<input> Hey, y.
<s> I'm s times y.
<input> Hey, y.
<if> y, are you equal to 10?
<jump> Let's talk about c.
<if> y, are you not equal to x?
<x> I'm 99.
<jump> Let's talk about b.
* main changes topic to 'c'
<if> x, are you less than 98?
<x> I'm 70.
<s> I'm s divided by 4494.
<output> What's your character, s?
<output> What's your character, x?
<output> What's your character, y?
<jump> Let's talk about a.

@bx4ay さんのコードを気合いで縮めた。ロジックは分かってない。 終了は * main has quit IRC (Quit: ) ではなく * main has quit でも行けるらしい。 後日感想戦で@nu4nuさんによって*でいいことも分かった。今後のコードゴルフの定石になりますね。

Red Team (@satos---jp, 692 bytes)

コードを見る
* main has joined #code
* c has joined #code
* f has joined #code
* s has joined #code
* main sets mode: +v c
* main sets mode: +v f
* main sets mode: +v s
* main changes topic to 'h'
<s> I'm 1.
<input> Hey, f.
<if> f, are you equal to 0?
* main has quit IRC (Quit: )
* main changes topic to 'g'
<input> Hey, c.
<s> I'm s times c.
<input> Hey, c.
<if> c, are you not equal to f?
<f> I'm f times c.
<if> c, are you not equal to 10?
<jump> Let's talk about g.
<c> I'm 83.
<if> s, are you equal to 344362200?
<output> What's your character, c?
<c> I'm 70.
<if> f, are you less than 999?
<output> What's your character, c?
<c> I'm 10.
<output> What's your character, c?
<jump> Let's talk about h.

nu4nuさんから『Egisonが取れたときにどうぞ』と託されていたコードを提出したもの。 (nu4nu追記) 演算や制御のコストが高いので短い式で計算する必要がある(nu4nuは最初二乗和==13015のコードを書いていたが、satos---jpさんに積==344362200の形に直してもらった)。labelやjumpを減らすために改行コードの判定をループ末尾に持ってきている。 最後緑チームに(Quit: )の省略で抜かれてしまったが、これ実は*だけの行でもよくて、要は何にもマッチしない行を書いてインタプリタを異常終了させればよい。また、緑チームの4494で割って直接文字を出す(PHPのchrは256でmodを取ったものを出してくれる)手法を使うと、cを発言させなくできるので* main sets mode: +v cを消せる。これらを合わせると以下のような609Bのコードが得られる。

609Bのコード
* main has joined #code
* c has joined #code
* f has joined #code
* s has joined #code
* main sets mode: +v f
* main sets mode: +v s
* main changes topic to 'h'
<s> I'm 1.
<input> Hey, f.
<if> f, are you equal to 0?
*
* main changes topic to 'g'
<input> Hey, c.
<s> I'm s times c.
<input> Hey, c.
<if> c, are you not equal to f?
<f> I'm f times c.
<if> c, are you not equal to 10?
<jump> Let's talk about g.
<s> I'm s divided by 4494.
<output> What's your character, s?
<s> I'm 70.
<if> f, are you less than 999?
<output> What's your character, s?
<output> What's your character, c?
<jump> Let's talk about h.

Clone this wiki locally