Записки начинающего функциональщика: восемь ферзей
Классическая формулировка этой задачи звучит так: найти все варианты расстановки ферзей на доске 8 на 8 таких, что ферзи не бьют друг друга. Обобщим её для доски N на N.
Доска будет хранится в виде списка горизонтальных позиций ферзей.
Для начала введём функцию проверки принадлежности некоторого числа списку:
|
|
Два ферзя стоят на одной диагонали, если выполняется одно из следующих условий (x – вертикаль, y – горизонталь):
Введём функцию, которая будет трансформировать некоторую текущую расстановку ферзей в сумму или разность вертикали и горизонтали:
|
|
Теперь можем написать функцию проверки корректности добавления нового ферзя к текущей расстановке:
|
|
p и g – горизонталь и вертикаль кандидата соответственно.
Вводим функцию генерации всех корректных положений ферзя для некоторой предварительно заданной расстановки:
|
|
g – вертикаль на которую мы ставим нового ферзя, size – размер доски, board – текущая расстановка. Не отходя от кассы, протестируем эту функцию:
|
|
Нам понадобиться вспомогательная функция, объединяющая вложенные списки, т.е. [[0],[1]] => [0,1]. Вот она:
|
|
Функция, находящая все перестановки для конкретной вертикали, т.е. на вход поступает множество всех перестановок для N ферзей, на выходе список всех перестановок для N+1 ферзей:
|
|
Протестируем её:
|
|
И, наконец, функция делающая этот процесс в цикле:
|
|
Маленькая обёртка, которую и будем в последствии вызывать для поиска всех расстановок (в т.ч. она генерирует самый первый список расстановок):
|
|
Наслаждаемся!
|
|
Таким образом, алгоритм для каждого шага генерирует список всех перестановок ферзей до конкретной вертикали. Пример для доски 4x4:
|
|
Учитываю что Haskell язык ленивый, эти списки никогда в чистом виде у нас в памяти не появляются.
Теперь некоторые замечания по производительности. Напишем C++ программу для поиска перестановок:
|
|
И сравним её скорость со скоростью нашей Haskell программы. Чтобы скорость вывода найденных позиций не влияла на программы, будем искать только количество всех перестановок. C++ программа компилировалась Visual Studio 2005 с оптимизацией по скорости. Haskell программа компилировалась GHC 6.4.1 с опцией -O. Результаты для доски 14x14:
|
|
Итак, Haskell программа получилась в 7 раза медленнее, учитывая, какими абстракциями мы пользовались (ленивые списки), качество оптимизации в GHC определённо вызывает уважение.