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
* примечание: на самом деле я не тестировал многие фрагменты кода в этом посте, поэтому не стесняйтесь сообщать, если есть ошибки.