Repository: gonum/gonum
Pull request #1663 (2021-06-12 09:36:38)

graph/formats/rdf: use isBlank and isIRI helpers
Please take a look.

<!--
Checklist:

- API changes have been discussed
- code is goformated correctly (goimports)
- packages with generated code have had code generation run
- tests pass locally
- linked to relevant issues

Please make sure your commit message summary line and pull request
title match the Go convention; a one-line summary of the change,
prefixed by the primary affected package that should complete the
sentence, "This change modifies Gonum to _____."
-->


codecov-commenter: # [Codecov](https://codecov.io/gh/gonum/gonum/pull/1663?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gonum) Report
> Merging [#1663](https://codecov.io/gh/gonum/gonum/pull/1663?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gonum) (c1377d0) into [master](https://codecov.io/gh/gonum/gonum/commit/0acd6516a6f90cb1b5f441ffbe389cb19b07f73b?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gonum) (0acd651) will **decrease** coverage by `0.14%`.
> The diff coverage is `93.10%`.

[![Impacted file tree graph](https://codecov.io/gh/gonum/gonum/pull/1663/graphs/tree.svg?width=650&height=150&src=pr&token=B7qHnqSPNL&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gonum)](https://codecov.io/gh/gonum/gonum/pull/1663?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gonum)

```diff
@@            Coverage Diff             @@
##           master    #1663      +/-   ##
==========================================
- Coverage   73.23%   73.08%   -0.15%     
==========================================
  Files         505      490      -15     
  Lines       59845    59418     -427     
==========================================
- Hits        43826    43428     -398     
+ Misses      13538    13531       -7     
+ Partials     2481     2459      -22     
```


| [Impacted Files](https://codecov.io/gh/gonum/gonum/pull/1663?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gonum) | Coverage Δ | |
|---|---|---|
| [graph/formats/rdf/iso\_canonical.go](https://codecov.io/gh/gonum/gonum/pull/1663/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gonum#diff-Z3JhcGgvZm9ybWF0cy9yZGYvaXNvX2Nhbm9uaWNhbC5nbw==) | `78.73% <90.47%> (ø)` | |
| [graph/formats/rdf/rdf.go](https://codecov.io/gh/gonum/gonum/pull/1663/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gonum#diff-Z3JhcGgvZm9ybWF0cy9yZGYvcmRmLmdv) | `78.26% <100.00%> (+0.27%)` | :arrow_up: |
| [graph/formats/rdf/urna.go](https://codecov.io/gh/gonum/gonum/pull/1663/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gonum#diff-Z3JhcGgvZm9ybWF0cy9yZGYvdXJuYS5nbw==) | `97.44% <100.00%> (ø)` | |
| [internal/asm/c64/conj.go](https://codecov.io/gh/gonum/gonum/pull/1663/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gonum#diff-aW50ZXJuYWwvYXNtL2M2NC9jb25qLmdv) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [interp/cubic.go](https://codecov.io/gh/gonum/gonum/pull/1663/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gonum#diff-aW50ZXJwL2N1YmljLmdv) | `97.50% <0.00%> (-2.50%)` | :arrow_down: |
| [mat/band.go](https://codecov.io/gh/gonum/gonum/pull/1663/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gonum#diff-bWF0L2JhbmQuZ28=) | `70.64% <0.00%> (ø)` | |
| [graph/internal/set/same\_appengine.go](https://codecov.io/gh/gonum/gonum/pull/1663/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gonum#diff-Z3JhcGgvaW50ZXJuYWwvc2V0L3NhbWVfYXBwZW5naW5lLmdv) | | |
| [internal/asm/f64/l2norm\_noasm.go](https://codecov.io/gh/gonum/gonum/pull/1663/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gonum#diff-aW50ZXJuYWwvYXNtL2Y2NC9sMm5vcm1fbm9hc20uZ28=) | | |
| [internal/asm/f32/ge\_noasm.go](https://codecov.io/gh/gonum/gonum/pull/1663/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gonum#diff-aW50ZXJuYWwvYXNtL2YzMi9nZV9ub2FzbS5nbw==) | | |
| [internal/asm/f32/stubs\_noasm.go](https://codecov.io/gh/gonum/gonum/pull/1663/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gonum#diff-aW50ZXJuYWwvYXNtL2YzMi9zdHVic19ub2FzbS5nbw==) | | |
| ... and [13 more](https://codecov.io/gh/gonum/gonum/pull/1663/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gonum) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/gonum/gonum/pull/1663?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gonum).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gonum)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/gonum/gonum/pull/1663?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gonum). Last update [0acd651...c1377d0](https://codecov.io/gh/gonum/gonum/pull/1663?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gonum). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gonum).


Patch:
diff --git a/graph/formats/rdf/iso_canonical.go b/graph/formats/rdf/iso_canonical.go
index 7f66ce1b..2f31e6a8 100644
--- a/graph/formats/rdf/iso_canonical.go
+++ b/graph/formats/rdf/iso_canonical.go
@@ -10,7 +10,6 @@ import (
 	"fmt"
 	"hash"
 	"sort"
-	"strings"
 )
 
 // See "Canonical Forms for Isomorphic and Equivalent RDF Graphs: Algorithms
@@ -160,7 +159,7 @@ func C14n(dst, src []*Statement, terms map[string]map[string]bool) ([]*Statement
 			s.Object.Value,
 			s.Label.Value,
 		} {
-			if !strings.HasPrefix(t, "_:") {
+			if !isBlank(t) {
 				continue
 			}
 			need[t] = true
@@ -172,7 +171,7 @@ func C14n(dst, src []*Statement, terms map[string]map[string]bool) ([]*Statement
 	for h, m := range terms {
 		var ok bool
 		for t := range m {
-			if strings.HasPrefix(t, "_:") {
+			if isBlank(t) {
 				ok = true
 				break
 			}
@@ -194,7 +193,7 @@ func C14n(dst, src []*Statement, terms map[string]map[string]bool) ([]*Statement
 			return nil, fmt.Errorf("rdf: no term for blank with hash %x", b)
 		}
 		for t := range terms[b] {
-			if !strings.HasPrefix(t, "_:") {
+			if !isBlank(t) {
 				continue
 			}
 			if _, exists := c14n[t]; exists {
@@ -275,13 +274,13 @@ func hashBNodes(statements []*Statement, h hash.Hash, zero []byte, hash0 map[str
 			switch {
 			case i == 3 && t == "":
 				continue
-			case strings.HasPrefix(t, "_:"):
+			case isBlank(t):
 				if hash0 == nil {
 					curr.set(t, zero)
 				} else {
 					curr.set(t, hash0[t])
 				}
-			case strings.HasPrefix(t, "<") && strings.HasSuffix(t, ">"):
+			case isIRI(t):
 				h.Reset()
 				h.Write([]byte(t[1 : len(t)-1])) //nolint:errcheck
 				curr.set(t, h.Sum(nil))
@@ -298,7 +297,7 @@ func hashBNodes(statements []*Statement, h hash.Hash, zero []byte, hash0 map[str
 	for {
 		curr, last = last, curr
 		for _, s := range statements {
-			if strings.HasPrefix(s.Subject.Value, "_:") {
+			if isBlank(s.Subject.Value) {
 				var lab []byte
 				if s.Label.Value != "" {
 					lab = last.hashOf[s.Label.Value]
@@ -307,7 +306,7 @@ func hashBNodes(statements []*Statement, h hash.Hash, zero []byte, hash0 map[str
 				bag.add(s.Subject.Value, c)
 			}
 
-			if strings.HasPrefix(s.Object.Value, "_:") {
+			if isBlank(s.Object.Value) {
 				var lab []byte
 				if s.Label.Value != "" {
 					lab = last.hashOf[s.Label.Value]
@@ -320,7 +319,7 @@ func hashBNodes(statements []*Statement, h hash.Hash, zero []byte, hash0 map[str
 			// required for RDF dataset hashing as described in
 			// https://doi.org/10.5281/zenodo.3154322 v1.0
 			// Readme.md#adaptation-of-the-algorithms-to-handle-datasets.
-			if strings.HasPrefix(s.Label.Value, "_:") {
+			if isBlank(s.Label.Value) {
 				c := hashTuple(h, last.hashOf[s.Subject.Value], last.hashOf[s.Predicate.Value], last.hashOf[s.Object.Value], []byte{'.'})
 				bag.add(s.Label.Value, c)
 			}
@@ -430,7 +429,7 @@ func (t *table) set(term string, hash []byte) {
 		t.termsFor[string(hash)] = map[string]bool{term: true}
 	}
 
-	if !t.wasCloned() && strings.HasPrefix(term, "_:") {
+	if !t.wasCloned() && isBlank(term) {
 		// We are in the original table, so note
 		// any blank node label that we see.
 		t.isBlank[term] = true
@@ -571,7 +570,7 @@ func appendOrdered(parts byLengthHash, partSets map[string]map[string]bool) byLe
 	for h, s := range partSets {
 		var p []string
 		for e := range s {
-			if strings.HasPrefix(e, "_:") {
+			if isBlank(e) {
 				p = append(p, e)
 			}
 		}
@@ -631,7 +630,7 @@ func split(statements []*Statement) [][]*Statement {
 	for _, s := range statements {
 		ds.add(s.Subject.Value)
 		ds.add(s.Object.Value)
-		if strings.HasPrefix(s.Subject.Value, "_:") && strings.HasPrefix(s.Object.Value, "_:") {
+		if isBlank(s.Subject.Value) && isBlank(s.Object.Value) {
 			ds.union(ds.find(s.Subject.Value), ds.find(s.Object.Value))
 		}
 	}
@@ -644,9 +643,9 @@ func split(statements []*Statement) [][]*Statement {
 	for _, s := range statements {
 		var t string
 		switch {
-		case strings.HasPrefix(s.Subject.Value, "_:"):
+		case isBlank(s.Subject.Value):
 			t = s.Subject.Value
-		case strings.HasPrefix(s.Object.Value, "_:"):
+		case isBlank(s.Object.Value):
 			t = s.Object.Value
 		default:
 			ground = append(ground, s)
@@ -929,15 +928,15 @@ type relabeledTerm struct {
 }
 
 func (a relabeledTerm) less(b relabeledTerm) bool {
-	aBlank := strings.HasPrefix(a.term.Value, "_:")
-	bBlank := strings.HasPrefix(b.term.Value, "_:")
+	aIsBlank := isBlank(a.term.Value)
+	bIsBlank := isBlank(b.term.Value)
 	switch {
-	case aBlank && bBlank:
+	case aIsBlank && bIsBlank:
 		return bytes.Compare(a.labels[a.term.Value], b.labels[b.term.Value]) < 0
-	case aBlank:
-		return "_:" < unquoteIRI(b.term.Value)
-	case bBlank:
-		return unquoteIRI(a.term.Value) < "_:"
+	case aIsBlank:
+		return blankPrefix < unquoteIRI(b.term.Value)
+	case bIsBlank:
+		return unquoteIRI(a.term.Value) < blankPrefix
 	default:
 		return unquoteIRI(a.term.Value) < unquoteIRI(b.term.Value)
 	}
@@ -951,7 +950,7 @@ func unquoteIRI(s string) string {
 }
 
 func (t relabeledTerm) String() string {
-	if !strings.HasPrefix(t.term.Value, "_:") {
+	if !isBlank(t.term.Value) {
 		return t.term.Value
 	}
 	h, ok := t.labels[t.term.Value]
diff --git a/graph/formats/rdf/rdf.go b/graph/formats/rdf/rdf.go
index 63e4ddc3..e82f767c 100644
--- a/graph/formats/rdf/rdf.go
+++ b/graph/formats/rdf/rdf.go
@@ -75,7 +75,13 @@ func NewBlankTerm(label string) (Term, error) {
 	if err != nil {
 		return Term{}, err
 	}
-	return Term{Value: "_:" + label}, nil
+	return Term{Value: blankPrefix + label}, nil
+}
+
+const blankPrefix = "_:"
+
+func isBlank(s string) bool {
+	return strings.HasPrefix(s, blankPrefix)
 }
 
 // NewIRITerm returns a Term based on the provided IRI which must
@@ -89,6 +95,10 @@ func NewIRITerm(iri string) (Term, error) {
 	return Term{Value: escape("<", iri, ">")}, nil
 }
 
+func isIRI(s string) bool {
+	return strings.HasPrefix(s, "<") && strings.HasSuffix(s, ">")
+}
+
 // NewLiteralTerm returns a Term based on the literal text and an
 // optional qualifier which may either be a "@"-prefixed language
 // tag or a valid IRI. The text will be escaped if necessary and quoted,
diff --git a/graph/formats/rdf/urna.go b/graph/formats/rdf/urna.go
index c51f17d9..a7b6dc5a 100644
--- a/graph/formats/rdf/urna.go
+++ b/graph/formats/rdf/urna.go
@@ -12,7 +12,6 @@ import (
 	"fmt"
 	"hash"
 	"sort"
-	"strings"
 
 	"gonum.org/v1/gonum/stat/combin"
 )
@@ -112,7 +111,7 @@ func (u *urna) relabel(dst, src []*Statement) ([]*Statement, error) {
 			s.Object.Value,
 			s.Label.Value,
 		} {
-			if !strings.HasPrefix(t, "_:") {
+			if !isBlank(t) {
 				continue
 			}
 			for _, e := range u.statementsFor[t] {
@@ -258,7 +257,7 @@ func (u *urna) hashFirstDegreeQuads(b string) string {
 // replaceBlank implements 3.1 of the algorithm described at
 // https://json-ld.github.io/rdf-dataset-canonicalization/spec/index.html#algorithm-1.
 func replaceBlank(b, matching, label string) string {
-	if !strings.HasPrefix(b, "_:") { // 3,1
+	if !isBlank(b) { // 3.1
 		return b
 	}
 	if label != "" { // URGNA2012 modification.
@@ -372,7 +371,7 @@ func (u *urna) hashToRelatedURDNA2015(b string, names *issuer) map[string][]stri
 			s.Object.Value,
 			s.Label.Value,
 		} {
-			if !strings.HasPrefix(term, "_:") || term == b {
+			if !isBlank(term) || term == b {
 				continue
 			}
 
@@ -404,10 +403,10 @@ func (u *urna) hashToRelatedURGNA2012(b string, names *issuer) map[string][]stri
 			pos  byte
 		)
 		switch {
-		case strings.HasPrefix(s.Subject.Value, "_:") && s.Subject.Value != b: // 1.1
+		case isBlank(s.Subject.Value) && s.Subject.Value != b: // 1.1
 			term = s.Subject.Value
 			pos = 'p'
-		case strings.HasPrefix(s.Object.Value, "_:") && s.Object.Value != b: // 1.2
+		case isBlank(s.Object.Value) && s.Object.Value != b: // 1.2
 			term = s.Object.Value
 			pos = 'r'
 		default:


Test patch:
diff --git a/graph/formats/rdf/iso_canonical_test.go b/graph/formats/rdf/iso_canonical_test.go
index 580b1e2c..74b94b13 100644
--- a/graph/formats/rdf/iso_canonical_test.go
+++ b/graph/formats/rdf/iso_canonical_test.go
@@ -14,7 +14,6 @@ import (
 	"path/filepath"
 	"reflect"
 	"sort"
-	"strings"
 	"testing"
 	"text/tabwriter"
 	"time"
@@ -287,7 +286,7 @@ func permuteBlanks(s []*Statement, src rand.Source) ([]*Statement, map[string]st
 
 	var blanks []string
 	for t := range terms {
-		if strings.HasPrefix(t, "_:") {
+		if isBlank(t) {
 			blanks = append(blanks, t)
 		}
 	}
@@ -309,7 +308,7 @@ func hashBlanks(s []*Statement, h hash.Hash) ([]*Statement, map[string]string) {
 			e.Object.Value,
 			e.Label.Value,
 		} {
-			if !strings.HasPrefix(t, "_:") {
+			if !isBlank(t) {
 				continue
 			}
 			h.Reset()
@@ -331,7 +330,7 @@ func mangleFirstIL(s []*Statement, h hash.Hash) ([]*Statement, map[string]string
 			e.Object.Value,
 			e.Label.Value,
 		} {
-			if strings.HasPrefix(t, "_:") {
+			if isBlank(t) {
 				continue
 			}
 			h.Reset()
@@ -354,7 +353,7 @@ func mergeFirst2B(s []*Statement) ([]*Statement, map[string]string) {
 			e.Object.Value,
 			e.Label.Value,
 		} {
-			if !strings.HasPrefix(t, "_:") {
+			if !isBlank(t) {
 				continue
 			}
 			terms[t] = t
@@ -444,7 +443,7 @@ func TestLexicalStatements(t *testing.T) {
 func termsFor(hashes map[string][]byte, hash hash.Hash) map[string]string {
 	terms := make(map[string]string)
 	for t, h := range hashes {
-		if strings.HasPrefix(t, "_:") {
+		if isBlank(t) {
 			terms[t] = fmt.Sprintf("_:%0*x", 2*hash.Size(), h)
 		}
 	}