github.com/gopherd/gonum@v0.0.4/graph/formats/rdf/graph_example_test.go (about) 1 // Copyright ©2022 The Gonum Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package rdf_test 6 7 import ( 8 "fmt" 9 "io" 10 "log" 11 "os" 12 "strings" 13 14 "github.com/gopherd/gonum/graph/formats/rdf" 15 ) 16 17 func ExampleGraph() { 18 f, err := os.Open("path/to/graph.nq") 19 if err != nil { 20 log.Fatal(err) 21 } 22 23 dec := rdf.NewDecoder(f) 24 var statements []*rdf.Statement 25 for { 26 s, err := dec.Unmarshal() 27 if err != nil { 28 if err != io.EOF { 29 log.Fatalf("error during decoding: %v", err) 30 } 31 break 32 } 33 34 // Statements can be filtered at this point to exclude unwanted 35 // or irrelevant parts of the graph. 36 statements = append(statements, s) 37 } 38 f.Close() 39 40 // Canonicalize blank nodes to reduce memory footprint. 41 statements, err = rdf.URDNA2015(statements, statements) 42 if err != nil { 43 log.Fatal(err) 44 } 45 46 g := rdf.NewGraph() 47 for _, s := range statements { 48 g.AddStatement(s) 49 } 50 51 // Do something with the graph. 52 } 53 54 const gods = ` 55 _:alcmene <l:type> "human" . 56 _:alcmene <p:name> "Alcmene" . 57 _:cerberus <a:lives> _:cerberushome . 58 _:cerberus <l:type> "monster" . 59 _:cerberus <p:name> "Cerberus" . 60 _:cerberushome <p:location> _:tartarus . 61 _:cronos <l:type> "titan" . 62 _:cronos <p:name> "Cronos" . 63 _:hades <a:lives> _:hadeshome . 64 _:hades <h:brother> _:poseidon . 65 _:hades <h:brother> _:zeus . 66 _:hades <h:pet> _:cerberus . 67 _:hades <l:type> "god" . 68 _:hades <p:name> "Hades" . 69 _:hadeshome <p:location> _:tartarus . 70 _:hadeshome <p:reason> "it is peaceful" . 71 _:heracles <a:battled> _:cerberus . 72 _:heracles <a:battled> _:hydra . 73 _:heracles <a:battled> _:nemean . 74 _:heracles <h:father> _:zeus . 75 _:heracles <h:mother> _:alcmene . 76 _:heracles <l:type> "demigod" . 77 _:heracles <p:name> "Heracles" . 78 _:hydra <l:type> "monster" . 79 _:hydra <p:name> "Lernean Hydra" . 80 _:nemean <l:type> "monster" . 81 _:nemean <p:name> "Nemean Lion" . 82 _:olympus <l:type> "location" . 83 _:olympus <p:name> "Olympus" . 84 _:poseidon <a:lives> _:poseidonhome . 85 _:poseidon <h:brother> _:hades . 86 _:poseidon <h:brother> _:zeus . 87 _:poseidon <l:type> "god" . 88 _:poseidon <p:name> "Poseidon" . 89 _:poseidonhome <p:location> _:sea . 90 _:poseidonhome <p:reason> "it was given to him" . 91 _:sea <l:type> "location" . 92 _:sea <p:name> "Sea" . 93 _:tartarus <l:type> "location" . 94 _:tartarus <p:name> "Tartarus" . 95 _:theseus <a:battled> _:cerberus . 96 _:theseus <h:father> _:poseidon . 97 _:theseus <l:type> "human" . 98 _:theseus <p:name> "Theseus" . 99 _:zeus <a:lives> _:zeushome . 100 _:zeus <h:brother> _:hades . 101 _:zeus <h:brother> _:poseidon . 102 _:zeus <h:father> _:cronos . 103 _:zeus <l:type> "god" . 104 _:zeus <p:name> "Zeus" . 105 _:zeushome <p:location> _:olympus . 106 _:zeushome <p:reason> "he can see everything" . 107 ` 108 109 func ExampleQuery() { 110 g := rdf.NewGraph() 111 dec := rdf.NewDecoder(strings.NewReader(gods)) 112 for { 113 s, err := dec.Unmarshal() 114 if err != nil { 115 if err != io.EOF { 116 log.Fatalf("error during decoding: %v", err) 117 } 118 break 119 } 120 g.AddStatement(s) 121 } 122 123 it := g.Nodes() 124 nodes := make([]rdf.Term, 0, it.Len()) 125 for it.Next() { 126 nodes = append(nodes, it.Node().(rdf.Term)) 127 } 128 129 // Construct a query start point. This can be reused. If a specific 130 // node is already known it can be used to reduce the work required here. 131 heracles := g.Query(nodes...).In(func(s *rdf.Statement) bool { 132 // Traverse in from the name "Heracles". 133 return s.Predicate.Value == "<p:name>" && s.Object.Value == `"Heracles"` 134 }) 135 136 // father and name filter statements on their predicate values. These 137 // are used in the queries that follow. 138 father := func(s *rdf.Statement) bool { 139 // Traverse across <h:father>. 140 return s.Predicate.Value == "<h:father>" 141 } 142 name := func(s *rdf.Statement) bool { 143 // Traverse across <p:name>. 144 return s.Predicate.Value == "<p:name>" 145 } 146 147 // g.V().has('name', 'heracles').out('father').out('father').values('name') 148 for _, r := range heracles. 149 Out(father). // Traverse out across <h:father> to get to Zeus. 150 Out(father). // and again to get to Cronos. 151 Out(name). // Retrieve the name by traversing the <p:name> edges. 152 Result() { 153 fmt.Printf("Heracles' grandfather: %s\n", r.Value) 154 } 155 156 // g.V().has('name', 'heracles').repeat(out('father')).emit().values('name') 157 var i int 158 heracles.Repeat(func(q rdf.Query) (rdf.Query, bool) { 159 q = q.Out(father) 160 for _, r := range q.Out(name).Result() { 161 fmt.Printf("Heracles' lineage %d: %s\n", i, r.Value) 162 } 163 i++ 164 return q, true 165 }) 166 167 // parents and typ are helper filters for queries below. 168 parents := func(s *rdf.Statement) bool { 169 // Traverse across <h:father> or <h:mother> 170 return s.Predicate.Value == "<h:father>" || s.Predicate.Value == "<h:mother>" 171 } 172 typ := func(s *rdf.Statement) bool { 173 // Traverse across <l:type>. 174 return s.Predicate.Value == "<l:type>" 175 } 176 177 // g.V(heracles).out('father', 'mother').label() 178 for _, r := range heracles.Out(parents).Out(typ).Result() { 179 fmt.Printf("Heracles' parents' types: %s\n", r.Value) 180 } 181 182 // battled is a helper filter for queries below. 183 battled := func(s *rdf.Statement) bool { 184 // Traverse across <a:battled>. 185 return s.Predicate.Value == "<a:battled>" 186 } 187 188 // g.V(heracles).out('battled').label() 189 for _, r := range heracles.Out(battled).Out(typ).Result() { 190 fmt.Printf("Heracles' antagonists' types: %s\n", r.Value) 191 } 192 193 // g.V(heracles).out('battled').valueMap() 194 for _, r := range heracles.Out(battled).Result() { 195 m := make(map[string]string) 196 g.Query(r).Out(func(s *rdf.Statement) bool { 197 // Store any p: namespace in the map. 198 if strings.HasPrefix(s.Predicate.Value, "<p:") { 199 prop := strings.TrimSuffix(strings.TrimPrefix(s.Predicate.Value, "<p:"), ">") 200 m[prop] = s.Object.Value 201 } 202 // But don't store the result into the query. 203 return false 204 }) 205 fmt.Println(m) 206 } 207 208 // g.V(heracles).as('h').out('battled').in('battled').where(neq('h')).values('name') 209 for _, r := range heracles.Out(battled).In(battled).Not(heracles).Out(name).Result() { 210 fmt.Printf("Heracles' allies: %s\n", r.Value) 211 } 212 213 // Construct a query start point for Hades, this time using a restricted 214 // starting set only including the name. It would also be possible to 215 // start directly from a query with the term _:hades, but that depends 216 // on the blank node identity, which may be altered, for example by 217 // canonicalization. 218 h, ok := g.TermFor(`"Hades"`) 219 if !ok { 220 log.Fatal("could not find term for Hades") 221 } 222 hades := g.Query(h).In(name) 223 224 // g.V(hades).as('x').out('lives').in('lives').where(neq('x')).values('name') 225 // 226 // This is more complex with RDF since properties are encoded by 227 // attachment to anonymous blank nodes, so we take two steps, the 228 // first to the blank node for where Hades lives and then the second 229 // to get the actual location. 230 lives := func(s *rdf.Statement) bool { 231 // Traverse across <a:lives>. 232 return s.Predicate.Value == "<a:lives>" 233 } 234 location := func(s *rdf.Statement) bool { 235 // Traverse across <p:location>. 236 return s.Predicate.Value == "<p:location>" 237 } 238 for _, r := range hades.Out(lives).Out(location).In(location).In(lives).Not(hades).Out(name).Result() { 239 fmt.Printf("Hades lives with: %s\n", r.Value) 240 } 241 242 // g.V(hades).out('brother').as('god').out('lives').as('place').select('god', 'place').by('name') 243 brother := func(s *rdf.Statement) bool { 244 // Traverse across <h:brother>. 245 return s.Predicate.Value == "<h:brother>" 246 } 247 for _, r := range hades.Out(brother).Result() { 248 m := make(map[string]string) 249 as := func(key string) func(s *rdf.Statement) bool { 250 return func(s *rdf.Statement) bool { 251 // Store any <p:name> objects in the map. 252 if s.Predicate.Value == "<p:name>" { 253 m[key] = s.Object.Value 254 } 255 // But don't store the result into the query. 256 return false 257 } 258 } 259 sub := g.Query(r) 260 sub.Out(as("god")) 261 sub.Out(lives).Out(location).Out(as("place")) 262 fmt.Println(m) 263 } 264 265 // The query above but with the reason for their choice. 266 for _, r := range hades.Out(brother).Result() { 267 m := make(map[string]string) 268 // as stores the query result under the provided key 269 // for m, and if cont is not nil, allows the chain 270 // to continue. 271 as := func(query, key string, cont func(s *rdf.Statement) bool) func(s *rdf.Statement) bool { 272 return func(s *rdf.Statement) bool { 273 // Store any objects matching the query in the map. 274 if s.Predicate.Value == query { 275 m[key] = s.Object.Value 276 } 277 // Continue with chain if cont is not nil and 278 // the statement satisfies its condition. 279 if cont == nil { 280 return false 281 } 282 return cont(s) 283 } 284 } 285 sub := g.Query(r) 286 sub.Out(as("<p:name>", "god", nil)) 287 sub.Out(lives). 288 Out(as("<p:reason>", "reason", location)). 289 Out(as("<p:name>", "place", nil)) 290 fmt.Println(m) 291 } 292 293 // Unordered output: 294 // 295 // Heracles' grandfather: "Cronos" 296 // Heracles' lineage 0: "Zeus" 297 // Heracles' lineage 1: "Cronos" 298 // Heracles' parents' types: "god" 299 // Heracles' parents' types: "human" 300 // Heracles' antagonists' types: "monster" 301 // Heracles' antagonists' types: "monster" 302 // Heracles' antagonists' types: "monster" 303 // map[name:"Cerberus"] 304 // map[name:"Lernean Hydra"] 305 // map[name:"Nemean Lion"] 306 // Heracles' allies: "Theseus" 307 // Hades lives with: "Cerberus" 308 // map[god:"Zeus" place:"Olympus"] 309 // map[god:"Poseidon" place:"Sea"] 310 // map[god:"Zeus" place:"Olympus" reason:"he can see everything"] 311 // map[god:"Poseidon" place:"Sea" reason:"it was given to him"] 312 }