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  }