2014-11-29 22:51:23 +00:00
|
|
|
{-# OPTIONS_HADDOCK ignore-exports #-}
|
|
|
|
|
2014-12-03 20:26:35 +00:00
|
|
|
module Algorithms.KDTree (kdTree
|
|
|
|
, kdFoldl
|
|
|
|
, kdFoldr
|
|
|
|
, kdTreeToRoseTree
|
|
|
|
, rangeSearch
|
|
|
|
, getValS
|
|
|
|
, isLeaf
|
|
|
|
, getVal
|
|
|
|
, getDirection
|
|
|
|
, goLeft
|
|
|
|
, goRight
|
|
|
|
, Direction(Vertical, Horizontal)
|
|
|
|
, KDTree(KTNil, KTNode))
|
2014-11-29 22:51:41 +00:00
|
|
|
where
|
2014-11-29 04:11:15 +00:00
|
|
|
|
|
|
|
|
|
|
|
import Algebra.Vector
|
|
|
|
import Data.Maybe (fromJust, catMaybes)
|
2014-11-29 22:45:53 +00:00
|
|
|
import Data.Tree
|
2014-11-29 04:11:15 +00:00
|
|
|
import Diagrams.TwoD.Types
|
2014-11-29 17:06:07 +00:00
|
|
|
import MyPrelude (pivot,if',Not, not')
|
2014-11-29 04:11:15 +00:00
|
|
|
import Safe
|
|
|
|
|
|
|
|
|
|
|
|
-- |The KDTree data structure.
|
|
|
|
data KDTree a
|
|
|
|
-- |An empty node.
|
|
|
|
= KTNil
|
|
|
|
-- |A node with a value and a left and right child
|
2014-11-29 17:06:07 +00:00
|
|
|
| KTNode (KDTree a) a Direction (KDTree a)
|
2014-11-29 04:11:15 +00:00
|
|
|
deriving (Show, Eq)
|
|
|
|
|
|
|
|
data Direction = Vertical
|
|
|
|
| Horizontal
|
2014-11-29 17:06:07 +00:00
|
|
|
deriving (Show, Eq, Enum)
|
|
|
|
|
|
|
|
instance Not Direction where
|
|
|
|
not' Vertical = Horizontal
|
|
|
|
not' Horizontal = Vertical
|
2014-11-29 04:11:15 +00:00
|
|
|
|
|
|
|
|
|
|
|
-- |Construct a kd-tree from a list of points in O(n log n).
|
2015-01-14 17:17:35 +00:00
|
|
|
kdTree :: [P2] -- ^ list of points to construct the kd-tree from
|
2014-11-29 17:06:07 +00:00
|
|
|
-> Direction -- ^ initial direction of the root-node
|
2015-01-14 17:17:35 +00:00
|
|
|
-> KDTree P2 -- ^ resulting kd-tree
|
2014-11-29 17:06:07 +00:00
|
|
|
kdTree xs' = go (sortedX xs') (sortedY xs')
|
2014-11-29 04:11:15 +00:00
|
|
|
where
|
|
|
|
go [] _ _ = KTNil
|
|
|
|
go _ [] _ = KTNil
|
2014-11-29 17:06:07 +00:00
|
|
|
go xs ys dir =
|
|
|
|
KTNode (go x1 y1 (not' dir))
|
|
|
|
(fromJust . pivot $ if' (dir == Vertical) ys xs)
|
|
|
|
dir
|
|
|
|
(go x2 y2 (not' dir))
|
2014-11-29 04:11:15 +00:00
|
|
|
where
|
2014-11-29 17:06:07 +00:00
|
|
|
((x1, x2), (y1, y2)) = if' (dir == Vertical)
|
|
|
|
(partitionY (xs, ys))
|
|
|
|
(partitionX (xs, ys))
|
2014-11-29 04:11:15 +00:00
|
|
|
|
|
|
|
|
|
|
|
-- |Partitions two sorted list of points X and Y against a pivot.
|
|
|
|
-- If you want to partition against the pivot of Y, then you pass
|
|
|
|
-- partition' (pivot ys) (xs, ys)
|
|
|
|
-- and get ((x1, x2), (y1, y2)).
|
|
|
|
-- If you want to partition against the pivot of X, then you pass
|
|
|
|
-- partition' (pivot xs) (ys, xs)
|
|
|
|
-- and get ((y1, y2), (x1, x2)).
|
2015-01-14 17:17:35 +00:00
|
|
|
partition' :: P2 -- ^ the pivot to partition against
|
|
|
|
-> (P2 -> P2 -> Ordering) -- ^ ptCmpY or ptCmpX
|
|
|
|
-> ([P2], [P2]) -- ^ both lists (X, Y) or (Y, X)
|
|
|
|
-> (([P2], [P2]), ([P2], [P2])) -- ^ ((x1, x2), (y1, y2)) or
|
2014-11-29 04:11:15 +00:00
|
|
|
-- ((y1, y2), (x1, x2))
|
2014-12-02 17:56:40 +00:00
|
|
|
partition' piv cmp' (xs, ys) = ((x1, x2), (y1, y2))
|
2014-11-29 04:11:15 +00:00
|
|
|
where
|
|
|
|
y1 = takeWhile (/= piv) ys
|
|
|
|
y2 = tailDef [] . dropWhile (/= piv) $ ys
|
2014-12-02 17:56:40 +00:00
|
|
|
x1 = foldr (\x y -> [x | cmp' x piv == LT] ++ y) [] xs
|
|
|
|
x2 = foldr (\x y -> [x | cmp' x piv == GT] ++ y) [] xs
|
2014-11-29 04:11:15 +00:00
|
|
|
|
|
|
|
|
2014-11-29 17:06:07 +00:00
|
|
|
-- |Partition two sorted lists of points X and Y against the pivot of
|
|
|
|
-- Y. This function is unsafe as it does not check if there is a valid
|
|
|
|
-- pivot.
|
2015-01-14 17:17:35 +00:00
|
|
|
partitionY :: ([P2], [P2]) -- ^ both lists (X, Y)
|
|
|
|
-> (([P2], [P2]), ([P2], [P2])) -- ^ ((x1, x2), (y1, y2))
|
2014-12-02 17:56:40 +00:00
|
|
|
partitionY (xs, ys) = partition' (fromJust . pivot $ ys) ptCmpY (xs, ys)
|
2014-11-29 17:06:07 +00:00
|
|
|
|
|
|
|
|
|
|
|
-- |Partition two sorted lists of points X and Y against the pivot of
|
|
|
|
-- X. This function is unsafe as it does not check if there is a valid
|
|
|
|
-- pivot.
|
2015-01-14 17:17:35 +00:00
|
|
|
partitionX :: ([P2], [P2]) -- ^ both lists (X, Y)
|
|
|
|
-> (([P2], [P2]), ([P2], [P2])) -- ^ ((x1, x2), (y1, y2))
|
2014-11-29 17:06:07 +00:00
|
|
|
partitionX (xs, ys) = (\(x, y) -> (y, x))
|
2014-12-02 17:56:40 +00:00
|
|
|
. partition' (fromJust . pivot $ xs) ptCmpX $ (ys, xs)
|
2014-11-29 17:06:07 +00:00
|
|
|
|
|
|
|
|
2014-11-29 22:45:53 +00:00
|
|
|
-- |Execute a range search in O(log n). It returns a tuple
|
|
|
|
-- of the points found in the range and also gives back a pretty
|
|
|
|
-- rose tree suitable for printing.
|
2015-01-14 17:17:35 +00:00
|
|
|
rangeSearch :: KDTree P2 -- ^ tree to search in
|
|
|
|
-> ((Double, Double), (Double, Double)) -- ^ square describing the range
|
|
|
|
-> ([P2], Tree String)
|
2014-11-29 22:45:53 +00:00
|
|
|
rangeSearch kd' sq' = (goPt kd' sq', goTree kd' sq' True)
|
|
|
|
where
|
|
|
|
-- either y1 or x1 depending on the orientation
|
2014-12-17 02:35:33 +00:00
|
|
|
p1' dir ((x1, y1), (_, _)) = if' (dir == Vertical) y1 x1
|
2014-11-29 22:45:53 +00:00
|
|
|
-- either y2 or x2 depending on the orientation
|
2014-12-17 02:35:33 +00:00
|
|
|
p2' dir ((_, _), (x2, y2)) = if' (dir == Vertical) y2 x2
|
2014-11-29 22:45:53 +00:00
|
|
|
-- either the second or first of the tuple, depending on the orientation
|
|
|
|
cur' dir = if' (dir == Vertical) snd fst
|
|
|
|
-- All points in the range.
|
2015-01-14 17:17:35 +00:00
|
|
|
goPt :: KDTree P2 -> ((Double, Double), (Double, Double)) -> [P2]
|
2014-11-29 22:45:53 +00:00
|
|
|
goPt KTNil _ = []
|
|
|
|
goPt (KTNode ln pt dir rn) sq =
|
|
|
|
[pt | inRange sq pt]
|
|
|
|
++ (if' (p1' dir sq < (cur' dir . unp2 $ pt))
|
|
|
|
(goPt ln sq)
|
|
|
|
[]
|
|
|
|
)
|
|
|
|
++ (if' ((cur' dir . unp2 $ pt) < p2' dir sq)
|
|
|
|
(goPt rn sq)
|
|
|
|
[])
|
|
|
|
-- A pretty rose tree suitable for printing.
|
2015-01-14 17:17:35 +00:00
|
|
|
goTree :: KDTree P2 -> ((Double, Double), (Double, Double)) -> Bool -> Tree String
|
2014-11-29 22:45:53 +00:00
|
|
|
goTree KTNil _ _ = Node "nil" []
|
2014-11-30 19:04:02 +00:00
|
|
|
goTree (KTNode ln pt dir rn) sq vis
|
|
|
|
| ln == KTNil && rn == KTNil = Node treeText []
|
|
|
|
| otherwise =
|
|
|
|
Node treeText
|
|
|
|
[if' (p1' dir sq < (cur' dir . unp2 $ pt))
|
|
|
|
(goTree ln sq vis)
|
|
|
|
(goTree ln sq False)
|
|
|
|
, if' ((cur' dir . unp2 $ pt) < p2' dir sq)
|
|
|
|
(goTree rn sq vis)
|
|
|
|
(goTree rn sq False)]
|
2014-11-29 22:45:53 +00:00
|
|
|
where
|
|
|
|
treeText
|
2014-12-03 20:20:34 +00:00
|
|
|
-- HACK: in order to give specific nodes a specific color
|
2014-11-29 22:45:53 +00:00
|
|
|
| vis && inRange sq pt = "** " ++ (show . unp2 $ pt)
|
|
|
|
| vis = "* " ++ (show . unp2 $ pt)
|
|
|
|
| otherwise = show . unp2 $ pt
|
2014-11-29 17:06:07 +00:00
|
|
|
|
2014-11-29 04:11:15 +00:00
|
|
|
|
|
|
|
|
|
|
|
-- |Left fold over ALL tree nodes.
|
|
|
|
kdFoldl :: (a -> KDTree b -> a) -> a -> KDTree b -> a
|
2014-11-29 17:06:07 +00:00
|
|
|
kdFoldl f sv kd@(KTNode ln _ _ rn) = foldl (kdFoldl f) (f sv kd) [ln, rn]
|
2014-11-29 04:11:15 +00:00
|
|
|
kdFoldl f sv kd = f sv kd
|
|
|
|
|
|
|
|
|
|
|
|
-- |Right fold over ALL tree nodes.
|
|
|
|
kdFoldr :: (KDTree b -> a -> a) -> a -> KDTree b -> a
|
|
|
|
kdFoldr f sv kd = kdFoldl (\g b x -> g (f b x)) id kd sv
|
|
|
|
|
|
|
|
|
|
|
|
-- |Get all values of a tree.
|
|
|
|
getValS :: KDTree a -> [a]
|
|
|
|
getValS = catMaybes . kdFoldl (\x y -> x ++ [getVal y]) []
|
|
|
|
|
|
|
|
|
|
|
|
-- |Whether the tree is a leaf.
|
|
|
|
isLeaf :: KDTree a -> Bool
|
2014-11-29 17:06:07 +00:00
|
|
|
isLeaf (KTNode KTNil _ _ KTNil) = True
|
2014-11-29 04:11:15 +00:00
|
|
|
isLeaf _ = False
|
|
|
|
|
|
|
|
|
|
|
|
-- |Get the value of the root node of the tree. Returns Nothing if it's a
|
|
|
|
-- leaf.
|
|
|
|
getVal :: KDTree a -> Maybe a
|
2014-11-29 17:06:07 +00:00
|
|
|
getVal (KTNode _ val _ _) = Just val
|
2014-11-29 04:11:15 +00:00
|
|
|
getVal _ = Nothing
|
|
|
|
|
2014-11-29 17:06:07 +00:00
|
|
|
|
|
|
|
-- |Get the direction of the current node/level.
|
|
|
|
getDirection :: KDTree a -> Maybe Direction
|
|
|
|
getDirection (KTNode _ _ dir _) = Just dir
|
|
|
|
getDirection _ = Nothing
|
|
|
|
|
|
|
|
|
2014-11-29 22:45:53 +00:00
|
|
|
-- |Convert a kd-tree to a rose tree, for pretty printing.
|
2015-01-14 17:17:35 +00:00
|
|
|
kdTreeToRoseTree :: KDTree P2 -> Tree String
|
2014-11-29 22:45:53 +00:00
|
|
|
kdTreeToRoseTree (KTNil) = Node "nil" []
|
|
|
|
kdTreeToRoseTree (KTNode ln val _ rn) =
|
|
|
|
Node (show . unp2 $ val) [kdTreeToRoseTree ln, kdTreeToRoseTree rn]
|
|
|
|
|
|
|
|
|
2014-11-29 22:49:39 +00:00
|
|
|
goLeft :: KDTree a -> Maybe (KDTree a)
|
|
|
|
goLeft (KTNode ln _ _ _) = Just ln
|
|
|
|
goLeft _ = Nothing
|
|
|
|
|
|
|
|
|
|
|
|
goRight :: KDTree a -> Maybe (KDTree a)
|
|
|
|
goRight (KTNode _ _ _ rn) = Just rn
|
|
|
|
goRight _ = Nothing
|
|
|
|
|