[Haskell-cafe] [Open Kattis Problem] How can I speed up my A* algorithm and/or binomial heaps?

Dominik Bollmann dominikbollmann at gmail.com
Sun May 24 19:07:24 UTC 2020


Hi Brent,

Thanks for your answer and hint! You gave me the clue how to approach the
problem from a different angle, thereby not needing search nor a
binomial heap anymore. I hope to implement it soon. :-)

Cheers, Dominik.

Brent Yorgey <byorgey at gmail.com> writes:

> Hi Dominik,
>
> Although optimizing your A* and binomial heap implementations is certainly
> a worthwhile challenge, I suspect it is not the real issue.  The input can
> be a grid of size 1000 by 1000, and you have to answer up to 1000 queries.
> In the worst case, the path between the two query points could pass through
> about half the cells (imagine a path of 1's snaking back and forth).  This
> suggests that even if your search algorithm took time linear in the number
> of cells it explored, it would still be too slow (a good rule of thumb is
> 10^8 operations per second, and we're looking at 10^3 * 10^3 * 10^3), and
> of course A* search is not even linear time.
>
> Hint: can you think of a way to preprocess the input so that queries can
> subsequently be answered very quickly, without doing any search?
>
> -Brent
>
> On Sat, May 23, 2020 at 3:42 PM Dominik Bollmann <dominikbollmann at gmail.com>
> wrote:
>
>>
>> Hi Haskell-Cafe,
>>
>> I've been trying to solve the Problem "10 Kinds Of People" at
>> https://open.kattis.com/problems/10kindsofpeople. My idea was to use the
>> A* algorithm to check whether the destination can be reached from the
>> source. To model the A* algorithm's priority queue, I also wrote a naive
>> implementation of a binomial heap. I've attached the code snippet below.
>>
>> Unfortunately, my solution doesn't pass all of Open Kattis' test cases.
>> In particular, it times out on the 22nd test case. Therefore I'm
>> wondering how to speed up my solution. Did I make any obvious, silly
>> mistakes?
>>
>> According to my time-profiling it seems that most of the time is spent
>> in the binomial heap's deleteMin function. Maybe I should therefore not
>> model the Heap as a list of trees, but rather as a vector of trees?
>>
>> Any hints on how to make the below snippet run faster is highly
>> appreciated! :-)
>>
>> Thanks!
>>
>> Dominik
>>
>> =============================================
>>
>> import Control.Monad
>> import Data.Function
>> import Data.Foldable (foldl')
>> import Data.List (minimumBy, delete)
>> import Data.Maybe
>> import qualified Data.Set as S
>> import qualified Data.Vector as V
>>
>> data Tree a = Node Int a [Tree a] deriving (Eq, Show)
>> type Heap a = [Tree a]
>>
>> rank :: Tree a -> Int
>> rank (Node k _ _) = k
>>
>> root :: Tree a -> a
>> root (Node _ x _) = x
>>
>> children :: Tree a -> [Tree a]
>> children (Node _ _ cs) = cs
>>
>> findMin :: Ord a => Heap a -> Maybe a
>> findMin []     = Nothing
>> findMin (t:ts) = Just . root $ foldl' selectMin t ts
>>   where
>>     selectMin e a
>>       | root e <= root a = e
>>       | otherwise        = a
>>
>> empty :: Heap a
>> empty = []
>>
>> singleton :: Ord a => a -> Heap a
>> singleton x = insert x empty
>>
>> insert :: Ord a => a -> Heap a -> Heap a
>> insert x ts = foldr increment singleton ts
>>   where
>>     singleton = [Node 0 x []]
>>     increment t (h:hs)
>>       | rank t > rank h  = t:h:hs
>>       | rank t == rank h = linkTrees t h : hs
>>       | rank t < rank h  = error "insert: invalid case!"
>>
>> linkTrees :: Ord a => Tree a -> Tree a -> Tree a
>> linkTrees t1 t2@(Node r x ts)
>>   | root t1 < root t2 = linkTrees t2 t1
>>   | otherwise         = Node (r+1) x (t1:ts)
>>
>> fromList :: Ord a => [a] -> Heap a
>> fromList = foldr insert empty
>>
>> union :: Ord a => Heap a -> Heap a -> Heap a
>> union h1 h2 = reverse $ reverse h1 `add` reverse h2
>>   where
>>     add (t1:t2:t1s) t2s -- take care of the carry bit
>>       | rank t1 == rank t2 = add (linkTrees t1 t2 : t1s) t2s
>>     add [] t2s = t2s
>>     add t1s [] = t1s
>>     add (t1:t1s) (t2:t2s)
>>       | rank t1 == rank t2 = add (linkTrees t1 t2 : t1s) t2s
>>       | rank t1 < rank t2  = t1 : add t1s (t2:t2s)
>>       | rank t1 > rank t2  = t2 : add (t1:t1s) t2s
>>
>> deleteMin :: Ord a => Heap a -> Heap a
>> deleteMin h = delete minTree h `union` children minTree
>>   where minTree = minimumBy (compare `on` root) h
>>
>> uncons :: Ord a => Heap a -> Maybe (a, Heap a)
>> uncons h = do
>>   min <- findMin h
>>   pure (min, deleteMin h)
>>
>> data Entry p a = Entry { priority :: p, payload :: a } deriving Show
>>
>> instance Eq p => Eq (Entry p a) where
>>   (Entry p1 _) == (Entry p2 _) = p1 == p2
>> instance Ord p => Ord (Entry p a) where
>>   (Entry p1 _) <= (Entry p2 _) = p1 <= p2
>>
>> data Point = Point { y :: !Int, x :: !Int } deriving (Eq, Ord, Show)
>> data PeopleKind = Binary | Decimal deriving Eq
>> newtype Map = Map { getMap :: (V.Vector (V.Vector PeopleKind)) } deriving
>> Show
>>
>> instance Show PeopleKind where
>>   show Binary = "binary"
>>   show Decimal = "decimal"
>>
>> readMap :: Int -> Int -> IO Map
>> readMap r c = do
>>   rows <- forM [1..r] $ \_ -> do
>>     row <- map readCell <$> getLine
>>     pure (V.fromListN c row)
>>   pure $ Map (V.fromList rows)
>>   where
>>     readCell :: Char -> PeopleKind
>>     readCell c = case c of
>>       '0' -> Binary
>>       '1' -> Decimal
>>       _   -> error "Map invalid!"
>>
>> readFromTo :: IO (Point, Point)
>> readFromTo = do
>>   [fy, fx, ty, tx] <- map read . words <$> getLine
>>   pure (Point (fy-1) (fx-1), Point (ty-1) (tx-1))
>>
>> reachable :: PeopleKind -> Map -> Point -> Point -> Bool
>> reachable kind grid from to = go S.empty (singleton entry0)
>>   where
>>     entry0 = Entry (0 + estimate from to, 0) from
>>     go explored frontier = case uncons frontier of
>>       Nothing -> False
>>       Just (Entry (_, cost) point, frontier')
>>         | point == to -> True
>>         | point `S.member` explored -> go explored frontier'
>>         | otherwise ->
>>             let successors = map (mkEntry cost) $ neighbors kind grid point
>>             in go (S.insert point explored) (frontier' `union` fromList
>> successors)
>>     mkEntry c s = Entry (c+1 + estimate s to, c+1) s
>>
>> estimate :: Point -> Point -> Double
>> estimate (Point y1 x1) (Point y2 x2) =
>>   sqrt . fromIntegral $ (y2 - y1)^2 + (x2 - x1)^2
>>
>> neighbors :: PeopleKind -> Map -> Point -> [Point]
>> neighbors k (Map m) (Point y x) =
>>   catMaybes [left, right, top, bottom]
>>   where
>>     left   = kindOk y (x-1)
>>     right  = kindOk y (x+1)
>>     top    = kindOk (y-1) x
>>     bottom = kindOk (y+1) x
>>     kindOk y x = do
>>       kind <- m V.!? y >>= (V.!? x)
>>       guard (kind == k)
>>       pure (Point y x)
>>
>> main :: IO ()
>> main = do
>>   [rows, cols] <- map read . words <$> getLine
>>   grid <- readMap rows cols
>>   queries <- read <$> getLine
>>   forM_ [1..queries] $ \_ -> do
>>     (from, to) <- readFromTo
>>     let kind = kindAt grid from
>>     if reachable kind grid from to
>>       then print kind
>>       else putStrLn "neither"
>>   where
>>     kindAt (Map m) (Point y x) = m V.! y V.! x
>> _______________________________________________
>> Haskell-Cafe mailing list
>> To (un)subscribe, modify options or view archives go to:
>> http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
>> Only members subscribed via the mailman list are allowed to post.


More information about the Haskell-Cafe mailing list