{-# LANGUAGE ScopedTypeVariables #-}
module Algorithms.Geometry.ClosestPair.DivideAndConquer( closestPair
, CP
, CCP(..)
, mergePairs
)
where
import Algorithms.DivideAndConquer
import Control.Lens
import Data.Ext
import Data.Geometry.Point
import Data.LSeq (LSeq)
import qualified Data.LSeq as LSeq
import qualified Data.List as List
import Data.List.NonEmpty (NonEmpty(..))
import qualified Data.List.NonEmpty as NonEmpty
import Data.Ord (comparing)
import Data.Semigroup.Foldable (toNonEmpty)
import Data.UnBounded
import Data.Util
closestPair :: (Ord r, Num r) => LSeq 2 (Point 2 r :+ p) -> Two (Point 2 r :+ p)
closestPair = f . divideAndConquer1 mkCCP . toNonEmpty
. LSeq.unstableSortBy (comparing (^.core))
where
mkCCP p = CCP (p :| []) Top
f = \case
CCP _ (ValT (SP cp _)) -> cp
CCP _ Top -> error "closestPair: absurd."
type CP q r = Top (SP (Two q) r)
data CCP p r = CCP (NonEmpty (Point 2 r :+ p))
!(CP (Point 2 r :+ p) r)
deriving (Show,Eq)
instance (Num r, Ord r) => Semigroup (CCP p r) where
(CCP ptsl cpl) <> (CCP ptsr cpr) = CCP (mergeSortedBy cmp ptsl ptsr)
(mergePairs (minBy getDist cpl cpr) ptsl ptsr)
where
cmp :: Point 2 r :+ p -> Point 2 r :+ p -> Ordering
cmp p q = comparing (^.core.yCoord) p q <> comparing (^.core.xCoord) p q
mergePairs :: forall p r. (Ord r, Num r)
=> CP (Point 2 r :+ p) r
-> NonEmpty (Point 2 r :+ p)
-> NonEmpty (Point 2 r :+ p)
-> CP (Point 2 r :+ p) r
mergePairs cp' ls' rs' = go cp' (NonEmpty.toList ls') (NonEmpty.toList rs')
where
go :: CP (Point 2 r :+ p) r -> [Point 2 r :+ p] -> [Point 2 r :+ p]
-> CP (Point 2 r :+ p) r
go cp _ [] = cp
go cp ls (r:rs) = let ls'' = trim (getDist cp) ls r
cp'' = run cp r ls''
in go cp'' ls'' rs
trim :: (Ord r, Num r)
=> Top r -> [Point 2 r :+ q] -> Point 2 r :+ a
-> [Point 2 r :+ q]
trim (ValT d) ls r = List.dropWhile (\l -> sqVertDist l r > d) ls
trim _ ls _ = ls
sqVertDist :: (Ord r, Num r) => Point 2 r :+ p -> Point 2 r :+ q -> r
sqVertDist l r = let d = 0 `max` (r^.core.yCoord - l^.core.yCoord) in d*d
run :: (Ord r, Num r)
=> CP (Point 2 r :+ p) r -> Point 2 r :+ p -> [Point 2 r :+ p]
-> CP (Point 2 r :+ p) r
run cp'' r ls =
runWhile cp'' ls
(\cp l -> (ValT $ sqVertDist r l) < getDist cp)
(\cp l -> minBy getDist cp (ValT $ SP (Two l r) (dist l r)))
where
dist (p :+ _) (q :+ _) = squaredEuclideanDist p q
runWhile :: s -> [a] -> (s -> a -> Bool) -> (s -> a -> s) -> s
runWhile s' ys p f = go s' ys
where
go s [] = s
go s (x:xs) | p s x = go (f s x) xs
| otherwise = s
minBy :: Ord b => (a -> b) -> a -> a -> a
minBy f a b | f a < f b = a
| otherwise = b
getDist :: CP a r -> Top r
getDist = fmap (view _2)