Деление , 1328 989 887 797 байт
Этот ответ немного неоправданно длинен (хотелось бы, чтобы у нас были разборные регионы ) ... пожалуйста, не забудьте прокрутить мимо этого и показать другим ответам некоторую любовь!
Работа над этим кодом была тем, что вдохновило этот вызов. Я хотел добавить ответ в Fission в EOEIS, что привело меня к этой последовательности. Тем не менее, на самом деле изучение Fission и его внедрение заняло несколько недель, включая и выключая его. В то же время, последовательность действительно выросла на мне, поэтому я решил опубликовать для нее отдельную задачу (плюс, в любом случае, EOEIS не был бы слишком далеко внизу дерева).
Итак, я представляю вам, чудовище:
R'0@+\
/ Y@</ /[@ Y]_L
[? % \ / \ J
\$@ [Z/;[{+++++++++L
UR+++++++++>/;
9\ ; 7A9
SQS {+L /$ \/\/\/\/\/ 5/ @ [~ &@[S\/ \ D /8/
~4X /A@[ %5 /; & K } [S//~KSA /
3 \ A$@S S\/ \/\/\/ \/>\ /S]@A / \ { +X
W7 X X /> \ +\ A\ / \ /6~@/ \/
/ ~A\; +;\ /@
ZX [K / {/ / @ @ } \ X @
\AS </ \V / }SZS S/
X ;;@\ /;X /> \ ; X X
; \@+ >/ }$S SZS\+; //\V
/ \\ /\; X X @ @ \~K{
\0X / /~/V\V / 0W//
\ Z [K \ //\
W /MJ $$\\ /\7\A /;7/\/ /
4}K~@\ &] @\ 3/\
/ \{ }$A/1 2 }Y~K <\
[{/\ ;@\@ / \@<+@^ 1;}++@S68
@\ <\ 2 ; \ /
$ ;}++ +++++++L
%@A{/
M \@+>/
~ @
SNR'0YK
\ A!/
Ожидается, что на входе нет завершающего символа новой строки, так что вы можете назвать его как echo -n 120 | ./Fission oeis256504.fis
.
Компоновка, возможно, все еще может быть более эффективной, поэтому я думаю, что здесь еще много возможностей для совершенствования (например, она содержит 911 581 461 374 пробелов).
Прежде чем мы перейдем к объяснению, обратите внимание на проверку: официальный переводчик работает не совсем так, как есть. а) Mirror.cpp
не компилируется во многих системах. Если вы столкнулись с этой проблемой, просто закомментируйте ошибочную строку - уязвимый компонент (случайное зеркало) не используется в этом коде. б) Есть пара ошибок, которые могут привести к неопределенному поведению (и, скорее всего, для такой сложной программы). Вы можете применить этот патч, чтобы исправить их. Как только вы это сделаете, вы сможете скомпилировать интерпретатор с
g++ -g --std=c++11 *.cpp -o Fission
Интересный факт: эта программа использует почти все компоненты, которые может предложить Fission, за исключением #
(случайное зеркало), :
(полупрозрачное зеркало) -
или |
(обычное зеркало) и "
(режим печати).
Что на земле?
Предупреждение: это будет довольно долго ... Я предполагаю, что вы действительно заинтересованы в том, как работает Fission и как можно программировать в ней. Потому что если нет, я не уверен, как я мог бы обобщить это. (Следующий абзац дает общее описание языка.)
Fission - это двумерный язык программирования, где потоки данных и управления представлены атомами, движущимися через сетку. Если вы видели или использовали Marbelous раньше, концепция должна быть смутно знакомой. Каждый атом имеет два целочисленных свойства: неотрицательную массу и произвольную энергию. Если масса становится отрицательной, атом удаляется из сетки. В большинстве случаев вы можете рассматривать массу как «значение» атома, а энергию как некое мета-свойство, которое используется несколькими компонентами для определения потока атомов (то есть большинство видов переключателей зависят от знака энергия). Я буду обозначать атомы (m,E)
, когда это необходимо. В начале программы сетка начинается со связки(1,0)
атомы, где бы вы ни находились, из четырех компонентов UDLR
(где буква указывает направление, в котором атом движется изначально). Затем доска заполняется целой кучей компонентов, которые изменяют массу и энергию атомов, меняют их направления или делают другие более сложные вещи. Полный список см. На странице esolangs , но я представлю большинство из них в этом объяснении. Другим важным моментом (который программа использует несколько раз) является то, что сетка является тороидальной: атом, который сталкивается с любой из сторон, вновь появляется на противоположной стороне, двигаясь в том же направлении.
Я написал программу в нескольких небольших частях и собрал их в конце, так что вот как я объясню.
atoi
Этот компонент может показаться довольно неинтересным, но он приятен и прост и позволяет мне представить множество важных понятий арифметики и потока управления деления. Поэтому я подробно расскажу об этой части, поэтому я могу свести остальные части к введению новой механики деления и выделению компонентов более высокого уровня, с подробным потоком управления которыми вы сможете следить самостоятельно.
Fission может считывать только байтовые значения отдельных символов, а не целых чисел. Несмотря на то, что это приемлемая практика , я решил, что могу сделать это правильно и проанализировать действительные целые числа в STDIN. Вот atoi
код:
;
R'0@+\
/ Y@</ /[@ Y]_L
[? % \ / \ J
\$@ [Z/;[{+++++++++L
UR+++++++++>/;
O
Двумя наиболее важными компонентами деления являются реакторы деления и синтеза. Реакторы деления являются любыми из V^<>
(код выше использует <
и >
). Реактор деления может хранить атом (отправляя его в клин персонажа), по умолчанию (2,0)
. Если атом достигнет вершины персонажа, два новых атома будут отосланы в стороны. Их масса определяется путем деления поступающей массы на сохраненную массу (т.е. делится пополам по умолчанию) - левый атом получает это значение, а правый атом получает остаток массы (т. Е. Масса сохраняется при делении) , Оба исходящих атома будут иметь входящую энергию минуснакопленная энергия. Это означает, что мы можем использовать реакторы деления для арифметики - как для вычитания, так и для деления. Если реактор деления попадает с площадки, атом просто отражается по диагонали и затем будет двигаться в направлении вершины персонажа.
Реакторы Fusion являются любыми из YA{}
(код выше использует Y
и {
). Их функция аналогична: они могут хранить атом (по умолчанию (1,0)
), и при попадании из вершины два новых атома будут отправлены в стороны. Однако в этом случае два атома будут идентичны, всегда сохраняя поступающую энергию и умножая поступающую массу на сохраненную массу. То есть по умолчанию термоядерный реактор просто дублирует любой атом, попадающий в его вершину. При ударе сбоку реакторы термоядерного синтеза немного сложнее: атом тожесохраняется (независимо от другой памяти), пока атом не попадет на противоположную сторону. Когда это происходит, новый атом высвобождается в направлении вершины, масса и энергия которой являются суммой двух старых атомов. Если новый атом попадает на ту же сторону до того, как соответствующий атом достигает противоположной стороны, старый атом просто перезаписывается. Реакторы синтеза могут быть использованы для осуществления сложения и умножения.
Другой простой компонент , я хочу , чтобы выйти из пути есть [
и ]
которые просто установить направление атома направо и налево, соответственно (независимо от входящего направления). Вертикальными эквивалентами являются M
(вниз) и W
(вверх), но они не используются для atoi
кода. UDLR
также действовать как WM][
после освобождения их начальных атомов.
В любом случае, давайте посмотрим на код там. Программа начинается с 5 атомов:
R
И L
на дне просто получить их прирост массы (с +
) , чтобы стать , (10,0)
а затем хранили в делении и термоядерного реактора, соответственно. Мы будем использовать эти реакторы для анализа ввода base-10.
- В
L
верхнем правом углу масса уменьшается (с _
), чтобы стать (0,0)
и хранится в стороне от термоядерного реактора Y
. Это нужно для того, чтобы отслеживать число, которое мы читаем - мы будем постепенно увеличивать и умножать это по мере того, как будем читать цифры.
- В
R
верхнем левом углу его масса устанавливается в код символа 0
(48) с '0
, затем масса и энергия меняются местами @
и, наконец, масса увеличивается один раз, +
чтобы дать (1,48)
. Затем он перенаправляется с диагональными зеркалами \
и /
хранится в реакторе деления. Мы будем использовать 48
вычитание для преобразования входных данных ASCII в фактические значения цифр. Нам также пришлось увеличить массу, 1
чтобы избежать деления на 0
.
- Наконец,
U
нижний левый угол - это то, что фактически приводит все в движение и изначально используется только для управления потоком.
После перенаправления вправо управляющий атом попадает ?
. Это входной компонент. Он читает символ и устанавливает массу атома в значение ASCII для чтения, а энергию - в 0
. Если вместо этого мы нажмем EOF, энергия будет установлена на 1
.
Атом продолжается, а затем ударяет %
. Это переключатель зеркала. Для неположительной энергии это действует как /
зеркало. Но для положительной энергии она действует как a \
(а также уменьшает энергию на 1). Поэтому, пока мы читаем символы, атом будет отражаться вверх, и мы можем обработать символ. Но когда мы закончим с вводом, атом будет отражен вниз, и мы можем применить другую логику для получения результата. К вашему сведению, противоположный компонент есть &
.
Итак, у нас сейчас атом движется вверх. Что мы хотим сделать для каждого символа, это прочитать его цифровое значение, добавить его к нашему промежуточному итогу и затем умножить это промежуточное значение на 10, чтобы подготовиться к следующей цифре.
Сначала атом персонажа попадает в (по умолчанию) термоядерный реактор Y
. Это разделяет атом, и мы используем левую копию в качестве управляющего атома, чтобы вернуться к компоненту ввода и прочитать следующий символ. Правильная копия будет обработана. Рассмотрим случай, когда мы прочитали персонажа 3
. Наш атом будет (51,0)
. Мы поменялись массой и энергией с тем @
, чтобы мы могли воспользоваться вычитанием следующего реактора деления. Реактор вычитает 48
энергию (без изменения массы), поэтому он отсылает две копии (0,3)
- энергия теперь соответствует цифре, которую мы прочитали. Исходящая копия просто отбрасывается ;
(компонент, который просто уничтожает все входящие атомы). Мы продолжим работать с исходящей копией. Вам нужно будет следовать по его пути через/
и \
немного отражает.
@
Непосредственно перед термоядерным реактором SWAPS массы и энергии снова, таким образом, что мы добавим (3,0)
к нашему нарастающему итогу в Y
. Обратите внимание, что сам промежуточный итог всегда будет иметь 0
энергию.
Сейчас J
прыжок. То, что он делает, это скачок любого входящего атома вперед его энергией. Если это так 0
, атом просто продолжает двигаться прямо. Если это 1
будет пропустить одну ячейку, если 2
это будет пропустить две ячейки и так далее. Энергия расходуется на прыжок, поэтому атом всегда заканчивается энергией 0
. Поскольку текущий итог имеет нулевую энергию, скачок пока игнорируется, и атом перенаправляется в термоядерный реактор, {
который умножает его массу на 10
. Нисходящая копия сбрасывается, в ;
то время как исходная копия возвращается в Y
реактор в качестве новой рабочей суммы.
Вышеприведенное повторяется (забавным конвейерным способом, где новые цифры обрабатываются до того, как будут выполнены предыдущие), пока мы не нажмем EOF. Теперь %
атом отправит вниз. Идея заключается в том , чтобы превратить этот атом в (0,1)
теперь перед ударять работает общий реактор так , что а) общее не влияет (нулевую массу) и б) мы получаем энергию 1
перепрыгнуть [
. Мы можем легко заботиться об энергии $
, которая увеличивает энергию.
Проблема в том, что при ?
сбросе EOF масса не сбрасывается, поэтому масса все равно будет равна массе последнего прочитанного символа, а энергия будет 0
(потому что %
уменьшена 1
обратно 0
). Итак, мы хотим избавиться от этой массы. Для этого мы переставляем массу и энергию @
снова.
Мне нужно ввести еще один компонент , прежде чем закончить этот раздел: Z
. По сути это то же самое, что %
и &
. Разница заключается в том, что он позволяет атомам положительной энергии проходить прямо (при уменьшении энергии) и отклоняет атомы неположительной энергии на 90 градусов влево. Мы можем использовать это для устранения энергии атома, повторяя ее Z
снова и снова - как только энергия исчезнет, атом отклонится и покинет цикл. Вот этот шаблон:
/ \
[Z/
где атом будет двигаться вверх, когда энергия равна нулю. Я буду использовать этот шаблон в той или иной форме несколько раз в других частях программы.
Таким образом , когда атом покидает эту маленькую петлю, он будет (1,0)
и мест , чтобы (0,1)
самые @
до попадания в реакторе слитого выпустить конечный результат ввода. Тем не менее, промежуточный итог будет отключен в 10 раз, потому что мы предварительно умножили его еще на одну цифру.
Так что теперь с энергией 1
этот атом будет пропускать [
и прыгать в /
. Это отклоняет его в реактор деления, который мы подготовили разделить на 10 и исправить наше постороннее умножение. Опять же, мы отбрасываем одну половину с помощью ;
и оставляем другую в качестве выходных данных (здесь представлено с помощью, O
которая просто напечатает соответствующий символ и уничтожит атом - в полной программе мы продолжаем использовать атом вместо этого).
itoa
/ \
input -> [{/\ ;@
@\ <\
$ ;}++ +++++++L
%@A{/
M \@+>/
~ @
SNR'0YK
\ A!/
Конечно, нам также нужно преобразовать результат обратно в строку и распечатать его. Вот для чего эта часть. Это предполагает, что ввод поступает не раньше тика 10 или около того, но в полной программе это легко дается. Этот бит можно найти в нижней части полной программы.
Этот код представляет новый очень мощный компонент Fission: стек K
. Стек изначально пуст. Когда атом с неотрицательной энергией попадает в стек, атом просто помещается в стек. Когда атом с отрицательной энергией попадает в стек, его масса и энергия будут заменены атомом на вершине стека (который таким образом выталкивается). Однако если стек пуст, направление атома меняется на противоположное, и его энергия становится положительной (т.е. умножается на -1
).
Хорошо, вернемся к фактическому коду. Идея itoa
фрагмента состоит в том, чтобы многократно использовать вход по модулю 10, чтобы найти следующую цифру, а целочисленное деление ввода на 10 для следующей итерации. Это даст все цифры в обратном порядке (от наименее значимого до наиболее значимого). Чтобы исправить порядок, мы помещаем все цифры в стопку и в конце выталкиваем их одну за другой, чтобы распечатать.
Верхняя половина кода выполняет вычисление цифр: L
плюсы дают 10, которые мы клонируем и подаем в реактор деления и термоядерного синтеза, так что мы можем делить и умножать на 10. Цикл, по сути, начинается после [
в верхнем левом углу. , Текущее значение делится: одна копия делится на 10, затем умножается на 10 и сохраняется в реакторе деления, в который затем попадает другая копия на вершине. Это вычисляется i % 10
как i - ((i/10) * 10)
. Также обратите внимание, что A
после деления и перед умножением разбивает промежуточный результат, так что мы можем ввести его i / 10
в следующую итерацию.
%
Прервет цикл когда переменные итерации хитов 0. Так как это более или менее делать-то время цикла, этот код будет работать даже для печати 0
(без создания ведущих нулей в противном случае). Как только мы покидаем цикл, мы хотим очистить стек и вывести цифры. S
это противоположность Z
, так что это переключатель, который будет отклонять входящий атом с неположительной энергией на 90 градусов вправо. Таким образом, атом фактически перемещается через край от S
прямой к точке, K
чтобы выскочить из цифры (обратите внимание на ~
то, что у входящего атома есть энергия -1
). Эта цифра увеличивается на единицу, 48
чтобы получить код ASCII соответствующего символа цифры. A
Расщепляет цифру напечатать одну копию с!
и подайте другую копию обратно в Y
реактор для следующей цифры. Напечатанная копия используется в качестве следующего триггера для стопки (обратите внимание, что зеркала также посылают ее по краю, чтобы ударить M
слева).
Когда стек пуст, он K
будет отражать атом и превращать его энергию так +1
, чтобы он проходил прямо через S
. N
печатает новую строку (просто потому, что она аккуратная :)). И тогда атом идет R'0
снова, чтобы оказаться в стороне Y
. Так как вокруг нет никаких атомов, это никогда не будет выпущено, и программа завершится.
Вычисление числа деления: рамки
Давайте перейдем к фактическому содержанию программы. Код в основном является портом моей эталонной реализации Mathematica:
fission[n_] := If[
(div =
SelectFirst[
Reverse@Divisors[2 n],
(OddQ@# == IntegerQ[n/#]
&& n/# > (# - 1)/2) &
]
) == 1,
1,
1 + Total[fission /@ (Range@div + n/div - (div + 1)/2)]
]
где div
число целых чисел в максимальном разделе.
Основные отличия в том, что мы не можем иметь дело с полуцелыми значениями в Fission, поэтому я делаю много вещей, умноженных на два, и что в Fission нет рекурсии. Чтобы обойти это, я помещаю все целые числа в раздел в очередь для последующей обработки. Для каждого числа, которое мы обрабатываем, мы увеличиваем счетчик на единицу, а когда очередь пуста, мы освобождаем счетчик и отправляем его на печать. (Очередь Q
работает точно так же K
, как в порядке FIFO.)
Вот основа для этой концепции:
+--- input goes in here
v
SQS ---> compute div from n D /8/
~4X | /~KSA /
3 +-----------> { +X
initial trigger ---> W 6~@/ \/
4
W ^ /
| 3
^ generate range |
| from n and div <-+----- S6
| -then-
+---- release new trigger
Наиболее важными новыми компонентами являются цифры. Это телепорты. Все телепорты с одной цифрой принадлежат друг другу. Когда атом попадает в любой телепорт, он будет немедленно перемещать следующего телепорта в той же группе, где следующий определяется в обычном порядке слева направо, сверху вниз. Это не обязательно, но помогает с макетом (и, следовательно, немного в гольф). Существует также тот, X
который просто дублирует атом, отправляя одну копию прямо вперед, а другую назад.
К настоящему времени вы могли бы самостоятельно разобраться с большинством фреймворков. В верхнем левом углу находится очередь значений, которые еще предстоит обработать, и выпускаются по одному n
за раз. Одна копия n
телепортируется в нижнюю часть, потому что она нужна нам при вычислении диапазона, другая копия переходит в блок в верхней части, который вычисляет div
(это, безусловно, самый большой раздел кода). После div
вычисления он дублируется - одна копия увеличивает счетчик в верхнем правом углу, который хранится в K
. Другая копия телепортируется вниз. Если div
это так 1
, мы немедленно отклоняем его вверх и используем в качестве триггера для следующей итерации, не ставя в очередь новые значения. В противном случае мы используем div
иn
в разделе внизу, чтобы создать новый диапазон (то есть поток атомов с соответствующими массами, которые впоследствии помещаются в очередь), а затем отпустите новый триггер после того, как диапазон будет завершен.
Как только очередь опустеет, триггер будет отражен, пройдя прямо через S
и снова появившись в верхнем правом углу, где он освобождает счетчик (конечный результат), из A
которого затем телепортируется itoa
через 8
.
Вычисление числа деления: тело цикла
Таким образом, все, что осталось, - это две секции для вычисления div
и генерации диапазона. Вычислительная div
это часть:
;
{+L /$ \/\/\/\/\/ 5/ @ [~ &@[S\/ \
/A@[ %5 /; & K } [S/
\ A$@S S\/ \/\/\/ \/>\ /S]@A / \
X X /> \ +\ A\ / \ /
/ ~A\; +;\ /@
ZX [K / {/ / @ @ } \ X @
\AS </ \V / }SZS S/
X ;;@\ /;X /> \ ; X X
\@+ >/ }$S SZS\+; //\V
/ \\ /\; X X @ @ \~K{
\0X / /~/V\V / 0W//
\ Z [K \ //\
\ /\7\A /;7/\/
Вы, наверное, уже видели достаточно, чтобы разобраться в этом с некоторым терпением. Разбивка высокого уровня такова: первые 12 столбцов или около того генерируют поток делителей 2n
. Следующие 10 столбцов отфильтровывают те, которые не удовлетворяют OddQ@# == IntegerQ[n/#]
. Следующие 8 столбцов отфильтровывают те, которые не удовлетворяют n/# > (# - 1)/2)
. Наконец, мы помещаем все действительные делители в стек, и как только мы закончим, мы опустошаем весь стек в реактор слияния (перезаписывая все, кроме последнего / самого большого делителя), а затем высвобождаем результат, после чего удаляем его энергию (которая была не Ноль от проверки неравенства).
Там много безумных путей, которые на самом деле ничего не делают. Преимущественно, \/\/\/\/
безумие наверху ( 5
s также являются его частью) и один путь вокруг дна (который проходит через 7
s). Я должен был добавить их, чтобы справиться с некоторыми неприятными условиями гонки. Деление может использовать компонент задержки ...
Код, который генерирует новый диапазон из n
и div
это:
/MJ $$\
4}K~@\ &] @\ 3/\
\{ }$A/1 2 }Y~K <\
\@ / \@<+@^ 1;}++@
2 ; \ /
Сначала мы вычисляем n/div - (div + 1)/2
(оба условия скомпонованы, что дает одинаковый результат) и сохраняем их для дальнейшего использования. Затем мы генерируем диапазон div
сверху вниз 1
и добавляем сохраненное значение к каждому из них.
В обоих из них есть два новых общих паттерна, которые я должен упомянуть: один - SX
или ZX
удар снизу (или повернутые версии). Это хороший способ дублировать атом, если вы хотите, чтобы одна копия шла прямо (поскольку перенаправление выходов термоядерного реактора иногда может быть громоздким). В S
или Z
вращается атом на , X
а затем поворачивает зеркальную копию обратно в исходное направление распространения.
Другая модель
[K
\A --> output
Если мы храним какое-либо значение, K
мы можем повторно получить его, ударяя K
отрицательной энергией сверху. A
Дублирует значение мы заинтересованы в том, и посылает то , что копировать правый на стек на следующий раз , когда нам это нужно.
Что ж, это был настоящий том ... но если вы на самом деле справились с этим, надеюсь, у вас возникла идея, что Fission i͝s̢̘̗̗ ͢i̟nç̮̩r̸̭̬̱͔e̟̹̟̜͟d̙i̠͙͎̖͓̯b̘̠͎̭̰̼l̶̪̙̮̥̮y̠̠͎̺͜ ͚̬̮f̟͞u̱̦̰͍n͍ ̜̠̙t̸̳̩̝o ̫͉̙͠p̯̱̭͙̜͙͞ŕ̮͓̜o̢̙̣̭g̩̼̣̝r̤͍͔̘̟ͅa̪̜͇m̳̭͔̤̞ͅ ͕̺͉̫̀ͅi͜n̳̯̗̳͇̹.̫̞̲̞̜̳