【问题标题】:Traversing game state space : more search leads to bad results遍历游戏状态空间:更多的搜索导致不好的结果
【发布时间】:2015-02-25 12:04:17
【问题描述】:

我从codereview 交叉发布这个问题,因为我发现它没有响应。

此问题可在hackerrank ai 获得。我不是在寻求解决方案,而是试图找出我的策略或代码有什么问题。

我正在尝试解决我认为是TSP on a 2-D grid 的问题。所以,我正在努力获得最好的结果。但是,前瞻 1 步比前瞻 2 步产生更好的结果。

问题是我必须以最少的移动次数清除二维网格上的脏块UP, DOWN, LEFT, RIGHT, CLEAN

另一个重要的事情是我采取了行动,然后处理restarted 与新的网格状态和我的新位置。所以我必须再次运行算法。这也意味着我必须避免陷入循环,这在单个进程的情况下很容易避免,但在进程的多个实例的情况下需要算法来保证。

简而言之,我只需要在我的过程中制作next_move

所以基本策略是找到离我当前位置最近的脏单元。

要向前看 1 步,我会这样做:对于每个脏单元,并找到最接近被占用的脏单元的脏单元。对于 2 步,对于每个脏单元,进行 1 步查找并找到最佳移动。对于多个步骤也是如此。

但是,当我只进行 1 步查找但 2 步查找的分数较低时,我获得了更高的分数。分数由(200 - steps_taken) 计算。所以,我认为我的代码/策略有问题。

输入格式

b 表示网格中的机器人。 - 是干净的单元格。 d 是脏单元格。

第一行是机器人位置的一对整数。这使得网格中的b 变得多余。如果机器人当前站在脏单元格上,d 将出现在网格中的那个单元格上。

第二条线是网格的尺寸。

第三个输入是行格式的网格。请参阅下面的示例输入。

我的 Haskell 代码是

module Main where
import Data.List 
import Data.Function (on)
import Data.Ord

-- slits up a string 
-- ** only used in IO. 
split sep = takeWhile (not . null) . unfoldr (Just . span (/= sep) . dropWhile (== sep))
-- ** only used in IO
getList :: Int -> IO [String]
getList n = if n==0 then return [] else do i <- getLine; is <- getList(n-1); return (i:is)

-- find positions of all dirty cells in the board
getAllDirtyCells :: (Int, Int) -> [String] -> [(Int, Int)]
getAllDirtyCells (h, w) board = [(x, y) | x <- [0..(h-1)], y <- [0..(w - 1)]
                               , ((board !! x) !! y) == 'd']

-- finally get the direction to print ;
-- first argument is my-position and second arg is next-position.
getDir :: (Int, Int) -> (Int, Int) -> String
getDir (x, y) (a, b) | a == x && y == b = "CLEAN"
                     | a < x = "UP"
                     | x == a && y < b = "RIGHT"
                     | x == a = "LEFT"
                     | otherwise = "DOWN"

-- only used in IO for converting strin gto coordinate.
getPos :: String -> (Int, Int)
getPos pos = let a = map (\x -> read x :: Int) (words pos)
             in ((a !! 0) , (a !! 1))


-- manhattan Distance :  sum of difference of x and y coordinates
manhattanDis :: (Int, Int) -> (Int, Int) -> Int
manhattanDis (a, b) (x, y) = (abs (a - x) + (abs (b - y)))

-- sort the positions from (botX, botY) position on manhattan-distance.
-- does not returns the cost.
getSortedPos :: (Int, Int) -> [(Int, Int)] -> [(Int, Int)]
getSortedPos (botX, botY) points = map (\x -> (snd x)) $ 
                                   sortBy (comparing fst)  -- compare on the basis of cost.
                                              [(cost, (a, b)) | 
                                                     (a, b) <- points, 
                                                     cost <- [manhattanDis (a,b) (botX, botY)]]
-- exclude the point `v` from the list `p`
excludePoint :: (Ord a) => [a] -> a -> [a]
excludePoint [] _ = []
excludePoint p v = [x | x <- p , x /= v]

