*(>:^]*(*>{<-!<:^>[:((-<)<(<!-)>>-_)_<<]>:]<]]}*<)]*(:)*=<*)>]
Потрібно запускати -ln
прапори командного рядка (отже, +4 байти). Відбитки 0
для складених чисел та 1
для простих чисел.
Спробуйте в Інтернеті!
Я думаю, що це перша нетривіальна програма Stack Cats.
Пояснення
Швидке введення Stack Cats:
- Stack Cats працює на нескінченній стрічці стеків, головка стрічки спрямована на поточний стек. Кожна стопка спочатку заповнюється нескінченною кількістю нулів. Я, як правило, ігнорую ці нулі у своїй формулюванні, тому коли я кажу "нижня частина стека", я маю на увазі найменше ненульове значення, а якщо я кажу "стек порожній", я маю на увазі, що на ньому є лише нулі.
- Перед запуском програми a
-1
висувається на початковий стек, а потім весь вхід висувається поверх цього. У цьому випадку через -n
прапор вхід зчитується у вигляді десяткового цілого числа.
- В кінці програми поточний стек використовується для виводу. Якщо є
-1
внизу, воно буде проігноровано. Знову ж таки, завдяки -n
прапору, значення зі стека просто друкуються у вигляді десяткових цілих чисел, розділених рядками.
- Stack Cats - це зворотна мова програми: кожен фрагмент коду можна скасувати (без Stack Cats відстежувати явну історію). Більш конкретно, щоб повернути будь-який фрагмент коду, ви просто відобразили його, наприклад,
<<(\-_)
став (_-/)>>
. Ця мета дизайну встановлює досить суворі обмеження щодо того, які види операторів і конструкцій керуючого потоку існують у мові та які функції ви можете обчислити в глобальній пам'яті.
На додаток до цього, кожна програма Stack Cats повинна бути самосиметричною. Ви можете помітити, що це не стосується вищевказаного вихідного коду. Це те, для чого -l
прапор: він неявно відображає код зліва, використовуючи перший символ у центрі. Отже, власне програма:
[<(*>=*(:)*[(>*{[[>[:<[>>_(_-<<(-!>)>(>-)):]<^:>!->}<*)*[^:<)*(>:^]*(*>{<-!<:^>[:((-<)<(<!-)>>-_)_<<]>:]<]]}*<)]*(:)*=<*)>]
Ефективне програмування за допомогою всього коду є нетривіальним та неінтуїтивним, але ще не зрозуміли, як людина може це зробити. Ми грубо змусили таку програму для більш простих завдань, але не змогли б дістатись до неї вручну. На щастя, ми знайшли основну схему, яка дозволяє ігнорувати половину програми. Хоча це, безумовно, неоптимально, на даний момент це єдиний відомий спосіб ефективно програмувати в Stack Cats.
Отже, у цій відповіді шаблон зазначеного шаблону такий (є певна варіативність у тому, як він виконується):
[<(...)*(...)>]
Коли програма запускається, стрічка стека виглядає приблизно так (для введення 4
, скажімо):
4
... -1 ...
0
^
У [
русі верхній частині стека вліво (і стрічка голови разом) - ми називаємо це «штовхаючи». І <
рухає стрічку головою поодинці. Отже, після перших двох команд у нас виникла така ситуація:
... 4 -1 ...
0 0 0
^
Тепер (...)
цикл - це цикл, який можна використовувати досить легко як умовний: цикл вводиться та залишається лише тоді, коли вершина поточного стеку є додатною. Оскільки наразі це нуль, ми пропускаємо всю першу половину програми. Тепер команда центру є *
. Це просто XOR 1
, тобто перемикає найменш значущий біт верхньої частини стека, і в цьому випадку перетворює 0
на 1
:
... 1 4 -1 ...
0 0 0
^
Тепер ми зустрічаємо дзеркальне зображення (...)
. На цей раз у верхній частині стека є позитивним , і ми робимо ввести код. Перш ніж ми розберемося, що відбувається всередині дужок, дозвольте мені пояснити, як ми завершимо завершення: ми хочемо переконатися, що в кінці цього блоку ми знову будемо мати головку стрічки на позитивному значенні (щоб цикл закінчується після однієї ітерації і використовується просто як лінійна умовна умова), що стек праворуч містить висновок, а права стека, що містить а -1
. Якщо це так, ми залишаємо цикл, >
переміщуємося на вихідне значення і ]
натискаємо на нього, -1
щоб у нас був чистий стек для виводу.
Ось це. Тепер усередині дужок ми можемо робити все, що завгодно, щоб перевірити первинність, якщо ми гарантуємо, що в кінці ми встановимо речі, як описано в попередньому абзаці (що можна легко зробити, коли натискання та переміщення головки стрічки). Я вперше спробував вирішити проблему з теоремою Вілсона, але закінчився набагато більше 100 байт, тому що обчислення факторних квадратів насправді є досить дорогим у Stack Cats (принаймні, я не знайшов короткий шлях). Тому я пішов із пробним поділом, і це справді виявилося набагато простіше. Давайте розглянемо перший лінійний біт:
>:^]
Ви вже бачили дві з цих команд. Крім того, :
підміняє два перших значення поточного стеку, а ^
XOR - друге значення на верхнє значення. Це робить :^
загальний шаблон для дублювання значення в порожній стеці (ми витягуємо нуль у верхній частині значення, а потім перетворюємо нуль у 0 XOR x = x
). Отже після цього розділу наш стрічка виглядає так:
4
... 1 4 -1 ...
0 0 0
^
Алгоритм пробного поділу, який я застосував, не працює для введення даних 1
, тому в цьому випадку нам слід пропустити код. Ми можемо легко зіставити 1
з 0
і всім іншим позитивних значень з *
, так ось як ми це робимо:
*(*...)
Тобто ми переходимо 1
в 0
, пропустити велику частину коду , якщо ми дійсно 0
, але всередині ми негайно скасувати , *
так що ми отримуємо наше вхідне значення назад. Нам потрібно ще раз переконатися, що ми закінчимося позитивним значенням наприкінці дужок, щоб вони не почали циклічно. Всередині умовного способу переміщуємо один стек прямо з, >
а потім починаємо основний цикл пробного поділу:
{<-!<:^>[:((-<)<(<!-)>>-_)_<<]>:]<]]}
Брекети (на відміну від дужок) визначають різний тип циклу: це цикл do-while, тобто він завжди працює щонайменше за одну ітерацію. Інша відмінність - умова завершення: при введенні в цикл Stack Cat запам’ятовує верхнє значення поточного стека ( 0
у нашому випадку). Потім цикл буде працювати, поки це ж значення знову не з’явиться в кінці ітерації. Це зручно для нас: у кожній ітерації ми просто обчислюємо залишок наступного потенційного дільника і переміщуємо його на цей стек, з якого ми починаємо цикл. Коли ми знайдемо дільник, залишок - 0
і петля зупиняється. Ми спробуємо дільники, починаючи з, n-1
а потім зменшуючи їх 1
. Це означає, що а) ми знаємо, що це закінчиться, коли ми дійдемо1
найпізніше та б) ми можемо визначити, чи є число простим чи ні, перевіривши останній дільник, який ми спробували (якщо він 1
є простим, інакше - ні).
Давайте перейдемо до цього. На початку є короткий лінійний розділ:
<-!<:^>[:
Ви знаєте, чим зараз займається більшість цих речей. Нові команди - -
і !
. Stack Cats не має операторів збільшення або зменшення. Однак він має -
(заперечення, тобто помножити на -1
) і !
(порозрядно НЕ, тобто помножити на -1
і зменшити). Вони можуть бути об'єднані у приріст !-
, або у зменшення -!
. Таким чином, ми декрементуємо копію n
зверху -1
, потім робимо ще одну копію n
на стеку зліва, а потім дістаємо новий пробний дільник і поміщаємо її внизу n
. Отже, на першій ітерації ми отримуємо це:
4
3
... 1 4 -1 ...
0 0 0
^
При подальших ітераціях 3
заповіт замінюється наступним тестовим дільником і так далі (тоді як дві копії n
файлу завжди будуть однаковими в цьому пункті).
((-<)<(<!-)>>-_)
Це обчислення модуля. Оскільки петлі закінчуються на позитивних значеннях, ідея полягає в тому, щоб почати з -n
і кілька разів додавати до нього пробний дільник, d
поки ми не отримаємо додатне значення. Як тільки ми це робимо, ми віднімаємо результат, d
і це дає нам залишок. Тут складний біт полягає в тому, що ми не можемо просто поставити -n
на вершину стека і запустити цикл, який додає d
: якщо верхня частина стека негативна, цикл не буде введено. Такі обмеження оборотної мови програмування.
Отже, щоб обійти цю проблему, ми починаємо з n
вершини стека, але заперечуємо її лише на першій ітерації. Знову ж таки, це звучить простіше, ніж виявляється ...
(-<)
Коли вершина стека є позитивною (тобто лише на першій ітерації), ми заперечуємо її -
. Однак ми не можемо просто зробити це, (-)
тому що тоді ми не залишатимемо цикл, поки -
не буде застосовано двічі. Таким чином, ми переміщуємо одну клітинку, що залишилася, <
тому що ми знаємо, що є позитивне значення (the 1
). Гаразд, тепер ми надійно заперечували n
за першою ітерацією. Але у нас є нова проблема: головка стрічки зараз знаходиться в іншому положенні за першою ітерацією, ніж у всіх інших. Нам потрібно закріпити це, перш ніж рухатись далі. Наступний <
рухає головку стрічки вліво. Ситуація на першій ітерації:
-4
3
... 1 4 -1 ...
0 0 0 0
^
А на другій ітерації (пам’ятайте, що ми додали d
один раз -n
):
-1
3
... 1 4 -1 ...
0 0 0
^
Наступний умовний спосіб знову об'єднує ці шляхи:
(<!-)
При першій ітерації головка стрічки вказує на нуль, тому це повністю пропускається. При подальших ітераціях голова стрічки вказує на одиницю, тому ми виконуємо це, рухаємося вліво і збільшуємо туди клітинку. Оскільки ми знаємо, що клітина починається з нуля, вона завжди буде позитивною, тому ми можемо залишити цикл. Це гарантує, що ми завжди закінчуємо два стеки зліва від основного стеку і тепер можемо повернутися назад >>
. Потім в кінці петлі модуля робимо -_
. Ви вже знаєте -
. _
полягає в відніманні , що ^
повинен XOR: якщо вершина стека a
і значення Нижче b
він замінює a
з b-a
. Так як ми перший заперечується , a
хоча, -_
замінює a
з b+a
, тим самим додавшиd
в наш загальний обсяг.
Після закінчення циклу (ми досягли позитивного) значення, стрічка виглядає приблизно так:
2
3
... 1 1 4 -1 ...
0 0 0 0
^
Найбільш лівим значенням може бути будь-яке додатне число. Насправді, це кількість повторень мінус одна. Зараз є ще один короткий лінійний біт:
_<<]>:]<]]
Як я вже говорив раніше, нам потрібно відняти результат, d
щоб отримати фактичний залишок ( 3-2 = 1 = 4 % 3
), тому ми просто робимо _
ще раз. Далі нам потрібно очистити стек, який ми нарощували ліворуч: коли ми спробуємо наступний дільник, він знову повинен бути нульовим, щоб перша ітерація спрацювала. Тож ми переміщаємось туди і підштовхуємо це позитивне значення до іншого стека помічників, <<]
а потім повертаємось назад до нашого операційного стека з іншим >
. Ми тягнемо вгору d
з :
і штовхати його назад на -1
з , ]
а потім ми переміщаємо залишок на наш умовний стек з <]]
. Ось і закінчується цикл пробного поділу: це триває, поки ми не отримаємо нульовий залишок, і в цьому випадку стек зліва міститьn
найбільший дільник (крім n
).
Після закінчення циклу, *<
перед тим, як ми знову з’єднаємось із шляхами із введенням , існує лише певний час 1
. *
Просто перетворює нуль в 1
, який ми будемо мати потребу в трохи, а потім ми перейдемо до дільнику з <
(так , що ми знаходимося на тому ж стеку , як і для введення 1
).
На даний момент це допомагає порівнювати три різні види входів. По-перше, особливий випадок, 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
стек, а для складених чисел у нас або число 0
чи додатнє число більше, ніж 2
. Ми перетворюємо цю ситуацію на необхідну 0
чи 1
наступну частину коду:
]*(:)*=<*
]
просто штовхає це значення праворуч. Тоді *
використовується для того, щоб значно спростити умовну ситуацію: переключивши найменш значущий біт, ми перетворимось 1
( перетворимось на ) 0
, 0
(складемо) у позитивне значення 1
, а всі інші позитивні значення залишаться позитивними. Тепер нам просто потрібно розрізняти 0
і позитивне. Ось тут ми використовуємо інше (:)
. Якщо верхня частина стека є 0
(а вхід був простим), це просто пропускається. Але якщо верхня частина стека є позитивною (а вхід був складеним числом), це поміняє її на 1
, так що тепер у нас є 0
композитний та1
для прайменів - лише два різних значення. Звичайно, вони протилежні тому, що ми хочемо вивести, але це легко фіксується з іншим *
.
Тепер все, що залишилося, - це відновити візерунок стеків, очікуваний нашим оточуючим фреймом: голова стрічки на позитивному значенні, результат у верхній частині стека праворуч і одиничний -1
у стеку праворуч від цього . Це для чого =<*
. =
підміняє вершини двох сусідніх стеків, тим самим переміщуючи -1
праворуч від результату, наприклад для введення 4
знову:
2 0
1 3
... 1 4 1 -1 ...
0 0 0 0 0
^
Тоді ми просто рухаємося ліворуч <
і перетворюємо цей нуль в один із *
. І це все.
Якщо ви хочете заглибитись у те, як працює програма, ви можете скористатися параметрами налагодження. Або додайте -d
прапор і вставте "
там, де ви хочете побачити поточний стан пам’яті, наприклад, як це , або використовуйте -D
прапор, щоб отримати повний слід всієї програми . Крім того, ви можете використовувати Езотерицид Timwi, який включає інтерпретатор Stack Cats з поетапним налагодженням.