apply-merge-0.1.0.0: src/Data/List/ApplyMerge/DoublyLinkedList.hs
-- SPDX-FileCopyrightText: Copyright Preetham Gujjula
-- SPDX-License-Identifier: BSD-3-Clause
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE NoFieldSelectors #-}
module Data.List.ApplyMerge.DoublyLinkedList (applyMerge) where
import Control.Monad (guard)
import Control.Monad.ST qualified as Strict
import Control.Monad.ST.Lazy qualified as Lazy
import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.Maybe (MaybeT (..), runMaybeT)
import Data.DoublyLinkedList.STRef qualified as DoublyLinked
import Data.List.NonEmpty (NonEmpty, nonEmpty)
import Data.List.NonEmpty qualified as NonEmpty
import Data.Maybe (fromMaybe)
import Data.PQueue.Prio.Min (MinPQueue)
import Data.PQueue.Prio.Min qualified as MinPQueue
data Node s a b c = Node
{ position :: DoublyLinked.DoublyLinkedNode s (Int, Int),
value :: c,
as :: NonEmpty a,
bs :: NonEmpty b
}
newtype Frontier s a b c = Frontier
{ queue :: MinPQueue c (Node s a b c)
}
applyMerge :: (Ord c) => (a -> b -> c) -> [a] -> [b] -> [c]
applyMerge f as bs =
fromMaybe [] $
applyMergeNonEmpty f <$> nonEmpty as <*> nonEmpty bs
applyMergeNonEmpty ::
(Ord c) => (a -> b -> c) -> NonEmpty a -> NonEmpty b -> [c]
applyMergeNonEmpty f as bs = Lazy.runST $ do
frontier <- Lazy.strictToLazyST (initialFrontier f as bs)
unfoldrM (Lazy.strictToLazyST . step f) frontier
unfoldrM :: (Monad m) => (b -> m (Maybe (a, b))) -> b -> m [a]
unfoldrM f seed = do
result <- f seed
case result of
Nothing -> pure []
Just (x, newSeed) -> (x :) <$> unfoldrM f newSeed
initialFrontier ::
(a -> b -> c) -> NonEmpty a -> NonEmpty b -> Strict.ST s (Frontier s a b c)
initialFrontier f as bs = do
list <- DoublyLinked.empty
position <- DoublyLinked.cons list (0 :: Int, 0 :: Int)
let c = f (NonEmpty.head as) (NonEmpty.head bs)
node =
Node
{ position = position,
value = c,
as = as,
bs = bs
}
pure $ Frontier $ MinPQueue.singleton c node
step ::
(Ord c) =>
(a -> b -> c) ->
Frontier s a b c ->
Strict.ST s (Maybe (c, Frontier s a b c))
step f frontier = runMaybeT $ do
(node, frontier') <- MaybeT (deleteMinNode frontier)
frontier'' <- lift $ insertChildA f node frontier'
frontier''' <- lift $ insertChildB f node frontier''
lift (DoublyLinked.delete node.position)
pure (node.value, frontier''')
deleteMinNode ::
(Ord c) => Frontier s a b c -> Strict.ST s (Maybe (Node s a b c, Frontier s a b c))
deleteMinNode frontier = runMaybeT $ do
(node, queue') <- hoistMaybe (MinPQueue.minView frontier.queue)
let frontier' = Frontier queue'
pure (node, frontier')
nextNodeValue :: DoublyLinked.DoublyLinkedNode s a -> Strict.ST s (Maybe a)
nextNodeValue valueNode = runMaybeT $ do
valueNode' <- MaybeT $ DoublyLinked.next valueNode
pure (DoublyLinked.value valueNode')
prevNodeValue :: DoublyLinked.DoublyLinkedNode s a -> Strict.ST s (Maybe a)
prevNodeValue valueNode = runMaybeT $ do
valueNode' <- MaybeT $ DoublyLinked.prev valueNode
pure (DoublyLinked.value valueNode')
insertChildA ::
(Ord c) =>
(a -> b -> c) ->
Node s a b c ->
Frontier s a b c ->
Strict.ST s (Frontier s a b c)
insertChildA f node frontier = fmap (fromMaybe frontier) $ runMaybeT $ do
let (ia, ib) = DoublyLinked.value node.position
nextPosition <- lift $ nextNodeValue node.position
guard (fmap fst nextPosition /= Just (ia + 1))
as' <- hoistMaybe (nonEmpty (NonEmpty.tail node.as))
let bs' = node.bs
position' <- lift (DoublyLinked.insertAfter node.position (ia + 1, ib))
let value' = f (NonEmpty.head as') (NonEmpty.head bs')
let node' =
Node
{ position = position',
value = value',
as = as',
bs = bs'
}
pure $ Frontier $ MinPQueue.insert value' node' frontier.queue
insertChildB ::
(Ord c) =>
(a -> b -> c) ->
Node s a b c ->
Frontier s a b c ->
Strict.ST s (Frontier s a b c)
insertChildB f node frontier = fmap (fromMaybe frontier) $ runMaybeT $ do
let (ia, ib) = DoublyLinked.value node.position
prevPosition <- lift $ prevNodeValue node.position
guard (fmap snd prevPosition /= Just (ib + 1))
bs' <- hoistMaybe (nonEmpty (NonEmpty.tail node.bs))
let as' = node.as
position' <- lift (DoublyLinked.insertBefore node.position (ia, ib + 1))
let value' = f (NonEmpty.head as') (NonEmpty.head bs')
let node' = mkNode f position' as' bs'
pure $ Frontier $ MinPQueue.insert value' node' frontier.queue
mkNode ::
(a -> b -> c) ->
DoublyLinked.DoublyLinkedNode s (Int, Int) ->
NonEmpty a ->
NonEmpty b ->
Node s a b c
mkNode f position as bs =
Node
{ position = position,
value = f (NonEmpty.head as) (NonEmpty.head bs),
as = as,
bs = bs
}
-- Remove this once we allow transformers-0.6
hoistMaybe :: (Applicative m) => Maybe b -> MaybeT m b
hoistMaybe = MaybeT . pure