-- playGame uses the nearest-node-policy. 
-- we start playing game when we are not going more deep. 
-- more about that in findBestMove
-- game is to reduce the nodes to one node with the total cost ;
-- reduction : take the next shortest node from the current-node.
playGame :: (Int, Int) -> [(Int, Int)] -> [(Int, Int)]
playGame pos [] = [pos]
playGame startPos points = let nextPos = (head (getSortedPos startPos points))
                           in (nextPos : playGame nextPos (excludePoint points nextPos))

-- sum up cost of all the points as they occur.
findCost :: [(Int, Int)] -> Int
findCost seq = sum $ map (\x -> (manhattanDis (fst x) (snd x))) $ zip seq (tail seq)

-- find the position which gives the smallest overall cost.
smallestCostMove :: [(Int, (Int, Int))] -> (Int, (Int, Int))
smallestCostMove [] = (0, (100, 100))
smallestCostMove [x] = x
smallestCostMove (x:y:xs) | (fst x) <= (fst y) = smallestCostMove (x : xs)
                          | otherwise = smallestCostMove (y : xs)                      

-- This is actual move-finder. It does the lookups upto `level` deep.
-- from startpoint, take each point and think it as starting pos and play the game with it.
-- this helps us in looking up one step.
-- when level is 0, just use basic `playGame` strategy. 
findBestMove :: (Int, Int) -> [(Int, Int)] -> Int -> (Int, (Int, Int))
findBestMove startPos  points level 
                                    -- returns the move that takes the smallest cost i.e. total distances.
                                    | level == 0 = smallestCostMove $ 
                                                     -- return pair of (cost-with-node-x-playGame, x)
                                                     map (\x -> (findCost (startPos : (x : (playGame x (excludePoint points x)))), 
                                                                x)) 
                                                         points
                                    | otherwise  = smallestCostMove $ 
                                                     map (\x -> 
                                                           -- return pair of (cost-with-node-x, x)
                                                            ( (findCost (startPos : [x])) + 
                                                              -- findBestMove returns the pair of (cost, next-move-from-x)
                                                              (fst (findBestMove x (excludePoint points x) (level - 1))),
                                                             x)) 
                                                         points

-- next_move is our entry point. go only 2 level deep for now, as it can be time-expensive.
next_move :: (Int, Int) -> (Int, Int) -> [String] ->  String
next_move pos dim board = let boardPoints = (getAllDirtyCells dim board)
                              numPoints = (length boardPoints)
                              -- ** Important : This is my question :
                              -- change the below `deep` to 1 for better results. 
                              deep = if (numPoints > 3) 
                                     then 2 
                                     else if (numPoints == 1) 
                                          then 1 
                                          else (numPoints - 1)                                
                          in if pos `elem` boardPoints 
                             then getDir pos pos
                             else getDir pos $ snd $ findBestMove pos boardPoints deep


main :: IO()
main = do
    -- Take input
   b <- getLine
   i <- getLine
   -- bot contains (Int, Int) : my-coordinates. like (0,0)
   let botPos = (read $ head s::Int,read $ head $ tail s::Int) where s = split (' ') b
   -- dimOfBoard contains dimension of board like (5,5)
   let dimOfBoard = (read $ head s::Int, read $ head $ tail s::Int) where s = split (' ') i
   board <- getList (fst dimOfBoard)
   putStrLn $ next_move botPos dimOfBoard board

我控制deep 如何使用变量deep

样板是:

0 0
5 5
b---d
-d--d
--dd-
--d--
----d

可能有三个答案:

输出

RIGHT or DOWN or LEFT

重要: 再次使用new boardmy bot new position 调用新进程,直到我清理所有脏单元为止。

我做错了什么?

