rdf-isomorphic 0.0.1 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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>