|
namespace SemanticWeb |
|
open System |
|
open System.Collections.Generic |
|
open System.IO |
|
open System.Text |
|
open Kent.Boogaart.KBCsv |
|
|
|
/// A simple triple store |
|
type SimpleGraph(spo: Map<string, Map<string, Set<string>>>, |
|
pos: Map<string, Map<string, Set<string>>>, |
|
osp: Map<string, Map<string, Set<string>>>) = |
|
|
|
/// Adds a triple to the specified index. |
|
let addToIndex index (a, b, c) = |
|
// TODO: Could this be simpler with Active Patterns? |
|
match Map.tryFind a index with |
|
| None -> index |> Map.add a (Map.ofList [(b, Set.ofList [c])]) |
|
| Some(indexA) -> match Map.tryFind b indexA with |
|
| None -> index |> Map.add a (indexA |> Map.add b (Set.ofList [c])) |
|
| Some(indexB) -> index |> Map.add a (indexA |> Map.add b (indexB |> Set.add c)) |
|
|
|
/// Removes a triple from the specified index. |
|
let removeFromIndex (index: Map<string, Map<string, Set<string>>>) (a, b, c) = |
|
try |
|
let bs = index.[a] |
|
let cset = bs.[b] |
|
let newCset = cset |> Set.remove c |
|
let newBs = bs |> if Set.isEmpty newCset then Map.remove b else Map.add b newCset |
|
index |> if Map.isEmpty newBs then Map.remove a else Map.add a newBs |
|
with |
|
| :? KeyNotFoundException -> index |
|
|
|
/// Retrieves tripes based on a filter. (None, None, None) returns all. |
|
let triples (sub, pred, obj) = |
|
// TODO: Could this be simpler with Active Patterns? |
|
try |
|
seq { match sub with |
|
| Some(s) -> match pred with |
|
| Some(p) -> match obj with |
|
| Some(o) -> if spo.[s].[p].Contains(o) then yield (s, p, o) |
|
| None -> for retObj in spo.[s].[p] do yield (s, p, retObj) |
|
| None -> match obj with |
|
| Some(o) -> for retPred in osp.[o].[s] do yield (s, retPred, o) |
|
| None -> for KeyValue(retPred, objSet) in spo.[s] do |
|
for retObj in objSet do |
|
yield (s, retPred, retObj) |
|
| None -> match pred with |
|
| Some(p) -> match obj with |
|
| Some(o) -> for retSub in pos.[p].[o] do yield (retSub, p, o) |
|
| None -> for KeyValue(retObj, subSet) in pos.[p] do |
|
for retSub in subSet do |
|
yield (retSub, p, retObj) |
|
| None -> match obj with |
|
| Some(o) -> for KeyValue(retSub, predSet) in osp.[o] do |
|
for retPred in predSet do |
|
yield (retSub, retPred, o) |
|
| None -> for KeyValue(retSub, predSet) in spo do |
|
for KeyValue(retPred, objSet) in predSet do |
|
for retObj in objSet do |
|
yield (retSub, retPred, retObj) |
|
} |
|
with |
|
| :? KeyNotFoundException -> Seq.empty |
|
|
|
/// A simple triple store |
|
new() = SimpleGraph(Map.empty, Map.empty, Map.empty) |
|
|
|
/// Add a new triple |
|
member this.Add(sub, pred, obj) = |
|
let newSpo = addToIndex spo (sub, pred, obj) |
|
let newPos = addToIndex pos (pred, obj, sub) |
|
let newOsp = addToIndex osp (obj, sub, pred) |
|
SimpleGraph(newSpo, newPos, newOsp) |
|
|
|
/// Remove a triple |
|
member this.Remove(sub, pred, obj) = |
|
let removeFromIndices (oldSpo, oldPos, oldOsp) (delSub, delPred, delObj) = |
|
let newSpo = removeFromIndex oldSpo (delSub, delPred, delObj) |
|
let newPos = removeFromIndex oldPos (delPred, delObj, delSub) |
|
let newOsp = removeFromIndex oldOsp (delObj, delSub, delPred) |
|
(newSpo, newPos, newOsp) |
|
|
|
let trips = triples (sub, pred, obj) |
|
let newSpo, newPos, newOsp = trips |> Seq.fold (removeFromIndices) (spo, pos, osp) |
|
SimpleGraph(newSpo, newPos, newOsp) |
|
|
|
/// Load triples from a csv file |
|
member this.Load(filename: string) = |
|
let loader (graph: SimpleGraph) (line: string []) = |
|
graph.Add(line.[0], line.[1], line.[2]) |
|
|
|
use rdr = new CsvReader(filename, Encoding.UTF8) |
|
rdr.DataRecordsAsStrings |> Seq.fold loader (SimpleGraph()) |
|
|
|
/// Save triples back to a csv file |
|
member this.Save(filename: string) = |
|
let trips = triples (None, None, None) |
|
|> Seq.map (fun (sub, pred, obj) -> [| sub; pred; obj |]) |
|
use wtr = new CsvWriter(filename, Encoding.UTF8) |
|
trips |> Seq.iter (wtr.WriteDataRecord) |
|
|
|
/// Queries triples based on options. |
|
member this.Triples(sub, pred, obj) = triples(sub, pred, obj) |
|
|
|
/// Retrieves a single value from a triple using None. All other values must be specified. |
|
member this.Value(sub, pred, obj) = |
|
triples (sub, pred, obj) |
|
|> Seq.pick (fun (retSub, retPred, retObj) -> |
|
if Option.isNone sub then Some(retSub) |
|
elif Option.isNone pred then Some(retPred) |
|
elif Option.isNone obj then Some(retObj) |
|
else None) |