スタック猫、62 + 4 = 66バイト
*(>:^]*(*>{<-!<:^>[:((-<)<(<!-)>>-_)_<<]>:]<]]}*<)]*(:)*=<*)>]
-ln
コマンドラインフラグ(+4バイト)を使用して実行する必要があります。0
合成数および1
素数の印刷。
オンラインでお試しください!
これは最初の重要なStack Catsプログラムです。
説明
Stack Catsの簡単な紹介:
- Stack Catsは、現在のスタックを指すテープヘッドで、スタックの無限のテープで動作します。すべてのスタックは、最初は無限のゼロで満たされています。私は一般にこれらのゼロを言葉遣いで無視しますので、「スタックの一番下」と言うときは最低のゼロ以外の値を意味し、「スタックは空です」と言うときはゼロしかありません。
- プログラムが開始する前に、a
-1
が初期スタックにプッシュされ、次に入力全体がその先頭にプッシュされます。この場合、-n
フラグにより、入力は10進整数として読み取られます。
- プログラムの最後に、現在のスタックが出力に使用されます。ある場合
-1
一番下に、それは無視されます。繰り返し-n
ますが、フラグのために、スタックからの値は単に改行で区切られた10進整数として印刷されます。
- Stack Catsは可逆的なプログラム言語です。すべてのコードを元に戻すことができます(Stack Catsが明示的な履歴を追跡する必要はありません)。より具体的には、コードの一部を反転するには、単にミラーリングします。たとえばに
<<(\-_)
なり(_-/)>>
ます。この設計目標は、言語に存在する演算子と制御フローの構成要素の種類、およびグローバルメモリ状態で計算できる関数の種類にかなり厳しい制限を課します。
さらに、すべてのStack Catsプログラムは自己対称である必要があります。上記のソースコードには当てはまらないことに気付くかもしれません。これが-l
フラグの目的です。中央の最初の文字を使用して、コードを暗黙的に左にミラーリングします。したがって、実際のプログラムは次のとおりです。
[<(*>=*(:)*[(>*{[[>[:<[>>_(_-<<(-!>)>(>-)):]<^:>!->}<*)*[^:<)*(>:^]*(*>{<-!<:^>[:((-<)<(<!-)>>-_)_<<]>:]<]]}*<)]*(:)*=<*)>]
コード全体で効果的にプログラミングすることは、非常に簡単で直感的ではなく、人間がどのようにそれを行うことができるかはまだ実際にはわかっていません。このようなプログラムを単純なタスクのために強引に強制しましたが、手作業でその近くに到達することはできませんでした。幸いなことに、プログラムの半分を無視できる基本的なパターンが見つかりました。これは確かに最適ではありませんが、現在のところStack Catsで効果的にプログラムする唯一の既知の方法です。
したがって、この答えでは、上記のパターンのテンプレートは次のとおりです(実行方法には多少のばらつきがあります)。
[<(...)*(...)>]
プログラムが起動すると、スタックテープは次のようになります(入力用4
など)。
4
... -1 ...
0
^
[
移動左に、スタックの最上位(と一緒にテープヘッドは) -私たちは、この「プッシュ」と呼びます。そして、<
テープヘッドだけを動かします。したがって、最初の2つのコマンドの後、次のような状況になります。
... 4 -1 ...
0 0 0
^
これは(...)
、条件として非常に簡単に使用できるループです。ループは、現在のスタックのトップが正の場合にのみ開始および終了します。現在はゼロなので、プログラムの前半全体をスキップします。現在、中央のコマンドは*
です。これは単純であるXOR 1
、それは、スタックの最上位の最下位ビットをトグルし、この場合に回転する。すなわち、0
へ1
:
... 1 4 -1 ...
0 0 0
^
今、私たちはの鏡像に遭遇し(...)
ます。この時間は、スタックの最上位は正であり、我々はやるのコードを入力してください。括弧内で何が起こるかを調べる前に、最後にどのようにまとめるかを説明しましょう。このブロックの最後で、テープヘッドが再び正の値になるようにしますループは1回の反復後に終了し、単に線形条件として使用されます)、右側のスタックが出力を保持し、その右側のスタックがを保持することを示します-1
。その場合は、ループを終了>
し、出力値に移動して]
プッシュ-1
します。これにより、出力用のクリーンスタックができます。
それだけです。括弧内で、最後の前の段落で説明したように設定することを確認する限り、素数性をチェックすることは何でもできます(プッシュとテープヘッドの移動で簡単に実行できます)。私は最初にウィルソンの定理で問題を解決しようとしましたが、2乗の階乗計算は実際にStack Catsで非常に高価です(少なくとも私は短い道を見つけていません)ので、100バイトをはるかに超えてしまいました。そのため、代わりにトライアル部門を使用しましたが、実際はもっと簡単になりました。最初の線形ビットを見てみましょう:
>:^]
これらのコマンドのうち2つをすでに見ました。さらに:
、現在のスタックの上位2つの値をスワップし^
、2番目の値を上位の値にXORします。これにより:^
、空のスタックに値を複製する一般的なパターンが作成されます(値の上にゼロを引いてから、ゼロをに変えます0 XOR x = x
)。したがって、この後、テープのセクションは次のようになります。
4
... 1 4 -1 ...
0 0 0
^
私が実装したトライアル除算アルゴリズムはinput 1
では機能しないため、その場合はコードをスキップする必要があります。を使用1
して0
、他のすべてを正の値に簡単にマッピングでき*
ます。その方法を次に示します。
*(*...)
それは我々がオンである1
に0
、私たちが実際に取得する場合、コードの大部分をスキップし0
、私たちはすぐに元に戻すの内側に*
、我々は戻って私たちの入力値を取得するようにします。かっこがループを開始しないように、かっこの最後で正の値で終了することを再度確認する必要があります。条件内で、1つのスタックを右に移動して>
から、メインの試行分割ループを開始します。
{<-!<:^>[:((-<)<(<!-)>>-_)_<<]>:]<]]}
中括弧(括弧ではなく)は、異なる種類のループを定義します。これは、do-whileループです。つまり、常に少なくとも1回は実行されます。もう1つの違いは終了条件です。ループに入ると、スタック猫は現在のスタックの最高値を記憶します(0
この場合)。ループは、反復の最後にこの同じ値が再び現れるまで実行されます。これは便利です。各反復で、次の潜在的な除数の残りを計算し、それをこのスタックに移動して、ループを開始します。除数が見つかると、残りはで0
あり、ループは停止します。で除数を試してから、n-1
それらを1に減らします1
。つまり、a)到達するとこれが終了することがわかっている1
遅くても、b)次に、最後に試した除数を調べて、数値が素数であるかどうかを判断できます(それがの場合1
、それは素数、そうでない場合は素数です)。
それに行きましょう。最初に短い線形セクションがあります。
<-!<:^>[:
あなたはそれらのほとんどが今では何をしているのか知っています。新しいコマンドがある-
と!
。Stack Catsには、インクリメント演算子またはデクリメント演算子はありません。ただし、-
(否定、つまり乗算-1
)と!
(ビットごとのNOT、つまり乗算-1
と減分)があります。これらは、increment !-
、、またはdecrementのいずれかに結合できます-!
。そのn
ため-1
、の上にあるのコピーをデクリメントしてn
から、スタックの左側に別のコピーを作成し、新しいトライアル除数を取得しての下に置きますn
。したがって、最初の反復で、これを取得します。
4
3
... 1 4 -1 ...
0 0 0
^
さらに反復する場合、3
次のテスト除数などに置き換えられます(一方、この2つのコピーn
は常に同じ値になります)。
((-<)<(<!-)>>-_)
これはモジュロ計算です。ループは正の値で終了するため、正の値が得られるまでループを開始し-n
、試行除数d
を繰り返し追加します。実行したら、結果を減算しd
、残りを取得します。ここで注意が必要なのは、スタックの先頭に-n
追加して追加するループを開始することはできないということです。スタックd
の先頭が負の場合、ループは開始されません。これは、リバーシブルプログラミング言語の制限です。
したがって、この問題を回避するためn
に、スタックの先頭から始めますが、最初の反復でのみ無効にします。繰り返しますが、それはそれが判明するよりも簡単に聞こえます...
(-<)
スタックの一番上が正の場合(つまり、最初の反復でのみ)、で否定し-
ます。しかし、私たちは行うことはできません(-)
し、我々はされないので、残してまで、ループを-
2回適用されました。その<
ため、正の値(1
)があることがわかっているため、1つのセルを左に移動します。さて、これでn
最初の反復で確実に否定されました。しかし、新しい問題があります。テープヘッドは、最初の反復では他のすべての反復と異なる位置になりました。先に進む前にこれを統合する必要があります。次に<
、テープヘッドを左に移動します。最初の反復の状況:
-4
3
... 1 4 -1 ...
0 0 0 0
^
そして、2回目の繰り返しで(今d
一度追加したことを思い出してください-n
):
-1
3
... 1 4 -1 ...
0 0 0
^
次の条件は、これらのパスを再びマージします。
(<!-)
最初の反復では、テープヘッドはゼロを指すため、これは完全にスキップされます。さらに繰り返して、テープヘッドは1を指すので、これを実行し、左に移動し、そこでセルをインクリメントします。セルはゼロから開始することがわかっているため、セルは常に正になり、ループを抜けることができます。これにより、メインスタックの左側に常に2つのスタックがあり、で戻ることができるようになります>>
。次に、モジュロループの最後に行います-_
。あなたはすでに知ってい-
ます。XORである_
ものを減算すること^
です:スタックの一番上がa
その下の値である場合、b
それはに置き換えa
られb-a
ます。まず否定するのでa
、しかし-_
代わるものa
でb+a
、これにより追加します、d
ランニング合計に。
ループが終了した(正の値に達した)値は、テープは次のようになります。
2
3
... 1 1 4 -1 ...
0 0 0 0
^
左端の値は任意の正の数にすることができます。実際には、反復回数から1を引いたものです。もう1つの短い線形ビットがあります。
_<<]>:]<]]
前に述べたようにd
、実際の剰余(3-2 = 1 = 4 % 3
)を取得するには結果を減算する必要があるため、_
もう一度行います。次に、左側でインクリメントしているスタックをクリーンアップする必要があります。次の除数を試すとき、最初の反復が機能するためには、再びゼロにする必要があります。そこでそこに移動し、その正の値を他のヘルパースタックにプッシュし<<]
、次に別のヘルパースタックに操作スタックに戻ります>
。私たちは、プルアップd
と:
し、上に戻ってそれをプッシュする-1
と]
、その後、私たちは私たちとの条件付きスタックに残りを動かします<]]
。これが試行除算ループの終わりです。これは、残りがゼロになるまで続きます。この場合、左側のスタックにはn
の最大の除数(以外n
)。
ループの終了後、*<
パスを1
再び入力に結合する直前があります。*
単純にゼロになります1
私たちは少しで必要があります、その後、我々はと除数に移動<
(私たちは入力と同じスタックにしているように1
)。
この時点で、3種類の入力を比較するのに役立ちます。まず、n = 1
私たちがその試用部門のことを何もしていない特別な場合:
0
... 1 1 -1 ...
0 0 0
^
次に、前の例n = 4
、合成数:
2
1 2 1
... 1 4 -1 1 ...
0 0 0 0
^
そして最後にn = 3
、素数:
3
1 1 1
... 1 3 -1 1 ...
0 0 0 0
^
したがって、素数の場合、1
このスタック上にa があり、複合数の場合、0
またはより大きい正の数があり2
ます。私たちは、にこのような状況を回す0
か、1
私たちは、次のコード最後の部分で必要があります。
]*(:)*=<*
]
この値を右に押すだけです。その後*
、条件付き状況を大幅に簡素化するために使用されます。最下位ビットを切り替えること1
により0
、0
(プライム)を、(コンポジット)を正の値1
に変換しますが、他のすべての正の値は正のままです。ここで0
、ポジティブとポジティブを区別する必要があります。そこで別のものを使用します(:)
。スタックの最上位が0
(かつ入力が素数である)場合、これは単にスキップされます。しかし、スタックの最上位は、(と、入力がコンポジット数はあった)、これはでそれを交換正の場合は1
、我々が今持っているように、0
複合のためにと1
プライムの場合-2つの異なる値のみ。もちろん、それらは出力したいものの反対ですが、それは別のもので簡単に修正され*
ます。
今残っているすべては、私たちの周囲のフレームワークによって期待されるスタックのパターン復元することです:右にスタックの一番上に、正の値にテープヘッドをもたらし、単一-1
のスタック右のこと。これが=<*
目的です。=
隣接する2つのスタックの上部を入れ替えて-1
、結果の右側に移動します。たとえば、入力のため4
に:
2 0
1 3
... 1 4 1 -1 ...
0 0 0 0 0
^
次に、左に移動して<
、そのゼロを1に変えます*
。そしてそれはそれです。
プログラムの動作をさらに詳しく調べたい場合は、デバッグオプションを利用できます。追加のいずれか-d
のフラグをして挿入"
あなたは現在のメモリの状態を見てみたいところはどこでも、このように、たとえば、または使用-D
フラグをプログラム全体の完全なトレースを取得します。別の方法としては、使用することができますTimwiのEsotericIDEステップバイステップデバッガでスタック猫のインタプリタを含んでいます。