packages feed

rp-tree-0.7: src/Data/RPTree/Internal/MedianHeap.hs

{-# language CPP #-}
{-# OPTIONS_GHC -Wno-unused-imports #-}
module Data.RPTree.Internal.MedianHeap (
  MedianHeap,
  insert, fromList,
  median) where

import Data.Bifunctor (Bifunctor(..))
import Data.Ord (Down(..))

import qualified Data.Heap as H (Heap, Entry(..), viewMin, insert, singleton)

type MinHeap p a = H.Heap (H.Entry p a)
type MaxHeap p a = H.Heap (H.Entry (Down p) a)
-- | The items @a@ in a 'MedianHeap' have type @p@
data MedianHeap p a = MedianHeap (MaxHeap p a) (MinHeap p a) deriving (Eq, Show)
instance Semigroup (MedianHeap p a) where
  MedianHeap l1 r1 <> MedianHeap l2 r2 = MedianHeap (l1 <> l2) (r1 <> r2)
instance Monoid (MedianHeap p a) where
  mempty = MedianHeap mempty mempty
insertMax :: Ord p => p -> a -> MaxHeap p a -> MaxHeap p a
insertMax p x = H.insert (H.Entry (Down p) x)
insertMin :: Ord p => p -> a -> MinHeap p a -> MinHeap p a
insertMin p x = H.insert (H.Entry p x)

fromList :: (Foldable t, Fractional p, Ord p) => t (p, a) -> MedianHeap p a
fromList = foldr (uncurry insert) mempty

-- | Insert a weighted entry in the heap
insert :: (Fractional p, Ord p) =>
          p -- ^ weight
       -> a
       -> MedianHeap p a
       -> MedianHeap p a
insert p x heap@(MedianHeap ll rr) =
  case median heap of
    Nothing -> MedianHeap (insertMax p x mempty) rr
    Just q ->
      if q > p
      then balance $ MedianHeap ll (insertMin p x rr)
      else balance $ MedianHeap (insertMax p x ll) rr

balance :: Ord p => MedianHeap p a -> MedianHeap p a
balance (MedianHeap ll rr)
  | length ll == length rr = MedianHeap ll rr
  | length ll > length rr =
    let (H.Entry (Down p) x, xs) = deconstruct ll
     in MedianHeap xs (insertMin p x rr)
  | otherwise =
    let (H.Entry p x, xs) = deconstruct rr
     in MedianHeap (insertMax p x ll) xs
  where
    deconstruct heap = case H.viewMin heap of
      Nothing -> error "cannot view empty heap"
      Just (x, xs) -> (x, xs)

-- | Compute the median weight
median :: Fractional p => MedianHeap p a -> Maybe p
median (MedianHeap lesser greater)
  | length lesser > length greater = H.priority . getHD <$> viewHead lesser
  | length lesser < length greater = H.priority <$> viewHead greater
  | otherwise = do
      leftHead <- getHD <$> viewHead lesser
      rightHead <- viewHead greater
      return $ (H.priority leftHead + H.priority rightHead) / 2

getHD :: H.Entry (Down b) c -> H.Entry b c
getHD = first getDown

#if MIN_VERSION_base(4,14,0)
#else
getDown :: Down a -> a
getDown (Down x) = x
#endif

viewHead :: H.Heap b -> Maybe b
viewHead h = fst <$> H.viewMin h