Funciton , неконкурентный
ОБНОВИТЬ! Массовое улучшение производительности! n = 7 теперь завершается менее чем за 10 минут! Смотрите объяснение внизу!
Это было весело писать. Это решатель грубой силы для этой задачи, написанный на Funciton. Некоторые факты:
- Он принимает целое число на STDIN. Любые посторонние пробелы разбивают его, включая перевод строки после целого числа.
- Используются числа от 0 до n - 1 (не от 1 до n ).
- Он заполняет сетку «в обратном направлении», поэтому вы получаете решение, в котором читается нижний ряд,
3 2 1 0
а не верхний ряд 0 1 2 3
.
- Он правильно выводит
0
(единственное решение) для n = 1.
- Пустой вывод для n = 2 и n = 3.
- При компиляции в исполняемый файл занимает около 8¼ минут для n = 7 (до улучшения производительности было около часа). Без компиляции (с использованием интерпретатора) это занимает в 1,5 раза больше времени, поэтому использование компилятора того стоит.
- В качестве личной вехи я впервые написал целую программу Funciton без предварительного написания программы на языке псевдокодов. Я действительно написал это в фактическом C # сначала все же.
- (Однако это не первый случай, когда я вносил изменения, чтобы значительно улучшить производительность чего-либо в Funciton. Первый раз, когда я сделал это, был в факториальной функции. Замена порядка операндов умножения имела огромное значение из-за как работает алгоритм умножения . На всякий случай, если вам было интересно.)
Без дальнейших церемоний:
┌────────────────────────────────────┐ ┌─────────────────┐
│ ┌─┴─╖ ╓───╖ ┌─┴─╖ ┌──────┐ │
│ ┌─────────────┤ · ╟─╢ Ӂ ╟─┤ · ╟───┤ │ │
│ │ ╘═╤═╝ ╙─┬─╜ ╘═╤═╝ ┌─┴─╖ │ │
│ │ └─────┴─────┘ │ ♯ ║ │ │
│ ┌─┴─╖ ╘═╤═╝ │ │
│ ┌────────────┤ · ╟───────────────────────────────┴───┐ │ │
┌─┴─╖ ┌─┴─╖ ┌────╖ ╘═╤═╝ ┌──────────┐ ┌────────┐ ┌─┴─╖│ │
│ ♭ ║ │ × ╟───┤ >> ╟───┴───┘ ┌─┴─╖ │ ┌────╖ └─┤ · ╟┴┐ │
╘═╤═╝ ╘═╤═╝ ╘══╤═╝ ┌─────┤ · ╟───────┴─┤ << ╟─┐ ╘═╤═╝ │ │
┌───────┴─────┘ ┌────╖ │ │ ╘═╤═╝ ╘══╤═╝ │ │ │ │
│ ┌─────────┤ >> ╟─┘ │ └───────┐ │ │ │ │ │
│ │ ╘══╤═╝ ┌─┴─╖ ╔═══╗ ┌─┴─╖ ┌┐ │ │ ┌─┴─╖ │ │
│ │ ┌┴┐ ┌───────┤ ♫ ║ ┌─╢ 0 ║ ┌─┤ · ╟─┤├─┤ ├─┤ Ӝ ║ │ │
│ │ ╔═══╗ └┬┘ │ ╘═╤═╝ │ ╚═╤═╝ │ ╘═╤═╝ └┘ │ │ ╘═╤═╝ │ │
│ │ ║ 1 ╟───┬┘ ┌─┴─╖ └───┘ ┌─┴─╖ │ │ │ │ │ ┌─┴─╖ │
│ │ ╚═══╝ ┌─┴─╖ │ ɓ ╟─────────────┤ ? ╟─┘ │ ┌─┴─╖ │ ├─┤ · ╟─┴─┐
│ ├─────────┤ · ╟─┐ ╘═╤═╝ ╘═╤═╝ ┌─┴────┤ + ╟─┘ │ ╘═╤═╝ │
┌─┴─╖ ┌─┴─╖ ╘═╤═╝ │ ╔═╧═╕ ╔═══╗ ┌───╖ ┌─┴─╖ ┌─┴─╖ ╘═══╝ │ │ │
┌─┤ · ╟─┤ · ╟───┐ └┐ └─╢ ├─╢ 0 ╟─┤ ⌑ ╟─┤ ? ╟─┤ · ╟──────────────┘ │ │
│ ╘═╤═╝ ╘═╤═╝ └───┐ ┌┴┐ ╚═╤═╛ ╚═╤═╝ ╘═══╝ ╘═╤═╝ ╘═╤═╝ │ │
│ │ ┌─┴─╖ ┌───╖ │ └┬┘ ┌─┴─╖ ┌─┘ │ │ │ │
│ ┌─┴───┤ · ╟─┤ Җ ╟─┘ └────┤ ? ╟─┴─┐ ┌─────────────┘ │ │
│ │ ╘═╤═╝ ╘═╤═╝ ╘═╤═╝ │ │╔════╗╔════╗ │ │
│ │ │ ┌──┴─╖ ┌┐ ┌┐ ┌─┴─╖ ┌─┴─╖ │║ 10 ║║ 32 ║ ┌─────────────────┘ │
│ │ │ │ << ╟─┤├─┬─┤├─┤ · ╟─┤ · ╟─┘╚══╤═╝╚╤═══╝ ┌──┴──┐ │
│ │ │ ╘══╤═╝ └┘ │ └┘ ╘═╤═╝ ╘═╤═╝ │ ┌─┴─╖ ┌─┴─╖ ┌─┴─╖ │
│ │ ┌─┴─╖ ┌─┴─╖ ┌─┴─╖ ┌─┴─╖ ╔═╧═╕ └─┤ ? ╟─┤ · ╟─┤ % ║ │
│ └─────┤ · ╟─┤ · ╟──┤ Ӂ ╟──┤ ɱ ╟─╢ ├───┐ ╘═╤═╝ ╘═╤═╝ ╘═╤═╝ │
│ ╘═╤═╝ ╘═╤═╝ ╘═╤═╝ ╘═══╝ ╚═╤═╛ ┌─┴─╖ ┌─┴─╖ │ └────────────────────┘
│ └─────┤ │ └───┤ ‼ ╟─┤ ‼ ║ │ ┌──────┐
│ │ │ ╘═══╝ ╘═╤═╝ │ │ ┌────┴────╖
│ │ │ ┌─┴─╖ │ │ │ str→int ║
│ │ └──────────────────────┤ · ╟───┴─┐ │ ╘════╤════╝
│ │ ┌─────────╖ ╘═╤═╝ │ ╔═╧═╗ ┌──┴──┐
│ └──────────┤ int→str ╟──────────┘ │ ║ ║ │ ┌───┴───┐
│ ╘═════════╝ │ ╚═══╝ │ │ ┌───╖ │
└───────────────────────────────────────────────────────┘ │ └─┤ × ╟─┘
┌──────────────┐ ╔═══╗ │ ╘═╤═╝
╔════╗ │ ╓───╖ ┌───╖ │ ┌───╢ 0 ║ │ ┌─┴─╖ ╔═══╗
║ −1 ║ └─╢ Ӝ ╟─┤ × ╟──┴──────┐ │ ╚═╤═╝ └───┤ Ӂ ╟─╢ 0 ║
╚═╤══╝ ╙───╜ ╘═╤═╝ │ │ ┌─┴─╖ ╘═╤═╝ ╚═══╝
┌─┴──╖ ┌┐ ┌───╖ ┌┐ ┌─┴──╖ ╔════╗ │ │ ┌─┤ ╟───────┴───────┐
│ << ╟─┤├─┤ ÷ ╟─┤├─┤ << ║ ║ −1 ║ │ │ │ └─┬─╜ ┌─┐ ┌─────┐ │
╘═╤══╝ └┘ ╘═╤═╝ └┘ ╘═╤══╝ ╚═╤══╝ │ │ │ └───┴─┘ │ ┌─┴─╖ │
│ └─┘ └──────┘ │ │ └───────────┘ ┌─┤ ? ╟─┘
└──────────────────────────────┘ ╓───╖ └───────────────┘ ╘═╤═╝
┌───────────╢ Җ ╟────────────┐ │
┌────────────────────────┴───┐ ╙───╜ │
│ ┌─┴────────────────────┐ ┌─┴─╖
┌─┴─╖ ┌─┴─╖ ┌─┴─┤ · ╟──────────────────┐
│ ♯ ║ ┌────────────────────┤ · ╟───────┐ │ ╘═╤═╝ │
╘═╤═╝ │ ╘═╤═╝ │ │ │ ┌───╖ │
┌─────┴───┘ ┌─────────────────┴─┐ ┌───┴───┐ ┌─┴─╖ ┌─┴─╖ ┌─┤ × ╟─┴─┐
│ │ ┌─┴─╖ │ ┌───┴────┤ · ╟─┤ · ╟──────────┤ ╘═╤═╝ │
│ │ ┌───╖ ┌───╖ ┌──┤ · ╟─┘ ┌─┴─┐ ╘═╤═╝ ╘═╤═╝ ┌─┴─╖ │ │
│ ┌────┴─┤ ♭ ╟─┤ × ╟──┘ ╘═╤═╝ │ ┌─┴─╖ ┌───╖└┐ ┌──┴─╖ ┌─┤ · ╟─┘ │
│ │ ╘═══╝ ╘═╤═╝ ┌───╖ │ │ │ × ╟─┤ Ӝ ╟─┴─┤ ÷% ╟─┐ │ ╘═╤═╝ ┌───╖ │
│ ┌─────┴───┐ ┌────┴───┤ Ӝ ╟─┴─┐ │ ╘═╤═╝ ╘═╤═╝ ╘══╤═╝ │ │ └───┤ Ӝ ╟─┘
│ ┌─┴─╖ ┌───╖ │ │ ┌────╖ ╘═╤═╝ │ └───┘ ┌─┴─╖ │ │ └────┐ ╘═╤═╝
│ │ × ╟─┤ Ӝ ╟─┘ └─┤ << ╟───┘ ┌─┴─╖ ┌───────┤ · ╟───┐ │ ┌─┴─╖ ┌───╖ │ │
│ ╘═╤═╝ ╘═╤═╝ ╘══╤═╝ ┌───┤ + ║ │ ╘═╤═╝ ├──┴─┤ · ╟─┤ × ╟─┘ │
└───┤ └────┐ ╔═══╗ ┌─┴─╖ ┌─┴─╖ ╘═╤═╝ │ ╔═══╗ ┌─┴─╖ ┌─┴─╖ ╘═╤═╝ ╘═╤═╝ │
┌─┴─╖ ┌────╖ │ ║ 0 ╟─┤ ? ╟─┤ = ║ ┌┴┐ │ ║ 0 ╟─┤ ? ╟─┤ = ║ │ │ ┌────╖ │
│ × ╟─┤ << ╟─┘ ╚═══╝ ╘═╤═╝ ╘═╤═╝ └┬┘ │ ╚═══╝ ╘═╤═╝ ╘═╤═╝ │ └─┤ << ╟─┘
╘═╤═╝ ╘═╤══╝ ┌┐ ┌┐ │ │ └───┘ ┌─┴─╖ ├──────┘ ╘═╤══╝
│ └────┤├──┬──┤├─┘ ├─────────────────┤ · ╟───┘ │
│ └┘┌─┴─╖└┘ │ ┌┐ ┌┐ ╘═╤═╝ ┌┐ ┌┐ │
└────────────┤ · ╟─────────┘ ┌─┤├─┬─┤├─┐ └───┤├─┬─┤├────────────┘
╘═╤═╝ │ └┘ │ └┘ │ └┘ │ └┘
└───────────────┘ │ └────────────┘
Объяснение первой версии
Первая версия заняла около часа, чтобы решить n = 7. Далее объясняется, как работает эта медленная версия. Внизу я объясню, какие изменения я внес, чтобы получить менее 10 минут.
Экскурсия на кусочки
Эта программа нуждается в битах. Ему нужно много битов, и они нужны во всех нужных местах. Опытные программисты Funciton уже знают, что если вам нужно n бит, вы можете использовать формулу
который в Funciton может быть выражен как
При выполнении оптимизации производительности мне пришло в голову, что я могу вычислить то же значение гораздо быстрее, используя эту формулу:
Я надеюсь, вы простите меня, что я не обновил всю графику уравнений в этом посте соответственно.
Теперь, допустим, вам не нужен непрерывный блок битов; на самом деле, вы хотите n бит через равные интервалы каждый k-й бит, вот так:
LSB
↓
00000010000001000000100000010000001
└──┬──┘
k
Формула для этого довольно проста, если вы знаете это:
В коде функция Ӝ
принимает значения n и k и вычисляет эту формулу.
Отслеживание используемых номеров
В итоговой сетке n n чисел, и каждое число может иметь любое из n возможных значений. Чтобы отслеживать, какие числа разрешены в каждой ячейке, мы поддерживаем число, состоящее из n ³ битов, в котором бит установлен, чтобы указать, что определенное значение взято. Первоначально это число 0, очевидно.
Алгоритм начинается в правом нижнем углу. После «угадывания» первое число равно 0, нам нужно отследить тот факт, что 0 больше не разрешен ни в одной ячейке в той же строке, столбце и диагонали:
LSB (example n=5)
↓
10000 00000 00000 00000 10000
00000 10000 00000 00000 10000
00000 00000 10000 00000 10000
00000 00000 00000 10000 10000
10000 10000 10000 10000 10000
↑
MSB
Для этого мы рассчитываем следующие четыре значения:
Текущая строка: нам нужно n битов каждый n-й бит (по одному на ячейку), а затем сдвинуть его к текущей строке r , помня, что каждая строка содержит n ² битов:
Текущий столбец: нам нужно n битов через каждый n ²-й бит (по одному на строку), а затем сдвинуть его к текущему столбцу c , помня, что каждый столбец содержит n битов:
Прямая диагональ: нам нужно n бит каждый ... (вы обратили внимание? Быстро, разберитесь!) ... n ( n +1) -й бит (хорошо!), Но только если мы на самом деле включены прямая диагональ:
Обратная диагональ: две вещи здесь. Во-первых, как мы узнаем, что находимся на обратной диагонали? Математически, это условие c = ( n - 1) - r , что совпадает с c = n + (- r - 1). Эй, это напоминает тебе о чем-то? Это верно, это дополнение к двум, поэтому мы можем использовать побитовое отрицание (очень эффективное в Funciton) вместо декремента. Во-вторых, вышеприведенная формула предполагает, что мы хотим установить младший значащий бит, но в обратной диагонали мы этого не делаем, поэтому мы должны сдвинуть его вверх на ... вы знаете? ... Это верно, n ( п - 1).
Это также единственный, где мы потенциально делим на 0, если n = 1. Однако, Funciton это не волнует. 0 ÷ 0 это просто 0, ты не знаешь?
В коде функция Җ
(нижняя) берет n и индекс (из которого она вычисляет r и c делением и остатком), вычисляет эти четыре значения и or
складывает их вместе.
Алгоритм перебора
Алгоритм грубой силы реализован Ӂ
(функция вверху). Он принимает n (размер сетки), индекс (где в сетке мы в настоящее время размещаем число) и берется (число с n ³ битами, указывающими, какие числа мы еще можем разместить в каждой ячейке).
Эта функция возвращает последовательность строк. Каждая строка является полным решением для сетки. Это полный решатель; он вернет все решения, если вы позволите, но вернет их в виде последовательности с отложенным вычислением.
Если индекс достиг 0, мы успешно заполнили всю сетку, поэтому мы возвращаем последовательность, содержащую пустую строку (единственное решение, которое не охватывает ни одну из ячеек). Пустая строка есть 0
, и мы используем библиотечную функцию, ⌑
чтобы превратить ее в одноэлементную последовательность.
Проверка, описанная ниже в разделе « Улучшение производительности», происходит здесь.
Если индекс еще не достиг 0, мы уменьшаем его на 1, чтобы получить индекс, по которому нам теперь нужно разместить число (назовите ix ).
Мы используем ♫
для генерации ленивую последовательность, содержащую значения от 0 до n - 1.
Затем мы используем ɓ
(монадное связывание) с лямбдой, которая делает следующее по порядку:
- Сначала посмотрите на соответствующий бит в кадре, чтобы решить, является ли число действительным здесь или нет. Мы можем поместить число i тогда и только тогда, когда принято & (1 << ( n × ix ) << i ) еще не установлено. Если оно установлено, вернуть
0
(пустая последовательность).
- Используется
Җ
для вычисления битов, соответствующих текущей строке, столбцу и диагонали. Сдвинь его на меня, а затем or
на взятый .
- Рекурсивно вызывать
Ӂ
для извлечения всех решений для оставшихся ячеек, передавая ему новые принятые и уменьшенные ix . Это возвращает последовательность неполных строк; каждая строка имеет символы ix (сетка заполнена до индекса ix ).
- Используйте
ɱ
(map), чтобы просмотреть найденные решения и использовать, ‼
чтобы объединить i до конца каждого. Добавьте новую строку, если индекс кратен n , в противном случае пробел.
Генерация результата
Основная программа вызывает Ӂ
(брутфорсер) с n , index = n ² (помните, что мы заполняем сетку задом наперед) и берется = 0 (изначально ничего не берется). Если результатом является пустая последовательность (решение не найдено), выведите пустую строку. В противном случае выведите первую строку в последовательности. Обратите внимание, что это означает, что он будет оценивать только первый элемент последовательности, поэтому решатель не продолжает работу, пока не найдет все решения.
Улучшение производительности
(Для тех, кто уже прочитал старую версию объяснения: программа больше не генерирует последовательность последовательностей, которую нужно отдельно превратить в строку для вывода; она просто генерирует последовательность строк напрямую. Я соответственно отредактировал объяснение Но это было не главное улучшение. Вот оно.)
На моей машине скомпилированный exe первой версии занял ровно 1 час, чтобы решить n = 7. Это не было в заданном временном интервале в 10 минут, поэтому я не отдыхал. (Ну, на самом деле, причина, по которой я не отдыхал, заключалась в том, что у меня была идея о том, как это значительно ускорить.)
Алгоритм, как описано выше, останавливает свой поиск и отслеживает каждый раз, когда он сталкивается с ячейкой, в которой установлены все биты в взятом числе, указывая, что в эту ячейку ничего нельзя вставить.
Однако алгоритм будет продолжать бесполезно заполнять сетку до ячейки, в которой установлены все эти биты. Было бы намного быстрее, если бы он мог остановиться, как только в любой еще не заполненной ячейке уже установлены все биты, что уже указывает на то, что мы никогда не сможем решить остальную часть сетки, независимо от того, какие числа мы вставим в Это. Но как вы эффективно проверяете, есть ли в любой ячейке свои n битов, не пройдя их все?
Трюк начинается с добавления одного бита на ячейку к взятому числу. Вместо того, что было показано выше, теперь это выглядит так:
LSB (example n=5)
↓
10000 0 00000 0 00000 0 00000 0 10000 0
00000 0 10000 0 00000 0 00000 0 10000 0
00000 0 00000 0 10000 0 00000 0 10000 0
00000 0 00000 0 00000 0 10000 0 10000 0
10000 0 10000 0 10000 0 10000 0 10000 0
↑
MSB
Вместо n ³ теперь в этом числе n ² ( n + 1) бит. Функция, которая заполняет текущую строку / столбец / диагональ, была соответствующим образом изменена (на самом деле, полностью переписана, если честно). Эта функция будет по-прежнему заполнять только n битов на ячейку, поэтому добавленный нами дополнительный бит будет всегда 0
.
Теперь предположим, что мы на полпути к вычислению, мы просто поместили a 1
в среднюю ячейку, и полученное число выглядит примерно так:
current
LSB column (example n=5)
↓ ↓
11111 0 10010 0 01101 0 11100 0 11101 0
00011 0 11110 0 01101 0 11101 0 11100 0
11111 0 11110 0[11101 0]11100 0 11100 0 ← current row
11111 0 11111 0 11111 0 11111 0 11111 0
11111 0 11111 0 11111 0 11111 0 11111 0
↑
MSB
Как видите, верхняя левая ячейка (индекс 0) и средняя левая ячейка (индекс 10) теперь невозможны. Как мы можем наиболее эффективно определить это?
Рассмотрим число, в котором установлен 0-й бит каждой ячейки, но только до текущего индекса. Такое число легко рассчитать по знакомой формуле:
Что мы получим, если сложим эти два числа вместе?
LSB LSB
↓ ↓
11111 0 10010 0 01101 0 11100 0 11101 0 10000 0 10000 0 10000 0 10000 0 10000 0 ╓───╖
00011 0 11110 0 01101 0 11101 0 11100 0 ║ 10000 0 10000 0 10000 0 10000 0 10000 0 ║
11111 0 11110 0 11101 0 11100 0 11100 0 ═══╬═══ 10000 0 10000 0 00000 0 00000 0 00000 0 ═════ ╓─╜
11111 0 11111 0 11111 0 11111 0 11111 0 ║ 00000 0 00000 0 00000 0 00000 0 00000 0 ═════ ╨
11111 0 11111 0 11111 0 11111 0 11111 0 00000 0 00000 0 00000 0 00000 0 00000 0 o
↑ ↑
MSB MSB
Результат:
OMG
↓
00000[1]01010 0 11101 0 00010 0 00011 0
10011 0 00001 0 11101 0 00011 0 00010 0
═════ 00000[1]00001 0 00011 0 11100 0 11100 0
═════ 11111 0 11111 0 11111 0 11111 0 11111 0
11111 0 11111 0 11111 0 11111 0 11111 0
Как видите, сложение переполняется добавленным нами дополнительным битом, но только если все биты для этой ячейки установлены! Поэтому все, что осталось сделать, это замаскировать эти биты (та же формула, что и выше, но << n ) и проверить, равен ли результат 0:
00000[1]01010 0 11101 0 00010 0 00011 0 ╓╖ 00000 1 00000 1 00000 1 00000 1 00000 1 ╓─╖ ╓───╖
10011 0 00001 0 11101 0 00011 0 00010 0 ╓╜╙╖ 00000 1 00000 1 00000 1 00000 1 00000 1 ╓╜ ╙╖ ║
00000[1]00001 0 00011 0 11100 0 11100 0 ╙╥╥╜ 00000 1 00000 1 00000 0 00000 0 00000 0 ═════ ║ ║ ╓─╜
11111 0 11111 0 11111 0 11111 0 11111 0 ╓╜╙╥╜ 00000 0 00000 0 00000 0 00000 0 00000 0 ═════ ╙╖ ╓╜ ╨
11111 0 11111 0 11111 0 11111 0 11111 0 ╙──╨─ 00000 0 00000 0 00000 0 00000 0 00000 0 ╙─╜ o
Если это не ноль, сетка невозможна, и мы можем остановиться.
- Снимок экрана, показывающий решение и время выполнения для n = 4 до 7.