rdf-isomorphic 0.0.1 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (6) hide show
  1. data/AUTHORS +1 -0
  2. data/README +25 -18
  3. data/VERSION +1 -1
  4. data/lib/rdf/isomorphic.rb +140 -102
  5. metadata +58 -23
  6. data/README.md +0 -70
data/AUTHORS CHANGED
@@ -1 +1,2 @@
1
1
  * Ben Lavender <blavender@gmail.com> (Lead developer)
2
+ * Arto Bendiken <arto.bendiken@gmail.com>
data/README CHANGED
@@ -1,7 +1,7 @@
1
1
  # RDF Isomorphism
2
2
 
3
- Provides RDF Isomorphism functionality for RDF.rb RDF::Enumerables. That
4
- includes RDF::Repository, RDF::Graph, query results, and more.
3
+ This is an RDF.rb plugin for RDF Isomorphism functionality for RDF::Enumerables.
4
+ That includes RDF::Repository, RDF::Graph, query results, and more.
5
5
 
6
6
  For more information about RDF.rb, see <http://rdf.rubyforge.org>
7
7
 
@@ -9,42 +9,46 @@ For more information about RDF.rb, see <http://rdf.rubyforge.org>
9
9
 
10
10
  require 'rdf/isomorphic'
11
11
  require 'rdf/ntriples'
12
+
13
+
12
14
  a = RDF::Repository.load './tests/isomorphic/test1/test1-1.nt'
13
15
  a.first
