Гольфскрипт - 12 символів
{,1\{)*}/}:f
Початок роботи з Гольфскриптом - покроково
Ось щось для людей, які намагаються вивчити гольфскрипт. Обов’язковою умовою є базове розуміння гольфскрипту та вміння читати документацію гольфскрипту.
Тому ми хочемо спробувати наш новий інструмент гольфскрипту . Завжди добре починати з чогось простого, тому ми починаємо з факторства. Ось перша спроба, заснована на простому імперативному псевдокоді:
# 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
Пробіл дуже рідко використовується в гольфскрипті. Найпростіший трюк позбутися пробілів - це використовувати різні назви змінних. Кожен маркер можна використовувати як змінну (див. Сторінку синтаксису ). Корисні маркери для використання в якості змінних спеціальні символи , такі як |
, &
, ?
- взагалі нічого не використовується в інших місцях в коді. Вони завжди розбираються як символи одного символу. На відміну від цього, для змінних типу n
потрібно буде пробіл, щоб натиснути число до стеку після. Числа по суті є предініціалізованими змінними.
Як завжди, будуть заяви, які ми можемо змінити, не впливаючи на кінцевий результат. У golfscript, все оцінюється як істина , за винятком 0
, []
, ""
та {}
(см це ). Тут ми можемо змінити умову виходу з циклу на просто {n}
(обв'язуємо додатковий час і закінчуємо, коли n = 0).
Як і для гольфу на будь-якій мові, це допомагає знати доступні функції. На щастя, список є дуже коротким для golfscript. Ми можемо змінити , 1-
щоб (
врятувати інший персонаж. В даний час код виглядає приблизно так: (ми могли б використовувати тут, 1
а не |
тут, якби хотіли, що скасує ініціалізацію.)
{:n;1:|;{n}{n|*:|;n(:n;}while|}:f
Важливо добре використовувати стек, щоб отримати найкоротші рішення (практика практичної практики). Як правило, якщо значення використовуються лише у невеликому сегменті коду, можливо, не потрібно буде зберігати їх у змінних. Видаляючи працюючу змінну продукту та просто використовуючи стек, ми можемо зберегти досить багато символів.
{: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, ...] (це має деякі нюанси, наприклад, проміжні значення видаляються зі стека перед кожним натиском)
{block}*
: push, push, block, push, block, ...
Документація golfscript має приклад використання {+}*
для підсумовування вмісту масиву. Це говорить про те, що ми можемо використати {*}*
для отримання добутку масиву.
{,{*}*}:f
На жаль, це не все так просто. Усі елементи вимкнено одним ( [0 1 2]
замість [1 2 3]
). Ми можемо використати {)}%
для виправлення цього питання.
{,{)}%{*}*}:f
Ну не зовсім. Це не обробляє нуль правильно. Ми можемо розрахувати (n + 1)! / (N + 1), щоб виправити це, хоча це коштує занадто дорого.
{).,{)}%{*}*\/}:f
Ми також можемо спробувати обробити n = 0 у тому ж відрі, що і n = 1. Це насправді надзвичайно мало, щоб спробувати відпрацювати якнайшвидше.
Не так добре , як сортування, в 7 символів: [1\]$1=
. Зауважте, що ця методика сортування має корисні цілі, такі як накладення меж на число (наприклад, `[0 \ 100] $ 1 =)
Ось переможець із лише 3 символами:! +
Якщо ми хочемо, щоб приріст і множення були в одному блоці, нам слід повторити кожен елемент масиву. Оскільки ми не будуємо масив, це означає, що нам слід користуватися {)*}/
, що приводить нас до найкоротшого впровадження гольфскрипту факторства! У 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
* Примітка: я фактично не перевірив багато фрагментів коду в цій публікації, тому сміливо повідомляйте, чи є помилки.