история
Моя компания рассылает еженедельную рассылку всем сотрудникам компании. В эти новостные рассылки включена загадка, а также крик тому, кто в компании первым отправил электронное письмо / предоставил решение загадки на прошлой неделе. Большинство из этих загадок довольно тривиальны и, честно говоря, довольно скучны для технической компании, но один, несколько месяцев назад, привлек мое внимание.
Оригинальная загадка:
Учитывая форму ниже:
У вас есть натуральные числа от 1 до 16. Подберите их все так, чтобы все смежные ряды и смежные столбцы суммировались до 29.
Например, одним из таких решений этой головоломки (которое было «каноническим» решением, которое я отправил в информационный бюллетень) было следующее:
Однако в процессе ее решения я нашел довольно интересную информацию:
- Есть значительно больше решений, чем только это; На самом деле существует 9368 решений.
- Если развернуть набор правил, чтобы требовать, чтобы только строки и столбцы были равны друг другу, а не обязательно 29, вы получите 33 608 решений:
- 4440 решений на сумму 27.
- 7400 решений на сумму 28.
- 9 368 решений на сумму 29.
- 6 096 Решений на сумму 30.
- 5,104 решения на сумму 31.
- 1200 решений на сумму 32.
Поэтому я и мои коллеги (хотя в основном только мой менеджер, так как он был единственным человеком, кроме меня, обладающим навыками программирования «Общего назначения») поставили перед собой задачу, которая длилась большую часть месяца - у нас была другая, реальная работа - сопутствующие обязательства, которые мы должны были выполнить - попытаться написать программу, которая найдет каждое решение максимально быстрым способом.
Исходная статистика
Самая первая программа, которую я написал для решения проблемы, просто проверяла случайные решения снова и снова и останавливалась, когда находила решение. Если вы провели математический анализ этой проблемы, вы, вероятно, уже знаете, что это не должно сработать; но как-то мне повезло, и программе понадобилось всего минуту, чтобы найти единственное решение (то, которое я выложил выше). Повторные запуски программы часто занимали целых 10 или 20 минут, поэтому очевидно, что это не было строгим решением проблемы.
Я переключился на Рекурсивное решение, которое перебирало каждую возможную перестановку головоломки, и отбрасывал множество решений одновременно, удаляя суммы, которые не складывались. То есть, если бы первая строка / столбец, которую я сравнивал, уже не была равна, я мог бы немедленно прекратить проверку этой ветви, зная, что ничто другое, переставленное в головоломку, не изменит это.
Используя этот алгоритм, я получил первый «правильный» успех: программа могла сгенерировать и выплюнуть все 33 608 решений примерно за 5 минут.
У моего менеджера был другой подход: зная, основываясь на моей работе, что единственно возможные решения имели суммы 27, 28, 29, 30, 31 или 32, он написал многопоточное решение, которое проверяло возможные суммы только для этих конкретных значений. Ему удалось запустить свою программу всего за 2 минуты. Так что я повторил еще раз; Я хэшировал все возможные суммы в 3/4 цифры (в начале программы; она учитывается в общем времени выполнения) и использовал «частичную сумму» строки, чтобы найти оставшееся значение на основе ранее заполненной строки, а не протестировал все оставшиеся значения и сократил время до 72 секунд. Затем, используя некоторую многопоточную логику, я сократил ее до 40 секунд. Мой менеджер забрал программу домой, выполнил некоторые оптимизации работы программы и довел ее до 12 секунд. Я переупорядочил оценку строк и столбцов,
Самые быстрые наши программы получили за месяц 0,15 секунды для моего менеджера и 0,33 секунды для меня. Я закончил тем, что утверждал, что моя программа была быстрее, так как программа моего менеджера, хотя и находила все решения, не печатала их в текстовый файл. Если он добавил эту логику в свой код, это часто занимало более 0,4-0,5 секунд.
С тех пор мы позволили существовать нашему внутриличностному вызову, но, конечно, остается вопрос: можно ли сделать эту программу быстрее?
Это вызов, который я собираюсь поставить перед вами, ребята.
Ваш вызов
Параметры, с которыми мы работали, ослабили правило «сумма 29», чтобы вместо них быть «равными суммам всех строк / столбцов», и я собираюсь установить это правило и для вас, ребята. Задача, следовательно, заключается в следующем: написать программу, которая находит (и печатает!) Все решения этой загадки в кратчайшие сроки. Я собираюсь установить предел для представленных решений: если программа занимает более 10 секунд на относительно приличном компьютере (<8 лет), вероятно, она слишком медленная для подсчета.
Также у меня есть несколько бонусов за головоломку:
- Можете ли вы обобщить решение так, чтобы оно работало для любого набора из 16 чисел, а не только
int[1,16]
? Оценка времени будет оцениваться на основе исходного набора номеров подсказок, но проходить через этот кодовый путь. (-10%) - Можете ли вы написать код таким образом, чтобы он изящно обрабатывал и решал дублирующиеся числа? Это не так просто, как может показаться! Решения, которые «визуально идентичны», должны быть уникальными в наборе результатов. (-5%)
- Можете ли вы обрабатывать отрицательные числа? (-5%)
Вы также можете попытаться сгенерировать решение, которое обрабатывает числа с плавающей запятой, но, конечно, не удивляйтесь, если это не удастся полностью. Если вы найдете надежное решение, это может стоить большого бонуса!
По сути, «Вращения» считаются уникальными решениями. Таким образом, решение, представляющее собой просто вращение другого решения, считается его собственным решением.
Средами разработки, на которых я работаю, являются Java и C ++. Я могу принимать ответы на других языках, но вам может понадобиться указать ссылку, где я могу получить простую в настройке среду выполнения для вашего кода.
источник
Ответы:
C - около 0,5 сек
Эта очень наивная программа дает все решения за полсекунды на моем 4-летнем ноутбуке. Нет многопоточности, нет хеширования.
Windows 10, Visual Studio 2010, процессорное ядро I7 64 бит
Попробуйте онлайн на ideone
источник
int inuse[16];
просто,int inuse;
а затем использовать побитовые операторы для управления им. Это не похоже , чтобы увеличить скорость , что много, но это помогает мало.dumbbench --precision=.01 -vvv --initial=500 ./solve
C ++ - 300 миллисекунд
По запросу я добавил свой код для решения этой головоломки. На моем компьютере он срабатывает в среднем за 0,310 секунды (310 миллисекунд), но в зависимости от дисперсии может работать так же быстро, как 287 миллисекунд. Я очень редко вижу, что он поднимается выше 350 миллисекунд, обычно только если моя система перегружена другой задачей.
Эти времена основаны на самоотчете, используемом в программе, но я также проверил с использованием внешнего таймера и получил аналогичные результаты. Накладные расходы в программе, кажется, добавляют около 10 миллисекунд.
Кроме того, мой код не совсем правильно обрабатывает дубликаты. Он может решить их использование, но не исключает «визуально идентичные» решения из набора решений.
источник
0.1038s +/- 0.0002
И вот время для вашего кода с упрощенным выводом:0.0850s +/- 0.0001
Итак, вы можете сэкономить ~ 18 мс, по крайней мере, на моей машине. Я запускал обе версии 500+ раз, выбросив выбросы, используя дамбенчПролог - 3 минуты
Этот вид головоломки кажется идеальным вариантом использования Пролога. Итак, я кодировал решение в Прологе! Вот:
К сожалению, это не так быстро, как я ожидал. Может быть, кто-то более опытный в декларативном программировании (или, в частности, в Прологе) может предложить несколько советов по оптимизации. Вы можете вызвать правило
puzzle
с помощью следующей команды:Попробуйте это онлайн здесь . Вы можете заменить любое число вместо
29
s в коде, чтобы сгенерировать все решения. В его нынешнем виде все 29 решений найдены примерно за 30 секунд, поэтому найти все возможные решения нужно примерно за 3 минуты.источник