Haskell 解决狮子、狼和山羊的问题

Haskell 解决狮子、狼和山羊的问题,haskell,Haskell,我正在努力解决狮子、狼和山羊的问题 这就是我的解决方案: eat :: (Int, Int, Int) -> (Int, Int, Int) eat (lions, wolves, goats) = eat (lions - 1, wolves - 1, goats - 1) eat (0, wolves, goats) = eat (1, wolves - 1, goats - 1) eat (lions, 0, goats) = eat (lions - 1, 1, goats - 1

我正在努力解决狮子、狼和山羊的问题

这就是我的解决方案:

eat :: (Int, Int, Int) -> (Int, Int, Int)
eat (lions, wolves, goats) = eat (lions - 1, wolves - 1, goats - 1)
eat (0, wolves, goats) = eat (1, wolves - 1, goats - 1)
eat (lions, 0, goats) = eat (lions - 1, 1, goats - 1)
eat (lions, wolves, 0) = eat (lions - 1, wolves - 1, 1)
eat (0, 0, goats) = (0, 0, goats)

此代码可以编译,但代码永远挂起。我很确定我错过了一场比赛,但我不知道是什么。请告诉我如何正确解决此问题。

模式是自上而下、从左到右读取的。因此,您的第一个模式匹配所有其他模式。重新排序:

eat :: (Int, Int, Int) -> (Int, Int, Int)
eat (0     , 0      , goats) =     (0         , 0          , goats    )
eat (0     , wolves , goats) = eat (1         , wolves - 1 , goats - 1)
eat (lions , 0      , goats) = eat (lions - 1 , 1          , goats - 1)
eat (lions , wolves , 0)     = eat (lions - 1 , wolves - 1 , 1        )
eat (lions , wolves , goats) = eat (lions - 1 , wolves - 1 , goats - 1)
请注意,这假设所有值最初都为正值。如果其中一个是负数,您仍然会得到循环。还要注意的是,你错过了其他的“稳定”状态,你对于狮子、狼和山羊的非零群的逻辑是错误的。如果有山羊和狼,狮子可能会吃掉它们中的任何一只。此外,狼可能比狮子先吃山羊


这就是非决定论将发挥作用的部分。

这里有一个解决方案,可行,但没有整理,因为实现了我想到的第一件事。我相信这是可以改进的

让我们将要跟踪的状态定义为(狮子、狼、山羊)

饮食规则如下所示

lw,lg,wg :: State -> State
lw (n,w,g) = (n-1,w-1,g+1)
lg (n,w,g) = (n-1,w+1,g-1)
wg (n,w,g) = (n+1,w-1,g-1)
由于每一步的步数都会呈指数增长,因此最好删除冗余状态和不允许的状态(负值),定义辅助函数

nonnegative:: State -> Bool
nonnegative (n,w,g) = n>=0 && w>=0 && g>=0
程序的核心是步进函数,在这里我们将规则应用于当前状态

step :: S.Set State -> S.Set State
step s = S.unions [mapfilter lg s, mapfilter lw s, mapfilter wg s]
此处唯一性外包给数据。使用带S前缀的限定项进行设置,需要导入

import qualified Data.Set as S (Set,toList,unions,singleton,map,filter,null)
mapfilter正在应用进食规则并过滤非负项

mapfilter f s = S.filter nonnegative $ S.map f s
我们将计算步骤,直到找到解决方案

solution :: S.Set State -> State
solution s | S.null final = solution $ step s
           | otherwise = result s
           where final = S.filter done s
其中状态的两个元素为零

done (n,w,g) | n==0 = w==0 || g==0
             | w==0 = g==0
             | otherwise = False
并从集合中检索第一个结果

result :: S.Set State -> State
result s = head $ Prelude.filter done $ S.toList s
使用链接中引用的初始值

initial :: S.Set State
initial = S.singleton (6,55,17)
当我们以初始状态运行时,就会得到结果

Prelude> solution initial
(23,0,0)
那是23只狮子

更新

我稍微清理了代码,仍然返回找到的第一个解决方案

import Data.Set (Set,toList,singleton,map,filter,null,union,empty)

type State = (Int, Int, Int)   -- lions, wolfs, goats

lw,lg,wg :: State -> State
lw (n,w,g) = (n-1,w-1,g+1)
lg (n,w,g) = (n-1,w+1,g-1)
wg (n,w,g) = (n+1,w-1,g-1)

done,nonnegative :: State -> Bool
done (n,w,g) | n==0 = w==0 || g==0
             | w==0 = g==0
             | otherwise = False
nonnegative (n,w,g) = n>=0 && w>=0 && g>=0

step :: Set State -> Set State
step s = mapfilters [lg,lw,wg] s

mapfilters :: [State -> State] -> Set State -> Set State
mapfilters [] s = empty
mapfilters (f:fs) s = union (mf f s) (mapfilters fs s)
      where mf f s = Data.Set.filter nonnegative $ Data.Set.map f s

solution :: Set State -> Set State
solution s | Data.Set.null final = solution (step s)
           | otherwise = final
           where final = Data.Set.filter done s

run = head $ toList $ solution $ singleton (6,55,17)

有效地做到这一点的一个关键是要认识到问题看起来只是不对称的。在每个步骤中,有三种可能性:

  • 现在只剩下一种动物了。这是最终状态
  • 剩下的动物有两种。在这种情况下,将创建一个失踪动物的样本,并且两个先前存在的类型在数量上各减少一个
  • 这三种动物都保留了下来。在这种情况下,其中一个数量增加,而其他两个数量减少
  • 由于这种对称性,我们只需要考虑数字,而不需要过多考虑动物

    由于每一步的动物总数都会减少一只,因此,最终获得最多剩余动物的方法是保持尽可能少的步数。那么,怎样才能使动物的数量减少到一只呢?那就意味着把另外两种动物的数量降到零。这听起来微不足道,但实际上是一个关键的见解

    假设我们已经知道我们想要生存的动物。我们怎样才能得到尽可能多的?我认为你可以说服自己,只有两种方法可以改善现状:要么增加赢家的数量,要么让输家的数量更加平衡。让输家的数量不那么均衡(唯一的选择)将减少赢家的数量(短期内不好),延长游戏时间(长期内不好)。在每个有选择的步骤中,这些选择中的任何一个都同样好。因此,现在只剩下决定谁应该获胜

    问题是17只山羊、55只狼和6只狮子。重要的是17和55都是奇数,而6是偶数。每一步,三方都会发生变化。因此,只有山羊和狼可以同时命中零。虽然一开始只有6头狮子,但它们最终必须存活下来!当这些数字不具有相同的奇偶性时,赢家必须是与其他数字不同的那个

    当所有三个数字具有相同的奇偶性时,选择赢家的最简单方法是计算每个可能性的分数并选择最大的。我们如何计算分数?假设我们选择山羊获胜,编号分别为
    g
    l
    w
    。所以我们需要平衡动作来平衡狮子和狼。我们还需要
    (l+w)/2个
    动作来杀死狮子和狼。因此,最终山羊的总数是
    g-| l-w |/2+(l+w)/2
    。因此

  • 如果狮子比狼多,最后会有山羊

  • 如果狼比狮子多,最后就会有山羊


  • 现在,您可以在固定时间内计算最终配置,并且可以在线性时间内计算到达该配置的路径。

    对于GHC 7.10.3,编译器会给出一个非常有用的警告,这恰好是问题的原因。它试图帮助你,不要忽视它!您有重叠的模式,如果您启用了它们,警告会告诉您。事实上,第一个方程式涵盖了任何输入三元组,因此最后一个方程式永远不会被使用,从而导致无限循环。此外,这个问题似乎比上面的代码所暗示的要复杂得多:它涉及一个不确定的过渡系统,我们需要计算它的所有最终状态。我们基本上需要做一个图形访问。注意这似乎是错误的
    吃(狮子,狼,山羊)=吃(狮子-1,狼-1,山羊-1)
    谁把它们都吃了?在这种状态下,您应该有三种不同的替代结果。你也没有其他终端状态可以只留下狮子和狼。对于非决定论,请查找列表单子。@PyRulez:如果你只使用列表单子,速度会非常慢。备注:不一定只有一个解决方案。如果你从(1,1,1)开始,你可以得到(2,0,0)(狼吃山羊),(0,2,0)(
    Prelude> solution initial
    (23,0,0)
    
    import Data.Set (Set,toList,singleton,map,filter,null,union,empty)
    
    type State = (Int, Int, Int)   -- lions, wolfs, goats
    
    lw,lg,wg :: State -> State
    lw (n,w,g) = (n-1,w-1,g+1)
    lg (n,w,g) = (n-1,w+1,g-1)
    wg (n,w,g) = (n+1,w-1,g-1)
    
    done,nonnegative :: State -> Bool
    done (n,w,g) | n==0 = w==0 || g==0
                 | w==0 = g==0
                 | otherwise = False
    nonnegative (n,w,g) = n>=0 && w>=0 && g>=0
    
    step :: Set State -> Set State
    step s = mapfilters [lg,lw,wg] s
    
    mapfilters :: [State -> State] -> Set State -> Set State
    mapfilters [] s = empty
    mapfilters (f:fs) s = union (mf f s) (mapfilters fs s)
          where mf f s = Data.Set.filter nonnegative $ Data.Set.map f s
    
    solution :: Set State -> Set State
    solution s | Data.Set.null final = solution (step s)
               | otherwise = final
               where final = Data.Set.filter done s
    
    run = head $ toList $ solution $ singleton (6,55,17)