gpt4 book ai didi

haskell - 在 Haskell 中找到 Knight's Tour 的一种解决方案

转载 作者:行者123 更新时间:2023-12-01 14:30:45 27 4
gpt4 key购买 nike

我正在尝试解决 Knight's Open Tour在 Haskell 中,并提出一个解决方案来生成所有可能的解决方案:

knightsTour :: Int -> [[(Int, Int)]]
knightsTour size = go 1 [(1, 1)]
where
maxSteps = size^2
isValid (x, y) = x >= 1 && x <= size && y >= 1 && y <= size

go :: Int -> [(Int, Int)] -> [[(Int, Int)]]
go count acc | count == maxSteps = return $ reverse acc
go count acc = do
next <- nextSteps (head acc)
guard $ isValid next && next `notElem` acc
go (count + 1) (next : acc)


fs = replicateM 2 [(*1), (*(-1))]
nextSteps :: (Int, Int) -> [(Int, Int)]
nextSteps (x, y) = do
(x', y') <- [(1, 2), (2, 1)]
[f, f'] <- fs
return (x + f x', y + f' y')

然而,当使用 8×8 棋盘进行测试时,上述功能从未停止,这是因为解决方案空间非常大(根据 1 有 19,591,828,170,979,904 种不同的开放路线)。所以我只想找到一个解决方案。首先,我尝试过:

-- First try    
head (knightsTour 8)

希望 Haskell 的惰性求值能够挽救局面。但这并没有发生,解决方案仍然永远运行。然后,我尝试了:

-- second try

import Data.List (find)
import Data.Maybe (fromMaybe)

knightsTour' :: Int -> [(Int, Int)]
knightsTour' size = go 1 [(1, 1)]
where
maxSteps = size^2
isValid (x, y) = x >= 1 && x <= size && y >= 1 && y <= size

go :: Int -> [(Int, Int)] -> [(Int, Int)]
go count acc | count == maxSteps = reverse acc
go count acc =
let
nextSteps' = [step | step <- nextSteps (head acc), isValid step && step `notElem` acc]
in
fromMaybe [] (find (not . null) $ fmap (\step -> go (count+1) (step:acc)) nextSteps')
fs = replicateM 2 [(*1), (*(-1))]
nextSteps :: (Int, Int) -> [(Int, Int)]
nextSteps (x, y) = do
(x', y') <- [(1, 2), (2, 1)]
[f, f'] <- fs
return (x + f x', y + f' y')

但上面的解决方案仍然无法交付,因为它仍然会永远运行。我的问题是:

  1. 为什么惰性求值不能像我预期的那样只产生第一个找到的解?在我看来,在这两种尝试中,只需要第一个解决方案。
  2. 如何更改上面的代码以仅生成第一个解决方案?

最佳答案

首先是个好消息:您的代码正在按照您的预期运行,并且只生成第一个解决方案!

这也是个坏消息:找到第一个解决方案确实需要这么长时间。我认为您大大低估了需要遇到多少“死胡同”才能产生解决方案。

例如,这是使用 Debug.Trace 模块对您的初始版本进行的调整,让我们知道您在尝试找到第一条路径时遇到了多少个死胡同:

import Control.Monad
import Debug.Trace (trace)
import System.Environment (getArgs)

knightsTour :: Int -> [[(Int, Int)]]
knightsTour size = go 1 [(1, 1)]
where
maxSteps = size * size
isValid (x, y) = x >= 1 && x <= size && y >= 1 && y <= size

go :: Int -> [(Int, Int)] -> [[(Int, Int)]]
go count acc | count == maxSteps = return $ reverse acc
go count acc = do
let nextPossible' = [ next |
next <- nextSteps (head acc)
, isValid next && next `notElem` acc]
nextPossible = if null nextPossible'
then trace ("dead end; count: " ++ show count) []
else nextPossible'
next <- nextPossible
-- guard $ isValid next && next `notElem` acc
go (count + 1) (next : acc)


fs = replicateM 2 [(*1), (*(-1))]
nextSteps :: (Int, Int) -> [(Int, Int)]
nextSteps (x, y) = do
(x', y') <- [(1, 2), (2, 1)]
[f, f'] <- fs
return (x + f x', y + f' y')

main :: IO ()
main = do
[n] <- getArgs
print (head $ knightsTour (read n))

现在,让我们看看对于不同的电路板尺寸,我们可以得到多少输出:

/tmp$ ghc -o kntest -O2 kntest.hs 
[1 of 1] Compiling Main ( kntest.hs, kntest.o )
Linking kntest ...
/tmp$ ./kntest 5 2>&1 | wc
27366 109461 547424
/tmp$ ./kntest 6 2>&1 | wc
783759 3135033 15675378
/tmp$ ./kntest 7 2>&1 | wc
818066 3272261 16361596

好的,所以我们在 5 号棋盘上遇到了 27,365 个死胡同,在 7 号棋盘上遇到了超过 80 万个死胡同。对于 8 号棋盘,我将其重定向到一个文件:

/tmp$ ./kntest 8 2> kn8.deadends.txt

它还在运行。此时,它遇到了超过 3800 万个死胡同:

/tmp$ wc -l kn8.deadends.txt 
38178728 kn8.deadends.txt

这些死胡同中有多少真的接近尾声?

/tmp$ wc -l kn8.deadends.txt ; fgrep 'count: 61' kn8.deadends.txt | wc -l ; fgrep 'count: 62' kn8.deadends.txt | wc -l; fgrep 'count: 63' kn8.deadends.txt | wc -l ; wc -l kn8.deadends.txt
52759655 kn8.deadends.txt
1448
0
0
64656651 kn8.deadends.txt

所以它现在已经超过 6400 万个死胡同,而且它仍然没有找到超过 61 步的死胡同。

现在是 8500 万,如果我花太长时间来写剩下的内容,到我完成这个答案时可能会超过 1 亿。

您可能会做一些事情来加速您的程序(例如使用向量来跟踪已经访问过的点而不是 O(n) notElem 查找),但从根本上来说它需要很长时间只得到第一个答案,因为到第一个答案的时间确实比您最初想象的要长得多。


编辑:如果您添加一个非常简单、朴素的 Warnsdorf's rule 实现那么即使对于非常大的 (40x40) 板,您也几乎可以立即获得第一个骑士之旅:

import Control.Monad
import System.Environment (getArgs)
import Data.List (sort)

knightsTour :: Int -> [[(Int, Int)]]
knightsTour size = go 1 [(1, 1)]
where
maxSteps = size * size
isValid (x, y) = x >= 1 && x <= size && y >= 1 && y <= size

getValidFor from acc = do
next <- nextSteps from
guard $ isValid next && next `notElem` acc
return next

go :: Int -> [(Int, Int)] -> [[(Int, Int)]]
go count acc | count == maxSteps = return $ reverse acc
go count acc = do
let allPoss = getValidFor (head acc) acc
sortedPossible = map snd $ sort $
map (\x -> (length $ getValidFor x acc, x))
allPoss
next <- sortedPossible
go (count + 1) (next : acc)

fs = replicateM 2 [(*1), (*(-1))]
nextSteps :: (Int, Int) -> [(Int, Int)]
nextSteps (x, y) = do
(x', y') <- [(1, 2), (2, 1)]
[f, f'] <- fs
return (x + f x', y + f' y')

main :: IO ()
main = do
[n] <- getArgs
print (head $ knightsTour (read n))

关于haskell - 在 Haskell 中找到 Knight's Tour 的一种解决方案,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45811233/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com