Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/haskell/8.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Haskell 如何使用Select单子解n皇后?_Haskell_Monads_Backtracking_N Queens - Fatal编程技术网

Haskell 如何使用Select单子解n皇后?

Haskell 如何使用Select单子解n皇后?,haskell,monads,backtracking,n-queens,Haskell,Monads,Backtracking,N Queens,我想知道单子是怎么工作的。显然,它是的表亲,可以用于回溯搜索 对于n皇后问题,我有一个基于列表的解决方案: -- All the ways of extracting an element from a list. oneOf :: [Int] -> [(Int,[Int])] oneOf [] = [] oneOf (x:xs) = (x,xs) : map (\(y,ys) -> (y,x:ys)) (oneOf xs) -- Adding a new queen at c

我想知道单子是怎么工作的。显然,它是的表亲,可以用于回溯搜索

对于n皇后问题,我有一个基于列表的解决方案:

-- All the ways of extracting an element from a list.
oneOf :: [Int] -> [(Int,[Int])] 
oneOf [] = [] 
oneOf (x:xs) = (x,xs) : map (\(y,ys) -> (y,x:ys)) (oneOf xs)

-- Adding a new queen at col x, is it threathened diagonally by any of the
-- existing queens?
safeDiag :: Int -> [Int] -> Bool
safeDiag x xs = all (\(y,i) -> abs (x-y) /= i) (zip xs [1..])

nqueens :: Int -> [[Int]]
nqueens queenCount = go [] [1..queenCount]
  where
    -- cps = columsn of already positioned queens. 
    -- fps = columns that are still available
    go :: [Int] -> [Int] -> [[Int]]
    go cps [] = [cps]
    go cps fps = [ps | (p,nfps) <- oneOf fps, ps <- go (p:cps) nfps, safeDiag p cps]
我正在努力将此解决方案改为使用Select

似乎Select允许您对用于比较答案的评估函数进行抽象。该函数被传递给。我觉得在我的解决方案中,像safeDiag这样的东西可以用作求值函数,但是如何构造Select计算本身呢


另外,单独使用Select单子就足够了,还是我需要在列表上使用transformer版本?

Select可以被看作是紧凑空间中搜索的抽象,由一些谓词引导。您在评论中提到SAT,您是否尝试过将问题建模为SAT实例,并根据选择的精神将其扔给解算器?您可以专门搜索,以硬连线您的内部的N-queens特定约束,并将SAT解算器转换为N-queens解算器。

受jd823592答案的启发,在查看中的SAT示例后,我编写了以下代码:

import Data.List 
import Control.Monad.Trans.Select

validBoard :: [Int] -> Bool
validBoard qs = all verify (tails qs)
  where
    verify [] = True
    verify (x : xs) = and $ zipWith (\i y -> x /= y && abs (x-y) /= i) [1..] xs

nqueens :: Int -> [Int]
nqueens boardSize = runSelect (traverse selectColumn columns) validBoard
  where
  columns = replicate boardSize [1..boardSize]
  selectColumn candidates = select $ \s -> head $ filter s candidates ++ candidates
这似乎是一个缓慢而有效的解决方案:

ghci> nqueens 8
[1,5,8,6,3,7,2,4]
然而,我不太明白。特别是,sequence处理Select的方式,将一个在整个板上工作的函数validBoard转换成只使用一个列索引的函数,看起来非常神奇

基于序列的解决方案的缺点是,在列中放置皇后并不排除为后续皇后选择相同列的可能性;我们不必要地去探索那些命丧黄泉的树枝

如果我们想让我们的专栏选择受到之前决定的影响,我们需要超越Applicative,使用Monad的强大功能:


一元版本仍然存在一个问题,即它只检查已完成的电路板,当发现部分完成的电路板存在冲突时,原始的基于列表的解决方案立即回溯。我不知道如何使用Select来实现这一点。

我意识到这是一个已经存在了将近4年的问题,并且已经有了答案,但为了将来遇到这个问题的人,我想补充一些信息。具体来说,我想尝试回答两个问题:

如何组合返回单个值的多个选择以创建返回值序列的单个选择? 当解决方案路径注定要失败时,是否可以提前返回? 链接选择 < P>选择在转换器库中作为一个Maad转换器实现,但是让我们看看一个可以如何实现> > =选择本身:< /P> >>=::选择r a->a->选择r b->选择r b 选择g>>=f=Select$\k-> 让我们选择x=runSelect f x k 选择$GK。选择 我们首先定义一个新的Select,它接受类型为a->r的输入k,Select包装了类型为a->r->a的函数。您可以将k视为一个函数,该函数返回给定a的类型r的分数,Select函数可以使用它来确定返回哪个a

