mini_graph 0.1.0
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.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.ruby-version +1 -0
- data/.travis.yml +7 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +22 -0
- data/LICENSE +20 -0
- data/README.md +144 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/mini_graph.rb +36 -0
- data/lib/mini_graph/core/edge.rb +65 -0
- data/lib/mini_graph/core/error.rb +14 -0
- data/lib/mini_graph/core/graph.rb +119 -0
- data/lib/mini_graph/core/search.rb +73 -0
- data/lib/mini_graph/dsl/graph_context.rb +79 -0
- data/lib/mini_graph/dsl/search_context.rb +28 -0
- data/lib/mini_graph/version.rb +3 -0
- data/mini_graph.gemspec +40 -0
- metadata +107 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f2793e4516fdd7679b69f8ab390e290de4cf03e3dcfb08e483d6aee3c09f6b68
|
4
|
+
data.tar.gz: 4399462a5da0cc58eae86e45922a5909cbecf338987f6739b48c67623a62ff82
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 328ccffe20e07a980dfd62052a293d759e8ce3b92bed28d941b09e3bbbff8724dcfcd20d6d2c83684f63a620ee8e0383d4ed9bc8b75b432a4907ab7ba79b7ffb
|
7
|
+
data.tar.gz: 0b85f02cb695a8d7e5ee5e81cd61ad36dc96917fbbabc91f2517672d039f530ff15d83845d884a32e82107981066be6bd3056f77aba94222a0b7f7b24a79cf5e
|
data/.gitignore
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.7.1
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
mini_graph (0.1.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
minitest (5.14.1)
|
10
|
+
rake (12.3.3)
|
11
|
+
|
12
|
+
PLATFORMS
|
13
|
+
ruby
|
14
|
+
|
15
|
+
DEPENDENCIES
|
16
|
+
bundler (~> 2.1)
|
17
|
+
mini_graph!
|
18
|
+
minitest (~> 5.0)
|
19
|
+
rake (~> 12.3.3)
|
20
|
+
|
21
|
+
BUNDLED WITH
|
22
|
+
2.1.4
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2020 Coroutine LLC
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
# MiniGraph
|
2
|
+
|
3
|
+
[](https://travis-ci.org/coroutine/mini_graph)
|
4
|
+
|
5
|
+
Hi! MiniGraph is a Ruby gem who's name says it all: it is mini--in functionality
|
6
|
+
and user experience--and it's a graph implementation. Imagine that!
|
7
|
+
|
8
|
+
MiniGraph provides a small DSL for defining and searching graphs. Behind the scenes,
|
9
|
+
MiniGraph uses a core set of classes to support the DSL. Of course, you are free
|
10
|
+
to directly use these classes for defining and searching graphs, if that better
|
11
|
+
suits your taste.
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
Add this line to your application's Gemfile:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
gem 'mini_graph'
|
19
|
+
```
|
20
|
+
|
21
|
+
And then execute:
|
22
|
+
|
23
|
+
$ bundle
|
24
|
+
|
25
|
+
Or install it yourself as:
|
26
|
+
|
27
|
+
$ gem install mini_graph
|
28
|
+
|
29
|
+
## Usage
|
30
|
+
|
31
|
+
Honestly, there's not a lot to cover here, so we'll keep it brief.
|
32
|
+
|
33
|
+
First, let's have a look at the DSL. The DSL covers two concerns:
|
34
|
+
|
35
|
+
1. Defining graphs: these can be directed or undirected
|
36
|
+
2. Searching graphs: we provide depth-first and breadth-first search functionality, feel free to extend this.
|
37
|
+
|
38
|
+
Now, let's take a closer look at the graph definition DSL.
|
39
|
+
|
40
|
+
### DSL: Defining graphs
|
41
|
+
|
42
|
+
By default, all graphs created using the graph DSL are undirected. Here's an
|
43
|
+
example of a new, undirected graph as created with the DSL:
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
jim = { name: 'Jim', likes: %w(bears quilts buttermilk) }
|
47
|
+
john = { name: 'John', likes: %w(tires mailboxes wombats marshmallows) }
|
48
|
+
jill = { name: 'Jill', likes: %w(wind wrappers socks clay) }
|
49
|
+
jane = { name: 'Jane', likes: %w(cds parachutes corndogs living) }
|
50
|
+
|
51
|
+
# Create a new undirected graph, with the 4 vertices: jim, john, jill, jane.
|
52
|
+
friends = MiniGraph.create(jim, john, jill, jane) do
|
53
|
+
|
54
|
+
# create edges from jim, to john, jill and jane.
|
55
|
+
edge from: jim, to: [john, jill, jane]
|
56
|
+
|
57
|
+
# create edges from john, to jill and jane
|
58
|
+
edge from: john, to: [jill, jane]
|
59
|
+
|
60
|
+
# create an edge from jane to jill
|
61
|
+
edge from: jane, to: jill
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
In the previous example, we create our graph to contain a know set of vertices.
|
66
|
+
Vertices can only be added to a graph during creation. At no other point in the
|
67
|
+
lifecycle of the graph may vertices be added or deleted.
|
68
|
+
|
69
|
+
Also notice the ability to define edges from a vertex to one or more vertices.
|
70
|
+
This is one of the nice little shortcuts provided by the DSL. But wait, there's more!
|
71
|
+
We can also easily define edges from many vertices to many vertices. Take, for
|
72
|
+
example, this complete, undirected graph:
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
poker_friends = MiniGraph.create(jim, john, jill, jane) do
|
76
|
+
edge from: [jim, john, jill, jane], to: [jim, john, jill, jane]
|
77
|
+
end
|
78
|
+
```
|
79
|
+
|
80
|
+
Dang! That was easy! Ok, enough feigning astonishment!
|
81
|
+
|
82
|
+
We've now seen undirected graphs, as defined using the MiniGraph DSL, let's
|
83
|
+
now take a look at directed graphs. Let's use our first example, but this time
|
84
|
+
we'll make the edges directed. Here we go!
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
# Create a new undirected graph, with the 4 vertices: jim, john, jill, jane.
|
88
|
+
friends = MiniGraph.create(jim, john, jill, jane) do
|
89
|
+
|
90
|
+
# Make this a directed graph.
|
91
|
+
directed!
|
92
|
+
|
93
|
+
# create edges from jim, to john, jill and jane.
|
94
|
+
edge from: jim, to: [john, jill, jane]
|
95
|
+
|
96
|
+
# create edges from john, to jill and jane
|
97
|
+
edge from: john, to: [jill, jane]
|
98
|
+
|
99
|
+
# create an edge from jane to jill
|
100
|
+
edge from: jane, to: jill
|
101
|
+
end
|
102
|
+
```
|
103
|
+
|
104
|
+
Did you see that?! We just added `directed!`, and that was it! Smooth and declarative.
|
105
|
+
|
106
|
+
Did I hear you say you wanted the same declarative syntax for undirected graphs? Boom!
|
107
|
+
Just use the `undirected!` declaration, and you're set!
|
108
|
+
|
109
|
+
Now, let's move on to searching.
|
110
|
+
|
111
|
+
### DSL: Searching graphs
|
112
|
+
|
113
|
+
Now that we know how to define graphs, we should probably be able to find stuff
|
114
|
+
in them, right?
|
115
|
+
|
116
|
+
The MiniGraph search DSL is pretty minimal. You wanna do a depth-first search?
|
117
|
+
Check it:
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
# Searching our previously defined friends graph, and starting our search at the 'john' vertex:
|
121
|
+
results = MiniGraph.dfs friends, start_at: john
|
122
|
+
|
123
|
+
# results implements Enumerable
|
124
|
+
results.entries # => [john, jim, jill, jane]
|
125
|
+
```
|
126
|
+
|
127
|
+
...and here's breadth-first search on the same graph:
|
128
|
+
|
129
|
+
```ruby
|
130
|
+
results = MiniGraph.bfs friends, start_at: jill
|
131
|
+
results.entries # => [jill, jim, john, jane]
|
132
|
+
```
|
133
|
+
|
134
|
+
That's it for the DSL! Not much to it, right? You might even call it _Mini_.
|
135
|
+
|
136
|
+
## Development
|
137
|
+
|
138
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
139
|
+
|
140
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
141
|
+
|
142
|
+
## Contributing
|
143
|
+
|
144
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/coroutine/mini_graph.
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "mini_graph"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/lib/mini_graph.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require "mini_graph/version"
|
2
|
+
require "mini_graph/dsl/graph_context"
|
3
|
+
require "mini_graph/dsl/search_context"
|
4
|
+
|
5
|
+
module MiniGraph
|
6
|
+
|
7
|
+
# Used to construct a new graph.
|
8
|
+
#
|
9
|
+
# Example usage:
|
10
|
+
#
|
11
|
+
# jim = Person.new
|
12
|
+
# john = Person.new
|
13
|
+
# jill = Person.new
|
14
|
+
# jane = Person.new
|
15
|
+
#
|
16
|
+
# friends = MiniGraph.create(jim, john, jill, jane) do
|
17
|
+
# # Creates a directed graph
|
18
|
+
# directed!
|
19
|
+
#
|
20
|
+
# edge from: jim, to: [john, jill, jane]
|
21
|
+
# edge from: john, to: [jill, jane]
|
22
|
+
# edge from: jane, to: jill
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
def self.create(*vertices, &block)
|
26
|
+
MiniGraph::DSL::GraphContext.evaluate(*vertices, &block)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.dfs(graph, start_at: nil)
|
30
|
+
MiniGraph::DSL::SearchContext.evaluate(graph, start_at, algorithm: :dfs)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.bfs(graph, start_at: nil)
|
34
|
+
MiniGraph::DSL::SearchContext.evaluate(graph, start_at, algorithm: :bfs)
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'mini_graph/core/error'
|
2
|
+
|
3
|
+
module MiniGraph
|
4
|
+
module Core
|
5
|
+
module Edge
|
6
|
+
|
7
|
+
# -----------------------------------------------------
|
8
|
+
# Directed Edge
|
9
|
+
# -----------------------------------------------------
|
10
|
+
|
11
|
+
class Directed
|
12
|
+
attr_reader :origin, :destination
|
13
|
+
|
14
|
+
def initialize(origin, destination)
|
15
|
+
@origin = origin
|
16
|
+
@destination = destination
|
17
|
+
end
|
18
|
+
|
19
|
+
def eql?(edge)
|
20
|
+
origin == edge.origin && destination == edge.destination
|
21
|
+
end
|
22
|
+
|
23
|
+
# cannot use alias or alias_method for this, as subclasses (e.g. UndirectedEdge)
|
24
|
+
# do not behave as expected. We'll be explicit.
|
25
|
+
def ==(edge)
|
26
|
+
eql?(edge)
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
"(#{origin} -> #{destination})"
|
31
|
+
end
|
32
|
+
|
33
|
+
# Another case of not using alias to allow subclasses to behave as expected.
|
34
|
+
def inspect
|
35
|
+
to_s
|
36
|
+
end
|
37
|
+
|
38
|
+
# Reverses the direction of the edge
|
39
|
+
def reverse
|
40
|
+
self.class.new(destination, origin)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Indicates a self-looping edge; i.e., an edge that connects a vertex to
|
44
|
+
# itself.
|
45
|
+
def self_loop?
|
46
|
+
origin == destination
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# -----------------------------------------------------
|
51
|
+
# Undirected
|
52
|
+
# -----------------------------------------------------
|
53
|
+
|
54
|
+
class Undirected < Directed
|
55
|
+
def eql?(edge)
|
56
|
+
super || (origin == edge.destination && destination == edge.origin)
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_s
|
60
|
+
"(#{origin} <-> #{destination})"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module MiniGraph
|
2
|
+
module Core
|
3
|
+
module Error
|
4
|
+
class GraphError < StandardError; end
|
5
|
+
|
6
|
+
class SelfLoopError < GraphError; end
|
7
|
+
class ParallelEdgeError < GraphError; end
|
8
|
+
class InvalidIndexError < GraphError; end
|
9
|
+
class InvalidEdgeError < GraphError; end
|
10
|
+
class InvalidEdgeTypeError < GraphError; end
|
11
|
+
class InvalidSearchAlgorithmError < GraphError; end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'mini_graph/core/edge'
|
3
|
+
require 'mini_graph/core/error'
|
4
|
+
|
5
|
+
module MiniGraph
|
6
|
+
module Core
|
7
|
+
class Graph
|
8
|
+
|
9
|
+
include Enumerable
|
10
|
+
extend Forwardable
|
11
|
+
|
12
|
+
def_delegators :@vertices, :each, :size, :[]
|
13
|
+
|
14
|
+
attr_reader :vertices
|
15
|
+
|
16
|
+
# Initialize a directed or undirected graph with a list of vertices
|
17
|
+
def initialize(vertices, directed: false)
|
18
|
+
@directed = !!directed
|
19
|
+
@vertices = [vertices].flatten.freeze
|
20
|
+
@edges = []
|
21
|
+
end
|
22
|
+
|
23
|
+
def edge_class
|
24
|
+
if directed?
|
25
|
+
MiniGraph::Core::Edge::Directed
|
26
|
+
else
|
27
|
+
MiniGraph::Core::Edge::Undirected
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Adds an edge from the vertex at origin_index to the vertex at
|
32
|
+
# destination_index
|
33
|
+
def add_edge(*args)
|
34
|
+
edge = args_to_edge(args)
|
35
|
+
|
36
|
+
# origin must reference a valid index within the graph
|
37
|
+
if edge.origin >= size
|
38
|
+
raise ::MiniGraph::Core::Error::InvalidIndexError,
|
39
|
+
'origin_index invalid'
|
40
|
+
end
|
41
|
+
|
42
|
+
# destination must reference a valid index within the graph
|
43
|
+
if edge.destination >= size
|
44
|
+
raise ::MiniGraph::Core::Error::InvalidIndexError,
|
45
|
+
'destination_index invalid'
|
46
|
+
end
|
47
|
+
|
48
|
+
edges << edge
|
49
|
+
end
|
50
|
+
|
51
|
+
def directed?
|
52
|
+
@directed
|
53
|
+
end
|
54
|
+
|
55
|
+
def undirected?
|
56
|
+
!directed?
|
57
|
+
end
|
58
|
+
|
59
|
+
# Returns an array of vertex indices that have an inbound edge from the vertex
|
60
|
+
# at the supplied index
|
61
|
+
def adjacent_vertices(index)
|
62
|
+
edges.reduce([]) do |adj, edge|
|
63
|
+
adj << edge.destination if edge.origin == index
|
64
|
+
adj << edge.origin if undirected? && edge.destination == index
|
65
|
+
adj
|
66
|
+
end
|
67
|
+
.uniq
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns a reversed copy of the digraph.
|
71
|
+
def reverse
|
72
|
+
self.class.new(vertices, directed: @directed).tap do |dg|
|
73
|
+
dg.edges = edges.map(&:reverse)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def to_s
|
78
|
+
edges.join
|
79
|
+
end
|
80
|
+
|
81
|
+
# -----------------------------------------------------
|
82
|
+
# Protected
|
83
|
+
# -----------------------------------------------------
|
84
|
+
|
85
|
+
protected
|
86
|
+
|
87
|
+
def edges
|
88
|
+
@edges
|
89
|
+
end
|
90
|
+
|
91
|
+
def edges=(edges)
|
92
|
+
@edges = edges
|
93
|
+
end
|
94
|
+
|
95
|
+
# -----------------------------------------------------
|
96
|
+
# Private
|
97
|
+
# -----------------------------------------------------
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def args_to_edge(args)
|
102
|
+
case args.length
|
103
|
+
when 1
|
104
|
+
args.first.tap do |edge|
|
105
|
+
if edge.class != edge_class
|
106
|
+
raise MiniGraph::Core::Error::InvalidEdgeTypeError,
|
107
|
+
"edge must be instance of #{edge_class.name}"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
when 2
|
111
|
+
edge_class.new(*args)
|
112
|
+
else
|
113
|
+
raise ArgumentError,
|
114
|
+
"wrong number of arguments. #{args.length} args were provided. 1 or 2 arguments were expected."
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module MiniGraph
|
2
|
+
module Core
|
3
|
+
module Search
|
4
|
+
|
5
|
+
# -----------------------------------------------------
|
6
|
+
# Base Search Implementation
|
7
|
+
# -----------------------------------------------------
|
8
|
+
|
9
|
+
class Base
|
10
|
+
include Enumerable
|
11
|
+
|
12
|
+
attr_reader :graph, :vertex_index
|
13
|
+
|
14
|
+
def initialize(graph, vertex_index)
|
15
|
+
@graph = graph
|
16
|
+
@vertex_index = vertex_index
|
17
|
+
end
|
18
|
+
|
19
|
+
def each
|
20
|
+
return enum_for(:each) unless block_given?
|
21
|
+
|
22
|
+
visit(vertex_index) do |vi|
|
23
|
+
yield graph[vi]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def visit(index, visited=Array.new(graph.size, false), &block)
|
28
|
+
raise NotImplementedError, "#visit must be implemented"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# -----------------------------------------------------
|
33
|
+
# Depth-First Search
|
34
|
+
# -----------------------------------------------------
|
35
|
+
|
36
|
+
class DFS < Base
|
37
|
+
def visit(index, visited=Array.new(graph.size, false), &block)
|
38
|
+
visited[index] = true
|
39
|
+
yield index
|
40
|
+
|
41
|
+
graph.adjacent_vertices(index).each do |vi|
|
42
|
+
visit(vi, visited, &block) unless visited[vi]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# -----------------------------------------------------
|
48
|
+
# Breadth-First Search
|
49
|
+
# -----------------------------------------------------
|
50
|
+
|
51
|
+
class BFS < Base
|
52
|
+
def visit(index, visited=Array.new(graph.size, false), &block)
|
53
|
+
queue = []
|
54
|
+
visited[index] = true
|
55
|
+
|
56
|
+
queue.push(index)
|
57
|
+
|
58
|
+
while !queue.empty?
|
59
|
+
next_index = queue.shift
|
60
|
+
yield next_index
|
61
|
+
|
62
|
+
graph.adjacent_vertices(next_index).each do |vi|
|
63
|
+
unless visited[vi]
|
64
|
+
visited[vi] = true
|
65
|
+
queue.push(vi)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'mini_graph/core/edge'
|
2
|
+
require 'mini_graph/core/graph'
|
3
|
+
|
4
|
+
module MiniGraph
|
5
|
+
module DSL
|
6
|
+
class GraphContext
|
7
|
+
def self.evaluate(*vertices, &block)
|
8
|
+
unless block_given?
|
9
|
+
raise ArgumentError, "cannot call .create without a block"
|
10
|
+
end
|
11
|
+
|
12
|
+
graph_context = new(vertices)
|
13
|
+
graph_context.instance_eval(&block)
|
14
|
+
graph_context.resolve
|
15
|
+
end
|
16
|
+
|
17
|
+
# -----------------------------------------------------
|
18
|
+
# Public Methods
|
19
|
+
# -----------------------------------------------------
|
20
|
+
|
21
|
+
def resolve
|
22
|
+
if @edges.any? { |edge| edge.any?(&:nil?) }
|
23
|
+
raise MiniGraph::Core::Error::InvalidEdgeError,
|
24
|
+
'One or more invalid edges were specified'
|
25
|
+
end
|
26
|
+
|
27
|
+
MiniGraph::Core::Graph.new(@vertices, directed: @directed).tap do |g|
|
28
|
+
@edges.each do |edge|
|
29
|
+
g.add_edge(*edge)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# -----------------------------------------------------
|
35
|
+
# Protected Methods
|
36
|
+
# -----------------------------------------------------
|
37
|
+
|
38
|
+
protected
|
39
|
+
|
40
|
+
attr_reader :vertex_index
|
41
|
+
|
42
|
+
def initialize(vertices)
|
43
|
+
@vertices = [vertices].flatten
|
44
|
+
@vertex_index = @vertices.map.with_index { |v, i| [v, i] }.to_h
|
45
|
+
@directed = false
|
46
|
+
@edges = []
|
47
|
+
end
|
48
|
+
|
49
|
+
def undirected!
|
50
|
+
@directed = false
|
51
|
+
end
|
52
|
+
|
53
|
+
def directed!
|
54
|
+
@directed = true
|
55
|
+
end
|
56
|
+
|
57
|
+
def edge(from:, to:)
|
58
|
+
from_indices = vertices_to_indices(from)
|
59
|
+
to_indices = vertices_to_indices(to)
|
60
|
+
|
61
|
+
@edges += from_indices.flat_map do |origin|
|
62
|
+
to_indices.map do |destination|
|
63
|
+
[origin, destination]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# -----------------------------------------------------
|
69
|
+
# Private Methods
|
70
|
+
# -----------------------------------------------------
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def vertices_to_indices(vertices)
|
75
|
+
[vertices].flatten.map { |v| vertex_index[v] }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'mini_graph/core/error'
|
2
|
+
require 'mini_graph/core/search'
|
3
|
+
|
4
|
+
module MiniGraph
|
5
|
+
module DSL
|
6
|
+
class SearchContext
|
7
|
+
|
8
|
+
ALGORITHM_IMPL = {
|
9
|
+
dfs: MiniGraph::Core::Search::DFS,
|
10
|
+
bfs: MiniGraph::Core::Search::BFS
|
11
|
+
}
|
12
|
+
|
13
|
+
def self.evaluate(graph, start_at, algorithm:)
|
14
|
+
algorithm_impl = ALGORITHM_IMPL[algorithm]
|
15
|
+
|
16
|
+
unless algorithm_impl
|
17
|
+
raise MiniGraph::Core::Error::InvalidSearchAlgorithmError,
|
18
|
+
"An unknown algorithm was provided to SearchContext: #{algorithm}"
|
19
|
+
end
|
20
|
+
|
21
|
+
vertex_index = graph.vertices.map.with_index { |v, i| [v, i] }.to_h
|
22
|
+
start_index = start_at.nil? ? 0 : vertex_index[start_at]
|
23
|
+
|
24
|
+
algorithm_impl.new(graph, start_index)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/mini_graph.gemspec
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "mini_graph/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "mini_graph"
|
8
|
+
spec.version = MiniGraph::VERSION
|
9
|
+
spec.authors = ["Tim Lowrimore"]
|
10
|
+
spec.email = ["tlowrimore@coroutine.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{A mini graph library + DSL}
|
13
|
+
spec.description = %q{A mini graph library + DSL}
|
14
|
+
spec.homepage = "https://github.com/coroutine/mini_graph"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
18
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
19
|
+
if spec.respond_to?(:metadata)
|
20
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
21
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
22
|
+
spec.metadata["changelog_uri"] = spec.homepage
|
23
|
+
else
|
24
|
+
raise "RubyGems 2.0 or newer is required to protect against " \
|
25
|
+
"public gem pushes."
|
26
|
+
end
|
27
|
+
|
28
|
+
# Specify which files should be added to the gem when it is released.
|
29
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
30
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
31
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
32
|
+
end
|
33
|
+
spec.bindir = "exe"
|
34
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
35
|
+
spec.require_paths = ["lib"]
|
36
|
+
|
37
|
+
spec.add_development_dependency "bundler", "~> 2.1"
|
38
|
+
spec.add_development_dependency "rake", "~> 12.3.3"
|
39
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
40
|
+
end
|
metadata
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mini_graph
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tim Lowrimore
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-06-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.1'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 12.3.3
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 12.3.3
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.0'
|
55
|
+
description: A mini graph library + DSL
|
56
|
+
email:
|
57
|
+
- tlowrimore@coroutine.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- ".ruby-version"
|
64
|
+
- ".travis.yml"
|
65
|
+
- Gemfile
|
66
|
+
- Gemfile.lock
|
67
|
+
- LICENSE
|
68
|
+
- README.md
|
69
|
+
- Rakefile
|
70
|
+
- bin/console
|
71
|
+
- bin/setup
|
72
|
+
- lib/mini_graph.rb
|
73
|
+
- lib/mini_graph/core/edge.rb
|
74
|
+
- lib/mini_graph/core/error.rb
|
75
|
+
- lib/mini_graph/core/graph.rb
|
76
|
+
- lib/mini_graph/core/search.rb
|
77
|
+
- lib/mini_graph/dsl/graph_context.rb
|
78
|
+
- lib/mini_graph/dsl/search_context.rb
|
79
|
+
- lib/mini_graph/version.rb
|
80
|
+
- mini_graph.gemspec
|
81
|
+
homepage: https://github.com/coroutine/mini_graph
|
82
|
+
licenses:
|
83
|
+
- MIT
|
84
|
+
metadata:
|
85
|
+
homepage_uri: https://github.com/coroutine/mini_graph
|
86
|
+
source_code_uri: https://github.com/coroutine/mini_graph
|
87
|
+
changelog_uri: https://github.com/coroutine/mini_graph
|
88
|
+
post_install_message:
|
89
|
+
rdoc_options: []
|
90
|
+
require_paths:
|
91
|
+
- lib
|
92
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
requirements: []
|
103
|
+
rubygems_version: 3.1.2
|
104
|
+
signing_key:
|
105
|
+
specification_version: 4
|
106
|
+
summary: A mini graph library + DSL
|
107
|
+
test_files: []
|