rdf4h-3.0.0: src/Data/RDF/Graph/HashMapS.hs
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TupleSections, GeneralizedNewtypeDeriving #-}
{-# LANGUAGE EmptyDataDecls #-}
-- |A graph implementation mapping hashed S to a mapping of
-- hashed P to hashed O, backed by 'Data.HashMap'.
module Data.RDF.Graph.HashMapS (HashS) where
import Prelude hiding (pred)
import Control.DeepSeq (NFData)
import Data.RDF.Types
import Data.RDF.Query
import Data.RDF.Namespace
import qualified Data.Map as Map
import Data.Hashable()
import Data.HashMap.Strict(HashMap)
import qualified Data.HashMap.Strict as HashMap
import Data.HashSet(HashSet)
import qualified Data.HashSet as Set
import Data.List
import GHC.Generics
import Data.Binary (Binary)
-- |A map-based graph implementation.
--
-- This instance of 'RDF' is an adjacency map with each subject
-- mapping to a mapping from a predicate node to to the adjacent nodes
-- via that predicate.
--
-- Given the following triples graph::
--
-- @
-- (http:\/\/example.com\/s1,http:\/\/example.com\/p1,http:\/\/example.com\/o1)
-- (http:\/\/example.com\/s1,http:\/\/example.com\/p1,http:\/\/example.com\/o2)
-- (http:\/\/example.com\/s1,http:\/\/example.com\/p2,http:\/\/example.com\/o1)
-- (http:\/\/example.com\/s2,http:\/\/example.com\/p3,http:\/\/example.com\/o3)
-- @
--
-- where
--
-- > hash "http://example.com/s1" = 1600134414
-- > hash "http://example.com/s2" = 1600134413
-- > hash "http://example.com/p1" = 1616912099
-- > hash "http://example.com/p2" = 1616912096
-- > hash "http://example.com/p3" = 1616912097
-- > hash "http://example.com/o1" = 1935686794
-- > hash "http://example.com/o2" = 1935686793
-- > hash "http://example.com/o3" = 1935686792
--
-- the in-memory hashmap representation of the triples graph is:
--
-- @
-- key:1600134414, value:(key:1616912099, value:[1935686794 -- (..\/s1,..\/p1,..\/o1)
-- ,1935686793]; -- (..\/s1,..\/p1,..\/o2)
-- key:1616912096, value:[1935686794]); -- (..\/s1,..\/p2,..\/o1)
-- key:1600134413, value:(key:1616912097, value:[1935686792]) -- (..\/s1,..\/p3,..\/o3)
-- @
--
-- Worst-case time complexity of the graph functions, with respect
-- to the number of triples, are:
--
-- * 'empty' : O(1)
--
-- * 'mkRdf' : O(n)
--
-- * 'triplesOf': O(n)
--
-- * 'select' : O(n)
--
-- * 'query' : O(log n)
-- newtype HashS = HashS (TMaps, Maybe BaseUrl, PrefixMappings)
-- deriving (NFData)
data HashS deriving (Generic)
instance Binary HashS
instance NFData HashS
data instance RDF HashS = HashS (TMaps, Maybe BaseUrl, PrefixMappings)
deriving (NFData,Generic)
instance Rdf HashS where
baseUrl = baseUrl'
prefixMappings = prefixMappings'
addPrefixMappings = addPrefixMappings'
empty = empty'
mkRdf = mkRdf'
triplesOf = triplesOf'
uniqTriplesOf = uniqTriplesOf'
select = select'
query = query'
showGraph = showGraph'
-- instance Show (HashS) where
-- show (HashS ((spoMap, _), _, _)) =
-- let ts = concatMap (uncurry tripsSubj) subjPredMaps
-- where subjPredMaps = HashMap.toList spoMap
-- in concatMap (\t -> show t ++ "\n") ts
showGraph' :: RDF HashS -> [Char]
showGraph' ((HashS ((spoMap, _), _, _))) =
let ts = concatMap (uncurry tripsSubj) subjPredMaps
where subjPredMaps = HashMap.toList spoMap
in concatMap (\t -> show t ++ "\n") ts
-- instance Show (RDF HashS) where
-- show gr = concatMap (\t -> show t ++ "\n") (triplesOf gr)
-- some convenience type alias for readability
-- An adjacency map for a subject, mapping from a predicate node to
-- to the adjacent nodes via that predicate.
type AdjacencyMap = HashMap Predicate (HashSet Node)
type Adjacencies = HashSet Node
type TMap = HashMap Node AdjacencyMap
type TMaps = (TMap, TMap)
baseUrl' :: RDF HashS -> Maybe BaseUrl
baseUrl' (HashS (_, baseURL, _)) = baseURL
prefixMappings' :: RDF HashS -> PrefixMappings
prefixMappings' (HashS (_, _, pms)) = pms
addPrefixMappings' :: RDF HashS -> PrefixMappings -> Bool -> RDF HashS
addPrefixMappings' (HashS (ts, baseURL, pms)) pms' replace =
let merge = if replace then flip mergePrefixMappings else mergePrefixMappings
in HashS (ts, baseURL, merge pms pms')
empty' :: RDF HashS
empty' = HashS ((HashMap.empty, HashMap.empty), Nothing, PrefixMappings Map.empty)
mkRdf' :: Triples -> Maybe BaseUrl -> PrefixMappings -> RDF HashS
mkRdf' ts baseURL pms = HashS (mergeTs (HashMap.empty, HashMap.empty) ts, baseURL, pms)
mergeTs :: TMaps -> [Triple] -> TMaps
mergeTs = foldl' mergeT
where
mergeT :: TMaps -> Triple -> TMaps
mergeT m t = mergeT' m (subjectOf t) (predicateOf t) (objectOf t)
mergeT' :: TMaps -> Subject -> Predicate -> Object -> TMaps
mergeT' (spo, ops) s p o = (mergeT'' spo s p o, mergeT'' ops o p s)
mergeT'' :: TMap -> Subject -> Predicate -> Object -> TMap
mergeT'' m s p o =
if s `HashMap.member` m then
(if p `HashMap.member` adjs then HashMap.insert s addPredObj m
else HashMap.insert s addNewPredObjMap m)
else HashMap.insert s newPredMap m
where
adjs = HashMap.lookupDefault HashMap.empty s m
newPredMap :: HashMap Predicate (HashSet Object)
newPredMap = HashMap.singleton p (Set.singleton o)
addNewPredObjMap :: HashMap Predicate (HashSet Object)
addNewPredObjMap = HashMap.insert p (Set.singleton o) adjs
addPredObj :: HashMap Predicate (HashSet Object)
addPredObj = HashMap.insert p (Set.insert o (get p adjs)) adjs
--get :: (Ord k, Hashable k) => k -> HashMap k v -> v
get = HashMap.lookupDefault Set.empty
-- 3 following functions support triplesOf
triplesOf' :: RDF HashS -> Triples
triplesOf' (HashS ((spoMap, _), _, _)) = concatMap (uncurry tripsSubj) subjPredMaps
where subjPredMaps = HashMap.toList spoMap
-- naive implementation for now
uniqTriplesOf' :: RDF HashS -> Triples
uniqTriplesOf' = nub . expandTriples
tripsSubj :: Subject -> AdjacencyMap -> Triples
tripsSubj s adjMap = concatMap (uncurry (tfsp s)) (HashMap.toList adjMap)
where tfsp = tripsForSubjPred
tripsForSubjPred :: Subject -> Predicate -> Adjacencies -> Triples
tripsForSubjPred s p adjs = map (Triple s p) (Set.toList adjs)
-- supports select
select' :: RDF HashS -> NodeSelector -> NodeSelector -> NodeSelector -> Triples
select' (HashS ((spoMap,_),_,_)) subjFn predFn objFn =
map (\(s,p,o) -> Triple s p o) $ Set.toList $ sel1 subjFn predFn objFn spoMap
sel1 :: NodeSelector -> NodeSelector -> NodeSelector -> TMap -> HashSet (Node, Node, Node)
sel1 (Just subjFn) p o spoMap =
Set.unions $ map (sel2 p o) $ filter (\(x,_) -> subjFn x) $ HashMap.toList spoMap
sel1 Nothing p o spoMap = Set.unions $ map (sel2 p o) $ HashMap.toList spoMap
sel2 :: NodeSelector -> NodeSelector -> (Node, HashMap Node (HashSet Node)) -> HashSet (Node, Node, Node)
sel2 (Just predFn) mobjFn (s, ps) =
Set.map (\(p,o) -> (s,p,o)) $
foldl' Set.union Set.empty $
map (sel3 mobjFn) poMapS :: HashSet (Node, Node, Node)
where
poMapS :: [(Node, HashSet Node)]
poMapS = filter (\(k,_) -> predFn k) $ HashMap.toList ps
sel2 Nothing mobjFn (s, ps) =
Set.map (\(p,o) -> (s,p,o)) $
foldl' Set.union Set.empty $
map (sel3 mobjFn) poMaps
where
poMaps = HashMap.toList ps
sel3 :: NodeSelector -> (Node, HashSet Node) -> HashSet (Node, Node)
sel3 (Just objFn) (p, os) = Set.map (\o -> (p, o)) $ Set.filter objFn os
sel3 Nothing (p, os) = Set.map (\o -> (p, o)) os
-- support query
query' :: RDF HashS -> Maybe Subject -> Maybe Predicate -> Maybe Object -> Triples
query' (HashS (m,_ , _)) subj pred obj = map f $ Set.toList $ q1 subj pred obj m
where f (s, p, o) = Triple s p o
q1 :: Maybe Node -> Maybe Node -> Maybe Node -> TMaps -> HashSet (Node, Node, Node)
q1 (Just s) p o (spoMap, _ ) = q2 p o (s, HashMap.lookupDefault HashMap.empty s spoMap)
q1 s p (Just o) (_ , opsMap) = Set.map (\(o',p',s') -> (s',p',o')) $ q2 p s (o, HashMap.lookupDefault HashMap.empty o opsMap)
q1 Nothing p o (spoMap, _ ) = Set.unions $ map (q2 p o) $ HashMap.toList spoMap
q2 :: Maybe Node -> Maybe Node -> (Node, HashMap Node (HashSet Node)) -> HashSet (Node, Node, Node)
q2 (Just p) o (s, pmap) =
maybe Set.empty (Set.map (\ (p', o') -> (s, p', o')) . q3 o . (p,)) $ HashMap.lookup p pmap
q2 Nothing o (s, pmap) = Set.map (\(x,y) -> (s,x,y)) $ Set.unions $ map (q3 o) opmaps
where opmaps ::[(Node, HashSet Node)]
opmaps = HashMap.toList pmap
q3 :: Maybe Node -> (Node, HashSet Node) -> HashSet (Node, Node)
q3 (Just o) (p, os) = if o `Set.member` os then Set.singleton (p, o) else Set.empty
q3 Nothing (p, os) = Set.map (\o -> (p, o)) os