23 | Welcome to GitHub Pages.
24 | 25 |This automatic page generator is the easiest way to create beautiful pages for all of your projects. Author your page content here using GitHub Flavored Markdown, select a template crafted by a designer, and publish. After your page is generated, you can check out the new gh-pages branch locally. If you’re using GitHub Desktop, simply sync your repository and you’ll see the new branch.
28 | Designer Templates
29 | 30 |We’ve crafted some handsome templates for you to use. Go ahead and click 'Continue to layouts' to browse through them. You can easily go back to edit your page before publishing. After publishing your page, you can revisit the page generator and switch to another theme. Your Page content will be preserved.
31 | 32 |33 | Creating pages manually
34 | 35 |If you prefer to not use the automatic generator, push a branch named gh-pages to your repository to create a page manually. In addition to supporting regular HTML content, GitHub Pages support Jekyll, a simple, blog aware static site generator. Jekyll makes it easy to create site-wide headers and footers without having to copy them across every page. It also offers intelligent blog support and other advanced templating features.
38 | Authors and Contributors
39 | 40 |You can @mention a GitHub username to generate a link to their profile. The resulting <a> element will link to the contributor’s GitHub Profile. For example: In 2007, Chris Wanstrath (@defunkt), PJ Hyett (@pjhyett), and Tom Preston-Werner (@mojombo) founded GitHub.
43 | Support or Contact
44 | 45 |Having trouble with Pages? Check out our documentation or contact support and we’ll help you sort it out.
46 | 47 | 48 | 49 | 54 | 55 |
132 |
133 | ```ruby
134 | adjacency_matrix = <<-matrix
135 | 0 7 9 0 0 14
136 | 7 0 10 15 0 0
137 | 9 10 0 11 0 2
138 | 0 15 11 0 6 0
139 | 0 0 0 6 0 9
140 | 14 0 2 0 9 0
141 | matrix
142 |
143 | graph = Abuelo::Graph.new(adjacency_matrix: adjacency_matrix)
144 | start_node = graph.find_node_by_name('node 1')
145 | node_5 = graph.find_node_by_name('node 5')
146 |
147 | dijkstra = Abuelo::Algorithms::Dijkstra.new(graph, start_node)
148 | dijkstra.shortest_distance_to(node_5) # => 20
149 | dijkstra.shortest_path_to(node_5).map(&:to_s) # => ['node 1', 'node 3', 'node 6', 'node 5']
150 | ```
151 |
152 | ## Documentation
153 | [YARD](http://yardoc.org) documentation is available at [rubydoc](http://www.rubydoc.info/github/dirkholzapfel/abuelo).
154 |
155 | ## Future
156 | * Implement graph algorithms
157 | * [Kruskal's algorithm](https://en.wikipedia.org/wiki/Kruskal%27s_algorithm)
158 | * [Prim's algorithm](https://en.wikipedia.org/wiki/Prim%27s_algorithm)
159 | * Implement visualization of graph
160 | * Add priority queue to Dijkstra's algorithm implementation
161 |
162 | ## Installation
163 | Abuelo is a gem which you can install with:
164 | ```
165 | gem install abuelo
166 | ```
167 |
168 | In Rails 3+, add the following to your ```Gemfile```:
169 | ```
170 | gem 'abuelo'
171 | ```
172 |
173 | ## Credits
174 | Dirk Holzapfel ([@cachezero](https://twitter.com/cachezero))
175 |
176 | [cachezero.net](http://cachezero.net)
177 |
178 | [Abuelo](http://www.ronabuelopanama.com)
179 |
180 | ### Contributors
181 | [dirkholzapfel](https://github.com/dirkholzapfel),
182 | [mbirman](https://github.com/mbirman),
183 | [sergey-kintsel](https://github.com/sergey-kintsel)
184 |
--------------------------------------------------------------------------------
/stylesheets/styles.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'Noto Sans';
3 | font-weight: 400;
4 | font-style: normal;
5 | src: url('../fonts/Noto-Sans-regular/Noto-Sans-regular.eot');
6 | src: url('../fonts/Noto-Sans-regular/Noto-Sans-regular.eot?#iefix') format('embedded-opentype'),
7 | local('Noto Sans'),
8 | local('Noto-Sans-regular'),
9 | url('../fonts/Noto-Sans-regular/Noto-Sans-regular.woff2') format('woff2'),
10 | url('../fonts/Noto-Sans-regular/Noto-Sans-regular.woff') format('woff'),
11 | url('../fonts/Noto-Sans-regular/Noto-Sans-regular.ttf') format('truetype'),
12 | url('../fonts/Noto-Sans-regular/Noto-Sans-regular.svg#NotoSans') format('svg');
13 | }
14 |
15 | @font-face {
16 | font-family: 'Noto Sans';
17 | font-weight: 700;
18 | font-style: normal;
19 | src: url('../fonts/Noto-Sans-700/Noto-Sans-700.eot');
20 | src: url('../fonts/Noto-Sans-700/Noto-Sans-700.eot?#iefix') format('embedded-opentype'),
21 | local('Noto Sans Bold'),
22 | local('Noto-Sans-700'),
23 | url('../fonts/Noto-Sans-700/Noto-Sans-700.woff2') format('woff2'),
24 | url('../fonts/Noto-Sans-700/Noto-Sans-700.woff') format('woff'),
25 | url('../fonts/Noto-Sans-700/Noto-Sans-700.ttf') format('truetype'),
26 | url('../fonts/Noto-Sans-700/Noto-Sans-700.svg#NotoSans') format('svg');
27 | }
28 |
29 | @font-face {
30 | font-family: 'Noto Sans';
31 | font-weight: 400;
32 | font-style: italic;
33 | src: url('../fonts/Noto-Sans-italic/Noto-Sans-italic.eot');
34 | src: url('../fonts/Noto-Sans-italic/Noto-Sans-italic.eot?#iefix') format('embedded-opentype'),
35 | local('Noto Sans Italic'),
36 | local('Noto-Sans-italic'),
37 | url('../fonts/Noto-Sans-italic/Noto-Sans-italic.woff2') format('woff2'),
38 | url('../fonts/Noto-Sans-italic/Noto-Sans-italic.woff') format('woff'),
39 | url('../fonts/Noto-Sans-italic/Noto-Sans-italic.ttf') format('truetype'),
40 | url('../fonts/Noto-Sans-italic/Noto-Sans-italic.svg#NotoSans') format('svg');
41 | }
42 |
43 | @font-face {
44 | font-family: 'Noto Sans';
45 | font-weight: 700;
46 | font-style: italic;
47 | src: url('../fonts/Noto-Sans-700italic/Noto-Sans-700italic.eot');
48 | src: url('../fonts/Noto-Sans-700italic/Noto-Sans-700italic.eot?#iefix') format('embedded-opentype'),
49 | local('Noto Sans Bold Italic'),
50 | local('Noto-Sans-700italic'),
51 | url('../fonts/Noto-Sans-700italic/Noto-Sans-700italic.woff2') format('woff2'),
52 | url('../fonts/Noto-Sans-700italic/Noto-Sans-700italic.woff') format('woff'),
53 | url('../fonts/Noto-Sans-700italic/Noto-Sans-700italic.ttf') format('truetype'),
54 | url('../fonts/Noto-Sans-700italic/Noto-Sans-700italic.svg#NotoSans') format('svg');
55 | }
56 |
57 | body {
58 | background-color: #fff;
59 | padding:50px;
60 | font: 14px/1.5 "Noto Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
61 | color:#727272;
62 | font-weight:400;
63 | }
64 |
65 | h1, h2, h3, h4, h5, h6 {
66 | color:#222;
67 | margin:0 0 20px;
68 | }
69 |
70 | p, ul, ol, table, pre, dl {
71 | margin:0 0 20px;
72 | }
73 |
74 | h1, h2, h3 {
75 | line-height:1.1;
76 | }
77 |
78 | h1 {
79 | font-size:28px;
80 | }
81 |
82 | h2 {
83 | color:#393939;
84 | }
85 |
86 | h3, h4, h5, h6 {
87 | color:#494949;
88 | }
89 |
90 | a {
91 | color:#39c;
92 | text-decoration:none;
93 | }
94 |
95 | a:hover {
96 | color:#069;
97 | }
98 |
99 | a small {
100 | font-size:11px;
101 | color:#777;
102 | margin-top:-0.3em;
103 | display:block;
104 | }
105 |
106 | a:hover small {
107 | color:#777;
108 | }
109 |
110 | .wrapper {
111 | width:860px;
112 | margin:0 auto;
113 | }
114 |
115 | blockquote {
116 | border-left:1px solid #e5e5e5;
117 | margin:0;
118 | padding:0 0 0 20px;
119 | font-style:italic;
120 | }
121 |
122 | code, pre {
123 | font-family:Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal, Consolas, Liberation Mono, DejaVu Sans Mono, Courier New, monospace;
124 | color:#333;
125 | font-size:12px;
126 | }
127 |
128 | pre {
129 | padding:8px 15px;
130 | background: #f8f8f8;
131 | border-radius:5px;
132 | border:1px solid #e5e5e5;
133 | overflow-x: auto;
134 | }
135 |
136 | table {
137 | width:100%;
138 | border-collapse:collapse;
139 | }
140 |
141 | th, td {
142 | text-align:left;
143 | padding:5px 10px;
144 | border-bottom:1px solid #e5e5e5;
145 | }
146 |
147 | dt {
148 | color:#444;
149 | font-weight:700;
150 | }
151 |
152 | th {
153 | color:#444;
154 | }
155 |
156 | img {
157 | max-width:100%;
158 | }
159 |
160 | header {
161 | width:270px;
162 | float:left;
163 | position:fixed;
164 | -webkit-font-smoothing:subpixel-antialiased;
165 | }
166 |
167 | header ul {
168 | list-style:none;
169 | height:40px;
170 | padding:0;
171 | background: #f4f4f4;
172 | border-radius:5px;
173 | border:1px solid #e0e0e0;
174 | width:270px;
175 | }
176 |
177 | header li {
178 | width:89px;
179 | float:left;
180 | border-right:1px solid #e0e0e0;
181 | height:40px;
182 | }
183 |
184 | header li:first-child a {
185 | border-radius:5px 0 0 5px;
186 | }
187 |
188 | header li:last-child a {
189 | border-radius:0 5px 5px 0;
190 | }
191 |
192 | header ul a {
193 | line-height:1;
194 | font-size:11px;
195 | color:#999;
196 | display:block;
197 | text-align:center;
198 | padding-top:6px;
199 | height:34px;
200 | }
201 |
202 | header ul a:hover {
203 | color:#999;
204 | }
205 |
206 | header ul a:active {
207 | background-color:#f0f0f0;
208 | }
209 |
210 | strong {
211 | color:#222;
212 | font-weight:700;
213 | }
214 |
215 | header ul li + li + li {
216 | border-right:none;
217 | width:89px;
218 | }
219 |
220 | header ul a strong {
221 | font-size:14px;
222 | display:block;
223 | color:#222;
224 | }
225 |
226 | section {
227 | width:500px;
228 | float:right;
229 | padding-bottom:50px;
230 | }
231 |
232 | small {
233 | font-size:11px;
234 | }
235 |
236 | hr {
237 | border:0;
238 | background:#e5e5e5;
239 | height:1px;
240 | margin:0 0 20px;
241 | }
242 |
243 | footer {
244 | width:270px;
245 | float:left;
246 | position:fixed;
247 | bottom:50px;
248 | -webkit-font-smoothing:subpixel-antialiased;
249 | }
250 |
251 | @media print, screen and (max-width: 960px) {
252 |
253 | div.wrapper {
254 | width:auto;
255 | margin:0;
256 | }
257 |
258 | header, section, footer {
259 | float:none;
260 | position:static;
261 | width:auto;
262 | }
263 |
264 | header {
265 | padding-right:320px;
266 | }
267 |
268 | section {
269 | border:1px solid #e5e5e5;
270 | border-width:1px 0;
271 | padding:20px 0;
272 | margin:0 0 20px;
273 | }
274 |
275 | header a small {
276 | display:inline;
277 | }
278 |
279 | header ul {
280 | position:absolute;
281 | right:50px;
282 | top:52px;
283 | }
284 | }
285 |
286 | @media print, screen and (max-width: 720px) {
287 | body {
288 | word-wrap:break-word;
289 | }
290 |
291 | header {
292 | padding:0;
293 | }
294 |
295 | header ul, header p.view {
296 | position:static;
297 | }
298 |
299 | pre, code {
300 | word-wrap:normal;
301 | }
302 | }
303 |
304 | @media print, screen and (max-width: 480px) {
305 | body {
306 | padding:15px;
307 | }
308 |
309 | header ul {
310 | width:99%;
311 | }
312 |
313 | header li, header ul li + li + li {
314 | width:33%;
315 | }
316 | }
317 |
318 | @media print {
319 | body {
320 | padding:0.4in;
321 | font-size:12pt;
322 | color:#444;
323 | }
324 | }
325 |
--------------------------------------------------------------------------------
/stylesheets/normalize.css:
--------------------------------------------------------------------------------
1 | /*! normalize.css v3.0.2 | MIT License | git.io/normalize */
2 |
3 | /**
4 | * 1. Set default font family to sans-serif.
5 | * 2. Prevent iOS text size adjust after orientation change, without disabling
6 | * user zoom.
7 | */
8 |
9 | html {
10 | font-family: sans-serif; /* 1 */
11 | -ms-text-size-adjust: 100%; /* 2 */
12 | -webkit-text-size-adjust: 100%; /* 2 */
13 | }
14 |
15 | /**
16 | * Remove default margin.
17 | */
18 |
19 | body {
20 | margin: 0;
21 | }
22 |
23 | /* HTML5 display definitions
24 | ========================================================================== */
25 |
26 | /**
27 | * Correct `block` display not defined for any HTML5 element in IE 8/9.
28 | * Correct `block` display not defined for `details` or `summary` in IE 10/11
29 | * and Firefox.
30 | * Correct `block` display not defined for `main` in IE 11.
31 | */
32 |
33 | article,
34 | aside,
35 | details,
36 | figcaption,
37 | figure,
38 | footer,
39 | header,
40 | hgroup,
41 | main,
42 | menu,
43 | nav,
44 | section,
45 | summary {
46 | display: block;
47 | }
48 |
49 | /**
50 | * 1. Correct `inline-block` display not defined in IE 8/9.
51 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
52 | */
53 |
54 | audio,
55 | canvas,
56 | progress,
57 | video {
58 | display: inline-block; /* 1 */
59 | vertical-align: baseline; /* 2 */
60 | }
61 |
62 | /**
63 | * Prevent modern browsers from displaying `audio` without controls.
64 | * Remove excess height in iOS 5 devices.
65 | */
66 |
67 | audio:not([controls]) {
68 | display: none;
69 | height: 0;
70 | }
71 |
72 | /**
73 | * Address `[hidden]` styling not present in IE 8/9/10.
74 | * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.
75 | */
76 |
77 | [hidden],
78 | template {
79 | display: none;
80 | }
81 |
82 | /* Links
83 | ========================================================================== */
84 |
85 | /**
86 | * Remove the gray background color from active links in IE 10.
87 | */
88 |
89 | a {
90 | background-color: transparent;
91 | }
92 |
93 | /**
94 | * Improve readability when focused and also mouse hovered in all browsers.
95 | */
96 |
97 | a:active,
98 | a:hover {
99 | outline: 0;
100 | }
101 |
102 | /* Text-level semantics
103 | ========================================================================== */
104 |
105 | /**
106 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome.
107 | */
108 |
109 | abbr[title] {
110 | border-bottom: 1px dotted;
111 | }
112 |
113 | /**
114 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
115 | */
116 |
117 | b,
118 | strong {
119 | font-weight: bold;
120 | }
121 |
122 | /**
123 | * Address styling not present in Safari and Chrome.
124 | */
125 |
126 | dfn {
127 | font-style: italic;
128 | }
129 |
130 | /**
131 | * Address variable `h1` font-size and margin within `section` and `article`
132 | * contexts in Firefox 4+, Safari, and Chrome.
133 | */
134 |
135 | h1 {
136 | font-size: 2em;
137 | margin: 0.67em 0;
138 | }
139 |
140 | /**
141 | * Address styling not present in IE 8/9.
142 | */
143 |
144 | mark {
145 | background: #ff0;
146 | color: #000;
147 | }
148 |
149 | /**
150 | * Address inconsistent and variable font size in all browsers.
151 | */
152 |
153 | small {
154 | font-size: 80%;
155 | }
156 |
157 | /**
158 | * Prevent `sub` and `sup` affecting `line-height` in all browsers.
159 | */
160 |
161 | sub,
162 | sup {
163 | font-size: 75%;
164 | line-height: 0;
165 | position: relative;
166 | vertical-align: baseline;
167 | }
168 |
169 | sup {
170 | top: -0.5em;
171 | }
172 |
173 | sub {
174 | bottom: -0.25em;
175 | }
176 |
177 | /* Embedded content
178 | ========================================================================== */
179 |
180 | /**
181 | * Remove border when inside `a` element in IE 8/9/10.
182 | */
183 |
184 | img {
185 | border: 0;
186 | }
187 |
188 | /**
189 | * Correct overflow not hidden in IE 9/10/11.
190 | */
191 |
192 | svg:not(:root) {
193 | overflow: hidden;
194 | }
195 |
196 | /* Grouping content
197 | ========================================================================== */
198 |
199 | /**
200 | * Address margin not present in IE 8/9 and Safari.
201 | */
202 |
203 | figure {
204 | margin: 1em 40px;
205 | }
206 |
207 | /**
208 | * Address differences between Firefox and other browsers.
209 | */
210 |
211 | hr {
212 | box-sizing: content-box;
213 | height: 0;
214 | }
215 |
216 | /**
217 | * Contain overflow in all browsers.
218 | */
219 |
220 | pre {
221 | overflow: auto;
222 | }
223 |
224 | /**
225 | * Address odd `em`-unit font size rendering in all browsers.
226 | */
227 |
228 | code,
229 | kbd,
230 | pre,
231 | samp {
232 | font-family: monospace, monospace;
233 | font-size: 1em;
234 | }
235 |
236 | /* Forms
237 | ========================================================================== */
238 |
239 | /**
240 | * Known limitation: by default, Chrome and Safari on OS X allow very limited
241 | * styling of `select`, unless a `border` property is set.
242 | */
243 |
244 | /**
245 | * 1. Correct color not being inherited.
246 | * Known issue: affects color of disabled elements.
247 | * 2. Correct font properties not being inherited.
248 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
249 | */
250 |
251 | button,
252 | input,
253 | optgroup,
254 | select,
255 | textarea {
256 | color: inherit; /* 1 */
257 | font: inherit; /* 2 */
258 | margin: 0; /* 3 */
259 | }
260 |
261 | /**
262 | * Address `overflow` set to `hidden` in IE 8/9/10/11.
263 | */
264 |
265 | button {
266 | overflow: visible;
267 | }
268 |
269 | /**
270 | * Address inconsistent `text-transform` inheritance for `button` and `select`.
271 | * All other form control elements do not inherit `text-transform` values.
272 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
273 | * Correct `select` style inheritance in Firefox.
274 | */
275 |
276 | button,
277 | select {
278 | text-transform: none;
279 | }
280 |
281 | /**
282 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
283 | * and `video` controls.
284 | * 2. Correct inability to style clickable `input` types in iOS.
285 | * 3. Improve usability and consistency of cursor style between image-type
286 | * `input` and others.
287 | */
288 |
289 | button,
290 | html input[type="button"], /* 1 */
291 | input[type="reset"],
292 | input[type="submit"] {
293 | -webkit-appearance: button; /* 2 */
294 | cursor: pointer; /* 3 */
295 | }
296 |
297 | /**
298 | * Re-set default cursor for disabled elements.
299 | */
300 |
301 | button[disabled],
302 | html input[disabled] {
303 | cursor: default;
304 | }
305 |
306 | /**
307 | * Remove inner padding and border in Firefox 4+.
308 | */
309 |
310 | button::-moz-focus-inner,
311 | input::-moz-focus-inner {
312 | border: 0;
313 | padding: 0;
314 | }
315 |
316 | /**
317 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in
318 | * the UA stylesheet.
319 | */
320 |
321 | input {
322 | line-height: normal;
323 | }
324 |
325 | /**
326 | * It's recommended that you don't attempt to style these elements.
327 | * Firefox's implementation doesn't respect box-sizing, padding, or width.
328 | *
329 | * 1. Address box sizing set to `content-box` in IE 8/9/10.
330 | * 2. Remove excess padding in IE 8/9/10.
331 | */
332 |
333 | input[type="checkbox"],
334 | input[type="radio"] {
335 | box-sizing: border-box; /* 1 */
336 | padding: 0; /* 2 */
337 | }
338 |
339 | /**
340 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain
341 | * `font-size` values of the `input`, it causes the cursor style of the
342 | * decrement button to change from `default` to `text`.
343 | */
344 |
345 | input[type="number"]::-webkit-inner-spin-button,
346 | input[type="number"]::-webkit-outer-spin-button {
347 | height: auto;
348 | }
349 |
350 | /**
351 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome.
352 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome
353 | * (include `-moz` to future-proof).
354 | */
355 |
356 | input[type="search"] {
357 | -webkit-appearance: textfield; /* 1 */ /* 2 */
358 | box-sizing: content-box;
359 | }
360 |
361 | /**
362 | * Remove inner padding and search cancel button in Safari and Chrome on OS X.
363 | * Safari (but not Chrome) clips the cancel button when the search input has
364 | * padding (and `textfield` appearance).
365 | */
366 |
367 | input[type="search"]::-webkit-search-cancel-button,
368 | input[type="search"]::-webkit-search-decoration {
369 | -webkit-appearance: none;
370 | }
371 |
372 | /**
373 | * Define consistent border, margin, and padding.
374 | */
375 |
376 | fieldset {
377 | border: 1px solid #c0c0c0;
378 | margin: 0 2px;
379 | padding: 0.35em 0.625em 0.75em;
380 | }
381 |
382 | /**
383 | * 1. Correct `color` not being inherited in IE 8/9/10/11.
384 | * 2. Remove padding so people aren't caught out if they zero out fieldsets.
385 | */
386 |
387 | legend {
388 | border: 0; /* 1 */
389 | padding: 0; /* 2 */
390 | }
391 |
392 | /**
393 | * Remove default vertical scrollbar in IE 8/9/10/11.
394 | */
395 |
396 | textarea {
397 | overflow: auto;
398 | }
399 |
400 | /**
401 | * Don't inherit the `font-weight` (applied by a rule above).
402 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
403 | */
404 |
405 | optgroup {
406 | font-weight: bold;
407 | }
408 |
409 | /* Tables
410 | ========================================================================== */
411 |
412 | /**
413 | * Remove most spacing between table cells.
414 | */
415 |
416 | table {
417 | border-collapse: collapse;
418 | border-spacing: 0;
419 | }
420 |
421 | td,
422 | th {
423 | padding: 0;
424 | }
425 |
--------------------------------------------------------------------------------
/spec/algorithms/dijkstra_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | RSpec.describe Abuelo::Algorithms::Dijkstra do
4 | describe '#initialize(graph, start_node)' do
5 | it 'raises an error if the start_node is not a Abuelo::Node' do
6 | expect do
7 | described_class.new(Abuelo::Graph.new, 'foo')
8 | end.to raise_error(Abuelo::Exceptions::NoNodeError)
9 | end
10 | end
11 |
12 | context 'undirected graph' do
13 | # This is the example from https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
14 | adjacency_matrix = <<-matrix
15 | 0 7 9 0 0 14
16 | 7 0 10 15 0 0
17 | 9 10 0 11 0 2
18 | 0 15 11 0 6 0
19 | 0 0 0 6 0 9
20 | 14 0 2 0 9 0
21 | matrix
22 |
23 | let(:undirected_graph) do
24 | Abuelo::Graph.new(adjacency_matrix: adjacency_matrix)
25 | end
26 | let(:start_node) { undirected_graph.find_node_by_name('node 1') }
27 | let(:dijkstra) { described_class.new(undirected_graph, start_node) }
28 |
29 | describe '#shortest_distance_to(node)' do
30 | it 'is 0 for the start node' do
31 | expect(dijkstra.shortest_distance_to(start_node)).to eq 0
32 | end
33 |
34 | it 'is Float::INFINITY for an unreachable node' do
35 | unreachable_node = Abuelo::Node.new('unreachable node')
36 | undirected_graph.add_node(unreachable_node)
37 | expect(dijkstra.shortest_distance_to(unreachable_node)).to eq Float::INFINITY
38 | end
39 |
40 | it 'is nil when the node is not part of the graph' do
41 | unknown_node = Abuelo::Node.new('foo')
42 | expect(dijkstra.shortest_distance_to(unknown_node)).to be nil
43 | end
44 |
45 | context 'wikipedia example' do
46 | it 'is 0 for node 1' do
47 | node = undirected_graph.find_node_by_name('node 1')
48 | expect(dijkstra.shortest_distance_to(node)).to eq 0
49 | end
50 |
51 | it 'is 7 for node 2' do
52 | node = undirected_graph.find_node_by_name('node 2')
53 | expect(dijkstra.shortest_distance_to(node)).to eq 7
54 | end
55 |
56 | it 'is 9 for node 3' do
57 | node = undirected_graph.find_node_by_name('node 3')
58 | expect(dijkstra.shortest_distance_to(node)).to eq 9
59 | end
60 |
61 | it 'is 20 for node 4' do
62 | node = undirected_graph.find_node_by_name('node 4')
63 | expect(dijkstra.shortest_distance_to(node)).to eq 20
64 | end
65 |
66 | it 'is 20 for node 5' do
67 | node = undirected_graph.find_node_by_name('node 5')
68 | expect(dijkstra.shortest_distance_to(node)).to eq 20
69 | end
70 |
71 | it 'is 11 for node 6' do
72 | node = undirected_graph.find_node_by_name('node 6')
73 | expect(dijkstra.shortest_distance_to(node)).to eq 11
74 | end
75 | end
76 | end
77 |
78 | describe '#shortest_path_to(node)' do
79 | it 'is nil for the start node' do
80 | expect(dijkstra.shortest_path_to(start_node)).to be nil
81 | end
82 |
83 | it 'is nil for an unreachable node' do
84 | unreachable_node = Abuelo::Node.new('unreachable node')
85 | undirected_graph.add_node(unreachable_node)
86 | expect(dijkstra.shortest_path_to(unreachable_node)).to be nil
87 | end
88 |
89 | it 'is nil when the node is not part of the graph' do
90 | unknown_node = Abuelo::Node.new('foo')
91 | expect(dijkstra.shortest_path_to(unknown_node)).to be nil
92 | end
93 |
94 | context 'wikipedia example' do
95 | it 'solves the problem for node 1' do
96 | node = undirected_graph.find_node_by_name('node 1')
97 | expect(dijkstra.shortest_path_to(node)).to be nil
98 | end
99 |
100 | it 'solves the problem for node 2' do
101 | node = undirected_graph.find_node_by_name('node 2')
102 | shortest_path = ['node 1', 'node 2']
103 | expect(dijkstra.shortest_path_to(node).map(&:to_s)).to eq shortest_path
104 | end
105 |
106 | it 'solves the problem for node 3' do
107 | node = undirected_graph.find_node_by_name('node 3')
108 | shortest_path = ['node 1', 'node 3']
109 | expect(dijkstra.shortest_path_to(node).map(&:to_s)).to eq shortest_path
110 | end
111 |
112 | it 'solves the problem for node 4' do
113 | node = undirected_graph.find_node_by_name('node 4')
114 | shortest_path = ['node 1', 'node 3', 'node 4']
115 | expect(dijkstra.shortest_path_to(node).map(&:to_s)).to eq shortest_path
116 | end
117 |
118 | it 'solves the problem for node 5' do
119 | node = undirected_graph.find_node_by_name('node 5')
120 | shortest_path = ['node 1', 'node 3', 'node 6', 'node 5']
121 | expect(dijkstra.shortest_path_to(node).map(&:to_s)).to eq shortest_path
122 | end
123 |
124 | it 'solves the problem for node 6' do
125 | node = undirected_graph.find_node_by_name('node 6')
126 | shortest_path = ['node 1', 'node 3', 'node 6']
127 | expect(dijkstra.shortest_path_to(node).map(&:to_s)).to eq shortest_path
128 | end
129 | end
130 | end
131 | end
132 |
133 | context 'directed graph' do
134 | adjacency_matrix = <<-matrix
135 | 0 7 0 0 0 0
136 | 0 0 10 15 0 0
137 | 9 0 0 11 0 2
138 | 0 0 11 0 6 0
139 | 0 0 0 0 0 0
140 | 14 0 0 0 9 0
141 | matrix
142 |
143 | let(:directed_graph) do
144 | Abuelo::Graph.new(adjacency_matrix: adjacency_matrix, directed: true)
145 | end
146 | let(:start_node) { directed_graph.find_node_by_name('node 1') }
147 | let(:dijkstra) { described_class.new(directed_graph, start_node) }
148 |
149 | describe '#shortest_distance_to(node)' do
150 | it 'is 0 for the start node' do
151 | expect(dijkstra.shortest_distance_to(start_node)).to eq 0
152 | end
153 |
154 | it 'is Float::INFINITY for an unreachable node' do
155 | unreachable_node = Abuelo::Node.new('unreachable node')
156 | directed_graph.add_node(unreachable_node)
157 | expect(dijkstra.shortest_distance_to(unreachable_node)).to eq Float::INFINITY
158 | end
159 |
160 | it 'is nil when the node is not part of the graph' do
161 | unknown_node = Abuelo::Node.new('foo')
162 | expect(dijkstra.shortest_distance_to(unknown_node)).to be nil
163 | end
164 |
165 | context 'example' do
166 | it 'is 0 for node 1' do
167 | node = directed_graph.find_node_by_name('node 1')
168 | expect(dijkstra.shortest_distance_to(node)).to eq 0
169 | end
170 |
171 | it 'is 7 for node 2' do
172 | node = directed_graph.find_node_by_name('node 2')
173 | expect(dijkstra.shortest_distance_to(node)).to eq 7
174 | end
175 |
176 | it 'is 9 for node 3' do
177 | node = directed_graph.find_node_by_name('node 3')
178 | expect(dijkstra.shortest_distance_to(node)).to eq 17
179 | end
180 |
181 | it 'is 20 for node 4' do
182 | node = directed_graph.find_node_by_name('node 4')
183 | expect(dijkstra.shortest_distance_to(node)).to eq 22
184 | end
185 |
186 | it 'is 20 for node 5' do
187 | node = directed_graph.find_node_by_name('node 5')
188 | expect(dijkstra.shortest_distance_to(node)).to eq 28
189 | end
190 |
191 | it 'is 11 for node 6' do
192 | node = directed_graph.find_node_by_name('node 6')
193 | expect(dijkstra.shortest_distance_to(node)).to eq 19
194 | end
195 | end
196 | end
197 |
198 | describe '#shortest_path_to(node)' do
199 | it 'is nil for the start node' do
200 | expect(dijkstra.shortest_path_to(start_node)).to be nil
201 | end
202 |
203 | it 'is nil for an unreachable node' do
204 | unreachable_node = Abuelo::Node.new('unreachable node')
205 | directed_graph.add_node(unreachable_node)
206 | expect(dijkstra.shortest_path_to(unreachable_node)).to be nil
207 | end
208 |
209 | it 'is nil when the node is not part of the graph' do
210 | unknown_node = Abuelo::Node.new('foo')
211 | expect(dijkstra.shortest_path_to(unknown_node)).to be nil
212 | end
213 |
214 | context 'example' do
215 | it 'solves the problem for node 1' do
216 | node = directed_graph.find_node_by_name('node 1')
217 | expect(dijkstra.shortest_path_to(node)).to be nil
218 | end
219 |
220 | it 'solves the problem for node 2' do
221 | node = directed_graph.find_node_by_name('node 2')
222 | shortest_path = ['node 1', 'node 2']
223 | expect(dijkstra.shortest_path_to(node).map(&:to_s)).to eq shortest_path
224 | end
225 |
226 | it 'solves the problem for node 3' do
227 | node = directed_graph.find_node_by_name('node 3')
228 | shortest_path = ['node 1', 'node 2', 'node 3']
229 | expect(dijkstra.shortest_path_to(node).map(&:to_s)).to eq shortest_path
230 | end
231 |
232 | it 'solves the problem for node 4' do
233 | node = directed_graph.find_node_by_name('node 4')
234 | shortest_path = ['node 1', 'node 2', 'node 4']
235 | expect(dijkstra.shortest_path_to(node).map(&:to_s)).to eq shortest_path
236 | end
237 |
238 | it 'solves the problem for node 5' do
239 | node = directed_graph.find_node_by_name('node 5')
240 | shortest_path = ['node 1', 'node 2', 'node 3', 'node 6', 'node 5']
241 | expect(dijkstra.shortest_path_to(node).map(&:to_s)).to eq shortest_path
242 | end
243 |
244 | it 'solves the problem for node 6' do
245 | node = directed_graph.find_node_by_name('node 6')
246 | shortest_path = ['node 1', 'node 2', 'node 3', 'node 6']
247 | expect(dijkstra.shortest_path_to(node).map(&:to_s)).to eq shortest_path
248 | end
249 | end
250 | end
251 | end
252 | end
253 |
--------------------------------------------------------------------------------
/spec/graph_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | RSpec.describe Abuelo::Graph do
4 | let(:node_1) { Abuelo::Node.new('node 1') }
5 | let(:node_2) { Abuelo::Node.new('node 2') }
6 | let(:node_3) { Abuelo::Node.new('node 3') }
7 | let(:edge_1) { Abuelo::Edge.new(node_1, node_2, 42) }
8 | let(:edge_2) { Abuelo::Edge.new(node_2, node_3, 23) }
9 |
10 | let(:directed_graph) do
11 | described_class.new(directed: true)
12 | .add_node(node_1)
13 | .add_node(node_2)
14 | .add_node(node_3)
15 | .add_edge(edge_1)
16 | .add_edge(edge_2)
17 | end
18 |
19 | let(:undirected_graph) do
20 | described_class.new
21 | .add_node(node_1)
22 | .add_node(node_2)
23 | .add_node(node_3)
24 | .add_edge(edge_1)
25 | .add_edge(edge_2)
26 | end
27 |
28 | context 'directed graph' do
29 | describe '#initialize' do
30 | it 'can be initialized with an adjacency matrix with weights' do
31 | adjacency_matrix = <<-matrix
32 | 0 7 9 0 0 14
33 | 7 0 10 15 0 0
34 | 9 10 0 11 0 2
35 | 0 0 11 0 6 0
36 | 0 0 0 6 0 9
37 | 0 0 2 0 9 0
38 | matrix
39 |
40 | graph = described_class.new(adjacency_matrix: adjacency_matrix, directed: true)
41 | node_1 = graph.find_node_by_name('node 1')
42 | node_2 = graph.find_node_by_name('node 2')
43 | node_4 = graph.find_node_by_name('node 4')
44 |
45 | expect(graph.order).to eq 6
46 | expect(graph.size).to eq 16
47 |
48 | expect(graph.has_node_with_name?('node 1')).to be true
49 | expect(graph.has_node_with_name?('node 0')).to be false
50 | expect(graph.has_node_with_name?('node 7')).to be false
51 |
52 | expect(graph.find_edge(node_1, node_4)).to be nil
53 |
54 | edge_1_2 = graph.find_edge(node_1, node_2)
55 | expect(edge_1_2.weight).to eq 7
56 | end
57 | end
58 |
59 | describe '#undirected?' do
60 | it 'is false' do
61 | expect(directed_graph.undirected?).to be false
62 | end
63 | end
64 |
65 | describe '#directed?' do
66 | it 'is true' do
67 | expect(directed_graph.directed?).to be true
68 | end
69 | end
70 |
71 | describe 'order' do
72 | it 'returns the count of nodes' do
73 | expect(directed_graph.order).to eq 3
74 | end
75 | end
76 |
77 | describe 'size' do
78 | it 'returns the count of edges' do
79 | expect(directed_graph.size).to eq 2
80 | end
81 | end
82 |
83 | context 'nodes' do
84 | describe '#nodes' do
85 | it 'returns the nodes of the graph' do
86 | expect(directed_graph.nodes).to match_array [node_1, node_2, node_3]
87 | end
88 | end
89 |
90 | describe '#add_node(node)' do
91 | it 'adds the node to the graph' do
92 | new_node = Abuelo::Node.new('node 4')
93 |
94 | expect do
95 | directed_graph.add_node(new_node)
96 | end.to change { directed_graph.nodes.count }.by 1
97 |
98 | expect(directed_graph.has_node?(new_node)).to be true
99 | end
100 |
101 | it 'raises an error if a node with the same name in the graph exists' do
102 | new_node = Abuelo::Node.new('node 1')
103 |
104 | expect do
105 | directed_graph.add_node(new_node)
106 | end.to raise_error(Abuelo::Exceptions::NodeAlreadyExistsError)
107 | end
108 |
109 | it 'sets the graph of the node to itself' do
110 | new_node = Abuelo::Node.new('node 4')
111 |
112 | expect do
113 | directed_graph.add_node(new_node)
114 | end.to change { new_node.graph }.to(directed_graph)
115 | end
116 | end
117 |
118 | describe '#has_node?(node)' do
119 | it 'returns true if node is in the graph. checks object name' do
120 | expect(directed_graph.has_node?(node_1)).to be true
121 | end
122 |
123 | it 'returns false if node is not in the graph. checks object name' do
124 | new_node = Abuelo::Node.new('node 99')
125 | expect(directed_graph.has_node?(new_node)).to be false
126 | end
127 | end
128 |
129 | describe '#has_node_with_name?(name)' do
130 | it 'returns true if a node with the given name is in the graph' do
131 | expect(directed_graph.has_node_with_name?('node 1')).to be true
132 | end
133 |
134 | it 'returns false if no node with the given name is in the graph' do
135 | expect(directed_graph.has_node_with_name?('foo')).to be false
136 | end
137 | end
138 | end
139 |
140 | context 'edges' do
141 | describe '#edges' do
142 | it 'returns the edges of the graph' do
143 | expect(directed_graph.edges).to match_array [edge_1, edge_2]
144 | end
145 | end
146 |
147 | describe '#add_edge(edge)' do
148 | it 'adds the edge to the graph' do
149 | new_edge = Abuelo::Edge.new(node_2, node_1, 23)
150 |
151 | expect do
152 | directed_graph.add_edge(new_edge)
153 | end.to change { directed_graph.edges.count }.by 1
154 |
155 | expect(directed_graph.has_edge?(new_edge)).to be true
156 | end
157 |
158 | it 'raises an error if an edge with the same name nodes in the graph exists' do
159 | new_edge = Abuelo::Edge.new(node_1, node_2, 23)
160 |
161 | expect do
162 | directed_graph.add_edge(new_edge)
163 | end.to raise_error(Abuelo::Exceptions::EdgeAlreadyExistsError)
164 | end
165 | end
166 |
167 | describe '#edges_for_node(node)' do
168 | it 'returns all edges that start from the given node' do
169 | expect(directed_graph.edges_for_node(node_1)).to eq [edge_1]
170 | expect(directed_graph.edges_for_node(node_2)).to eq [edge_2]
171 | expect(directed_graph.edges_for_node(node_3)).to eq []
172 | end
173 | end
174 |
175 | describe '#has_edge?(edge)' do
176 | it 'returns true if there is an edge with similiar nodes in the graph' do
177 | expect(directed_graph.has_edge?(edge_1)).to be true
178 | end
179 |
180 | it 'does not have a symmetric edge automatically' do
181 | expect(directed_graph.has_edge?(edge_1.symmetric)).to be false
182 | end
183 |
184 | it 'returns false if there is no edge with similiar nodes in the graph' do
185 | other_edge = Abuelo::Edge.new(node_2, node_1, 23)
186 | expect(directed_graph.has_edge?(other_edge)).to be false
187 | end
188 | end
189 |
190 | describe '#find_edge(node_1, node_2)' do
191 | it 'returns the edge if it can be found in the graph' do
192 | expect(directed_graph.find_edge(node_1, node_2)).to eq edge_1
193 | end
194 |
195 | it 'returns nil if the edge cannot be found in the graph' do
196 | expect(directed_graph.find_edge(node_2, node_1)).to be nil
197 | end
198 | end
199 | end
200 | end
201 |
202 | context 'undirected graph' do
203 | describe '#initialize' do
204 | it 'can be initialized with an adjacency matrix with weights' do
205 | adjacency_matrix = <<-matrix
206 | 0 7 9 0 0 14
207 | 7 0 10 15 0 0
208 | 9 10 0 11 0 2
209 | 0 15 11 0 6 0
210 | 0 0 0 6 0 9
211 | 14 0 2 0 9 0
212 | matrix
213 |
214 | graph = described_class.new(adjacency_matrix: adjacency_matrix)
215 | node_1 = graph.find_node_by_name('node 1')
216 | node_2 = graph.find_node_by_name('node 2')
217 | node_4 = graph.find_node_by_name('node 4')
218 |
219 | expect(graph.order).to eq 6
220 | expect(graph.size).to eq 9
221 |
222 | expect(graph.has_node_with_name?('node 1')).to be true
223 | expect(graph.has_node_with_name?('node 0')).to be false
224 | expect(graph.has_node_with_name?('node 7')).to be false
225 |
226 | expect(graph.find_edge(node_1, node_4)).to be nil
227 |
228 | edge_1_2 = graph.find_edge(node_1, node_2)
229 | expect(edge_1_2.weight).to eq 7
230 | end
231 | end
232 |
233 | describe '#undirected?' do
234 | it 'is true' do
235 | expect(undirected_graph.undirected?).to be true
236 | end
237 | end
238 |
239 | describe '#directed?' do
240 | it 'is false' do
241 | expect(undirected_graph.directed?).to be false
242 | end
243 | end
244 |
245 | describe 'order' do
246 | it 'returns the count of nodes' do
247 | expect(undirected_graph.order).to eq 3
248 | end
249 | end
250 |
251 | describe 'size' do
252 | it 'returns the count of edges' do
253 | expect(undirected_graph.size).to eq 2
254 | end
255 | end
256 |
257 | context 'nodes' do
258 | describe '#nodes' do
259 | it 'returns the nodes of the graph' do
260 | expect(undirected_graph.nodes).to match_array [node_1, node_2, node_3]
261 | end
262 | end
263 |
264 | describe '#add_node(node)' do
265 | it 'adds the node to the graph' do
266 | new_node = Abuelo::Node.new('node 4')
267 |
268 | expect do
269 | undirected_graph.add_node(new_node)
270 | end.to change { undirected_graph.nodes.count }.by 1
271 |
272 | expect(undirected_graph.has_node?(new_node)).to be true
273 | end
274 |
275 | it 'raises an error if a node with the same name in the graph exists' do
276 | new_node = Abuelo::Node.new('node 1')
277 |
278 | expect do
279 | undirected_graph.add_node(new_node)
280 | end.to raise_error(Abuelo::Exceptions::NodeAlreadyExistsError)
281 | end
282 |
283 | it 'sets the graph of the node to itself' do
284 | new_node = Abuelo::Node.new('node 4')
285 |
286 | expect do
287 | undirected_graph.add_node(new_node)
288 | end.to change { new_node.graph }.to(undirected_graph)
289 | end
290 | end
291 |
292 | describe '#has_node?(node)' do
293 | it 'returns true if node is in the graph. checks object name' do
294 | expect(undirected_graph.has_node?(node_1)).to be true
295 | end
296 |
297 | it 'returns false if node is not in the graph. checks object name' do
298 | new_node = Abuelo::Node.new('node 4')
299 | expect(undirected_graph.has_node?(new_node)).to be false
300 | end
301 | end
302 |
303 | describe '#has_node_with_name?(name)' do
304 | it 'returns true if a node with the given name is in the graph' do
305 | expect(undirected_graph.has_node_with_name?('node 1')).to be true
306 | end
307 |
308 | it 'returns false if no node with the given name is in the graph' do
309 | expect(undirected_graph.has_node_with_name?('foo')).to be false
310 | end
311 | end
312 | end
313 |
314 | context 'edges' do
315 | describe '#edges' do
316 | it 'returns the edges of the graph' do
317 | edges = [[edge_1, edge_1.symmetric], [edge_2, edge_2.symmetric]]
318 | expect(undirected_graph.edges).to match_array edges
319 | end
320 | end
321 |
322 | describe '#add_edge(edge)' do
323 | it 'adds the edge to the graph' do
324 | new_edge = Abuelo::Edge.new(node_1, node_3, 23)
325 |
326 | expect do
327 | undirected_graph.add_edge(new_edge)
328 | end.to change { undirected_graph.edges.count }.by 1
329 |
330 | expect(undirected_graph.has_edge?(new_edge)).to be true
331 | expect(undirected_graph.has_edge?(new_edge.symmetric)).to be true
332 | end
333 |
334 | it 'raises an error if an edge with the same name nodes in the graph exists' do
335 | new_edge = Abuelo::Edge.new(node_1, node_2, 23)
336 |
337 | expect do
338 | undirected_graph.add_edge(new_edge)
339 | end.to raise_error(Abuelo::Exceptions::EdgeAlreadyExistsError)
340 | end
341 | end
342 |
343 | describe '#edges_for_node(node)' do
344 | it 'returns all edges that start from the given node' do
345 | expect(undirected_graph.edges_for_node(node_1)).to eq [edge_1]
346 | expect(undirected_graph.edges_for_node(node_2)).to eq [edge_1.symmetric, edge_2]
347 | expect(undirected_graph.edges_for_node(node_3)).to eq [edge_2.symmetric]
348 | end
349 | end
350 |
351 | describe '#has_edge?(edge)' do
352 | it 'returns true if there is an edge with similiar nodes in the graph' do
353 | expect(undirected_graph.has_edge?(edge_1)).to be true
354 | expect(undirected_graph.has_edge?(edge_1.symmetric)).to be true
355 | end
356 |
357 | it 'returns false if there is no edge with similiar nodes in the graph' do
358 | other_edge = Abuelo::Edge.new(node_1, node_3, 23)
359 | expect(undirected_graph.has_edge?(other_edge)).to be false
360 | end
361 | end
362 |
363 | describe '#find_edge(node_1, node_2)' do
364 | it 'returns the edge if it can be found in the graph' do
365 | expect(undirected_graph.find_edge(node_1, node_2)).to eq edge_1
366 | expect(undirected_graph.find_edge(node_2, node_1)).to eq edge_1.symmetric
367 | end
368 |
369 | it 'returns nil if the edge cannot be found in the graph' do
370 | expect(undirected_graph.find_edge(node_1, node_3)).to be nil
371 | end
372 | end
373 | end
374 | end
375 | end
376 |
--------------------------------------------------------------------------------