connected 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.rubocop.yml +38 -27
- data/.ruby-version +1 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +90 -42
- data/README.md +29 -3
- data/Rakefile +5 -5
- data/bin/console +3 -3
- data/connected.gemspec +24 -19
- data/lib/connected/generic_connection.rb +5 -5
- data/lib/connected/generic_graph.rb +8 -0
- data/lib/connected/generic_node.rb +2 -2
- data/lib/connected/graph.rb +73 -0
- data/lib/connected/path.rb +26 -13
- data/lib/connected/version.rb +1 -1
- data/lib/connected/vertex.rb +55 -0
- data/lib/connected.rb +12 -6
- metadata +74 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b64513edd7cde21b6e0bc6f9de81c212fd221fd6e5bbfa288ff740c4c77d1c91
|
4
|
+
data.tar.gz: ea3e881fbe51977b4c9af37ce16ef1b37c401b7496ee1d816a4c393e1531678d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fa98e68ddf6456ab303edc691af58e0b33d149562d7b0cd50ab52289e512bda2dc4fc62bf5320929c5ca1f4b393af9acb9a9b7eede3a16999f32ecf4ec089330
|
7
|
+
data.tar.gz: 8a4a5311a6b928d845859943986a71de53e4b904b4bd83a0585d4f72ac889d4c265c95225019c3955f5a7daccf02300e3e0cc3cbbd49765ea1272a2ee12475a9
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
@@ -1,44 +1,55 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
Metrics/MethodLength:
|
5
|
-
Max: 30
|
1
|
+
require:
|
2
|
+
- rubocop-rake
|
3
|
+
- rubocop-rspec
|
6
4
|
|
7
5
|
Layout/LineLength:
|
8
6
|
Max: 100
|
9
7
|
|
10
|
-
|
11
|
-
|
8
|
+
AllCops:
|
9
|
+
Exclude:
|
10
|
+
- 'db/schema.rb'
|
11
|
+
- 'vendor/**/*'
|
12
|
+
TargetRubyVersion: 3.1
|
13
|
+
NewCops: enable
|
12
14
|
|
13
|
-
Metrics/
|
14
|
-
Max:
|
15
|
+
Metrics/AbcSize:
|
16
|
+
Max: 21
|
15
17
|
|
16
|
-
Metrics/
|
17
|
-
Max:
|
18
|
+
Metrics/BlockLength:
|
19
|
+
Max: 35
|
20
|
+
Exclude:
|
21
|
+
- 'spec/**/*_spec.rb'
|
22
|
+
- 'Rakefile'
|
23
|
+
- '*.gemspec'
|
18
24
|
|
19
|
-
Metrics/
|
25
|
+
Metrics/MethodLength:
|
20
26
|
Max: 25
|
21
27
|
|
22
|
-
Metrics/
|
23
|
-
Max:
|
28
|
+
Metrics/ModuleLength:
|
29
|
+
Max: 160
|
24
30
|
Exclude:
|
25
|
-
|
31
|
+
- 'spec/**/*_spec.rb'
|
26
32
|
|
27
|
-
Metrics/
|
28
|
-
Max:
|
33
|
+
Metrics/ClassLength:
|
34
|
+
Max: 300
|
29
35
|
Exclude:
|
30
|
-
|
31
|
-
- Rakefile
|
32
|
-
- 'spec/**/*_spec.rb'
|
33
|
-
- 'spec/spec_helper.rb'
|
36
|
+
- 'spec/**/*_spec.rb'
|
34
37
|
|
35
|
-
|
36
|
-
|
38
|
+
Gemspec/RequireMFA:
|
39
|
+
Enabled: false
|
37
40
|
|
38
|
-
|
41
|
+
Style/MixinUsage:
|
39
42
|
Exclude:
|
40
|
-
|
43
|
+
- "bin/console"
|
44
|
+
|
45
|
+
Style/StringLiterals:
|
46
|
+
Enabled: true
|
47
|
+
EnforcedStyle: double_quotes
|
48
|
+
|
49
|
+
Style/StringLiteralsInInterpolation:
|
50
|
+
Enabled: true
|
51
|
+
EnforcedStyle: double_quotes
|
41
52
|
|
42
|
-
|
53
|
+
Style/StringConcatenation:
|
43
54
|
Exclude:
|
44
|
-
|
55
|
+
- 'Rakefile'
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
3.1.2
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,54 +1,99 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
connected (0.1.
|
4
|
+
connected (0.1.1)
|
5
|
+
matrix
|
5
6
|
|
6
7
|
GEM
|
7
8
|
remote: https://rubygems.org/
|
8
9
|
specs:
|
9
|
-
ast (2.4.
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
10
|
+
ast (2.4.2)
|
11
|
+
backport (1.2.0)
|
12
|
+
benchmark (0.2.1)
|
13
|
+
diff-lcs (1.5.0)
|
14
|
+
docile (1.4.0)
|
15
|
+
e2mmap (0.1.0)
|
16
|
+
jaro_winkler (1.5.4)
|
17
|
+
json (2.6.3)
|
18
|
+
kramdown (2.4.0)
|
19
|
+
rexml
|
20
|
+
kramdown-parser-gfm (1.1.0)
|
21
|
+
kramdown (~> 2.0)
|
22
|
+
matrix (0.4.2)
|
23
|
+
mini_portile2 (2.8.0)
|
24
|
+
nokogiri (1.13.10)
|
25
|
+
mini_portile2 (~> 2.8.0)
|
26
|
+
racc (~> 1.4)
|
27
|
+
parallel (1.22.1)
|
28
|
+
parser (3.1.3.0)
|
29
|
+
ast (~> 2.4.1)
|
30
|
+
racc (1.6.1)
|
31
|
+
rainbow (3.1.1)
|
32
|
+
rake (13.0.6)
|
33
|
+
regexp_parser (2.6.1)
|
34
|
+
reverse_markdown (2.1.1)
|
35
|
+
nokogiri
|
36
|
+
rexml (3.2.5)
|
37
|
+
rspec (3.12.0)
|
38
|
+
rspec-core (~> 3.12.0)
|
39
|
+
rspec-expectations (~> 3.12.0)
|
40
|
+
rspec-mocks (~> 3.12.0)
|
41
|
+
rspec-core (3.12.0)
|
42
|
+
rspec-support (~> 3.12.0)
|
43
|
+
rspec-expectations (3.12.0)
|
26
44
|
diff-lcs (>= 1.2.0, < 2.0)
|
27
|
-
rspec-support (~> 3.
|
28
|
-
rspec-mocks (3.
|
45
|
+
rspec-support (~> 3.12.0)
|
46
|
+
rspec-mocks (3.12.1)
|
29
47
|
diff-lcs (>= 1.2.0, < 2.0)
|
30
|
-
rspec-support (~> 3.
|
31
|
-
rspec-support (3.
|
32
|
-
rubocop (
|
48
|
+
rspec-support (~> 3.12.0)
|
49
|
+
rspec-support (3.12.0)
|
50
|
+
rubocop (1.40.0)
|
51
|
+
json (~> 2.3)
|
33
52
|
parallel (~> 1.10)
|
34
|
-
parser (>=
|
53
|
+
parser (>= 3.1.2.1)
|
35
54
|
rainbow (>= 2.2.2, < 4.0)
|
36
|
-
regexp_parser (>= 1.
|
37
|
-
rexml
|
38
|
-
rubocop-ast (>= 0.0
|
55
|
+
regexp_parser (>= 1.8, < 3.0)
|
56
|
+
rexml (>= 3.2.5, < 4.0)
|
57
|
+
rubocop-ast (>= 1.23.0, < 2.0)
|
39
58
|
ruby-progressbar (~> 1.7)
|
40
|
-
unicode-display_width (>= 1.4.0, <
|
41
|
-
rubocop-ast (
|
42
|
-
parser (>=
|
43
|
-
|
44
|
-
|
59
|
+
unicode-display_width (>= 1.4.0, < 3.0)
|
60
|
+
rubocop-ast (1.24.0)
|
61
|
+
parser (>= 3.1.1.0)
|
62
|
+
rubocop-rake (0.6.0)
|
63
|
+
rubocop (~> 1.0)
|
64
|
+
rubocop-rspec (2.15.0)
|
65
|
+
rubocop (~> 1.33)
|
66
|
+
ruby-progressbar (1.11.0)
|
67
|
+
simplecov (0.21.2)
|
45
68
|
docile (~> 1.1)
|
46
69
|
simplecov-html (~> 0.11)
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
70
|
+
simplecov_json_formatter (~> 0.1)
|
71
|
+
simplecov-cobertura (2.1.0)
|
72
|
+
rexml
|
73
|
+
simplecov (~> 0.19)
|
74
|
+
simplecov-html (0.12.3)
|
75
|
+
simplecov_json_formatter (0.1.4)
|
76
|
+
solargraph (0.47.2)
|
77
|
+
backport (~> 1.2)
|
78
|
+
benchmark
|
79
|
+
bundler (>= 1.17.2)
|
80
|
+
diff-lcs (~> 1.4)
|
81
|
+
e2mmap
|
82
|
+
jaro_winkler (~> 1.5)
|
83
|
+
kramdown (~> 2.3)
|
84
|
+
kramdown-parser-gfm (~> 1.1)
|
85
|
+
parser (~> 3.0)
|
86
|
+
reverse_markdown (>= 1.0.5, < 3)
|
87
|
+
rubocop (>= 0.52)
|
88
|
+
thor (~> 1.0)
|
89
|
+
tilt (~> 2.0)
|
90
|
+
yard (~> 0.9, >= 0.9.24)
|
91
|
+
thor (1.2.1)
|
92
|
+
tilt (2.0.11)
|
93
|
+
unicode-display_width (2.3.0)
|
94
|
+
webrick (1.7.0)
|
95
|
+
yard (0.9.28)
|
96
|
+
webrick (~> 1.7.0)
|
52
97
|
|
53
98
|
PLATFORMS
|
54
99
|
ruby
|
@@ -57,11 +102,14 @@ DEPENDENCIES
|
|
57
102
|
bundler (~> 2)
|
58
103
|
connected!
|
59
104
|
rake (~> 13)
|
60
|
-
rspec (~> 3.
|
61
|
-
rubocop (~>
|
62
|
-
|
63
|
-
|
105
|
+
rspec (~> 3.10)
|
106
|
+
rubocop (~> 1.40)
|
107
|
+
rubocop-rake (~> 0.6)
|
108
|
+
rubocop-rspec (~> 2.11)
|
109
|
+
simplecov (~> 0.21)
|
110
|
+
simplecov-cobertura (~> 2.1)
|
111
|
+
solargraph (~> 0.45)
|
64
112
|
yard (~> 0.9)
|
65
113
|
|
66
114
|
BUNDLED WITH
|
67
|
-
2.
|
115
|
+
2.3.26
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
# Connected
|
4
4
|
|
5
|
-
_Connected_ aims to be useful for overlaying and
|
5
|
+
_Connected_ aims to be useful for overlaying and searching both directed and undirected graphs. The goal is to provide generic mixins that add graph search capabilities to real code, rather than an academic demonstration of how Dijkstra's algorithm works.
|
6
6
|
|
7
7
|
## Installation
|
8
8
|
|
@@ -24,7 +24,7 @@ Or install it yourself as:
|
|
24
24
|
|
25
25
|
### Simple Uses
|
26
26
|
|
27
|
-
For
|
27
|
+
For simpler use-cases, the classes `Connected::GenericNode`, `Connected::GenericConnection`, and `Connected::Path` will suffice. These can either be subclassed or extended to wrap any real work. First, I'll just demonstrate how they work with an undirected graph (meaning a set of bidirectional connections):
|
28
28
|
|
29
29
|
```ruby
|
30
30
|
require 'connected'
|
@@ -115,7 +115,33 @@ Path.find(from: node_a, to: node_d).to_s
|
|
115
115
|
# => "a -> b -> d"
|
116
116
|
```
|
117
117
|
|
118
|
-
All the above examples are based on connections created using `node.connects_to other_node`, which creates a bi-directional connection. For representing _directed_ graph, you can add `directed: true` to the method which causes it to only create the connection you explicitly described (meaning it won't create the connection back for you).
|
118
|
+
All the above examples are based on connections created using `node.connects_to other_node`, which creates a bi-directional connection. For representing a _directed_ graph, you can add `directed: true` to the method which causes it to only create the connection you explicitly described (meaning it won't create the connection back for you). Under the hood, however, the graph itself is _always_ directed; this is just a convenient way to not automatically make both directions of a connection.
|
119
|
+
|
120
|
+
Speaking of graphs, there is also a `Connected::GenericGraph` class that can be used either explicitly for slightly more complicated scenarios or it can be created dynamically as needed:
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
node_a.subtree # returns a GenericGraph based on all nodes reachable from `node_a`
|
124
|
+
# => #<Connected::GenericGraph:0x00007f...
|
125
|
+
node_a.subtree.radius
|
126
|
+
# => 4
|
127
|
+
node_a.subtree.diameter
|
128
|
+
# => 7
|
129
|
+
node_a.subtree.adjacency_matrix
|
130
|
+
# => Matrix[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 0], [1, 1, 0, 1]]
|
131
|
+
node_a.subtree.complete?
|
132
|
+
# => false
|
133
|
+
```
|
134
|
+
|
135
|
+
This dynamic graph is also used on some methods directly on Edges:
|
136
|
+
|
137
|
+
```ruby
|
138
|
+
node_a.eccentricity
|
139
|
+
# => 7
|
140
|
+
node_a.neighborhood
|
141
|
+
# => #<Connected::GenericGraph:0x00007fd35...
|
142
|
+
node_a.neighborhood.vertices.map(&:name)
|
143
|
+
# => ["b", "c"]
|
144
|
+
```
|
119
145
|
|
120
146
|
### Overlaying on Other Objects
|
121
147
|
|
data/Rakefile
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
require "rspec/core/rake_task"
|
5
|
+
require "rubocop/rake_task"
|
6
|
+
require "yard"
|
7
7
|
|
8
8
|
RSpec::Core::RakeTask.new(:spec)
|
9
9
|
RuboCop::RakeTask.new(:rubocop)
|
10
10
|
YARD::Rake::YardocTask.new do |y|
|
11
11
|
y.options = [
|
12
|
-
|
12
|
+
"--markup", "markdown"
|
13
13
|
]
|
14
14
|
end
|
15
15
|
|
data/bin/console
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require
|
5
|
-
require
|
4
|
+
require "bundler/setup"
|
5
|
+
require "connected"
|
6
6
|
|
7
7
|
# You can add fixtures and/or initialization code here to make experimenting
|
8
8
|
# with your gem easier. You can also use a different console, if you like.
|
@@ -11,5 +11,5 @@ require 'connected'
|
|
11
11
|
# require "pry"
|
12
12
|
# Pry.start
|
13
13
|
|
14
|
-
require
|
14
|
+
require "irb"
|
15
15
|
IRB.start(__FILE__)
|
data/connected.gemspec
CHANGED
@@ -1,35 +1,40 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
3
|
+
require_relative "lib/connected/version"
|
4
4
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
|
-
spec.name =
|
6
|
+
spec.name = "connected"
|
7
7
|
spec.version = Connected::VERSION
|
8
|
-
spec.authors = [
|
9
|
-
spec.email = [
|
8
|
+
spec.authors = ["Jonathan Gnagy"]
|
9
|
+
spec.email = ["jonathan.gnagy@gmail.com"]
|
10
10
|
|
11
|
-
spec.summary =
|
12
|
-
spec.description =
|
13
|
-
|
14
|
-
spec.homepage =
|
11
|
+
spec.summary = "A shortest path first gem"
|
12
|
+
spec.description = "A Ruby object-oriented search library for directed and undirected " \
|
13
|
+
"graphs based loosely on Dijkstra's algorithm"
|
14
|
+
spec.homepage = "https://github.com/jgnagy/connected"
|
15
15
|
|
16
|
-
spec.license =
|
17
|
-
spec.required_ruby_version =
|
16
|
+
spec.license = "MIT"
|
17
|
+
spec.required_ruby_version = "~> 3.1"
|
18
18
|
|
19
19
|
# Specify which files should be added to the gem when it is released.
|
20
20
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
21
21
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
22
22
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
23
23
|
end
|
24
|
-
spec.bindir =
|
24
|
+
spec.bindir = "exe"
|
25
25
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
26
|
-
spec.require_paths = [
|
26
|
+
spec.require_paths = ["lib"]
|
27
27
|
|
28
|
-
spec.
|
29
|
-
|
30
|
-
spec.add_development_dependency
|
31
|
-
spec.add_development_dependency
|
32
|
-
spec.add_development_dependency
|
33
|
-
spec.add_development_dependency
|
34
|
-
spec.add_development_dependency
|
28
|
+
spec.add_dependency "matrix"
|
29
|
+
|
30
|
+
spec.add_development_dependency "bundler", "~> 2"
|
31
|
+
spec.add_development_dependency "rake", "~> 13"
|
32
|
+
spec.add_development_dependency "rspec", "~> 3.10"
|
33
|
+
spec.add_development_dependency "rubocop", "~> 1.40"
|
34
|
+
spec.add_development_dependency "rubocop-rake", "~> 0.6"
|
35
|
+
spec.add_development_dependency "rubocop-rspec", "~> 2.11"
|
36
|
+
spec.add_development_dependency "simplecov", "~> 0.21"
|
37
|
+
spec.add_development_dependency "simplecov-cobertura", "~> 2.1"
|
38
|
+
spec.add_development_dependency "solargraph", "~> 0.45"
|
39
|
+
spec.add_development_dependency "yard", "~> 0.9"
|
35
40
|
end
|
@@ -13,7 +13,7 @@ module Connected
|
|
13
13
|
validate_metric(metric)
|
14
14
|
validate_state(state)
|
15
15
|
|
16
|
-
raise
|
16
|
+
raise "Invalid Connection from and to same Vertex" if from == to
|
17
17
|
|
18
18
|
@from = from
|
19
19
|
@to = to
|
@@ -34,19 +34,19 @@ module Connected
|
|
34
34
|
private
|
35
35
|
|
36
36
|
def validate_from(node)
|
37
|
-
raise
|
37
|
+
raise "Invalid from Node" unless node.is_a?(Vertex)
|
38
38
|
end
|
39
39
|
|
40
40
|
def validate_to(node)
|
41
|
-
raise
|
41
|
+
raise "Invalid to Node" unless node.is_a?(Vertex)
|
42
42
|
end
|
43
43
|
|
44
44
|
def validate_metric(num)
|
45
|
-
raise
|
45
|
+
raise "Invalid metric" unless num.is_a?(Numeric) && num.positive?
|
46
46
|
end
|
47
47
|
|
48
48
|
def validate_state(value)
|
49
|
-
raise
|
49
|
+
raise "Invalid state" unless %i[open closed].include?(value)
|
50
50
|
end
|
51
51
|
end
|
52
52
|
end
|
@@ -18,10 +18,10 @@ module Connected
|
|
18
18
|
return true if neighbors.include?(other)
|
19
19
|
|
20
20
|
connections << GenericConnection.new(
|
21
|
-
from: self, to: other, metric
|
21
|
+
from: self, to: other, metric:, state: state.to_sym
|
22
22
|
)
|
23
23
|
|
24
|
-
other.connects_to(self, metric
|
24
|
+
other.connects_to(self, metric:, state:) unless directed
|
25
25
|
end
|
26
26
|
|
27
27
|
def disconnect_from(other, directed: false)
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Connected
|
4
|
+
# A collection of related vertices and edges
|
5
|
+
module Graph
|
6
|
+
include Comparable
|
7
|
+
|
8
|
+
def initialize(vertices:, edges: nil)
|
9
|
+
@vertices = vertices
|
10
|
+
@edges = edges
|
11
|
+
end
|
12
|
+
|
13
|
+
def adjacency_matrix
|
14
|
+
rows = @vertices.map do |i|
|
15
|
+
@vertices.map do |j|
|
16
|
+
i == j || i.neighbors.include?(j) ? 1 : 0
|
17
|
+
end.to_a
|
18
|
+
end.to_a
|
19
|
+
Matrix.rows(rows)
|
20
|
+
end
|
21
|
+
|
22
|
+
# A "complete graph" is one where all vertices connect to all others
|
23
|
+
def complete?
|
24
|
+
density == 1 || vertices.size == 1
|
25
|
+
end
|
26
|
+
|
27
|
+
# The density is the ratio to a complete graph of the same number of vertices
|
28
|
+
def density
|
29
|
+
return 0 if edges.size.zero? # otherwise we'd divide by 0!
|
30
|
+
|
31
|
+
# Assumes an directed graph for now, therefore e / n(n -1)
|
32
|
+
# is the ratio of edges (e) to edges in a complete graph
|
33
|
+
edges.size.to_f / ((vertices.size * (vertices.size - 1)))
|
34
|
+
end
|
35
|
+
|
36
|
+
# The diameter of a graph is the longest of all shortest paths between all vertices on a graph
|
37
|
+
def diameter
|
38
|
+
longest = nil
|
39
|
+
vertices.each do |v|
|
40
|
+
dist = v.eccentricity
|
41
|
+
longest = dist if longest.nil? || dist > longest
|
42
|
+
end
|
43
|
+
|
44
|
+
longest
|
45
|
+
end
|
46
|
+
|
47
|
+
def edges
|
48
|
+
@edges || vertices.map(&:connections).flatten.uniq
|
49
|
+
end
|
50
|
+
|
51
|
+
def length
|
52
|
+
edges.size
|
53
|
+
end
|
54
|
+
|
55
|
+
def radius
|
56
|
+
shortest = nil
|
57
|
+
vertices.each do |v|
|
58
|
+
ecc = v.eccentricity
|
59
|
+
shortest = ecc if shortest.nil? || ecc < shortest
|
60
|
+
end
|
61
|
+
|
62
|
+
shortest
|
63
|
+
end
|
64
|
+
|
65
|
+
def vertices
|
66
|
+
@vertices
|
67
|
+
end
|
68
|
+
|
69
|
+
def <=>(other)
|
70
|
+
vertices == other.vertices && edges == other.edges
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/lib/connected/path.rb
CHANGED
@@ -46,14 +46,22 @@ module Connected
|
|
46
46
|
nodes.last
|
47
47
|
end
|
48
48
|
|
49
|
-
def to_s(separator =
|
49
|
+
def to_s(separator = " -> ")
|
50
50
|
nodes.map(&:name).join(separator)
|
51
51
|
end
|
52
52
|
|
53
53
|
# rubocop:disable Metrics/AbcSize
|
54
54
|
# rubocop:disable Metrics/CyclomaticComplexity
|
55
55
|
# rubocop:disable Metrics/PerceivedComplexity
|
56
|
-
def self.all(
|
56
|
+
def self.all(
|
57
|
+
from:,
|
58
|
+
to:,
|
59
|
+
include_closed: false,
|
60
|
+
debug: false,
|
61
|
+
suboptimal: false,
|
62
|
+
min_by: :cost,
|
63
|
+
cache: {}
|
64
|
+
)
|
57
65
|
paths = []
|
58
66
|
|
59
67
|
path_queue = from.neighbors.map { |n| new([from, n]) }
|
@@ -62,50 +70,55 @@ module Connected
|
|
62
70
|
this_path = path_queue.pop
|
63
71
|
next unless this_path.open? || include_closed
|
64
72
|
|
65
|
-
|
73
|
+
unless suboptimal
|
74
|
+
next if cache[this_path.to.name]&.<= this_path.send(min_by.to_sym)
|
75
|
+
|
76
|
+
cache[this_path.to.name] = this_path.send(min_by.to_sym)
|
77
|
+
end
|
78
|
+
|
79
|
+
puts "Walking from #{this_path.nodes.map(&:name).join(" to ")}" if debug
|
66
80
|
|
67
81
|
if this_path.to == to
|
68
|
-
puts "Found destination with #{this_path.nodes.map(&:name).join(
|
82
|
+
puts "Found destination with #{this_path.nodes.map(&:name).join(" to ")}" if debug
|
69
83
|
paths << this_path
|
70
84
|
else
|
71
|
-
|
72
|
-
highops = paths.max_by(&:hops)&.hops
|
85
|
+
maxmeasure = paths.map(&min_by.to_sym).max
|
73
86
|
|
74
87
|
this_path.to.neighbors.each do |n|
|
75
88
|
new_path = this_path.branch(n)
|
76
89
|
next unless new_path
|
77
90
|
|
78
|
-
if paths.empty? || new_path.
|
91
|
+
if paths.empty? || new_path.send(min_by.to_sym) <= maxmeasure || suboptimal
|
79
92
|
path_queue.unshift(new_path)
|
80
93
|
elsif debug
|
81
|
-
puts "Skipping #{new_path.nodes.map(&:name).join(
|
94
|
+
puts "Skipping #{new_path.nodes.map(&:name).join(" to ")}"
|
82
95
|
end
|
83
96
|
end
|
84
97
|
end
|
85
98
|
end
|
86
99
|
|
87
100
|
# Return the list of paths, sorted first by cost then by hops
|
88
|
-
paths.sort_by { |p|
|
101
|
+
paths.sort_by { |p| min_by == :cost ? p.cost : p.hops }
|
89
102
|
end
|
90
103
|
# rubocop:enable Metrics/AbcSize
|
91
104
|
# rubocop:enable Metrics/CyclomaticComplexity
|
92
105
|
# rubocop:enable Metrics/PerceivedComplexity
|
93
106
|
|
94
|
-
def self.find(from:, to:, include_closed: false)
|
95
|
-
all(from
|
107
|
+
def self.find(from:, to:, include_closed: false, min_by: :cost, cache: {})
|
108
|
+
all(from:, to:, include_closed:, min_by:, cache:).first
|
96
109
|
end
|
97
110
|
|
98
111
|
private
|
99
112
|
|
100
113
|
def validate_nodes(list)
|
101
114
|
# Want to throw an exception if there are loops
|
102
|
-
raise
|
115
|
+
raise "Invalid Nodes list, duplicates found" unless list.size == list.uniq.size
|
103
116
|
|
104
117
|
list.each_with_index do |item, index|
|
105
118
|
break if index == list.size - 1
|
106
119
|
|
107
120
|
# Each node should connect to the next (no leaves except the end of the list)
|
108
|
-
raise
|
121
|
+
raise "Invalid Nodes list, broken chain" unless item.neighbors.include?(list[index + 1])
|
109
122
|
end
|
110
123
|
|
111
124
|
true
|
data/lib/connected/version.rb
CHANGED
data/lib/connected/vertex.rb
CHANGED
@@ -8,14 +8,69 @@ module Connected
|
|
8
8
|
raise "#connections() MUST be implemented on #{self.class.name}"
|
9
9
|
end
|
10
10
|
|
11
|
+
def edges
|
12
|
+
connections
|
13
|
+
end
|
14
|
+
|
15
|
+
def graph
|
16
|
+
@graph || subtree
|
17
|
+
end
|
18
|
+
|
11
19
|
# A shortcut for retrieving this node's neighbors
|
12
20
|
def neighbors
|
13
21
|
connections.map(&:to).uniq
|
14
22
|
end
|
15
23
|
|
24
|
+
# The neighborhood of a Vertex is a Graph of all neighbors and any connections
|
25
|
+
# they have to each other.
|
26
|
+
# @see https://en.wikipedia.org/wiki/Neighbourhood_(graph_theory)
|
27
|
+
def neighborhood(closed: false)
|
28
|
+
vertices = neighbors
|
29
|
+
vertices << self if closed
|
30
|
+
n_edges = vertices.map(&:connections).flatten.uniq.select { |c| vertices.include?(c.to) }
|
31
|
+
GenericGraph.new(vertices:, edges: n_edges)
|
32
|
+
end
|
33
|
+
|
16
34
|
# Retrieves the Connection object responsible for connecting to a Node
|
17
35
|
def connection_to(other)
|
18
36
|
connections.select { |c| c.to == other }.min_by(&:metric)
|
19
37
|
end
|
38
|
+
|
39
|
+
def eccentricity
|
40
|
+
reachable_vertices.map do |r|
|
41
|
+
self == r ? nil : Path.find(from: self, to: r).cost
|
42
|
+
end.compact.max
|
43
|
+
end
|
44
|
+
|
45
|
+
def reaches?(other)
|
46
|
+
reachable_vertices.include?(other)
|
47
|
+
end
|
48
|
+
|
49
|
+
def reachable_from?(other)
|
50
|
+
other.reachable_vertices.include?(self)
|
51
|
+
end
|
52
|
+
|
53
|
+
def reachable_vertices
|
54
|
+
rverts = neighbors + [self]
|
55
|
+
|
56
|
+
queue = (neighbors.map(&:neighbors).flatten.uniq - neighbors)
|
57
|
+
|
58
|
+
until queue.empty?
|
59
|
+
this_vert = queue.pop
|
60
|
+
next if this_vert == self
|
61
|
+
|
62
|
+
rverts << this_vert
|
63
|
+
this_vert.neighbors.each do |v|
|
64
|
+
queue << v unless rverts.include?(v)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
rverts.uniq
|
69
|
+
end
|
70
|
+
|
71
|
+
# A subtree is a subgraph including all edges and vertices reachable from a vertex
|
72
|
+
def subtree
|
73
|
+
GenericGraph.new(vertices: reachable_vertices)
|
74
|
+
end
|
20
75
|
end
|
21
76
|
end
|
data/lib/connected.rb
CHANGED
@@ -1,8 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
require
|
5
|
-
|
6
|
-
|
7
|
-
require
|
8
|
-
require
|
3
|
+
# Standard Library
|
4
|
+
require "matrix"
|
5
|
+
|
6
|
+
# Internal
|
7
|
+
require "connected/version"
|
8
|
+
require "connected/edge"
|
9
|
+
require "connected/vertex"
|
10
|
+
require "connected/graph"
|
11
|
+
require "connected/generic_graph"
|
12
|
+
require "connected/generic_node"
|
13
|
+
require "connected/generic_connection"
|
14
|
+
require "connected/path"
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: connected
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jonathan Gnagy
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-12-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: matrix
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: bundler
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -44,56 +58,98 @@ dependencies:
|
|
44
58
|
requirements:
|
45
59
|
- - "~>"
|
46
60
|
- !ruby/object:Gem::Version
|
47
|
-
version: '3.
|
61
|
+
version: '3.10'
|
48
62
|
type: :development
|
49
63
|
prerelease: false
|
50
64
|
version_requirements: !ruby/object:Gem::Requirement
|
51
65
|
requirements:
|
52
66
|
- - "~>"
|
53
67
|
- !ruby/object:Gem::Version
|
54
|
-
version: '3.
|
68
|
+
version: '3.10'
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
70
|
name: rubocop
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
58
72
|
requirements:
|
59
73
|
- - "~>"
|
60
74
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
75
|
+
version: '1.40'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.40'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rubocop-rake
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0.6'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0.6'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rubocop-rspec
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '2.11'
|
62
104
|
type: :development
|
63
105
|
prerelease: false
|
64
106
|
version_requirements: !ruby/object:Gem::Requirement
|
65
107
|
requirements:
|
66
108
|
- - "~>"
|
67
109
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
110
|
+
version: '2.11'
|
69
111
|
- !ruby/object:Gem::Dependency
|
70
112
|
name: simplecov
|
71
113
|
requirement: !ruby/object:Gem::Requirement
|
72
114
|
requirements:
|
73
115
|
- - "~>"
|
74
116
|
- !ruby/object:Gem::Version
|
75
|
-
version: '0.
|
117
|
+
version: '0.21'
|
76
118
|
type: :development
|
77
119
|
prerelease: false
|
78
120
|
version_requirements: !ruby/object:Gem::Requirement
|
79
121
|
requirements:
|
80
122
|
- - "~>"
|
81
123
|
- !ruby/object:Gem::Version
|
82
|
-
version: '0.
|
124
|
+
version: '0.21'
|
83
125
|
- !ruby/object:Gem::Dependency
|
84
126
|
name: simplecov-cobertura
|
85
127
|
requirement: !ruby/object:Gem::Requirement
|
86
128
|
requirements:
|
87
129
|
- - "~>"
|
88
130
|
- !ruby/object:Gem::Version
|
89
|
-
version: '1
|
131
|
+
version: '2.1'
|
90
132
|
type: :development
|
91
133
|
prerelease: false
|
92
134
|
version_requirements: !ruby/object:Gem::Requirement
|
93
135
|
requirements:
|
94
136
|
- - "~>"
|
95
137
|
- !ruby/object:Gem::Version
|
96
|
-
version: '1
|
138
|
+
version: '2.1'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: solargraph
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0.45'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0.45'
|
97
153
|
- !ruby/object:Gem::Dependency
|
98
154
|
name: yard
|
99
155
|
requirement: !ruby/object:Gem::Requirement
|
@@ -108,8 +164,8 @@ dependencies:
|
|
108
164
|
- - "~>"
|
109
165
|
- !ruby/object:Gem::Version
|
110
166
|
version: '0.9'
|
111
|
-
description: A Ruby object-oriented
|
112
|
-
loosely on Dijkstra's algorithm
|
167
|
+
description: A Ruby object-oriented search library for directed and undirected graphs
|
168
|
+
based loosely on Dijkstra's algorithm
|
113
169
|
email:
|
114
170
|
- jonathan.gnagy@gmail.com
|
115
171
|
executables: []
|
@@ -120,6 +176,7 @@ files:
|
|
120
176
|
- ".images/logo.png"
|
121
177
|
- ".rspec"
|
122
178
|
- ".rubocop.yml"
|
179
|
+
- ".ruby-version"
|
123
180
|
- ".travis.yml"
|
124
181
|
- Gemfile
|
125
182
|
- Gemfile.lock
|
@@ -132,7 +189,9 @@ files:
|
|
132
189
|
- lib/connected.rb
|
133
190
|
- lib/connected/edge.rb
|
134
191
|
- lib/connected/generic_connection.rb
|
192
|
+
- lib/connected/generic_graph.rb
|
135
193
|
- lib/connected/generic_node.rb
|
194
|
+
- lib/connected/graph.rb
|
136
195
|
- lib/connected/path.rb
|
137
196
|
- lib/connected/version.rb
|
138
197
|
- lib/connected/vertex.rb
|
@@ -146,16 +205,16 @@ require_paths:
|
|
146
205
|
- lib
|
147
206
|
required_ruby_version: !ruby/object:Gem::Requirement
|
148
207
|
requirements:
|
149
|
-
- - "
|
208
|
+
- - "~>"
|
150
209
|
- !ruby/object:Gem::Version
|
151
|
-
version:
|
210
|
+
version: '3.1'
|
152
211
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
153
212
|
requirements:
|
154
213
|
- - ">="
|
155
214
|
- !ruby/object:Gem::Version
|
156
215
|
version: '0'
|
157
216
|
requirements: []
|
158
|
-
rubygems_version: 3.
|
217
|
+
rubygems_version: 3.3.7
|
159
218
|
signing_key:
|
160
219
|
specification_version: 4
|
161
220
|
summary: A shortest path first gem
|