14
- => < RDF::Statement:0xd344c4(<http://example.org/a> <http://example.org/prop> <_:abc> .) >
16
+ # < RDF::Statement:0xd344c4(<http://example.org/a> <http://example.org/prop> <_:abc> .) >
15
17
 
16
18
  b = RDF::Repository.load './tests/isomorphic/test1/test1-2.nt'
17
19
  b.first
18
- => < RDF::Statement:0xd3801a(<http://example.org/a> <http://example.org/prop> <_:testing> .) >
20
+ # < RDF::Statement:0xd3801a(<http://example.org/a> <http://example.org/prop> <_:testing> .) >
19
21
 
20
22
  a.isomorphic_with? b
21
- => true
23
+ # true
24
+
22
25
  a.bijection_to b
23
- => { #<RDF::Node:0xd345a0(_:abc)>=>#<RDF::Node:0xd38574(_:testing)> }
26
+ # { #<RDF::Node:0xd345a0(_:abc)>=>#<RDF::Node:0xd38574(_:testing)> }
27
+
24
28
 
25
29
  ## Algorithm
26
30
 
27
- More discussion on the algorithm used will be in a forthcoming blog post, but
28
- it is very similar to the one described by Jeremy Carroll in
29
- <http://www.hpl.hp.com/techreports/2001/HPL-2001-293.pdf>.
31
+ The algorithm used here is very similar to the one described by Jeremy Carroll
32
+ in <http://www.hpl.hp.com/techreports/2001/HPL-2001-293.pdf>. See
33
+ <http://blog.datagraph.org/2010/03/rdf-isomorphism>.
30
34
 
31
35
  Generally speaking, the Carroll algorithm is a very good fit for RDF graphs. It
32
36
  is a specialization of the naive factorial-time test for graph isomorphism,
33
- wherin non-anonymous RDF data lets us eliminate vast quantities of options well
37
+ wherein non-anonymous RDF data lets us eliminate vast quantities of options well
34
38
  before we try them. Pathological cases, such as graphs which only contain
35
- anonymous resources, will experience poor performance.
39
+ anonymous resources, may experience poor performance.
36
40
 
37
41
  ### Equality
38
42
 
39
- Although it was considered to provide `==` to mean isomorphic, RDF isomorphism is a
40
- factorial-complexity problem and it seemed better to perhaps not overwrite such
41
- a commonly used method for that. But it's really useful for specs in RDF
42
- libraries. Try this:
43
+ Although it was considered to provide `==` to mean isomorphic, RDF isomorphism
44
+ can sometimes be a factorial-complexity problem and it seemed better to perhaps
45
+ not overwrite such a commonly used method for that. But it's really useful for
46
+ specs in RDF libraries. Try this in your tests:
43
47
 
44
48
  require 'rdf/isomorphic'
45
49
  module RDF
46
50
  module Isomorphic
47
- alias_method :==, :isomorphic_with
51
+ alias_method :==, :isomorphic_with?
48
52
  end
49
53
  end
50
54
 
@@ -57,7 +61,8 @@ libraries. Try this:
57
61
  end
58
62
 
59
63
  ### Information
60
- * Author: Ben Lavender <blavender@gmail.com>
64
+ * Author: Ben Lavender <blavender@gmail.com> - <http://bhuga.net>
65
+ * Author: Arto Bendiken <arto.bendiken@gmail.com> - <http://ar.to>
61
66
  * Source: <http://github.com/bhuga/RDF-Isomorphic>
62
67
  * Issues: <http://github.com/bhuga/RDF-Isomorphic/issues>
63
68
 
@@ -67,4 +72,6 @@ libraries. Try this:
67
72
 
68
73
  ### "License"
69
74
 
70
- rdf-isomorphic is free and unemcumbered software in the public domain. For more information, see the accompanying UNLICENSE file or <http://unlicense.org>
75
+ rdf-isomorphic is free and unemcumbered software in the public domain. For
76
+ more information, see the accompanying UNLICENSE file or <http://unlicense.org>
77
+
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.1
1
+ 0.1.1
@@ -16,39 +16,41 @@ module RDF
16
16
  # @return [Boolean]
17
17
  # @example
18
18
  # repository_a.isomorphic_with repository_b #=> true
19
- def isomorphic_with(other)
19
+ def isomorphic_with?(other)
20
20
  !(bijection_to(other).nil?)
21
21
  end
22
22
 
23
- alias_method :isomorphic?, :isomorphic_with
24
- alias_method :isomorphic_with?, :isomorphic_with
23
+ alias_method :isomorphic?, :isomorphic_with?
25
24
 
26
25
 
27
26
  # Returns a hash of RDF::Nodes => RDF::Nodes representing an isomorphic
28
- # bijection of this RDF::Enumerable's blank nodes, or nil if a bijection
29
- # cannot be found.
27
+ # bijection of this RDF::Enumerable's to another RDF::Enumerable's blank
28
+ # nodes, or nil if a bijection cannot be found.
30
29
  # @example
31
30
  # repository_a.bijection_to repository_b
32
31
  # @param other [RDF::Enumerable]
33
32
  # @return [Hash, nil]
34
33
  def bijection_to(other)
35
- named_statements_match = true
36
- each_statement do |statement|
37
- unless statement.has_blank_nodes?
38
- named_statements_match = other.has_statement?(statement)
39
- end
40
- break unless named_statements_match
34
+
35
+ grounded_stmts_match = each_statement.all? do | stmt |
36
+ stmt.has_blank_nodes? || other.has_statement?(stmt)
41
37
  end
42
38
 
43
- unless named_statements_match
44
- nil
45
- else
39
+ if grounded_stmts_match
40
+ # blank_stmts and other_blank_stmts are just a performance
41
+ # consideration--we could just as well pass in self and other. But we
42
+ # will be iterating over this list quite a bit during the algorithm, so
43
+ # we break it down to the parts we're interested in.
46
44
  blank_stmts = find_all { |statement| statement.has_blank_nodes? }
47
45
  other_blank_stmts = other.find_all { |statement| statement.has_blank_nodes? }
48
- nodes = blank_nodes_in(blank_stmts)
49
- other_nodes = blank_nodes_in(other_blank_stmts)
46
+
47
+ nodes = RDF::Isomorphic.blank_nodes_in(blank_stmts)
48
+ other_nodes = RDF::Isomorphic.blank_nodes_in(other_blank_stmts)
50
49
  build_bijection_to blank_stmts, nodes, other_blank_stmts, other_nodes
50
+ else
51
+ nil
51
52
  end
53
+
52
54
  end
53
55
 
54
56
  private
@@ -60,90 +62,76 @@ module RDF
60
62
  # relevant pseudocode.
61
63
  #
62
64
  # Many more comments are in the method itself.
65
+ #
66
+ # @param [RDF::Enumerable] anon_stmts
67
+ # @param [Array] nodes
68
+ # @param [RDF::Enumerable] other_anon_stmts
69
+ # @param [Array] other_nodes
70
+ # @param [Hash] these_grounded_hashes
71
+ # @param [Hash] other_grounded_hashes
72
+ # @return [nil,Hash]
63
73
  # @private
64
- def build_bijection_to(anon_stmts, nodes, other_anon_stmts, other_nodes, hashes = {})
65
-
66
- # Some variable descriptions:
67
- # anon_stmts, other_anon_stmts: All statements from this and other with anonymous nodes
68
- # nodes, other_nodes: All anonymous nodes from this and other
69
- # hashes: hashes of signature of an anonymous nodes' relevant statements. Only contains hashes for grounded nodes.
70
- # potential_hashes: as hashes, but not limited to grounded nodes
71
- # bijection: node => node mapping representing an anonymous node bijection
72
- # bijection_hashes: duplicate of hashes from which we remove hashes to make sure bijection is one to one
73
-
74
- # A grounded node, the difference between the contents of
75
- # potential_hashes and hashes, is a node which has no ungrounded
76
- # anonymous neighbors in a relevant statement.
77
- potential_hashes = {}
78
- [ [anon_stmts,nodes], [other_anon_stmts,other_nodes] ].each do | tuple |
79
- hash_needed = true
80
- while hash_needed
81
- hash_needed = false
82
- tuple.last.each do | node |
83
- unless hashes.member? node
84
- grounded, hash = node_hash_for(node, tuple.first, hashes) unless hashes.member? node
85
- if grounded
86
- hash_needed = true
87
- hashes[node] = hash
88
- end
89
- potential_hashes[node] = hash
90
- end
91
- end
92
- end
93
- end
74
+ def build_bijection_to(anon_stmts, nodes, other_anon_stmts, other_nodes, these_grounded_hashes = {}, other_grounded_hashes = {})
75
+
76
+
77
+ # Create a hash signature of every node, based on the signature of
78
+ # statements it exists in.
79
+ # We also save hashes of nodes that cannot be reliably known; we will use
80
+ # that information to eliminate possible recursion combinations.
81
+ #
82
+ # Any mappings given in the method parameters are considered grounded.
83
+ these_hashes, these_ungrounded_hashes = RDF::Isomorphic.hash_nodes(anon_stmts, nodes, these_grounded_hashes)
84
+ other_hashes, other_ungrounded_hashes = RDF::Isomorphic.hash_nodes(other_anon_stmts, other_nodes, other_grounded_hashes)
94
85
 
95
- # see variables above
96
- bijection = {}
97
- bijection_hashes = hashes.dup
98
86
 
99
- # We are looking for nodes such that
100
- # hashes[node] == hashes[some_other_node]
87
+ # Using the created hashes, map nodes to other_nodes
88
+ bijection = {}
101
89
  nodes.each do | node |
102
- tuple = bijection_hashes.find do |k, v|
103
- (v == bijection_hashes[node]) &&
104
- # eql? instead of include? since RDF.rb coincedentally-same-named identifiers will be ==
105
- other_nodes.any? do | item | k.eql?(item) end
90
+ other_node, other_hash = other_hashes.find do | other_node, other_hash |
91
+ # we need to use eql?, as coincedentally-named bnode identifiers are == in rdf.rb
92
+ these_hashes[node].eql? other_hash
106
93
  end
107
- next unless tuple
108
- target = tuple.first
109
- bijection_hashes.delete target
110
- bijection[node] = target
94
+ next unless other_node
95
+ bijection[node] = other_node
96
+
97
+ # we need to delete , as we don't want two nodes with the same hash
98
+ # to be mapped to the same other_node.
99
+ other_hashes.delete other_node
111
100
  end
112
101
 
113
- # This if is the return statement, believe it or not.
102
+ # bijection is now a mapping of nodes to other_nodes. If all are
103
+ # accounted for on both sides, we have a bijection.
114
104
  #
115
- # First, is the anonymous node mapping 1 to 1?
116
- # If so, we have a bijection and are done
117
- if (bijection.keys.sort == nodes.sort) && (bijection.values.sort == other_nodes.sort)
118
- bijection
119
- # So we've got unhashed nodes that can't be definitively grounded. Make
120
- # a tentative bijection between two with identical ungrounded signatures
121
- # in the graph and recurse.
122
- else
105
+ # If not, we will speculatively mark pairs with matching ungrounded
106
+ # hashes as bijected and recurse.
107
+ unless (bijection.keys.sort == nodes.sort) && (bijection.values.sort == other_nodes.sort)
123
108
  bijection = nil
124
- nodes.each do | node |
109
+ nodes.any? do | node |
110
+
125
111
  # We don't replace grounded nodes' hashes
126
- next if hashes.member? node
127
- bijectable = other_nodes.any? do | other_node |
128
- # We don't replace grounded nodes' hashes
129
- next if hashes.member? other_node
130
- # The ungrounded signature must match for this pair to have a chance.
131
- # If the signature doesn't match, skip it.
132
- next unless potential_hashes[node] == potential_hashes[other_node]
112
+ next if these_hashes.member? node
113
+ other_nodes.any? do | other_node |
114
+
115
+ # We don't replace grounded other_nodes' hashes
116
+ next if other_hashes.member? other_node
117
+
118
+ # The ungrounded signature must match for this to potentially work
119
+ next unless these_ungrounded_hashes[node] == other_ungrounded_hashes[other_node]
120
+
133
121
  hash = Digest::SHA1.hexdigest(node.to_s)
134
- test_hashes = { node => hash, other_node => hash}
135
- bijection = build_bijection_to(anon_stmts, nodes, other_anon_stmts, other_nodes, hashes.merge(test_hashes))
122
+ bijection = build_bijection_to(anon_stmts, nodes, other_anon_stmts, other_nodes, these_hashes.merge( node => hash), other_hashes.merge(other_node => hash))
136
123
  end
137
- break if bijection
124
+ bijection
138
125
  end
139
- bijection
140
126
  end
127
+
128
+ bijection
141
129
  end
142
130
 
131
+ # Blank nodes appearing in given list of statements
143
132
  # @private
144
133
  # @return [RDF::Node]
145
- # Blank nodes appearing in given list of statements
146
- def blank_nodes_in(blank_stmt_list)
134
+ def self.blank_nodes_in(blank_stmt_list)
147
135
  nodes = []
148
136
  blank_stmt_list.each do | statement |
149
137
  nodes << statement.object if statement.object.anonymous?
@@ -151,7 +139,46 @@ module RDF
151
139
  end
152
140
  nodes.uniq
153
141
  end
154
-
142
+
143
+ # Given a set of statements, create a mapping of node => SHA1 for a given
144
+ # set of blank nodes. grounded_hashes is a mapping of node => SHA1 pairs
145
+ # that we will take as a given, and use those to make more specific
146
+ # signatures of other nodes.
147
+ #
148
+ # Returns a tuple of hashes: one of grounded hashes, and one of all
149
+ # hashes. grounded hashes are based on non-blank nodes and grounded blank
150
+ # nodes, and can be used to determine if a node's signature matches
151
+ # another.
152
+ #
153
+ # @param [Array] statements
154
+ # @param [Array] nodes
155
+ # @param [Hash] grounded_hashes
156
+ # @private
157
+ # @return [Hash, Hash]
158
+ def self.hash_nodes(statements, nodes, grounded_hashes)
159
+ hashes = grounded_hashes.dup
160
+ ungrounded_hashes = {}
161
+ hash_needed = true
162
+
163
+ # We may have to go over the list multiple times. If a node is marked as
164
+ # grounded, other nodes can then use it to decide their own state of
165
+ # grounded.
166
+ while hash_needed
167
+ hash_needed = false
168
+ nodes.each do | node |
169
+ unless hashes.member? node
170
+ grounded, hash = node_hash_for(node, statements, hashes)
171
+ if grounded
172
+ hash_needed = true
173
+ hashes[node] = hash
174
+ end
175
+ ungrounded_hashes[node] = hash
176
+ end
177
+ end
178
+ end
179
+ [hashes,ungrounded_hashes]
180
+ end
181
+
155
182
  # Generate a hash for a node based on the signature of the statements it
156
183
  # appears in. Signatures consist of grounded elements in statements
157
184
  # associated with a node, that is, anything but an ungrounded anonymous
@@ -166,53 +193,64 @@ module RDF
166
193
  # for the hash
167
194
  # @private
168
195
  # @return [Boolean, String]
169
- def node_hash_for(node,statements,hashes)
196
+ def self.node_hash_for(node,statements,hashes)
170
197
  statement_signatures = []
171
198
  grounded = true
172
199
  statements.each do | statement |
173
200
  if (statement.object == node) || (statement.subject == node)
174
- statement_signatures << hash_string_for(statement,hashes)
201
+ statement_signatures << hash_string_for(statement,hashes,node)
175
202
  [statement.subject, statement.object].each do | resource |
176
- grounded = false unless grounded(resource, hashes)
203
+ grounded = false unless grounded(resource, hashes) || resource == node
177
204
  end
178
205
  end
179
206
  end
207
+ # Note that we sort the signatures--without a canonical ordering,
208
+ # we might get different hashes for equivalent nodes.
180
209
  [grounded,Digest::SHA1.hexdigest(statement_signatures.sort.to_s)]
181
210
  end
182
211
 
183
- # Provide a string signature for the given statement.
212
+ # Provide a string signature for the given statement, collecting
213
+ # string signatures for grounded node elements.
214
+ # return [String]
184
215
  # @private
185
- def hash_string_for(statement,hashes)
186
- hash = ""
187
- hash << string_for_node(statement.subject,hashes)
188
- hash << statement.predicate.to_s
189
- hash << string_for_node(statement.object,hashes)
190
- hash
216
+ def self.hash_string_for(statement,hashes,node)
217
+ string = ""
218
+ string << string_for_node(statement.subject,hashes,node)
219
+ string << statement.predicate.to_s
220
+ string << string_for_node(statement.object,hashes,node)
221
+ string
191
222
  end
192
223
 
193
224
  # Returns true if a given node is grounded
225
+ # A node is groundd if it is not a blank node or it is included
226
+ # in the given mapping of grounded nodes.
227
+ # @return [Boolean]
194
228
  # @private
195
- def grounded(node, hashes)
229
+ def self.grounded(node, hashes)
196
230
  (!(node.anonymous?)) || (hashes.member? node)
197
231
  end
198
232
 
199
233
  # Provides a string for the given node for use in a string signature
234
+ # Non-anonymous nodes will return their string form. Grounded anonymous
235
+ # nodes will return their hashed form.
236
+ # @return [String]
200
237
  # @private
201
- def string_for_node(node, hashes)
202
- if node.anonymous?
203
- if hashes.member? node
238
+ def self.string_for_node(node, hashes,target)
239
+ case
240
+ when node == target
241
+ "itself"
242
+ when node.anonymous? && hashes.member?(node)
204
243
  hashes[node]
244
+ when node.anonymous?
245
+ "a blank node"
205
246
  else
206
- ""
207
- end
208
- else
209
- node.to_s
247
+ node.to_s
210
248
  end
211
249
  end
212
250
  end
213
251
 
214
252
 
215
-
253
+ # Extend RDF::Enumerables with these functions.
216
254
  module Enumerable
217
255
  include RDF::Isomorphic
218
256
  end
metadata CHANGED
@@ -1,48 +1,80 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rdf-isomorphic
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 1
9
+ version: 0.1.1
5
10
  platform: ruby
6
11
  authors:
7
12
  - Ben Lavender
13
+ - Arto Bendiken
8
14
  autorequire:
9
15
  bindir: bin
10
16
  cert_chain: []
11
17
 
12
- date: 2010-02-01 00:00:00 +01:00
18
+ date: 2010-03-12 00:00:00 +01:00
13
19
  default_executable:
14
20
  dependencies:
15
21
  - !ruby/object:Gem::Dependency
16
22
  name: rdf
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ - 1
31
+ - 0
32
+ version: 0.1.0
17
33
  type: :runtime
18
- version_requirement:
19
- version_requirements: !ruby/object:Gem::Requirement
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: rdf-spec
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
20
39
  requirements:
21
40
  - - ">="
22
41
  - !ruby/object:Gem::Version
23
- version: 0.0.9
24
- version:
42
+ segments:
43
+ - 0
44
+ - 1
45
+ - 0
46
+ version: 0.1.0
47
+ type: :development
48
+ version_requirements: *id002
25
49
  - !ruby/object:Gem::Dependency
26
50
  name: rspec
27
- type: :development
28
- version_requirement:
29
- version_requirements: !ruby/object:Gem::Requirement
51
+ prerelease: false
52
+ requirement: &id003 !ruby/object:Gem::Requirement
30
53
  requirements:
31
54
  - - ">="
32
55
  - !ruby/object:Gem::Version
33
- version: 1.2.9
34
- version:
56
+ segments:
57
+ - 1
58
+ - 3
59
+ - 0
60
+ version: 1.3.0
61
+ type: :development
62
+ version_requirements: *id003
35
63
  - !ruby/object:Gem::Dependency
36
64
  name: yard
37
- type: :development
38
- version_requirement:
39
- version_requirements: !ruby/object:Gem::Requirement
65
+ prerelease: false
66
+ requirement: &id004 !ruby/object:Gem::Requirement
40
67
  requirements:
41
68
  - - ">="
42
69
  - !ruby/object:Gem::Version
43
- version: 0.5.2
44
- version:
45
- description: " rdf-isomorphic provides bijections mapping blank nodes from one\n RDF::Enumerable to another, and thus equivalence (isomorphism) testing.\n"
70
+ segments:
71
+ - 0
72
+ - 5
73
+ - 3
74
+ version: 0.5.3
75
+ type: :development
76
+ version_requirements: *id004
77
+ description: RDF.rb plugin for graph bijections and isomorphic equivalence.
46
78
  email: blavender@gmail.com
47
79
  executables: []
48
80
 
@@ -55,7 +87,6 @@ files:
55
87
  - README
56
88
  - UNLICENSE
57
89
  - VERSION
58
- - README.md
59
90
  - lib/rdf/isomorphic.rb
60
91
  has_rdoc: false
61
92
  homepage: http://rdf.rubyforge.org/
@@ -70,20 +101,24 @@ required_ruby_version: !ruby/object:Gem::Requirement
70
101
  requirements:
71
102
  - - ">="
72
103
  - !ruby/object:Gem::Version
104
+ segments:
105
+ - 1
106
+ - 8
107
+ - 2
73
108
  version: 1.8.2
74
- version:
75
109
  required_rubygems_version: !ruby/object:Gem::Requirement
76
110
  requirements:
77
111
  - - ">="
78
112
  - !ruby/object:Gem::Version
113
+ segments:
114
+ - 0
79
115
  version: "0"
80
- version:
81
116
  requirements: []
82
117
 
83
- rubyforge_project: rdf-isomorphic
84
- rubygems_version: 1.3.5
118
+ rubyforge_project: rdf
119
+ rubygems_version: 1.3.6
85
120
  signing_key:
86
121
  specification_version: 3
87
- summary: Graph bijections and isomorphic equivalence for rdf.rb
122
+ summary: RDF.rb plugin for graph bijections and isomorphic equivalence.
88
123
  test_files: []
89
124
 
data/README.md DELETED
@@ -1,70 +0,0 @@
1
- # RDF Isomorphism
2
-
3
- Provides RDF Isomorphism functionality for RDF.rb RDF::Enumerables. That
4
- includes RDF::Repository, RDF::Graph, query results, and more.
5
-
6
- For more information about RDF.rb, see <http://rdf.rubyforge.org>
7
-
8
- ## Synopsis:
9
-
10
- require 'rdf/isomorphic'
11
- require 'rdf/ntriples'
12
- a = RDF::Repository.load './tests/isomorphic/test1/test1-1.nt'
13
- a.first
14
- => < RDF::Statement:0xd344c4(<http://example.org/a> <http://example.org/prop> <_:abc> .) >
15
-
16
- b = RDF::Repository.load './tests/isomorphic/test1/test1-2.nt'
17
- b.first
18
- => < RDF::Statement:0xd3801a(<http://example.org/a> <http://example.org/prop> <_:testing> .) >
19
-
20
- a.isomorphic_with? b
21
- => true
22
- a.bijection_to b
23
- => { #<RDF::Node:0xd345a0(_:abc)>=>#<RDF::Node:0xd38574(_:testing)> }
24
-
25
- ## Algorithm
26
-
27
- More discussion on the algorithm used will be in a forthcoming blog post, but
28
- it is very similar to the one described by Jeremy Carroll in
29
- <http://www.hpl.hp.com/techreports/2001/HPL-2001-293.pdf>.
30
-
31
- Generally speaking, the Carroll algorithm is a very good fit for RDF graphs. It
32
- is a specialization of the naive factorial-time test for graph isomorphism,
33
- wherin non-anonymous RDF data lets us eliminate vast quantities of options well
34
- before we try them. Pathological cases, such as graphs which only contain
35
- anonymous resources, will experience poor performance.
36
-
37
- ### Equality
38
-
39
- Although it was considered to provide `==` to mean isomorphic, RDF isomorphism is a
40
- factorial-complexity problem and it seemed better to perhaps not overwrite such
41
- a commonly used method for that. But it's really useful for specs in RDF
42
- libraries. Try this:
43
-
44
- require 'rdf/isomorphic'
45
- module RDF
46
- module Isomorphic
47
- alias_method :==, :isomorphic_with
48
- end
49
- end
50
-
51
- describe 'something' do
52
- context 'does' do
53
- it 'should be equal' do
54
- repository_a.should == repository_b
55
- end
56
- end
57
- end
58
-
59
- ### Information
60
- * Author: Ben Lavender <blavender@gmail.com>
61
- * Source: <http://github.com/bhuga/RDF-Isomorphic>
62
- * Issues: <http://github.com/bhuga/RDF-Isomorphic/issues>
63
-
64
- ### See also
65
- * RDF.rb: <http://rdf.rubyforge.org>
66
- * RDF.rb source: <http://github.com/bendiken/rdf>
67
-
68
- ### "License"
69
-
70
- rdf-isomorphic is free and unemcumbered software in the public domain. For more information, see the accompanying UNLICENSE file or <http://unlicense.org>