在新的Select中,我们定义了一个名为choose的函数。这个函数将一些x传递给函数f,它是一元绑定的a->mb部分:它将ma计算的结果转换为新的计算mb。所以f将取x并返回一个新的Select,choose然后使用我们的评分函数k运行。您可以将choose看作一个函数,它询问如果我选择x并将其传递到下游,最终结果会是什么

在第二行,我们返回choose$gk。选择函数k。choose是choose和原始评分函数k的组合:它接受一个值,计算选择该值的下游结果,并返回该下游结果的评分。换句话说,我们创建了一种透视计分函数:它不是返回给定值的分数,而是返回如果我们选择该值将得到的最终结果的分数。通过将我们的透视计分函数传递给我们绑定到的原始选择,我们能够选择中间值,从而得到我们想要的最终结果。一旦我们得到了中间值,我们只需将其传递回choose并返回结果

这就是为什么我们能够将单个值选择串在一起,同时传递一个对值数组进行操作的评分函数:每个选择都是对选择值的假设最终结果进行评分,而不一定是值本身。应用程序实例遵循相同的策略,唯一的区别是如何计算下游选择,而不是将候选值传递到a->mb函数,它将候选函数映射到第二个选择上

早归 那么,我们如何在提前返回时使用Select?我们需要一些访问评分函数的方法 在构造Select的代码范围内启用。一种方法是在另一个选择中构造每个选择,如下所示:

sequenceSelect::Eq a=>[a]->选择布尔[a] sequenceSelect[]=返回[] 序列选择domain@x:xs=选择$\k-> 如果k[],则运行选择s k else[] 哪里 s=do 选择布尔 有效板qs=所有验证尾部qs 哪里 验证[]=True 验证x:xs=和$zipWith\i y->x/=y&&abs x-y/=i[1..]xs nqueens::Int->[Int] nqueens boardSize=运行选择序列选择[1..boardSize]有效板 sequenceSelect::Eq a=>[a]->选择布尔[a] sequenceSelect[]=返回[] 序列选择domain@x:xs=选择$\k-> 如果k[],则运行选择s k else[] 哪里 s=do 选择布尔a elementSelect domain=select$\p->epsilon p domain -喜欢寻找,但总会有回报 ε::a->Bool->非空a->a εx:|[]=x εpx:| y:ys=如果px那么x其他εpy:| ys 简言之:我们以递归方式构造一个Select,在使用元素时从域中删除元素,如果域已耗尽或走错了方向,则终止递归

另一个补充是基于希尔伯特函数的ε函数。对于大小为N的域,它最多会检查N-1项。。。这听起来可能不是一个巨大的节约,但正如您从上面的解释中所知道的,p通常会启动整个计算的剩余部分,因此最好将谓词调用保持在最小值

sequenceSelect的优点在于它的通用性:它可以用于创建任何Select Bool[a],其中

我们在一个由不同元素组成的有限区域内搜索 我们希望创建一个序列,其中包含每个元素,即域的置换 我们想测试部分序列,如果它们不能通过谓词,就放弃它们 希望这有助于澄清问题


另外,这里有一个指向可观察笔记本的链接,我在其中用Javascript实现了Select monad,并演示了n-queens解算器:

您确定要选择monad吗?我对Select的理解是,它试图证明存在一个可能的解决方案作为证人证据。选择的典型示例是SAT解算器。您可能可以在列表单子上强制使用SelectT,但我更确信您会真正使用select单子。@Alec我读到select适合回溯搜索,而n-queens是这种类型的典型问题,所以我认为这是monad的一个很好的用例。区别可能在于回溯以找到所有解决方案和回溯直到找到解决方案。再说一次,我以前只玩过一次Select,所以不要把我说的话当真。不是Select单子,而是这个项目:使用逻辑单子通过回溯解决n皇后。相关:特别是Select[…]的序列工作方式似乎很神奇-是的,那个实用的例子确实令人费解。回答得好。一个措辞上的诡辩:移位似乎并没有捕捉到剩余计算意义上的延续。正如您所写,它只是明确地掌握了评分函数。让它返回所有解决方案有多容易?@danidiaz说得好!我实际上一直在考虑删除shift,因为它只是一个方便的函数,实际上没有那么多功能,而且名称本身可能有误导性。@is7s这是一个有趣的问题。。。我不认为有一个简单的方法可以做到这一点,但我会考虑一下。@is7s请记住,Select包装了一个类型为a->r->a的函数。。。如果它返回所有的解决方案,那么类型将是[a]->Bool->[[a]],这更像是a->r->MA。但是,使用SelectT transformer可以使其工作,它包装了a->m r->m a。
nqueens :: Int -> [Int]
nqueens boardSize = fst $ runSelect (go ([],[1..boardSize])) (validBoard . fst)
  where
  go (cps,[]) = return (cps,[])
  go (cps,fps) = (select $ \s ->
    let candidates = map (\(z,zs) -> (z:cps,zs)) (oneOf fps)
    in  head $ filter s candidates ++ candidates) >>= go