РЕДАКТИРОВАТЬ: Как некоторые из вас подозревали, была ошибка в официальном переводчике: порядок композиции в .
был обратный. У меня было две версии переводчика, и я использовал неправильную. Примеры были также написаны для этой неправильной версии. Я исправил переводчик в репозитории, и примеры ниже. Описание >
также было немного двусмысленным, поэтому я исправил это. Кроме того, извинения за то, что это заняло так много времени, я застрял в некоторых реальных вещах.
РЕДАКТИРОВАТЬ 2: У моего интерпретатора была ошибка в реализации, .
которая была отражена в примерах (они полагались на неопределенное поведение). Теперь проблема исправлена.
Введение
Shift - это эзотерический функциональный язык программирования, который я создал пару лет назад, но опубликовал сегодня. Он основан на стеке, но также имеет автоматическое каррирование, как Haskell.
Спецификация
В Shift есть два типа данных:
- Функции, которые имеют произвольную положительную арность (количество входов) и возвращают список выходов. Например, функция, которая дублирует свой единственный вход, имеет арность 1, а функция, которая меняет два своих входа, имеет арность 2.
- Бланки, которые все идентичны и не имеют никакой другой цели, кроме как не быть функциями.
Программа Shift состоит из нуля или более команд , каждая из которых представляет собой один символ ASCII. Всего 8 команд:
!
( применить ) извлекает функциюf
и значениеx
из стека и применяетсяf
кx
. Еслиf
имеет арность 1, списокf(x)
добавляется в начало стека. Если он имеет арностьn > 1
, новая(n-1)
-арная функция помещаетсяg
в стек. Он принимает входные данные и возвращает .x1,x2,...,xn-1
f(x,x1,x2,...,xn-1)
?
( пусто ) толкает пробел в стек.+
( клон ) помещает в стек унарную функцию, которая дублирует входные данные: любое значениеx
отображается на[x,x]
.>
( shift ) помещает в стек унарную функцию, которая принимаетn
-ary-функциюf
, и возвращает(n+1)
-ary-функцию,g
которая игнорирует свой первый аргументx
, вызываетf
оставшиеся и привязываетx
перед результатом. Например,shift(clone)
это двоичная функция, которая принимает входные данныеa,b
и возвращает[a,b,b]
./
( fork ) помещает в стек троичную функцию, которая принимает три входаa,b,c
и возвращает,[b]
еслиa
пусто, и в[c]
противном случае.$
( Вызов ) толкает в стек двоичную функцию , которая появляется функциейf
и значениеx
, и применяетf
кx
точности так , как!
делает..
( цепочка ) помещает в стек двоичную функцию, которая выдает две функцииf
иg
и возвращает их композицию: функция,h
которая имеет ту же самую арностьf
, что и которая обычно принимает свои входные данные, применяетсяf
к ним, а затем полностью применяетсяg
к результату (вызывает это столько раз, сколько диктует его арность), с неиспользованными предметами из результата,f
оставшимися в результатеh
. Например, предположим, чтоf
это двоичная функция, которая клонирует свой второй аргумент иg
называется call . Если стек содержит[f,g,a,b,c]
и мы.!!
, то он содержит[chain(f,g),a,b,c]
; если мы делаем!!
дальше, тоf
сначала применяетсяa,b
, производя[a,b,b]
, затемg
применяется к первым двум элементам этого, поскольку его арность равна 2, производя[a(b),b]
, и стек, наконец, будет[a(b),b,c]
.@
( скажем ) выдвигает унарную функцию, которая просто возвращает свой ввод и печатает,0
если она была пустой, и1
если это была функция.
Обратите внимание, что все команды, кроме !
простого переноса значения в стек, не позволяют выполнять ввод, и единственный способ вывести что-либо - использовать @
. Программа интерпретируется путем оценки команд одна за другой, вывода 0
s или 1
s при каждом вызове «say» и выхода. Любое поведение, не описанное здесь (применение пробела, применение стека длиной 0 или 1, вызов «цепочки» для пробела и т. Д.) Не определено: интерпретатор может аварийно завершить работу, потерять молчание, запросить ввод или что-то еще.
Задание
Ваша задача - написать переводчика для Shift. Он должен принимать из STDIN, командной строки или аргумента функции интерпретируемую программу Shift и печатать в STDOUT или возвращать результирующий (возможно, бесконечный) вывод 0
s и 1
s. Если вы пишете функцию, вы должны каким-то образом получить доступ к выходам бесконечной длины (генератор в Python, ленивый список в Haskell и т. Д.). В качестве альтернативы вы можете взять другой ввод, число n
и вернуть хотя бы n
символы вывода, если он длиннее n
.
Побеждает меньшее количество байтов, и стандартные лазейки запрещены.
Тестовые случаи
Эта программа Shift печатает 01
:
?@!@@!
Начиная слева: нажмите пробел, нажмите сказать , затем примените слово к пробелу. Это выводы 0
. Затем дважды нажмите « сказать» и примените второе слово к первому. Это выводы 1
.
Эта программа зацикливается навсегда, не выводя ничего:
$+.!!+!!
Нажмите call и clone , затем примените к ним цепочку (нам нужно два !
s, так как цепочка является двоичной функцией). Теперь в стеке есть функция, которая принимает один аргумент, дублирует его и вызывает первую копию второго. С +!!
, мы дублируем эту функцию и вызываем ее на себя.
Эта программа печатает 0010
:
?@$.++>!.!!.!!.!!!!+?/!!!@!@>!!!
Нажмите пробел и скажите . Затем создайте двоичную функцию, которая копирует свой второй аргумент b
, затем копирует первый a
и составляет его с собой, затем применяет композицию к копии b
, возвращая [a(a(b)),b]
. Примените его, чтобы сказать и пусто, затем примените слово к двум элементам, остающимся в стеке.
Эта программа печатает 0
. Для каждого, !!!
что вы добавляете к нему, печатается дополнительный 0
.
?@+$>!>!+>!///!!>!>!.!!.!!.!!+!!!!
Нажмите пробел и скажите . Затем составьте троичную функцию, которая принимает в f,g,x
качестве входных данных и возвращает [f,f,g,g(x)]
. Клонируйте эту функцию и примените ее к себе, скажем , и к пробелу. Это приложение не меняет стек, поэтому мы можем применить функцию снова столько раз, сколько захотим.
Эта программа печатает бесконечную последовательность 001011011101111...
, где число 1
s всегда увеличивается на единицу:
@?/!@>!??/!!>!+.!!.!!.!!.+>!.!!$$$$+$>!>!$>!>!+>!$>!>!>!+>!>!///!!>!>!>!.!!.!!.!!.!!.!!.!!.!!.!!.!!.!!+!!!!!
Репозиторий содержит аннотированную версию.
источник
f(x1, x2, ..., xn)
иg(y1, y2, ..., ym)
. Вызов.
вызывает оба из них и толкает функциюh(z1, z2, ..., zn)
. Теперь вы можете съесть все эти аргументы, постепенно вычеркивая их!
. Послеn
таких приложений оставшаяся функция имела только один аргумент, и в этот момент она вычисляетf(z1, z2, ..., zn)
(то естьf
применяется ко всем аргументам, в которых вы каррировали), который выдвигает некоторые новые значения, а затем сразу же получаетm
значения из стека и вызываетg
их..
работает точно так же, как описал Мартин, за исключением того, что если онf
возвращает списокm
значений, меньших значений, результат не определен (композиция имеет арностьn
, поэтому она не может использовать больше аргументов из стека). По сути, выходные данныеf
используются в качестве временного стека, на которыйg
проталкиваются и применяютсяm
времена!
, а результат этого добавляется в основной стек.Ответы:
Python 2,
752667534506445436427404398393 байтаЭто ни в коем случае не коротко ... но я сделал все возможное. Любые предложения по гольфу будут очень признательны ...
EDIT6: теперь это скрипт вместо функции. Сохраните его в файл (shift.py, forex), затем запустите его с помощью
$ python shift.py '<my_input>'
. Убедитесь, что входные данные заключены в одинарные кавычки, иначе bash сойдет с ума от введенных символов.EDIT7: Aaaaaaand ... это больше не читается. Но я избавился от еще 23 байтов, так что это хорошо, я думаю? Я тоже выложу версию без гольфа.
EDIT8: еще один гольф, благодаря @Zgarb.
РЕДАКТИРОВАТЬ: спасибо @DLosc за помощь в игре в гольф! Удалось уменьшить его на 85 байт.
РЕДАКТИРОВАТЬ 2: вырезать тонну ненужных упаковщиков, и бросил его еще на 133 байта!
EDIT3: ... и еще 28 благодаря @ Sp3000 и @orlp в чате!
EDIT4: с помощью @orlp & @ Sp3000 удалил все декораторы и теперь он на 61 байт короче.
РЕДАКТИРОВАТЬ5: помочь meeeeee, я не могу перестать играть в гольф в этом .... еще 9 байтов прошло. Избавление от окончательного оператора печати спасло бы еще 7, но затем, если вы запустите m () в цикле, все выходные данные будут в одной строке ... это нормально?
Вот негольфированная версия:
Основная идея состоит в том, что список python очень хорошо работает как стек, и, сохраняя
u=k.append
, я не только сохраняю символы,но я также могу использовать их(больше нет!).@u
как декоратор для проталкивания функцийТак как пара функций, которые действуют на функции n-arity, должны иметь возможность принимать произвольное количество аргументов, я должен был использовать
*args
, что означало, что мой первоначальный план отслеживания f.func_code.co_argcount должен был быть заменен на arity атрибутдекоратора.С точки зрения обработки бесконечных программ, интерпретатор работает, пока не достигнет максимальной рекурсивной глубины; обработчик RuntimeError внизу имеет тихий выход в этот момент и затем выводит текущую строку вывода.
Тестовые случаи:
источник
['1','0'][...]
просто'10'[...]
. 3) Почему быx is 0
и нетx==0
(илиx<1
)? 4) Не надо уточнятьRuntimeError
, простоexcept
сделаю. 5) Поскольку вы используете Python 2, табуляция и пробелы считаются разными уровнями отступов - конечно, но должны сэкономить ~ 25 байт.x.a==1and x(y)or u(a(x.a-1)(b.partial(x,y)))
- логические операторы все еще коротки, но используют меньше символов, чем троичные. Затем сохраните другие байты, используя вx.a-1
качестве условия (0 / ложного , еслиx
это 1, отлично от нуля / верно в противном случае) и поменять местами « затем» и «еще» выражения:x.a-1and u(a(x.a-1)(b.partial(x,y)))or x(y)
. (Надо еще раз поиграть в гольф, когда ты меняx.a==1
получается - если это правда, ноx(y)
возвращает что-то ложное, он тоже пытается оценитьu(...)
. Но, похоже, вам не нужно экономить те жалкие 3 байта, которые вам дали бы! Я признаю, сэр: вы превзошли меня.RuntimeError
while, а моя просто просит пользователя перенаправить stderr ... так что мы, вероятно, даже на придирках. ; ^)*_
нужны лямбды?Ghostscript
Пока не играю в гольф, так как мне все еще нужно отработать функцию разбора.
Эта реализация использует
_
и:
вместо>
и/
, и требует, чтобы все символы программы были разделены пробелами. Это происходит потому , что>
и/
не являются допустимыми именами в Postscript, и операторы не саморазгранично, но это будет исправлено , когда я пишу анализатор.Первая часть кода должна быть достаточно прозрачной, поскольку она просто повторяет определения операторских функций. Магия происходит в определении
!
.То , как
!
работает просто: Во- первых, это добавляет аргументx
вf
предваряяx
к содержаниюf
, толкая его обратно в стек, и называя копию результатаfun
.Затем он упаковывает весь стек в массив.
.runandhide
является расширением Ghostscript для запуска изолированного кода, скрывающего содержимое предыдущего массива от процедуры, в которой он вызывается. Командаdict
помещает новый словарь в стек dict, сужая область имен, определенных внутри, до тех пор, покаend
он не выскочит обратно. Он также заменяет=only
(оператор вывода, в котором я использую@
) фиктивный, подавляя вывод во время пробного запуска.stopped
является PostScript-эквивалентомtry
оператора, найденного в других языках, и возвращает true, если его процедура вызвала ошибку, и false, если он завершился.После завершения пробного запуска
fun
программа восстанавливает исходный стек из скрытого массива и, если онfun
завершен без ошибок, запускает его по-настоящему, сохраняя вывод.источник
Python3,
685670634633 байтаЯ уверен, что это самая длинная вещь, которую я когда-либо играл в гольф. Раньше он был несколько читабельным, но следование советам @ sirpercival устранило этот недостаток!
k
это стек, который содержит функции , представленные в виде строк , как"h(0,0)"
(что с ч айн ). Когда функция передается в качестве аргумента другой функции, он получаетrepr
«d и все числа увеличивается:"h('h(1,1)',0)"
. Как только все0
s заменяются в функции, все это передаетсяeval
, вызывая, таким образом, соответствующую функцию Python - большинство из которых являются лямбда-функциями, сгенерированными из большой строки в строке 6exec
строкой 7.Увеличение нескольких уровней вложенных функций, их цитирование и экранирование были самой большой головной болью. Я мог бы сэкономить немного больше на операциях с регулярными выражениями, если бы мог предположить, что вложение функций не будет происходить глубже, чем на 9 уровней, но, как указано в комментариях, это, вероятно, не является безопасным предположением.
Разрушенная более ранняя версия кода:
Единственным потенциальным недостатком этой реализации является то, что она использует рекурсию, поэтому программы, которые должны быть бесконечными, достигают максимальной глубины рекурсии довольно быстро. (Возможно, вы захотите перенаправить stderr при запуске бесконечной программы - в противном случае трассировка стека затопит фактический вывод.) Кроме этого, все, кажется, работает.
источник
lambda a
иk.pop()
.k.pop()
ситуацию немного менее повторяющейся.)Цейлон,
116710571031Я не понимаю это так коротко, как монотипные версии Python ...
import ceylon.language.meta.model{N=Function}import ceylon.collection{H=HashMap}interface D of F|b{}object b satisfies D{}class F(shared Integer a,[D+](D+)f,[D*]c=[])satisfies D{shared[D+]o(D i){[D+]s=[i].prepend(c);return a==1then f(*s)else[F(a-1,f,s)];}shared[D+]y([D+]i){return f(*i.prepend(c));}}F m<A>(N<[D+],A>f)given A satisfies[D+]=>F(f.parameterTypes.size,(D+i)=>f.apply(*i));[D,D]e(D x)=>[x,x];[F]t(F f){[D+]g(D+i){assert(is[D+]r=i.rest);return[i[0],*f.y(r)];}return[F(f.a+1,g)];}[D]k(D a,D d,D c)=>a==b then[d]else[c];[D+]l(F a,D x)=>a.o(x);[F]n(F f,F g){[D+]h(D+i){[D+]r=f.y(i);assert(is[D+]d=r[0:g.a]);return g.y(d).append(r[g.a...]);}return[F(f.a,h)];}[D]y(D x){process.write(x==b then"0"else"1");return[x];}class I(){variable D[]s=[];value c=H{'?'->b,'+'->m(`e`),'>'->m(`t`),'/'->m(`k`),'$'->m(`l`),'.'->m(`n`),'@'->m(`y`)};shared void r(Character i){if(i=='!'){assert(is F f=s[0],is D x=s[1]);s=f.o(x).append(s[2...]);}else{assert(is D d=c[i]);s=[d].append(s);}}}shared void z(){process.readLine()?.collect(I().r);}
Вот отформатированная (и закомментированная) версия того же кода (с пробелами / символами новой строки / комментариями она становится 4867 байтами):
Функции clone
e
, shiftt
, forkk
, calll
, sayy
и chainn
используют последнюю букву имен для сокращенной версии, потому что это дает меньше коллизий. (Общая информация : вилка была первоначально определена следующим образом:[Data] fork(Data a, Data b, Data c) => a == blank then [b] else [c];
- когда я переименовалblank
вb
это сломалось, потому что теперь сравнивают параметрыa
иb
вместо того, чтобыa
с заготовкой мне потребовалось некоторое время для отладки.) .z
Функция разделяет , потому что мой IDE работает эти функции - инструмент командной строки можно также запустить неразделяемые из них.источник