【问题讨论】:

  • 如果您认为您在错误的位置发布了某些内容,请让版主将其移动。交叉发布不受欢迎,因为它会拆分答案。
  • 我可以提供的提示是先清理您的代码。你有很多多余的括号((read (head s)) :: Int 可以替换为read $ head s,这不是 lisp 并且类型签名是多余的)。您应该将方向转换为数据类型,而不是使用字符串。您的代码正在离开页面的右侧,将其分解一下。在需要的where 块中引入一些新功能,使您的代码更易于理解。这样做会像其他人理解你的代码一样帮助你。如果你更好地理解你的代码,你会更容易发现问题。
  • 可以提供样板吗?
  • 你能举一个具体的例子说明什么不起作用吗?
  • 按照 bheklilr 的建议,您还应该使用像 data Square=Clean|Dirty 这样的数据类型。并记录每个函数的作用。并在某处解释董事会是如何代表的以及您选择该代表的原因。并链接到问题的原始来源。并更好地解释问题。就目前而言,您的代码几乎不可读。

标签: algorithm haskell artificial-intelligence traveling-salesman


【解决方案1】:

经过大量工作,我找到了一个确定最佳路径的示例 by findBestMove 在级别 1 返回比调用时更糟糕的路径 级别设置为 0:

 points = [(6,8),(9,7),(9,4),(4,10),(4,6),(7,10),(5,7),(2,4),(8,8),(6,5)]
 start: (1,10)

  level = 0:
    cost: 31
    path: [(1,10),(4,10),(7,10),(5,7),(6,8),(8,8),(9,7),(9,4),(6,5),(4,6),(2,4)]

  level = 1:
    cost: 34
    path: [(1,10),(2,4),(6,5),(6,8),(5,7),(4,6),(4,10),(7,10),(8,8),(9,7),(9,4)]

问题在于playGame 只探索了可能的最佳动作之一。 我发现如果您探索所有 像这样的最佳动作:

 greedy start [] = 0
 greedy start points =
   let sorted@((d0,_):_) = sort [ (dist start x, x) | x <- points ]
       nexts = map snd $ takeWhile (\(d,_) -> d == d0) sorted
   in d0 + minimum [ greedy n (delete n points)  | n <- nexts ]

这里greedy 结合了findCostplayGame。通过只看 排序列表中的第一个移动playGame 取决于排序算法 以及点的顺序。

你也可以这样写bestMove

 bestMove _ start [] = (0,start)
 bestMove depth start points
   | depth == 0 = minimum [ (d0+d,x) | x <- points,
                              let d0 = dist start x,
                              let d = greedy x (delete x points) ]
   | otherwise  = minimum [ (d0+d,x) | x <- points,
                              let d0 = dist start x,
                              let (d,_) = bestMove (depth-1) x (delete x points  ) ]

这更清楚地突出了两种情况之间的对称性。

这是我用来查找和显示上述板的最佳路径的代码: http://lpaste.net/121294 要使用它,只需将代码放入名为 Ashish 的模块中即可。

最后我的直觉告诉我,你的方法可能不是一个好的方法 解决问题。你正在做的类似于 A* 算法 与playGame 扮演启发式函数的角色。然而, 为了使 A* 起作用,启发式函数不应高估 最短距离。但是playGame 总是给你一个上限 最短距离。无论如何 - 这是需要考虑的事情。

【讨论】:

  • 我在 ghci 上收到 *Main&gt; findBestMove (1,10) [(6,8),(9,7),(9,4),(4,10),(4,6),(7,10),(5,7),(2,4),(8,8),(6,5)] 0 (35,(4,10)) *Main&gt; findBestMove (1,10) [(6,8),(9,7),(9,4),(4,10),(4,6),(7,10),(5,7),(2,4),(8,8),(6,5)] 1 (34,(2,4)) *Main&gt; 。即 0 级的 35 成本和 1 级的 34 成本。
  • 尽管如此,你是对的,只取第一个“随机”(取决于排序)点会导致糟糕的结果..
猜你喜欢
  • 2014-09-30
  • 1970-01-01
  • 1970-01-01
  • 2021-05-17
  • 1970-01-01
  • 1970-01-01
  • 2013-07-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多