Golfscript - 12 символов
{,1\{)*}/}:f
Начало работы с Golfscript - Факториал шаг за шагом
Вот кое-что для людей, которые пытаются выучить гольф. Обязательным условием является базовое понимание гольф-скрипта и умение читать документацию по гольф-скрипту.
Поэтому мы хотим опробовать наш новый инструмент golfscript . Всегда хорошо начинать с чего-то простого, поэтому мы начинаем с факториала. Вот начальная попытка, основанная на простом императивном псевдокоде:
# pseudocode: f(n){c=1;while(n>1){c*=n;n--};return c}
{:n;1:c;{n 1>}{n c*:c;n 1-:n;}while c}:f
Пробелы в гольскрипте очень редко используются. Самый простой способ избавиться от пробелов - использовать разные имена переменных. Каждый токен может использоваться как переменная (см. Страницу синтаксиса ). Полезные маркеры для использования в качестве переменных специальные символы , такие как |
, &
, ?
- вообще ничего не используется в других местах в коде. Они всегда анализируются как токены одного символа. Напротив, переменные типа like n
потребуют пробела для добавления числа в стек после. Числа по существу являются преинициализированными переменными.
Как всегда, будут утверждения, которые мы можем изменить, не влияя на конечный результат. В golfscript, все оценивается как истина , за исключением 0
, []
, ""
и {}
(см это ). Здесь мы можем изменить условие выхода из цикла на простое {n}
(мы добавляем цикл в дополнительное время и завершаем работу, когда n = 0).
Как и при игре в гольф на любом языке, это помогает узнать о доступных функциях. К счастью, список очень короток для гольфа. Мы можем изменить , 1-
чтобы (
спасти другой персонаж. В настоящее время код выглядит следующим образом: (мы могли бы использовать 1
вместо этого, |
если бы мы хотели, что бы сбросить инициализацию.)
{:n;1:|;{n}{n|*:|;n(:n;}while|}:f
Важно правильно использовать стек, чтобы получить кратчайшие решения (практика, практика). Как правило, если значения используются только в небольшом сегменте кода, может не потребоваться сохранять их в переменных. Удаляя переменную product product и просто используя стек, мы можем сохранить довольно много символов.
{:n;1{n}{n*n(:n;}while}:f
Вот о чем еще подумать. Мы удаляем переменную n
из стека в конце тела цикла, но затем добавляем ее сразу после. Фактически, перед началом цикла мы также удаляем его из стека. Вместо этого мы должны оставить его в стеке, и мы можем оставить условие цикла пустым.
{1\:n{}{n*n(:n}while}:f
Может быть, мы можем даже полностью исключить переменную. Для этого нам нужно будет постоянно хранить переменную в стеке. Это означает, что нам нужно две копии переменной в стеке в конце проверки условия, чтобы мы не потеряли ее после проверки. Это означает, что у нас будет избыточность 0
в стеке после окончания цикла, но это легко исправить.
Это приводит нас к нашему оптимальному while
циклическому решению!
{1\{.}{.@*\(}while;}:f
Теперь мы все еще хотим сделать это короче. Очевидной целью должно быть слово while
. Глядя на документацию, есть две жизнеспособные альтернативы - развернуть и сделать . Если у вас есть выбор различных маршрутов, попробуйте взвесить преимущества обоих. Развернуть это «довольно много времени цикла», так что в качестве оценки мы сократим 5 символов while
на 4 в /
. Что касается do
, мы разрезаем while
на 3 символа и получаем объединение двух блоков, что может сохранить еще один или два символа.
На самом деле есть большой недостаток в использовании do
цикла. Поскольку проверка условия выполняется после однократного выполнения тела, значение 0
будет неправильным, поэтому нам может понадобиться оператор if. Я вам сейчас скажу, что разворачивается короче ( do
в конце представлены некоторые решения с ). Попробуйте и попробуйте, код, который у нас уже есть, требует минимальных изменений.
{1\{}{.@*\(}/;}:f
Большой! Наше решение теперь очень короткое, и мы здесь, верно? Нет. Это 17 символов, а у J 12 символов. Никогда не признай поражение!
Теперь вы думаете с ... рекурсией
Использование рекурсии означает, что мы должны использовать ветвящуюся структуру. К сожалению, но так как факториал можно выразить так кратко, рекурсивно, это кажется жизнеспособной альтернативой итерации.
# pseudocode: f(n){return n==0?n*f(n-1):1}
{:n{n.(f*}1if}:f # taking advantage of the tokeniser
Ну, это было легко - если бы мы попробовали рекурсию раньше, мы могли бы даже не взглянуть на использование while
цикла! Тем не менее, мы только на 16 символов.
Массивы
Массивы обычно создаются двумя способами - с помощью [
и ]
символов, или с ,
функцией. Если выполняется с целым числом в верхней части стека, ,
возвращает массив этой длины с arr [i] = i.
Для перебора массивов у нас есть три варианта:
{block}/
: толкать, блокировать, толкать, блокировать, ...
{block}%
: [push, block, push, block, ...] (здесь есть некоторые нюансы, например, промежуточные значения удаляются из стека перед каждым push)
{block}*
: толкать, толкать, блокировать, толкать, блокировать, ...
В документации по гольфу есть пример использования {+}*
для суммирования содержимого массива. Это говорит о том, что мы можем использовать, {*}*
чтобы получить произведение массива.
{,{*}*}:f
К сожалению, не все так просто. Все элементы отключены одним ( [0 1 2]
вместо [1 2 3]
). Мы можем использовать, {)}%
чтобы исправить эту проблему.
{,{)}%{*}*}:f
Ну, не совсем. Это не обрабатывает ноль правильно. Мы можем вычислить (n + 1)! / (N + 1), чтобы исправить это, хотя это стоит слишком дорого.
{).,{)}%{*}*\/}:f
Мы также можем попытаться обработать n = 0 в том же сегменте, что и n = 1. Это на самом деле очень коротко, попытаться выработать как можно меньше.
Не так хорошо , как сортировка, в 7 символов: [1\]$1=
. Обратите внимание, что этот метод сортировки имеет полезные цели, такие как наложение границ на число (например, `[0 \ 100] $ 1 =).
Вот победитель, имеющий только 3 символа:.! +
Если мы хотим, чтобы приращение и умножение были в одном и том же блоке, мы должны выполнять итерации по каждому элементу в массиве. Поскольку мы не создаем массив, это означает, что мы должны его использовать {)*}/
, что приводит нас к кратчайшей реализации факториала в golfscript! В 12 символов, это связано с J!
{,1\{)*}/}:f
Бонусные решения
Начиная с простого if
решения для do
цикла:
{.{1\{.@*\(.}do;}{)}if}:f
Мы можем выжать пару лишних из этого. Немного сложно, так что вам придется убедить себя, что эти работы. Убедитесь, что вы понимаете все это.
{1\.!!{{.@*\(.}do}*+}:f
{.!{1\{.@*\(.}do}or+}:f
{.{1\{.@*\(.}do}1if+}:f
Лучшей альтернативой является вычисление (n + 1)! / (N + 1), что устраняет необходимость в if
структуре.
{).1\{.@*\(.}do;\/}:f
Но самое короткое do
решение здесь занимает несколько символов, чтобы отобразить 0 к 1, и все остальное для себя - так что нам не нужно никаких ветвлений. Этот вид оптимизации чрезвычайно легко пропустить.
{.!+1\{.@*\(.}do;}:f
Для тех, кто заинтересован, здесь представлено несколько альтернативных рекурсивных решений такой же длины, как указано выше:
{.!{.)f*0}or+}:f
{.{.)f*0}1if+}:f
{.{.(f*}{)}if}:f
* примечание: на самом деле я не тестировал многие фрагменты кода в этом посте, поэтому не стесняйтесь сообщать, если есть ошибки.