├── .gitignore ├── LICENSE ├── Makefile ├── Makefile.win ├── README.md ├── demo ├── data │ ├── board1.txt │ ├── graph1.txt │ ├── graph2.txt │ ├── graph3.txt │ └── graph4.txt └── src │ ├── a_star_demo.erl │ ├── bfs_demo.erl │ ├── demo.erl │ ├── dfs_demo.erl │ ├── dijkstra_demo.erl │ ├── flow_demo.erl │ ├── graph_demo.erl │ ├── heap_demo.erl │ ├── import_export_demo.erl │ ├── kruskal_demo.erl │ └── union_find_demo.erl ├── doc ├── .gitignore └── overview.edoc ├── ebin └── .gitignore ├── makedoc.rb ├── rebar.config ├── rebar.lock ├── rundemo.rb └── src ├── a_star.erl ├── bfs.erl ├── dfs.erl ├── dijkstra.erl ├── doc.erl ├── edmonds_karp.erl ├── erlang-algorithms.app.src ├── graph.erl ├── graph_lib.erl ├── heap.erl ├── kruskal.erl └── union_find.erl /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.bak 3 | 4 | _build 5 | .plt 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all clean demo dialyzer edoc distclean 2 | 3 | TOP = $(PWD) 4 | SRC = $(PWD)/src 5 | EBIN = $(PWD)/ebin 6 | DEMO = $(PWD)/demo/src 7 | DEMO_DATA = $(PWD)/demo/data 8 | DOC = doc 9 | ERLC = erlc 10 | 11 | WARNS = +warn_exported_vars +warn_unused_import +warn_missing_spec 12 | DIALYZER_APPS = erts kernel stdlib compiler crypto syntax_tools 13 | DIALYZER_FLAGS = -Wunmatched_returns 14 | ERLC_FLAGS = +native +debug_info $(WARNS) 15 | ERLC_MACROS = -DDEMO_DATA=\"$(DEMO_DATA)\" 16 | 17 | SRC_MODULES = \ 18 | graph \ 19 | graph_lib \ 20 | dijkstra \ 21 | bfs \ 22 | dfs \ 23 | kruskal \ 24 | heap \ 25 | union_find \ 26 | edmonds_karp \ 27 | a_star 28 | 29 | DEMO_MODULES = \ 30 | demo \ 31 | graph_demo \ 32 | heap_demo \ 33 | union_find_demo \ 34 | bfs_demo \ 35 | dfs_demo \ 36 | dijkstra_demo \ 37 | kruskal_demo \ 38 | flow_demo \ 39 | a_star_demo \ 40 | import_export_demo 41 | 42 | EDOC_MODULES = \ 43 | doc \ 44 | 45 | TARGETS = \ 46 | src_target \ 47 | demo_target \ 48 | edoc_target 49 | 50 | ERL_DIRS = \ 51 | $(SRC) \ 52 | $(DEMO) 53 | 54 | vpath %.erl $(ERL_DIRS) 55 | 56 | default: src_target demo_target 57 | 58 | all: $(TARGETS) dialyzer 59 | 60 | src_target: $(SRC_MODULES:%=$(EBIN)/%.beam) 61 | 62 | demo_target: $(DEMO_MODULES:%=$(EBIN)/%.beam) 63 | 64 | edoc_target: $(EDOC_MODULES:%=$(EBIN)/%.beam) 65 | 66 | $(EBIN)/%.beam: %.erl 67 | $(ERLC) $(ERLC_FLAGS) $(ERLC_MACROS) -o $(EBIN) $< 68 | 69 | edoc: $(TARGETS) 70 | @(./makedoc.rb) 71 | 72 | demo: $(TARGETS) 73 | @(./rundemo.rb) 74 | 75 | dialyzer: .plt $(TARGETS) 76 | dialyzer -n -nn --plt $< $(DIALYZER_FLAGS) $(EBIN)/*.beam 77 | 78 | .plt: 79 | dialyzer --build_plt --output_plt $@ --apps $(DIALYZER_APPS) 80 | 81 | clean: 82 | $(RM) $(EBIN)/*.beam 83 | 84 | distclean: clean 85 | $(RM) $(DOC)/*.html $(DOC)/*.css $(DOC)/*.png $(DOC)/edoc-info .plt 86 | -------------------------------------------------------------------------------- /Makefile.win: -------------------------------------------------------------------------------- 1 | 2 | all: 3 | erlc +native +debug_info +warn_exported_vars +warn_unused_import +warn_missing_spec -DDEMO_DATA=\"demo/data\" -o ebin src/graph.erl 4 | erlc +native +debug_info +warn_exported_vars +warn_unused_import +warn_missing_spec -DDEMO_DATA=\"demo/data\" -o ebin src/graph_lib.erl 5 | erlc +native +debug_info +warn_exported_vars +warn_unused_import +warn_missing_spec -DDEMO_DATA=\"demo/data\" -o ebin src/dijkstra.erl 6 | erlc +native +debug_info +warn_exported_vars +warn_unused_import +warn_missing_spec -DDEMO_DATA=\"demo/data\" -o ebin src/bfs.erl 7 | erlc +native +debug_info +warn_exported_vars +warn_unused_import +warn_missing_spec -DDEMO_DATA=\"demo/data\" -o ebin src/dfs.erl 8 | erlc +native +debug_info +warn_exported_vars +warn_unused_import +warn_missing_spec -DDEMO_DATA=\"demo/data\" -o ebin src/kruskal.erl 9 | erlc +native +debug_info +warn_exported_vars +warn_unused_import +warn_missing_spec -DDEMO_DATA=\"demo/data\" -o ebin src/heap.erl 10 | erlc +native +debug_info +warn_exported_vars +warn_unused_import +warn_missing_spec -DDEMO_DATA=\"demo/data\" -o ebin src/union_find.erl 11 | erlc +native +debug_info +warn_exported_vars +warn_unused_import +warn_missing_spec -DDEMO_DATA=\"demo/data\" -o ebin src/edmonds_karp.erl 12 | erlc +native +debug_info +warn_exported_vars +warn_unused_import +warn_missing_spec -DDEMO_DATA=\"demo/data\" -o ebin src/a_star.erl 13 | erlc +native +debug_info +warn_exported_vars +warn_unused_import +warn_missing_spec -DDEMO_DATA=\"demo/data\" -o ebin demo/src/demo.erl 14 | erlc +native +debug_info +warn_exported_vars +warn_unused_import +warn_missing_spec -DDEMO_DATA=\"demo/data\" -o ebin demo/src/graph_demo.erl 15 | erlc +native +debug_info +warn_exported_vars +warn_unused_import +warn_missing_spec -DDEMO_DATA=\"demo/data\" -o ebin demo/src/heap_demo.erl 16 | erlc +native +debug_info +warn_exported_vars +warn_unused_import +warn_missing_spec -DDEMO_DATA=\"demo/data\" -o ebin demo/src/union_find_demo.erl 17 | erlc +native +debug_info +warn_exported_vars +warn_unused_import +warn_missing_spec -DDEMO_DATA=\"demo/data\" -o ebin demo/src/bfs_demo.erl 18 | erlc +native +debug_info +warn_exported_vars +warn_unused_import +warn_missing_spec -DDEMO_DATA=\"demo/data\" -o ebin demo/src/dfs_demo.erl 19 | erlc +native +debug_info +warn_exported_vars +warn_unused_import +warn_missing_spec -DDEMO_DATA=\"demo/data\" -o ebin demo/src/dijkstra_demo.erl 20 | erlc +native +debug_info +warn_exported_vars +warn_unused_import +warn_missing_spec -DDEMO_DATA=\"demo/data\" -o ebin demo/src/kruskal_demo.erl 21 | erlc +native +debug_info +warn_exported_vars +warn_unused_import +warn_missing_spec -DDEMO_DATA=\"demo/data\" -o ebin demo/src/flow_demo.erl 22 | erlc +native +debug_info +warn_exported_vars +warn_unused_import +warn_missing_spec -DDEMO_DATA=\"demo/data\" -o ebin demo/src/a_star_demo.erl 23 | erlc +native +debug_info +warn_exported_vars +warn_unused_import +warn_missing_spec -DDEMO_DATA=\"demo/data\" -o ebin demo/src/import_export_demo.erl 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | erlang-algorithms 2 | ================= 3 | 4 | ### About the project 5 | 6 | The goal of this project is to implement some useful algorithms and data structures in Erlang so as to help anyone who may need them. 7 | 8 | * Currently Implemented Data Structures: 9 | * Directed, Undirected, Weighted, Unweighted Graphs 10 | * Min / Max Heaps 11 | * Union / Find 12 | 13 | * Currently Implemented Algorithms: 14 | * BFS 15 | * DFS 16 | * Dijkstra 17 | * Kruskal 18 | * Edmonds-Karp 19 | * Ford-Fulkerson 20 | * A* 21 | 22 | ### How to Compile and Run 23 | 24 | * Compile the source code and the demo : `make` 25 | * Run dialyzer : `make dialyzer` 26 | * All of the above : `make all` 27 | * Run the demo : `make demo` 28 | * Make edoc : `make edoc` 29 | 30 | The files in the `demo` folder contain functions that demostrate the code in action. 31 | 32 | For full documentation check the [site](http://aggelgian.github.com/erlang-algorithms) 33 | 34 | 35 | ### License 36 | 37 | This project is released under the [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0). 38 | -------------------------------------------------------------------------------- /demo/data/board1.txt: -------------------------------------------------------------------------------- 1 | 25 30 undirected d 2 | (1,1) (1,2) (1,3) (1,4) (2,1) (2,2) (2,4) (2,5) (2,6) (3,1) (3,5) (3,6) (4,1) (4,6) (5,1) (5,2) (5,3) (5,5) (5,6) (6,1) (6,2) (6,3) (6,4) (6,5) (6,6) 3 | (1,1) (1,2) 1 4 | (1,2) (1,3) 1 5 | (1,3) (1,4) 1 6 | (1,1) (2,1) 1 7 | (1,2) (2,2) 1 8 | (1,4) (2,4) 1 9 | (2,1) (2,2) 1 10 | (2,4) (2,5) 1 11 | (2,5) (2,6) 1 12 | (2,1) (3,1) 1 13 | (2,5) (3,5) 1 14 | (2,6) (3,6) 1 15 | (3,5) (3,6) 1 16 | (3,1) (4,1) 1 17 | (3,6) (4,6) 1 18 | (4,1) (5,1) 1 19 | (4,6) (5,6) 1 20 | (5,1) (5,2) 1 21 | (5,2) (5,3) 1 22 | (5,5) (5,6) 1 23 | (5,1) (6,1) 1 24 | (5,2) (6,2) 1 25 | (5,3) (6,3) 1 26 | (5,5) (6,5) 1 27 | (5,6) (6,6) 1 28 | (6,1) (6,2) 1 29 | (6,2) (6,3) 1 30 | (6,3) (6,4) 1 31 | (6,4) (6,5) 1 32 | (6,5) (6,6) 1 33 | 34 | ============================================= 35 | The map is the following: 36 | 37 | ....** 38 | ..*... 39 | .***.. 40 | .****. 41 | ...*.. 42 | ...... 43 | 44 | . is a valid square 45 | * is an obstacle 46 | 47 | Top left corner is (1,1). 48 | Bottom right corner is (6,6). 49 | Every move has a cost of 1. 50 | -------------------------------------------------------------------------------- /demo/data/graph1.txt: -------------------------------------------------------------------------------- 1 | 11 13 undirected d 2 | 0 1 4 3 | 0 4 1 4 | 0 6 2 5 | 1 2 7 6 | 1 8 2 7 | 2 3 8 8 | 4 5 7 9 | 4 6 5 10 | 6 7 1 11 | 7 8 6 12 | 7 9 4 13 | 8 10 3 14 | 9 10 2 15 | 16 | -------------------------------------------------------------------------------- /demo/data/graph2.txt: -------------------------------------------------------------------------------- 1 | 9 16 undirected d 2 | 1 2 4 3 | 1 5 5 4 | 1 4 5 5 | 2 5 8 6 | 4 5 10 7 | 4 7 2 8 | 5 7 5 9 | 7 8 1 10 | 5 8 4 11 | 8 0 5 12 | 8 6 12 13 | 5 6 3 14 | 2 6 6 15 | 2 3 10 16 | 3 6 6 17 | 3 0 7 18 | 19 | -------------------------------------------------------------------------------- /demo/data/graph3.txt: -------------------------------------------------------------------------------- 1 | 6 9 directed d 2 | 0 1 6 3 | 0 2 3 4 | 1 2 3 5 | 4 3 1 6 | 3 5 5 7 | 4 5 3 8 | 1 3 3 9 | 2 4 3 10 | 2 3 3 11 | 12 | -------------------------------------------------------------------------------- /demo/data/graph4.txt: -------------------------------------------------------------------------------- 1 | 5 6 directed d 2 | a b c d e f 3 | a c 3 4 | a b 5 5 | c e 3 6 | e d 3 7 | d f 1 8 | f c 7 9 | -------------------------------------------------------------------------------- /demo/src/a_star_demo.erl: -------------------------------------------------------------------------------- 1 | -module(a_star_demo). 2 | -export([b1/0, dump_vertex/1]). 3 | 4 | %% The representation of a vertex. 5 | -type my_vertex() :: {integer(), integer()}. 6 | 7 | -spec b1() -> ok. 8 | b1() -> 9 | File = ?DEMO_DATA ++ "/board1.txt", 10 | %% Heurestic underestimate function 11 | F = fun({X1, Y1}, {X2, Y2}) -> abs(X1 - X2) + abs(Y1 - Y2) end, 12 | G = graph:import(File, fun parse_vertex/1), 13 | {Cost, Path} = a_star:run(G, {1,1}, {6,6}, F), 14 | io:format("Cost: ~p~nPath: ~p~n", [Cost, Path]). 15 | 16 | %% Parses the string that holds a vertex. 17 | -spec parse_vertex(string()) -> my_vertex(). 18 | parse_vertex([$(, X, $,, Y, $)]) -> {X - $0, Y - $0}. 19 | 20 | %% Dumps a vertex to a string. 21 | -spec dump_vertex(my_vertex()) -> string(). 22 | dump_vertex({X, Y}) -> [$(, X + $0, $,, Y + $0, $)]. 23 | -------------------------------------------------------------------------------- /demo/src/bfs_demo.erl: -------------------------------------------------------------------------------- 1 | -module(bfs_demo). 2 | -export([s1/0, s2/0]). 3 | 4 | -spec s1() -> ok. 5 | s1() -> 6 | Root = 0, 7 | G = graph_demo:g1(), 8 | BFS = bfs:run(G, Root), 9 | io:format("~p~n", [BFS]). 10 | 11 | -spec s2() -> ok. 12 | s2() -> 13 | Root = "a", 14 | G = graph_demo:g4(), 15 | BFS = bfs:run(G, Root), 16 | io:format("~p~n", [BFS]). 17 | -------------------------------------------------------------------------------- /demo/src/demo.erl: -------------------------------------------------------------------------------- 1 | -module(demo). 2 | -compile(export_all). 3 | 4 | -spec flow() -> 'true'. 5 | flow() -> 6 | {'ok', RootDir} = file:get_cwd(), 7 | File = RootDir ++ "/demo/data/graph3.txt", 8 | G = graph:from_file(File), 9 | Edmonds = edmonds_karp:run(G, 0, 5, 'bfs'), 10 | Ford = edmonds_karp:run(G, 0, 5, 'dfs'), 11 | io:format("Edmonds-Karp: ~p~n", [Edmonds]), 12 | io:format("Ford-Fulkerson: ~p~n", [Ford]), 13 | erlang:display('demo_ok'). 14 | 15 | -------------------------------------------------------------------------------- /demo/src/dfs_demo.erl: -------------------------------------------------------------------------------- 1 | -module(dfs_demo). 2 | -export([s1/0, s2/0]). 3 | 4 | -spec s1() -> ok. 5 | s1() -> 6 | Root = 0, 7 | G = graph_demo:g1(), 8 | DFS = dfs:run(G, Root), 9 | io:format("~p~n", [DFS]). 10 | 11 | -spec s2() -> ok. 12 | s2() -> 13 | Root = "a", 14 | G = graph_demo:g4(), 15 | DFS = dfs:run(G, Root), 16 | io:format("~p~n", [DFS]). 17 | -------------------------------------------------------------------------------- /demo/src/dijkstra_demo.erl: -------------------------------------------------------------------------------- 1 | -module(dijkstra_demo). 2 | -export([s1/0, s2/0]). 3 | 4 | -spec s1() -> ok. 5 | s1() -> 6 | Root = 0, 7 | G = graph_demo:g1(), 8 | Dijkstra = dijkstra:run(G, Root), 9 | io:format("Root : ~p~n~p~n", [Root, Dijkstra]). 10 | 11 | -spec s2() -> ok. 12 | s2() -> 13 | Root = "a", 14 | G = graph_demo:g4(), 15 | Dijkstra = dijkstra:run(G, Root), 16 | io:format("Root : ~p~n~p~n", [Root, Dijkstra]). 17 | -------------------------------------------------------------------------------- /demo/src/flow_demo.erl: -------------------------------------------------------------------------------- 1 | -module(flow_demo). 2 | -export([s1/0]). 3 | 4 | -spec s1() -> ok. 5 | 6 | s1() -> 7 | G = graph_demo:g3(), 8 | Edmonds = edmonds_karp:run(G, 0, 5, bfs), 9 | Ford = edmonds_karp:run(G, 0, 5, dfs), 10 | io:format("Edmonds-Karp: ~p~n", [Edmonds]), 11 | io:format("Ford-Fulkerson: ~p~n", [Ford]), 12 | ok. 13 | 14 | -------------------------------------------------------------------------------- /demo/src/graph_demo.erl: -------------------------------------------------------------------------------- 1 | -module(graph_demo). 2 | -export([from_file_default/0, from_file_custom/0, manual/0, g1/0, g2/0, g3/0, g4/0]). 3 | 4 | %% Load a graph from file using the default file syntax. 5 | -spec from_file_default() -> ok. 6 | 7 | from_file_default() -> 8 | G = g1(), 9 | graph:pprint(G). 10 | 11 | -spec g1() -> graph:graph(). 12 | g1() -> 13 | File = ?DEMO_DATA ++ "/graph1.txt", 14 | graph:from_file(File). 15 | 16 | %% Load a graph from file using custom vertices. 17 | -spec from_file_custom() -> ok. 18 | 19 | from_file_custom() -> 20 | G = g4(), 21 | graph:pprint(G). 22 | 23 | -spec g4() -> graph:graph(). 24 | g4() -> 25 | File = ?DEMO_DATA ++ "/graph4.txt", 26 | %% Function to read the vertices 27 | ReadVertices = 28 | fun(IO, _N) -> 29 | Ln = io:get_line(IO, ">"), 30 | string:tokens(string:strip(Ln, right, $\n), " ") 31 | end, 32 | %% Function to read each edge 33 | ReadEdge = 34 | fun(IO, _WT) -> 35 | {ok, [V1, V2, W]} = io:fread(IO, ">", "~s ~s ~d"), 36 | {V1, V2, W} 37 | end, 38 | graph:from_file(File, ReadVertices, ReadEdge). 39 | 40 | %% Manually create a graph. 41 | -spec manual() -> ok. 42 | 43 | manual() -> 44 | G = graph:empty(undirected), %% Empty graph 45 | %% Add vertices 46 | lists:foreach(fun(V) -> graph:add_vertex(G, V) end, [athens, paris, london]), 47 | %% Add edges 48 | Es = [{athens, paris, 2096}, {athens, london, 2389}, {paris, london, 342}], 49 | lists:foreach(fun({F, T, W}) -> graph:add_edge(G, F, T, W) end, Es), 50 | graph:pprint(G). 51 | 52 | 53 | -spec g2() -> graph:graph(). 54 | g2() -> 55 | File = ?DEMO_DATA ++ "/graph2.txt", 56 | graph:from_file(File). 57 | 58 | -spec g3() -> graph:graph(). 59 | g3() -> 60 | File = ?DEMO_DATA ++ "/graph3.txt", 61 | graph:from_file(File). 62 | -------------------------------------------------------------------------------- /demo/src/heap_demo.erl: -------------------------------------------------------------------------------- 1 | -module(heap_demo). 2 | -export([min_heap/0, max_heap/0]). 3 | 4 | -spec min_heap() -> ok. 5 | 6 | min_heap() -> 7 | %% Creating a new min-heap 8 | H = heap:new(min), 9 | %% Checking if heap is empty 10 | true = heap:is_empty(H), 11 | %% Adding elements 12 | {6, _R1} = heap:insert(H, 6), 13 | {9, R2} = heap:insert(H, 9), 14 | {3, _R3} = heap:insert(H, 3), 15 | {12, _R4} = heap:insert(H, 12), 16 | %% Checking number of elements in the heap 17 | 4 = heap:heap_size(H), 18 | %% Finding the minimum element (without removing it) 19 | 3 = heap:min(H), 20 | 4 = heap:heap_size(H), 21 | %% Removing the minimum element 22 | 3 = heap:take_min(H), 23 | 3 = heap:heap_size(H), 24 | %% Lowering the priority of an element 25 | true = heap:update(H, R2, 2), 26 | 2 = heap:min(H), 27 | %% Increasing the priority of an element 28 | true = heap:update(H, R2, 20), 29 | 6 = heap:min(H), 30 | %% Removing all the elements 31 | 6 = heap:take_min(H), 32 | 12 = heap:take_min(H), 33 | 20 = heap:take_min(H), 34 | true = heap:is_empty(H), 35 | %% Delete the heap 36 | true = heap:delete(H), 37 | %% Construct heap from a list 38 | L = [{1,6},{2,9},{3,3},{4,12}], 39 | {H2, _R} = heap:from_list(min, L), 40 | true = heap:delete(H2), 41 | ok. 42 | 43 | -spec max_heap() -> ok. 44 | 45 | max_heap() -> 46 | %% Creating a new max-heap 47 | H = heap:new(max), 48 | %% Checking if heap is empty 49 | true = heap:is_empty(H), 50 | %% Adding elements 51 | {6, _R1} = heap:insert(H, 6), 52 | {9, R2} = heap:insert(H, 9), 53 | {3, _R3} = heap:insert(H, 3), 54 | {12, _R4} = heap:insert(H, 12), 55 | %% Checking number of elements in the heap 56 | 4 = heap:heap_size(H), 57 | %% Finding the maximum element (without removing it) 58 | 12 = heap:max(H), 59 | 4 = heap:heap_size(H), 60 | %% Removing the maximum element 61 | 12 = heap:take_max(H), 62 | 3 = heap:heap_size(H), 63 | %% Lowering the priority of an element 64 | true = heap:update(H, R2, 5), 65 | 6 = heap:max(H), 66 | %% Increasing the priority of an element 67 | true = heap:update(H, R2, 20), 68 | 20 = heap:max(H), 69 | %% Removing all the elements 70 | 20 = heap:take_max(H), 71 | 6 = heap:take_max(H), 72 | 3 = heap:take_max(H), 73 | true = heap:is_empty(H), 74 | %% Delete the heap 75 | true = heap:delete(H), 76 | %% Construct heap from a list 77 | L = [{1,6},{2,9},{3,3},{4,12}], 78 | {H2, _R} = heap:from_list(max, L), 79 | true = heap:delete(H2), 80 | ok. 81 | -------------------------------------------------------------------------------- /demo/src/import_export_demo.erl: -------------------------------------------------------------------------------- 1 | -module(import_export_demo). 2 | -export([f/0]). 3 | 4 | -spec f() -> true. 5 | f() -> 6 | G = graph:empty(directed, d), 7 | Foo = graph:add_vertex(G, "foo"), 8 | Bar = graph:add_vertex(G, "bar"), 9 | Baz = graph:add_vertex(G, "baz"), 10 | _ = graph:add_edge(G, Foo, Bar, 20), 11 | _ = graph:add_edge(G, Bar, Foo, 20), 12 | _ = graph:add_edge(G, Bar, Baz, 80), 13 | Id = fun(V) -> V end, 14 | File = "graph.txt", 15 | graph:export(G, File, Id), 16 | G1 = graph:import(File, Id), 17 | true = graph:equal(G, G1). 18 | -------------------------------------------------------------------------------- /demo/src/kruskal_demo.erl: -------------------------------------------------------------------------------- 1 | -module(kruskal_demo). 2 | -export([s1/0, s2/0]). 3 | 4 | -spec s1() -> ok. 5 | s1() -> 6 | G = graph_demo:g1(), 7 | Kruskal = kruskal:run(G), 8 | io:format("~p~n", [Kruskal]). 9 | 10 | -spec s2() -> ok. 11 | s2() -> 12 | G = graph_demo:g4(), 13 | Kruskal = kruskal:run(G), 14 | io:format("~p~n", [Kruskal]). 15 | -------------------------------------------------------------------------------- /demo/src/union_find_demo.erl: -------------------------------------------------------------------------------- 1 | -module(union_find_demo). 2 | -export([uf1/0]). 3 | 4 | -spec uf1() -> ok. 5 | 6 | uf1() -> 7 | L = [a,b,c,d,e,f,g,h,i,j], 8 | F = union_find:singletons_from_list(L), 9 | %% union_find:pprint(F), 10 | 10 = union_find:number_of_sets(F), 11 | true = union_find:union(F, a, e), 12 | %% union_find:pprint(F), 13 | true = union_find:union(F, a, d), 14 | %% union_find:pprint(F), 15 | true = union_find:union(F, g, i), 16 | %% union_find:pprint(F), 17 | true = union_find:union(F, h, f), 18 | union_find:pprint(F), 19 | 6 = union_find:number_of_sets(F), 20 | [a,d,e] = lists:sort(union_find:set_elements(F, e)), 21 | g = union_find:find(F, i), 22 | 3 = union_find:set_size(F, e), 23 | true = union_find:delete(F), 24 | ok. 25 | -------------------------------------------------------------------------------- /doc/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !overview.edoc 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /doc/overview.edoc: -------------------------------------------------------------------------------- 1 | @author Aggelos Giantsios 2 | @copyright 2013-2014 Aggelos Giantsios 3 | @title Welcome to erlang-algorithms! 4 | @doc The goal of this project is to implement some useful algorithms and data structures in Erlang so as to help anyone who may need them. 5 | 6 | == Currently Implemented Data Structures == 7 | 12 | 13 | == Currently Implemented Algorithms == 14 | 22 | 23 | -------------------------------------------------------------------------------- /ebin/.gitignore: -------------------------------------------------------------------------------- 1 | *.beam 2 | -------------------------------------------------------------------------------- /makedoc.rb: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | 3 | puts `erl -noinput -pa ebin/ -eval "doc:make_doc()" -s init stop` 4 | 5 | 6 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {erl_opts, [ debug_info ]}. 2 | 3 | {deps, []}. 4 | -------------------------------------------------------------------------------- /rebar.lock: -------------------------------------------------------------------------------- 1 | []. 2 | -------------------------------------------------------------------------------- /rundemo.rb: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | 3 | puts "Creating Graphs" 4 | puts "===============" 5 | puts "\ngraph_demo:from_file_default()" 6 | puts `erl -noshell -pa ebin -eval "graph_demo:from_file_default()" -s init stop` 7 | puts "\ngraph_demo:from_file_custom()" 8 | puts `erl -noshell -pa ebin -eval "graph_demo:from_file_custom()" -s init stop` 9 | puts "\ngraph_demo:manual()" 10 | puts `erl -noshell -pa ebin -eval "graph_demo:manual()" -s init stop` 11 | 12 | puts "\nHeaps" 13 | puts "======" 14 | puts "\nheap_demo:min_heap()" 15 | puts `erl -noshell -pa ebin -eval "heap_demo:min_heap()" -s init stop` 16 | puts "\nheap_demo:max_heap()" 17 | puts `erl -noshell -pa ebin -eval "heap_demo:max_heap()" -s init stop` 18 | 19 | puts "\nUnion Find" 20 | puts "===========" 21 | puts "\nunion_find_demo:uf1()" 22 | puts `erl -noshell -pa ebin -eval "union_find_demo:uf1()" -s init stop` 23 | 24 | puts "\nBFS Algorithm" 25 | puts "==============" 26 | puts "\nbfs_demo:s1()" 27 | puts `erl -noshell -pa ebin -eval "bfs_demo:s1()" -s init stop` 28 | puts "\nbfs_demo:s2()" 29 | puts `erl -noshell -pa ebin -eval "bfs_demo:s2()" -s init stop` 30 | 31 | puts "\nDFS Algorithm" 32 | puts "==============" 33 | puts "\ndfs_demo:s1()" 34 | puts `erl -noshell -pa ebin -eval "dfs_demo:s1()" -s init stop` 35 | puts "\ndfs_demo:s2()" 36 | puts `erl -noshell -pa ebin -eval "dfs_demo:s2()" -s init stop` 37 | 38 | puts "\nDijkstra Algorithm" 39 | puts "===================" 40 | puts "\ndijkstra_demo:s1()" 41 | puts `erl -noshell -pa ebin -eval "dijkstra_demo:s1()" -s init stop` 42 | puts "\ndijkstra_demo:s2()" 43 | puts `erl -noshell -pa ebin -eval "dijkstra_demo:s2()" -s init stop` 44 | 45 | puts "\nKruskal Algorithm" 46 | puts "==================" 47 | puts "\nkruskal_demo:s1()" 48 | puts `erl -noshell -pa ebin -eval "kruskal_demo:s1()" -s init stop` 49 | puts "\nkruskal_demo:s2()" 50 | puts `erl -noshell -pa ebin -eval "kruskal_demo:s2()" -s init stop` 51 | 52 | puts "\nEdmonds-Karp and Ford-Fulkerson algorithms" 53 | puts "===========================================" 54 | puts "\nflow_demo:s1()" 55 | puts `erl -noshell -pa ebin -eval "flow_demo:s1()" -s init stop` 56 | 57 | puts "\nA* Search Algorithm" 58 | puts "===================" 59 | puts "\na_star_demo:b1()" 60 | puts `erl -noshell -pa ebin -eval "a_star_demo:b1()" -s init stop` 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/a_star.erl: -------------------------------------------------------------------------------- 1 | %% @doc A* Algorithm 2 | %% 3 | %%

A* calculates the least expensive path from a Root node to a Target 4 | %% node. It uses an admissible heuristic function H (that underestimates the cost) in order to visit 5 | %% nodes that are more likely to lead to the Target faster.

6 | %% 7 | %%

For examples you can check the a_star_demo module.

8 | %% 9 | 10 | -module(a_star). 11 | -export([run/4]). 12 | 13 | -export_type([astar_result/0]). 14 | 15 | -type open_set() :: dict:dict(). 16 | -type closed_set() :: gb_sets:set(). 17 | -type f_score() :: heap:heap(). 18 | -type g_score() :: dict:dict(). 19 | -type parents() :: dict:dict(). 20 | 21 | -record(sts, { 22 | graph :: graph:graph(), 23 | target :: graph:vertex(), 24 | h :: function(), 25 | open :: open_set(), 26 | closed :: closed_set(), 27 | fscore :: f_score(), 28 | gscore :: g_score(), 29 | parents :: parents() 30 | }). 31 | -type astar_state() :: #sts{}. 32 | -type astar_result() :: {graph:weight(), graph_lib:vpath()} | unreachable. 33 | 34 | %% ---------------------------------------------------------- 35 | %% A* Abstractions 36 | %% ---------------------------------------------------------- 37 | 38 | %% Graph Abstractions 39 | -define(GET_NEIGHBOURS(Vertex, Graph), graph:out_neighbours(Graph, Vertex)). 40 | -define(EDGE_WEIGHT(Graph, From, To), graph:edge_weight(Graph, {From, To})). 41 | %% Open Set Abstractions 42 | -define(EMPTY_OPEN, dict:new()). 43 | -define(IS_OPEN_EMPTY(Open), dict:size(Open) =:= 0). 44 | -define(ADD_TO_OPEN(Node, HeapRef, Open), dict:store(Node, HeapRef, Open)). 45 | -define(REMOVE_FROM_OPEN(Node, Open), dict:erase(Node, Open)). 46 | -define(IS_IN_OPEN(Node, Open), dict:is_key(Node, Open)). 47 | -define(GET_REF(Node, Open), dict:fetch(Node, Open)). 48 | %% Closed Set Abstractions 49 | -define(EMPTY_CLOSED, gb_sets:new()). 50 | -define(ADD_TO_CLOSED(Node, Closed), gb_sets:add(Node, Closed)). 51 | -define(IS_IN_CLOSED(Node, Closed), gb_sets:is_element(Node, Closed)). 52 | %% F score Abstractions 53 | -define(EMPTY_FSCORE, heap:new(min)). 54 | -define(SET_FSCORE(Node, Score, Fscore), erlang:element(2,heap:insert(Fscore, {Score, Node}))). 55 | -define(MIN_FSCORE(Fscore), erlang:element(2, heap:take_min(Fscore))). 56 | -define(UPDATE_FSCORE(Node, Ref, Score, Fscore), heap:update(Fscore, Ref, {Score, Node})). 57 | %% G score Abstractions 58 | -define(EMPTY_GSCORE, dict:new()). 59 | -define(SET_GSCORE(Node, Score, Gscore), dict:store(Node, Score, Gscore)). 60 | -define(UPDATE_GSCORE(Node, Score, Gscore), dict:store(Node, Score, Gscore)). 61 | -define(GET_GSCORE(Node, Gscore), dict:fetch(Node, Gscore)). 62 | %% Parents Abstractions 63 | -define(EMPTY_PARENTS, dict:new()). 64 | -define(ADD_PARENT(Node, Prev, Ps), dict:store(Node, Prev, Ps)). 65 | -define(SET_PARENT(Node, Prev, Ps), dict:store(Node, Prev, Ps)). 66 | -define(GET_PARENT(Node, Ps), dict:fetch(Node, Ps)). 67 | 68 | %% ========================================================== 69 | %% Exported Functions 70 | %% ========================================================== 71 | 72 | %% @doc Runs the A* algorithm on a graph Graph 73 | %% with Root as the point of origin 74 | %% and Target as the point of destination. 75 | %% 76 | %%

If such a path exists, it returns a tuple with the cost 77 | %% and the actual best path. If not, it returns unreachable.

78 | %% 79 | %%

H is the admissible heuristic function. Its spec is 80 | %% fun(Node :: graph:vertex(), Target :: graph:vertex()) -> EstimatedCost :: graph:weight().

81 | -spec run(graph:graph(), graph:vertex(), graph:vertex(), function()) -> astar_result(). 82 | 83 | run(Graph, Root, Target, H) -> 84 | State = astar_init(Graph, Root, Target, H), 85 | R = astar_step(State), 86 | astar_cleanup(State), 87 | R. 88 | 89 | %% ========================================================== 90 | %% A* Functions 91 | %% ========================================================== 92 | 93 | %% Initalize State Graph, Target Node, H function, Open Set, Closed Set, Fscore, Gscore, Parents 94 | -spec astar_init(graph:graph(), graph:vertex(), graph:vertex(), function()) -> astar_state(). 95 | astar_init(G, Root, Target, H) -> 96 | Fscore = ?EMPTY_FSCORE, 97 | RootRef = ?SET_FSCORE(Root, H(Root, Target), Fscore), 98 | Open = ?ADD_TO_OPEN(Root, RootRef, ?EMPTY_OPEN), 99 | Csd = ?EMPTY_CLOSED, 100 | Gscore = ?SET_GSCORE(Root, 0, ?EMPTY_GSCORE), 101 | Ps = ?ADD_PARENT(Root, root, ?EMPTY_PARENTS), 102 | #sts{graph=G, target=Target, h=H, open=Open, closed=Csd, fscore=Fscore, gscore=Gscore, parents=Ps}. 103 | 104 | %% Cleanup the state. 105 | astar_cleanup(State) -> 106 | heap:delete(State#sts.fscore). 107 | 108 | %% A* loop 109 | -spec astar_step(astar_state()) -> astar_result(). 110 | astar_step(S) -> 111 | case ?IS_OPEN_EMPTY(S#sts.open) of 112 | true -> unreachable; 113 | false -> astar_step_reachable(S) 114 | end. 115 | 116 | %% A* loop (helper function) 117 | -spec astar_step_reachable(astar_state()) -> astar_result(). 118 | astar_step_reachable(S) -> 119 | Node = ?MIN_FSCORE(S#sts.fscore), 120 | case Node =:= S#sts.target of 121 | true -> astar_result(Node, S); 122 | false -> 123 | Open_n = ?REMOVE_FROM_OPEN(Node, S#sts.open), 124 | Csd_n = ?ADD_TO_CLOSED(Node, S#sts.closed), 125 | Moves = ?GET_NEIGHBOURS(Node, S#sts.graph), 126 | Gcurr = ?GET_GSCORE(Node, S#sts.gscore), 127 | S_n = consider(Moves, Node, Gcurr, S#sts{open=Open_n, closed=Csd_n}), 128 | astar_step(S_n) 129 | end. 130 | 131 | %% Consider all the possible moves and update the state (if necessary) 132 | -spec consider([graph:vertex()], graph:vertex(), graph:weight(), astar_state()) -> astar_state(). 133 | consider([], _Curr, _Gcurr, S) -> S; 134 | consider([M|Ms], Curr, Gcurr, S) -> 135 | case ?IS_IN_CLOSED(M, S#sts.closed) of 136 | true -> consider(Ms, Curr, Gcurr, S); 137 | false -> 138 | Gv = Gcurr + ?EDGE_WEIGHT(S#sts.graph, Curr, M), 139 | case ?IS_IN_OPEN(M, S#sts.open) of 140 | true -> 141 | G_old = ?GET_GSCORE(M, S#sts.gscore), 142 | case Gv =< G_old of 143 | false -> consider(Ms, Curr, Gcurr, S); 144 | true -> 145 | Gscore_n = ?UPDATE_GSCORE(M, Gv, S#sts.gscore), 146 | Ref = ?GET_REF(M, S#sts.open), 147 | Fv = Gv + (S#sts.h)(M, S#sts.target), 148 | true = ?UPDATE_FSCORE(M, Ref, Fv, S#sts.fscore), 149 | Ps_n = ?SET_PARENT(M, Curr, S#sts.parents), 150 | consider(Ms, Curr, Gcurr, S#sts{gscore=Gscore_n, parents=Ps_n}) 151 | end; 152 | false -> 153 | Fv = Gv + (S#sts.h)(M, S#sts.target), 154 | MRef = ?SET_FSCORE(M, Fv, S#sts.fscore), 155 | Open_n = ?ADD_TO_OPEN(M, MRef, S#sts.open), 156 | Gscore_n = ?SET_GSCORE(M, Gv, S#sts.gscore), 157 | Ps_n = ?ADD_PARENT(M, Curr, S#sts.parents), 158 | consider(Ms, Curr, Gcurr, S#sts{open=Open_n, gscore=Gscore_n, parents=Ps_n}) 159 | end 160 | end. 161 | 162 | %% Form the result of the algorithm 163 | -spec astar_result(graph:vertex(), astar_state()) -> astar_result(). 164 | astar_result(Node, S) -> 165 | Gv = ?GET_GSCORE(Node, S#sts.gscore), 166 | Path = astar_path(Node, S#sts.parents), 167 | {Gv, Path}. 168 | 169 | %% Reconstruct the path 170 | -spec astar_path(graph:vertex(), parents()) -> graph_lib:vpath(). 171 | astar_path(Node, Ps) -> astar_path(Node, Ps, [Node]). 172 | 173 | %% Reconstruct the path (helper function) 174 | -spec astar_path(graph:vertex(), parents(), graph_lib:vpath()) -> graph_lib:vpath(). 175 | astar_path(Node, Ps, Acc) -> 176 | case ?GET_PARENT(Node, Ps) of 177 | root -> Acc; 178 | P -> astar_path(P, Ps, [P|Acc]) 179 | end. 180 | -------------------------------------------------------------------------------- /src/bfs.erl: -------------------------------------------------------------------------------- 1 | %% @doc BFS Algorithm 2 | %% 3 | %%

For examples you can check the bfs_demo module.

4 | %% 5 | 6 | -module(bfs). 7 | 8 | -export([run/2]). 9 | 10 | -type states() :: dict:dict(). 11 | -type parents() :: dict:dict(). 12 | 13 | %% ---------------------------------------------------------- 14 | %% BFS Abstractions 15 | %% ---------------------------------------------------------- 16 | 17 | %% Queue Abstraction 18 | -define(EMPTY_QUEUE(), queue:new()). 19 | -define(IS_EMPTY(Q), queue:is_empty(Q)). 20 | -define(ADD_TO_QUEUE(Node, Cost, Q), queue:in({Node, Cost}, Q)). 21 | -define(FILTER_EXTRACT(R), {erlang:element(2, erlang:element(1, R)), erlang:element(2, R)}). 22 | -define(EXTRACT_FROM_QUEUE(Q), ?FILTER_EXTRACT(queue:out(Q))). 23 | %% States Abstractions 24 | %% State 'A' : not visited 25 | %% State 'Y' : explored but not added to the result set 26 | %% State 'E' : explored and added to result set 27 | -define(EMPTY_STATES(), dict:new()). 28 | -define(SET_STATE(Node, State, States), dict:store(Node, State, States)). 29 | -define(GET_STATE(Node, States), dict:fetch(Node, States)). 30 | %% Parents Abstractions 31 | -define(EMPTY_PARENTS(), dict:new()). 32 | -define(ADD_TO_PARENTS(Node, Cost, Prev, R), dict:store(Node, {Cost, Prev}, R)). 33 | 34 | %% ========================================================== 35 | %% Exported Functions 36 | %% ========================================================== 37 | 38 | %% @doc Runs the BFS algorithm on a graph Graph 39 | %% with Root as point of origin 40 | -spec run(graph:graph(), graph:vertex()) -> [graph_lib:path_info()]. 41 | run(Graph, Root) -> 42 | {Q, M, P, Vertices} = bfs_init(Graph, Root), 43 | Result = bfs_step(Graph, Q, M, P), 44 | graph_lib:reconstruct_all_paths(Vertices, Result). 45 | 46 | %% ========================================================== 47 | %% BFS Functions 48 | %% ========================================================== 49 | 50 | %% Initialize data structures 51 | -spec bfs_init(graph:graph(), graph:vertex()) -> {queue:queue(), states(), parents(), [graph:vertex()]}. 52 | bfs_init(Graph, Root) -> 53 | Q = ?ADD_TO_QUEUE(Root, 0, ?EMPTY_QUEUE()), 54 | Ps = ?ADD_TO_PARENTS(Root, 0, root, ?EMPTY_PARENTS()), 55 | Vs = graph:vertices(Graph), 56 | Ms = lists:foldl(fun(V, M) -> ?SET_STATE(V, 'A', M) end, ?EMPTY_STATES(), Vs), 57 | NMs = ?SET_STATE(Root, 'Y', Ms), 58 | {Q, NMs, Ps, Vs}. 59 | 60 | %% BFS loop 61 | -spec bfs_step(graph:graph(), queue:queue(), states(), parents()) -> parents(). 62 | bfs_step(Graph, Qe, Ms, Ps) -> 63 | case ?IS_EMPTY(Qe) of 64 | true -> Ps; 65 | false -> 66 | {{U, UCost}, NQ} = ?EXTRACT_FROM_QUEUE(Qe), 67 | NMs = ?SET_STATE(U, 'E', Ms), 68 | Neighbours = graph:out_neighbours(Graph, U), 69 | {NxtQ, NxtM, NxtP} = 70 | lists:foldl( 71 | fun(V, {Q, M, P}) -> 72 | case ?GET_STATE(V, M) of 73 | 'A' -> 74 | W = graph:edge_weight(Graph, {U, V}), 75 | Cost = UCost + W, 76 | QQ = ?ADD_TO_QUEUE(V, Cost, Q), 77 | MM = ?SET_STATE(V, 'Y', M), 78 | PP = ?ADD_TO_PARENTS(V, Cost, U, P), 79 | {QQ, MM, PP}; 80 | _ -> {Q, M, P} 81 | end 82 | end, 83 | {NQ, NMs, Ps}, 84 | Neighbours 85 | ), 86 | bfs_step(Graph, NxtQ, NxtM, NxtP) 87 | end. 88 | 89 | -------------------------------------------------------------------------------- /src/dfs.erl: -------------------------------------------------------------------------------- 1 | %% @doc DFS Algorithm 2 | %% 3 | %%

For examples you can check the dfs_demo module.

4 | %% 5 | 6 | -module(dfs). 7 | 8 | -export([run/2]). 9 | 10 | -type stack() :: [{graph:vertex(), term()}]. 11 | -type states() :: dict:dict(). 12 | -type parents() :: dict:dict(). 13 | 14 | %% ---------------------------------------------------------- 15 | %% DFS Abstractions 16 | %% ---------------------------------------------------------- 17 | 18 | %% Stack Abstractions 19 | -define(EMPTY_STACK(), []). 20 | -define(IS_EMPTY(S), S =:= []). 21 | -define(ADD_TO_STACK(Node, Cost, S), [{Node, Cost}|S]). 22 | -define(REMOVE_FROM_STACK(S), {hd(S), tl(S)}). 23 | %% States Abstractions 24 | %% State 'A' : not visited 25 | %% State 'Y' : explored but not added to the result set 26 | %% State 'E' : explored and added to result set 27 | -define(EMPTY_STATES(), dict:new()). 28 | -define(SET_STATE(Node, State, States), dict:store(Node, State, States)). 29 | -define(GET_STATE(Node, States), dict:fetch(Node, States)). 30 | %% Parents Abstractions 31 | -define(EMPTY_PARENTS(), dict:new()). 32 | -define(ADD_TO_PARENTS(Node, Cost, Prev, R), dict:store(Node, {Cost, Prev}, R)). 33 | 34 | %% ========================================================== 35 | %% Exported Functions 36 | %% ========================================================== 37 | 38 | %% @doc Runs the DFS algorithm on a graph Graph 39 | %% with Root as point of origin 40 | -spec run(graph:graph(), graph:vertex()) -> [graph_lib:path_info()]. 41 | run(Graph, Root) -> 42 | {S, M, P, Vertices} = dfs_init(Graph, Root), 43 | Result = dfs_step(Graph, S, M, P), 44 | graph_lib:reconstruct_all_paths(Vertices, Result). 45 | 46 | %% ========================================================== 47 | %% BFS Functions 48 | %% ========================================================== 49 | 50 | %% Initialize data structures 51 | -spec dfs_init(graph:graph(), graph:vertex()) -> {stack(), states(), parents(), [graph:vertex()]}. 52 | dfs_init(Graph, Root) -> 53 | Ms = ?EMPTY_STATES(), 54 | S = ?ADD_TO_STACK(Root, 0, ?EMPTY_STACK()), 55 | Ps = ?ADD_TO_PARENTS(Root, 0, root, ?EMPTY_PARENTS()), 56 | Vs = graph:vertices(Graph), 57 | NMs = lists:foldl(fun(V, M) -> ?SET_STATE(V, 'A', M) end, Ms, Vs), 58 | {S, NMs, Ps, Vs}. 59 | 60 | %% DFS loop 61 | -spec dfs_step(graph:graph(), stack(), states(), parents()) -> parents(). 62 | dfs_step(Graph, S, M, P) -> 63 | case ?IS_EMPTY(S) of 64 | true -> P; 65 | false -> 66 | {{V, Cost}, NS} = ?REMOVE_FROM_STACK(S), 67 | NM = ?SET_STATE(V, 'Y', M), 68 | Neighbours = graph:out_neighbours(Graph, V), 69 | {NxtP, NxtS} = 70 | lists:foldl( 71 | fun(U, {FP, FS}) -> 72 | case ?GET_STATE(U, NM) of 73 | 'A' -> 74 | W = graph:edge_weight(Graph, {V, U}), 75 | NCost = Cost + W, 76 | SS = ?ADD_TO_STACK(U, NCost, FS), 77 | PP = ?ADD_TO_PARENTS(U, NCost, V, FP), 78 | {PP, SS}; 79 | _ -> 80 | {FP, FS} 81 | end 82 | end, 83 | {P, NS}, Neighbours), 84 | NxtM = ?SET_STATE(V, 'E', M), 85 | dfs_step(Graph, NxtS, NxtM, NxtP) 86 | end. 87 | 88 | -------------------------------------------------------------------------------- /src/dijkstra.erl: -------------------------------------------------------------------------------- 1 | %% @doc Dijkstra's Algorithm 2 | %% 3 | %%

For examples you can check the dijsktra_demo module.

4 | %% 5 | 6 | -module(dijkstra). 7 | 8 | -export([run/2]). 9 | 10 | -type open_set() :: gb_trees:tree(). 11 | -type closed_set() :: dict:dict(). 12 | -type parents() :: dict:dict(). 13 | 14 | %% ---------------------------------------------------------- 15 | %% Dijkstra Abstractions 16 | %% ---------------------------------------------------------- 17 | 18 | %% Graph Abstractions 19 | -define(EDGE_WEIGHT(Graph, Edge), graph:edge_weight(Graph, Edge)). 20 | -define(GET_NEIGHBOURS(Vertex, Graph), graph:out_neighbours(Graph, Vertex)). 21 | %% Heap (Open Set) Abstractions 22 | -define(IS_EMPTY_HEAP(Heap), gb_trees:is_empty(Heap)). 23 | -define(EMPTY_HEAP, gb_trees:empty()). 24 | -define(INSERT_NODE_TO_HEAP(Node, Prev, Cost, Heap), gb_trees:insert({Cost, Node, Prev}, 0, Heap)). 25 | -define(FILTER_MIN_HEAP(R), erlang:append_element(element(1, R), element(3, R))). 26 | -define(GET_MIN_HEAP(Heap), ?FILTER_MIN_HEAP(gb_trees:take_smallest(Heap))). 27 | %% Visited (Closed Set) Abstractions 28 | -define(EMPTY_VISITED, dict:new()). 29 | -define(IS_VISITED(V, Visited), dict:is_key(V, Visited)). 30 | -define(ADD_TO_VISITED(Node, OldVisited), dict:store(Node, 'true', OldVisited)). 31 | %% Parents Abstractions 32 | -define(EMPTY_PARENTS, dict:new()). 33 | -define(ADD_TO_PARENTS(Node, Cost, Prev, P), dict:store(Node, {Cost, Prev}, P)). 34 | 35 | %% ========================================================== 36 | %% Exported Functions 37 | %% ========================================================== 38 | 39 | %% @doc Runs the Dijkstra algorithm on a graph Graph 40 | %% with Root as point of origin 41 | -spec run(graph:graph(), graph:vertex()) -> [graph_lib:path_info()]. 42 | run(Graph, Root) -> 43 | {Heap, Visited, Parents} = dijkstra_init(Root), 44 | R = dijkstra_step(Graph, Heap, Visited, Parents), 45 | Vertices = graph:vertices(Graph), 46 | graph_lib:reconstruct_all_paths(Vertices, R). 47 | 48 | %% ========================================================== 49 | %% Dijkstra Functions 50 | %% ========================================================== 51 | 52 | %% Initalize Heap (Open Set), Visited (Closed Set), Parents 53 | -spec dijkstra_init(graph:vertex()) -> {open_set(), closed_set(), parents()}. 54 | dijkstra_init(Root) -> 55 | Heap = ?INSERT_NODE_TO_HEAP(Root, root, 0, ?EMPTY_HEAP), 56 | Visited = ?EMPTY_VISITED, 57 | Parents = ?EMPTY_PARENTS, 58 | {Heap, Visited, Parents}. 59 | 60 | %% Dijkstra loop 61 | -spec dijkstra_step(graph:graph(), open_set(), closed_set(), parents()) -> parents(). 62 | dijkstra_step(Graph, Heap, Visited, Parents) -> 63 | case ?IS_EMPTY_HEAP(Heap) of 64 | true -> 65 | Parents; 66 | false -> 67 | {Cost, Node, Prev, NewHeap} = ?GET_MIN_HEAP(Heap), 68 | case ?IS_VISITED(Node, Visited) of 69 | true -> 70 | dijkstra_step(Graph, NewHeap, Visited, Parents); 71 | false -> 72 | NewParents = ?ADD_TO_PARENTS(Node, Cost, Prev, Parents), 73 | NewVisited = ?ADD_TO_VISITED(Node, Visited), 74 | AdjList = ?GET_NEIGHBOURS(Node, Graph), 75 | NextHeap = 76 | lists:foldl( 77 | fun(V, H) -> 78 | case ?IS_VISITED(V, NewVisited) of 79 | true -> 80 | H; 81 | false -> 82 | Edge = {Node, V}, 83 | Weight = ?EDGE_WEIGHT(Graph, Edge), 84 | ?INSERT_NODE_TO_HEAP(V, Node, Cost + Weight, H) 85 | end 86 | end, 87 | NewHeap, AdjList 88 | ), 89 | dijkstra_step(Graph, NextHeap, NewVisited, NewParents) 90 | end 91 | end. 92 | 93 | -------------------------------------------------------------------------------- /src/doc.erl: -------------------------------------------------------------------------------- 1 | -module(doc). 2 | -export([make_doc/0]). 3 | 4 | -spec make_doc() -> ok. 5 | make_doc() -> 6 | Src = filename:absname("src"), 7 | Mods = [graph, heap, union_find, dfs, bfs, graph_lib, dijkstra, kruskal, edmonds_karp, a_star], 8 | Fs = [Src ++ "/" ++ atom_to_list(M) ++ ".erl" || M <- Mods], 9 | edoc:files(Fs, [{dir, "doc"}]). 10 | -------------------------------------------------------------------------------- /src/edmonds_karp.erl: -------------------------------------------------------------------------------- 1 | %% @doc Edmonds-Karp / Ford-Fulkerson Algorithms 2 | %% 3 | %%

Calculates the Maximum Flow in a Network (Directed Graph)

4 | %% 5 | %%

For examples you can check the flow_demo module.

6 | %% 7 | 8 | -module(edmonds_karp). 9 | 10 | -export([run/4]). 11 | 12 | -type mode() :: bfs | dfs. 13 | 14 | %% ========================================================== 15 | %% Exported Functions 16 | %% ========================================================== 17 | 18 | %% @doc Runs the Edmonds-Karp or Ford-Fulkerson algorithm 19 | %% on a graph G with S as source 20 | %% as T as sink. 21 | %% 22 | %%

When Mode is dfs the algorithm is 23 | %% called Ford-Fulkerson and when Mode is 24 | %% bfs the algorithm is called Edmonds-Karp.

25 | %% 26 | -spec run(graph:graph(), graph:vertex(), graph:vertex(), mode()) -> graph_lib:flow() | {'error', 'not_network'}. 27 | 28 | run(G, S, T, Mode) when Mode =:= bfs; Mode =:= dfs -> 29 | case graph:graph_type(G) of 30 | directed -> 31 | {Flow, RN} = init_residual_network(G), 32 | ok = edmonds_karp_step(G, RN, Flow, S, T, Mode), 33 | Out = graph_lib:reconstruct_flow(ets:tab2list(Flow)), 34 | %% clean up residual graph and flow list 35 | graph:del_graph(RN), 36 | ets:delete(Flow), 37 | Out; 38 | undirected -> 39 | {error, not_network} 40 | end. 41 | 42 | %% ========================================================== 43 | %% Edmonds-Karp / Ford-FulkersonFunctions 44 | %% ========================================================== 45 | 46 | %% Edmonds-Karp loop 47 | -spec edmonds_karp_step(graph:graph(), graph:graph(), ets:tid(), graph:vertex(), graph:vertex(), mode()) -> 'ok'. 48 | edmonds_karp_step(G, RN, Flow, S, T, M) -> 49 | case augmenting_path(RN, S, T, M) of 50 | no_path -> ok; 51 | EPath -> 52 | RN_n = update_residual_network(G, RN, Flow, EPath), 53 | edmonds_karp_step(G, RN_n, Flow, S, T, M) 54 | end. 55 | 56 | %% Initialize Residual Network 57 | -spec init_residual_network(graph:graph()) -> {ets:tid(), graph:graph()}. 58 | init_residual_network(G) -> 59 | Vs = graph:vertices(G), 60 | Es = graph:edges_with_weights(G), 61 | RN = graph:empty(directed), 62 | F = ets:new(f, [ordered_set]), 63 | ets:insert(F, {flow, 0}), 64 | lists:foreach(fun({E, _W}) -> ets:insert(F, {E, 0}) end, Es), 65 | lists:foreach(fun(V) -> graph:add_vertex(RN, V) end, Vs), 66 | lists:foreach(fun({{U, V}, W}) -> graph:add_edge(RN, U, V, W) end, Es), 67 | {F, RN}. 68 | 69 | %% Find an augmenting path 70 | -spec augmenting_path(graph:graph(), graph:vertex(), graph:vertex(), mode()) -> graph_lib:epath_weighted() | 'no_path'. 71 | augmenting_path(RN, S, T, M) -> 72 | Traversal = 73 | case M of 74 | dfs -> dfs:run(RN, S); 75 | bfs -> bfs:run(RN, S) 76 | end, 77 | case proplists:get_value(T, Traversal) of 78 | unreachable -> no_path; 79 | {_Cost, VPath} -> path_edges(RN, VPath) 80 | end. 81 | 82 | %% Return the edges visited in a path 83 | -spec path_edges(graph:graph(), graph_lib:vpath()) -> graph_lib:epath_weighted(). 84 | path_edges(RN, Path) -> path_edges(RN, Path, []). 85 | 86 | %% Helper funciton path_edges/3 87 | -spec path_edges(graph:graph(), graph_lib:vpath(), graph_lib:epath_weighted()) -> graph_lib:epath_weighted(). 88 | path_edges(_RN, [_V], Es) -> 89 | lists:reverse(Es); 90 | path_edges(RN, [U, V|Ps], Es) -> 91 | E = {U, V}, 92 | W = graph:edge_weight(RN, E), 93 | path_edges(RN, [V|Ps], [{E, W}|Es]). 94 | 95 | %% Update the Residual Network with the information 96 | %% of an augmenting path 97 | -spec update_residual_network(graph:graph(), graph:graph(), ets:tid(), graph_lib:epath_weighted()) -> graph:graph(). 98 | update_residual_network(G, RN, Flow, EPath) -> 99 | %% Get the flow increase 100 | [{_, Cf}|_] = lists:sort(fun({_E1, W1}, {_E2, W2}) -> W1 < W2 end, EPath), 101 | %% Update the flow 102 | [{flow, CurrFlow}] = ets:lookup(Flow, flow), 103 | ets:insert(Flow, {flow, CurrFlow + Cf}), 104 | %% Update the residual network 105 | lists:foreach( 106 | fun({{U, V}, _W}) -> 107 | {{From, To}=E, W} = 108 | case {graph:edge_weight(G, {U,V}), graph:edge_weight(G, {V,U})} of 109 | {false, C} -> {{V, U}, C}; 110 | {C, false} -> {{U, V}, C} 111 | end, 112 | graph:del_edge(RN, {U, V}), 113 | graph:del_edge(RN, {V, U}), 114 | [{E, F}] = ets:lookup(Flow, E), 115 | NF = F + Cf, 116 | ets:insert(Flow, {E, NF}), 117 | _ = case W-NF > 0 of 118 | true -> graph:add_edge(RN, From, To, W-NF); 119 | false -> ok 120 | end, 121 | _ = case NF > 0 of 122 | true -> graph:add_edge(RN, To, From, NF); 123 | false -> ok 124 | end 125 | end, 126 | EPath 127 | ), 128 | RN. 129 | 130 | -------------------------------------------------------------------------------- /src/erlang-algorithms.app.src: -------------------------------------------------------------------------------- 1 | {application, 'erlang-algorithms', [ 2 | {registered, []}, 3 | {vsn, "1.0.0"}, 4 | {description, 5 | "Implementations of popular data structures and algorithms http://aggelgian.github.com/erlang-algorithms"}, 6 | {applications, [ kernel, stdlib ]} 7 | ]}. 8 | -------------------------------------------------------------------------------- /src/graph.erl: -------------------------------------------------------------------------------- 1 | %% @doc Directed / Undirected Graphs 2 | %% 3 | %%

This module implements directed and undirected graphs that are either 4 | %% weighted with numeric weights or unweighted.

5 | %% 6 | %%

It is basically syntactic sugar for the digraph module with added 7 | %% support for undirected graphs.

8 | %% 9 | %%

How to use

10 | %%

The fastest way to create a graph is to load it from a file with from_file/1. 11 | %% The file that contains the graph must have the following format.

12 | %% 13 | %% 32 | %% 33 | %%

Alternative syntax

34 | %%

There is also the ability to explicitly state the vertices of the graph and load the graph with from_file/3. 35 | %% In this case the above format is amended as follows:

36 | %% 37 | %% 53 | %% 54 | %%

For examples you can check the graph_demo module.

55 | %% 56 | 57 | -module(graph). 58 | 59 | -export([from_file/1, from_file/3, del_graph/1, vertices/1, edges/1, edge_weight/2, 60 | edges_with_weights/1, out_neighbours/2, num_of_vertices/1, equal/2, 61 | num_of_edges/1, pprint/1, empty/1, empty/2, add_vertex/2, add_edge/3, 62 | add_edge/4, graph_type/1, del_edge/2, weight_type/1, export/3, import/2]). 63 | 64 | -export_type([graph/0, vertex/0, edge/0, weight/0]). 65 | 66 | %% 67 | %% @type graph(). A directed or undirected graph. 68 | %%

It is wrapper for a digraph with the extra information on its type.

69 | %% 70 | -record(graph, { 71 | type :: graphtype(), 72 | graph :: digraph:graph(), 73 | weightType :: weighttype() 74 | }). 75 | -type graph() :: #graph{}. 76 | -type vertex() :: term(). 77 | -type edge() :: {vertex(), vertex()}. 78 | -type graphtype() :: directed | undirected. 79 | -type weight() :: number(). 80 | -type weighttype() :: unweighted | d | f. 81 | 82 | 83 | %% @doc Create a new empty unweighted graph. 84 | -spec empty(graphtype()) -> graph(). 85 | 86 | empty(Type) when Type =:= directed; Type =:= undirected -> 87 | #graph{type=Type, graph=digraph:new(), weightType = unweighted}. 88 | 89 | %% @doc Create a new empty graph with a specific weight type. 90 | -spec empty(graphtype(), weighttype()) -> graph(). 91 | 92 | empty(T, WT) when (T =:= directed orelse T =:= undirected) andalso 93 | (WT =:= unweighted orelse WT =:= d orelse WT =:= f) -> 94 | #graph{type=T, graph=digraph:new(), weightType=WT}. 95 | 96 | %% @doc Create a new graph from a file using the default behaviour. 97 | -spec from_file(file:name()) -> graph(). 98 | 99 | from_file(File) -> 100 | from_file(File, fun read_vertices/2, fun read_edge/2). 101 | 102 | %% @doc Create a new graph from a file using a custom behaviour. 103 | %%

The user must provide the 2 functions for reading the vertices and the 104 | %% edge descriptions.

105 | %% 106 | %%

ReadVertices will take the file descriptor and the number of vertices and 107 | %% return the list of the vertices.

108 | %% 109 | %%

ReadEdge will take the file descriptor and the type of the weights of the graph and 110 | %% return the tuple {V1, V2, W}, where V1 is the start of the edge, V2 is the end of the edge 111 | %% and W is the weight of the edge (for unweighted graphs the edge must be 1).

112 | -spec from_file(file:name(), function(), function()) -> graph(). 113 | 114 | from_file(File, ReadVertices, ReadEdge) -> 115 | {ok, IO} = file:open(File, [read]), 116 | %% N = Number of Vertices :: non_neg_integer() 117 | %% M = Number of Edges :: non_neg_integer() 118 | %% T = Graph Type :: directed | undirected 119 | %% W = Edge Weight weighted :: Weight Type (d | f) | unweighted 120 | {ok, [N, M, T, W]} = io:fread(IO, ">", "~d ~d ~a ~a"), 121 | G = #graph{type=T, graph=digraph:new(), weightType=W}, 122 | ok = init_vertices(IO, G, N, ReadVertices), 123 | ok = init_edges(IO, G, M, ReadEdge, T, W), 124 | G. 125 | 126 | %% @doc Default function for reading the vertices from a file. 127 | %%

The default behaviour is that, given the number of vertices, each vertex 128 | %% is assigned to an integer.

129 | -spec read_vertices(file:io_device(), integer()) -> [integer()]. 130 | 131 | read_vertices(_IO, N) -> lists:seq(0, N-1). 132 | 133 | %% @doc Default function for reading the edge description from a file. 134 | %%

The default behaviour is that the edge description is a line that containts 135 | %% three terms: U V W (W applies only to weighted graphs).

136 | %% 137 | %%

U is the integer that denotes the start of the edge.

138 | %%

V is the integer that denotes the end of the edge.

139 | %%

W is the number that denotes the weight of the edge.

140 | -spec read_edge(file:io_device(), weighttype()) -> {vertex(), vertex(), weight()}. 141 | 142 | read_edge(IO, WT) -> 143 | read_edge(IO, WT, fun erlang:list_to_integer/1). 144 | 145 | read_edge(IO, unweighted, MapVertex) -> 146 | {ok, [V1, V2]} = io:fread(IO, ">", "~s ~s"), 147 | {MapVertex(V1), MapVertex(V2), 1}; 148 | read_edge(IO, WT, MapVertex) -> 149 | Format = "~s ~s ~" ++ atom_to_list(WT), 150 | {ok, [V1, V2, W]} = io:fread(IO, ">", Format), 151 | {MapVertex(V1), MapVertex(V2), W}. 152 | 153 | %% Initialize the vertices of the graph 154 | -spec init_vertices(file:io_device(), graph(), integer(), function()) -> ok. 155 | 156 | init_vertices(IO, Graph, N, ReadVertices) -> 157 | Vs = ReadVertices(IO, N), 158 | lists:foreach(fun(V) -> add_vertex(Graph, V) end, Vs). 159 | 160 | %% Initialize the edges of the graph 161 | -spec init_edges(file:io_device(), graph(), integer(), function(), graphtype(), weighttype()) -> ok. 162 | 163 | init_edges(_IO, _G, 0, _ReadEdge, _T, _WT) -> ok; 164 | init_edges(IO, G, M, ReadEdge, T, WT) -> 165 | {V1, V2, W} = ReadEdge(IO, WT), 166 | _ = add_edge(G, V1, V2, W), 167 | init_edges(IO, G, M-1, ReadEdge, T, WT). 168 | 169 | %% @doc Delete a graph 170 | -spec del_graph(graph()) -> 'true'. 171 | 172 | del_graph(G) -> 173 | digraph:delete(G#graph.graph). 174 | 175 | %% @doc Return the type of the graph. 176 | -spec graph_type(graph()) -> graphtype(). 177 | 178 | graph_type(G) -> 179 | G#graph.type. 180 | 181 | %% @doc Return the type of the weights. 182 | -spec weight_type(graph()) -> weighttype(). 183 | 184 | weight_type(G) -> 185 | G#graph.weightType. 186 | 187 | %% @doc Add a vertex to a graph 188 | -spec add_vertex(graph(), vertex()) -> vertex(). 189 | 190 | add_vertex(G, V) -> 191 | digraph:add_vertex(G#graph.graph, V). 192 | 193 | %% @doc Return a list of the vertices of a graph 194 | -spec vertices(graph()) -> [vertex()]. 195 | 196 | vertices(G) -> 197 | digraph:vertices(G#graph.graph). 198 | 199 | %% @doc Return the number of vertices in a graph 200 | -spec num_of_vertices(graph()) -> non_neg_integer(). 201 | 202 | num_of_vertices(G) -> 203 | digraph:no_vertices(G#graph.graph). 204 | 205 | %% @doc Add an edge to an unweighted graph. 206 | %%

Create an edge with unit weight/

207 | -spec add_edge(graph(), vertex(), vertex()) -> edge(). 208 | 209 | add_edge(G, From, To) -> 210 | add_edge(G, From, To, 1). 211 | 212 | %% @doc Add an edge to a weighted graph 213 | -spec add_edge(graph(), vertex(), vertex(), weight()) -> edge() | {error, not_numeric_weight}. 214 | 215 | add_edge(#graph{type=directed, graph=G}, From, To, W) when is_number(W) -> 216 | digraph:add_edge(G, {From, To}, From, To, W); 217 | add_edge(#graph{type=undirected, graph=G}, From, To, W) when is_number(W) -> 218 | digraph:add_edge(G, {From, To}, From, To, W), 219 | digraph:add_edge(G, {To, From}, To, From, W); 220 | add_edge(_G, _From, _To, _W) -> 221 | {error, not_numeric_weight}. 222 | 223 | %% @doc Delete an edge from a graph 224 | -spec del_edge(graph(), edge()) -> 'true'. 225 | 226 | del_edge(G, E) -> 227 | digraph:del_edge(G#graph.graph, E). 228 | 229 | %% @doc Return a list of the edges of a graph 230 | -spec edges(graph()) -> [edge()]. 231 | 232 | edges(G) -> 233 | Es = digraph:edges(G#graph.graph), 234 | case G#graph.type of 235 | directed -> Es; 236 | undirected -> remove_duplicate_edges(Es, []) 237 | end. 238 | 239 | %% Remove the duplicate edges of a undirected graph 240 | remove_duplicate_edges([], Acc) -> 241 | Acc; 242 | remove_duplicate_edges([{From, To}=E|Es], Acc) -> 243 | remove_duplicate_edges(Es -- [{To, From}], [E|Acc]). 244 | 245 | %% @doc Return the number of edges in a graph 246 | -spec num_of_edges(graph()) -> non_neg_integer(). 247 | 248 | num_of_edges(G) -> 249 | M = digraph:no_edges(G#graph.graph), 250 | case G#graph.type of 251 | directed -> M; 252 | undirected -> M div 2 253 | end. 254 | 255 | %% @doc Return the weight of an edge 256 | -spec edge_weight(graph(), edge()) -> weight() | 'false'. 257 | 258 | edge_weight(G, E) -> 259 | case digraph:edge(G#graph.graph, E) of 260 | {E, _V1, _V2, W} -> W; 261 | false -> false 262 | end. 263 | 264 | %% @doc Return a list of the edges of a graph along with their weights 265 | -spec edges_with_weights(graph()) -> [{edge(), weight()}]. 266 | 267 | edges_with_weights(G) -> 268 | Es = edges(G), 269 | lists:map(fun(E) -> {E, edge_weight(G, E)} end, Es). 270 | 271 | %% @doc Return a list of the out neighbours of a vertex 272 | -spec out_neighbours(graph(), vertex()) -> [vertex()]. 273 | 274 | out_neighbours(G, V) -> 275 | digraph:out_neighbours(G#graph.graph, V). 276 | 277 | %% @doc Pretty print a graph 278 | -spec pprint(graph()) -> ok. 279 | 280 | pprint(G) -> 281 | Vs = digraph:vertices(G#graph.graph), 282 | F = 283 | fun(V) -> 284 | Es = digraph:out_edges(G#graph.graph, V), 285 | Ns = lists:map( 286 | fun(E) -> 287 | {E, _V1, V2, W} = digraph:edge(G#graph.graph, E), 288 | {V2, W} 289 | end, 290 | Es), 291 | {V, Ns} 292 | end, 293 | N = lists:sort(fun erlang:'<'/2, lists:map(F, Vs)), 294 | io:format("[{From, [{To, Weight}]}]~n"), 295 | io:format("========================~n"), 296 | io:format("~p~n", [N]). 297 | 298 | %% @doc Exports a graph to a file. 299 | %%

The user must provide the function that will generate the textual 300 | %% representation of a vertex.

301 | %% 302 | %%

DumpVertex will take a vertex and return its textual representation.

303 | -spec export(graph(), file:name(), fun((vertex()) -> string())) -> ok. 304 | 305 | export(Graph, Filename, DumpVertex) -> 306 | {ok, IO} = file:open(Filename, [write]), 307 | export_graph_info(IO, Graph), 308 | export_vertices(IO, Graph, DumpVertex), 309 | export_edges(IO, Graph, DumpVertex), 310 | file:close(IO). 311 | 312 | export_graph_info(IO, Graph) -> 313 | N = num_of_vertices(Graph), 314 | M = num_of_edges(Graph), 315 | GT = graph_type(Graph), 316 | WT = weight_type(Graph), 317 | io:fwrite(IO, "~w ~w ~w ~w~n", [N, M, GT, WT]). 318 | 319 | export_vertices(IO, Graph, DumpVertex) -> 320 | Vs = vertices(Graph), 321 | Rs = [io_lib:format("~s", [DumpVertex(V)]) || V <- Vs], 322 | io:fwrite(IO, "~s~n", [string:join(Rs, " ")]). 323 | 324 | export_edges(IO, Graph, DumpVertex) -> 325 | Es = edges(Graph), 326 | lists:foreach( 327 | fun({V1, V2}=E) -> 328 | W = edge_weight(Graph, E), 329 | io:fwrite(IO, "~s ~s ~w~n", [DumpVertex(V1), DumpVertex(V2), W]) 330 | end, 331 | Es). 332 | 333 | %% @doc Imports an exported graph. 334 | %%

The user must provide the function that will parse the textual 335 | %% representation of a vertex.

336 | %% 337 | %%

MapVertex will take the textual representation of a vertex and return 338 | %% the actual vertex.

339 | -spec import(file:name(), fun((string()) -> vertex())) -> graph(). 340 | import(File, MapVertex) -> 341 | ImportVs = fun(IO, _N) -> import_vertices(IO, MapVertex) end, 342 | ImportEs = fun(IO, WT) -> read_edge(IO, WT, MapVertex) end, 343 | from_file(File, ImportVs, ImportEs). 344 | 345 | import_vertices(IO, MapVertex) -> 346 | Line = io:get_line(IO, ""), 347 | Strip1 = string:strip(Line, right, $\n), 348 | Strip2 = string:strip(Strip1, right, $\n), 349 | Vs = string:tokens(Strip2, " "), 350 | [MapVertex(V) || V <- Vs]. 351 | 352 | %% @doc Checks if two graphs are equal. 353 | -spec equal(graph(), graph()) -> boolean(). 354 | equal(G1, G2) -> 355 | graph_type(G1) =:= graph_type(G2) 356 | andalso weight_type(G1) =:= weight_type(G2) 357 | andalso num_of_vertices(G1) =:= num_of_vertices(G2) 358 | andalso num_of_edges(G1) =:= num_of_edges(G2) 359 | andalso lists:sort(vertices(G1)) =:= lists:sort(vertices(G2)) 360 | andalso equal_edges(G1, G2). 361 | 362 | equal_edges(G1, G2) -> 363 | Es1 = lists:sort(edges(G1)), 364 | Es2 = lists:sort(edges(G2)), 365 | case Es1 =:= Es2 of 366 | false -> false; 367 | true -> 368 | [edge_weight(G1, E) || E <- Es1] =:= [edge_weight(G2, E) || E <- Es2] 369 | end. 370 | -------------------------------------------------------------------------------- /src/graph_lib.erl: -------------------------------------------------------------------------------- 1 | %% @doc Library Functions for Graph Algorithms 2 | %% 3 | 4 | -module(graph_lib). 5 | 6 | -export([reconstruct_all_paths/2, reconstruct_flow/1]). 7 | 8 | -export_type([vpath/0, path_info/0, mst/0, mst_info/0, 9 | epath/0, epath_weighted/0, flow/0]). 10 | 11 | %% 12 | %% @type path_info(). It is used for the result of BFS, DFS 13 | %% and Dijkstra algorithms. 14 | %%

It's a tuple {Vertex, {Cost, Path}} that 15 | %% contains the information about the Cost :: number() 16 | %% and Path :: vpath() of a Vertex. 17 | %% If a vertex cannot be reached from the root vertex then 18 | %% instead of{Cost, Path} there will be the 19 | %% atom unreachable.

20 | %% 21 | %% @type flow(). It is used for the result of Edmonds-Karp and 22 | %% Ford-Fulkerson algorithms. 23 | %%

It's a tuple {Val, Flow} that contains the information 24 | %% about the value of the flow Val :: number() and how it is 25 | %% achieved by the network's Flow :: [{edge(), number()}].

26 | %% 27 | -type mst() :: [graph:edge()]. 28 | -type mst_info() :: {graph:weight(), mst()}. 29 | -type vpath() :: [graph:vertex()]. 30 | -type epath() :: [graph:edge()]. 31 | -type epath_weighted() :: [{graph:edge(), graph:weight()}]. 32 | -type path_info() :: {graph:vertex(), {graph:weight(), vpath()} | 'unreachable'}. 33 | -type flow() :: {graph:weight(), [{graph:edge(), graph:weight()}]}. 34 | 35 | %% ========================================================== 36 | %% Exported Functions 37 | %% ========================================================== 38 | 39 | %% @doc Reconstruct all the path information from a graph algorithm's result. 40 | %% (Algorithms included: Dijkstra, DFS, BFS). 41 | -spec reconstruct_all_paths([graph:vertex()], dict:dict()) -> [path_info()]. 42 | 43 | reconstruct_all_paths(Vertices, Result) -> 44 | SortedVs = lists:sort(fun erlang:'<'/2, Vertices), 45 | lists:map(fun(V) -> reconstruct_path(Result, V) end, SortedVs). 46 | 47 | %% @doc Reconstruct the flow information for a flow algortihm's result. 48 | %% (Algorithms included: Edmonds-Karp, Ford-Fulkerson). 49 | -spec reconstruct_flow([proplists:property()]) -> flow(). 50 | 51 | reconstruct_flow(L) -> 52 | Flow = proplists:get_value('flow', L), 53 | Es = lists:sort(L -- [{'flow', Flow}]), 54 | {Flow, Es}. 55 | 56 | %% ========================================================== 57 | %% Internal Functions 58 | %% ========================================================== 59 | 60 | %% ---------------------------------------------------------- 61 | %% Helper functions to reconstruct a path 62 | %% ---------------------------------------------------------- 63 | 64 | %% Result :: dict of {Node, {Cost, Prev}} 65 | -spec reconstruct_path(dict:dict(), graph:vertex()) -> path_info(). 66 | 67 | reconstruct_path(Result, Node) -> 68 | try dict:fetch(Node, Result) of 69 | {Cost, Prev} -> 70 | {Node, reconstruct_path(Result, Prev, Cost, [Node])} 71 | catch 72 | error:badarg -> 73 | {Node, 'unreachable'} 74 | end. 75 | 76 | -spec reconstruct_path(dict:dict(), graph:vertex(), term(), vpath()) -> {term(), vpath()}. 77 | 78 | reconstruct_path(_Result, root, Cost, Path) -> 79 | {Cost, Path}; 80 | reconstruct_path(Result, Node, Cost, Path) -> 81 | {_, Prev} = dict:fetch(Node, Result), 82 | reconstruct_path(Result, Prev, Cost, [Node|Path]). 83 | 84 | 85 | -------------------------------------------------------------------------------- /src/heap.erl: -------------------------------------------------------------------------------- 1 | %% @doc Min-Heap, Max-Heap for Priority Queues 2 | %% 3 | %%

This module implements min-heaps and max-heaps for use in priority queues. 4 | %% Each value in the heap is assosiated with a reference so that 5 | %% the user can change its priority in O(log n).

6 | %% 7 | %%

The implementation is based on ETS tables for the O(1) lookup time. 8 | %% It supports all the basic heap operations: 9 | %%

16 | %%

17 | %% 18 | %%

In order to achieve the above complexities the heap needs to store 19 | %% an extra tuple {Key, Reference} for every 20 | %% Key stored. In addition, the size of the heap is 21 | %% stored as a tuple {size, Size}.

22 | %% 23 | %%

For examples you can check the heap_demo module.

24 | %% 25 | 26 | -module(heap). 27 | 28 | -export([new/1, heap_size/1, is_empty/1, max/1, min/1, insert/2, delete/1, 29 | take_min/1, take_max/1, update/3, from_list/2, to_list/1]). 30 | 31 | -export_type([heap/0]). 32 | 33 | %% 34 | %% @type heap(). Min / Max Heap. 35 | %% 36 | -record(heap, { 37 | mode :: mode(), 38 | htab :: ets:tab() 39 | }). 40 | -type heap() :: #heap{}. 41 | -type mode() :: max | min. 42 | -type refterm() :: {term(), reference()}. 43 | 44 | %% ======================================================================= 45 | %% External Exports 46 | %% ======================================================================= 47 | 48 | %% @doc Returns a list of the terms in a heap. 49 | -spec to_list(heap()) -> [term()]. 50 | 51 | to_list(H) -> 52 | L = ets:tab2list(H#heap.htab), 53 | M = fun(A) -> 54 | case A of 55 | {_, {X, _}} -> {true, X}; 56 | _ -> false 57 | end 58 | end, 59 | lists:filtermap(M, L). 60 | 61 | %% @doc Creates an empty heap. 62 | %%

If M is max then it will be a max heap, 63 | %% else if M is min it will be a min heap.

64 | -spec new(mode()) -> heap(). 65 | 66 | new(M) when M =:= max; M=:= min -> 67 | H = ets:new(?MODULE, [ordered_set, public]), 68 | ets:insert(H, {size, 0}), 69 | #heap{mode=M, htab=H}; 70 | new(_Mode) -> 71 | erlang:error(badarg). 72 | 73 | %% @doc Deletes a heap. 74 | -spec delete(heap()) -> true. 75 | 76 | delete(H) -> ets:delete(H#heap.htab). 77 | 78 | %% @doc Returns the number of elements contained in a heap. 79 | -spec heap_size(heap()) -> non_neg_integer(). 80 | 81 | heap_size(H) -> 82 | [{size, Len}] = ets:lookup(H#heap.htab, size), 83 | Len. 84 | 85 | %% @doc Checks whether a heap is empty or not. 86 | -spec is_empty(heap()) -> boolean(). 87 | 88 | is_empty(H) -> heap_size(H) =:= 0. 89 | 90 | %% @doc Returns the element of a max heap with the maximum priority. 91 | %%

If it is a min heap, it returns {error, min_heap}.

92 | -spec max(heap()) -> term() | {error, min_heap | empty_heap}. 93 | 94 | max(H) when H#heap.mode =:= max -> 95 | case ets:lookup(H#heap.htab, 1) of 96 | [] -> {error, empty_heap}; 97 | [{1, {Max, _Ref}}] -> Max 98 | end; 99 | max(_H) -> {error, min_heap}. 100 | 101 | %% @doc Returns the element of a min heap with the minimum priority. 102 | %%

If it is a max heap, it returns {error, max_heap}.

103 | -spec min(heap()) -> term() | {error, max_heap | empty_heap}. 104 | 105 | min(H) when H#heap.mode =:= min -> 106 | case ets:lookup(H#heap.htab, 1) of 107 | [] -> {error, empty_heap}; 108 | [{1, {Min, _Ref}}] -> Min 109 | end; 110 | min(_H) -> {error, max_heap}. 111 | 112 | %% @doc Add a new element to a heap. 113 | %%

It returns a tuple with the element added and a reference 114 | %% so that one can change its priority.

115 | -spec insert(heap(), term()) -> refterm(). 116 | 117 | insert(H, X) -> 118 | HS = heap_size(H), 119 | HS_n = HS + 1, 120 | Ref = erlang:make_ref(), 121 | ets:insert(H#heap.htab, {HS_n, {X, Ref}}), 122 | ets:insert(H#heap.htab, {Ref, HS_n}), 123 | ets:insert(H#heap.htab, {size, HS_n}), 124 | I = HS_n, 125 | P = I div 2, 126 | insert_loop(H, I, P), 127 | {X, Ref}. 128 | 129 | %% @doc Removes and returns the maximum priority element of a max heap. 130 | -spec take_max(heap()) -> term() | {error, min_heap | empty_heap}. 131 | 132 | take_max(H) when H#heap.mode =:= max -> pop(H); 133 | take_max(_H) -> {error, min_heap}. 134 | 135 | %% @doc Removes and returns the minimum priority element of a min heap. 136 | -spec take_min(heap()) -> term() | {error, max_heap | empty_heap}. 137 | 138 | take_min(H) when H#heap.mode =:= min -> pop(H); 139 | take_min(_H) -> {error, max_heap}. 140 | 141 | %% Deletes and returns the element at the top of the heap 142 | %% and re-arranges the rest of the heap 143 | -spec pop(heap()) -> term(). 144 | 145 | pop(H) -> 146 | case ets:lookup(H#heap.htab, 1) of 147 | [] -> {error, empty_heap}; 148 | [{1, {Head, RefHead}}] -> 149 | HS = heap_size(H), 150 | [{HS, {X, RefX}}] = ets:lookup(H#heap.htab, HS), 151 | ets:delete(H#heap.htab, HS), %% Can be commented 152 | ets:delete(H#heap.htab, RefHead), %% Can be commented 153 | HS_n = HS - 1, 154 | ets:insert(H#heap.htab, {size, HS_n}), 155 | case HS_n =:= 0 of %% Can be commented 156 | true -> ok; %% Can be commented 157 | false -> %% Can be commented 158 | ets:insert(H#heap.htab, {1, {X, RefX}}), 159 | ets:insert(H#heap.htab, {RefX, 1}) 160 | end, %% Can be commented 161 | combine(H, 1, HS_n), 162 | Head 163 | end. 164 | 165 | %% @doc Change the priority of an element. 166 | %%

It changes the priority of the element referenced with 167 | %% Ref to Value and then re-arranges the heap.

168 | -spec update(heap(), reference(), term()) -> true. 169 | 170 | update(H, Ref, X) -> 171 | case ets:lookup(H#heap.htab, Ref) of 172 | [] -> true; 173 | [{Ref, I}] -> 174 | [{I, {OldX, Ref}}] = ets:lookup(H#heap.htab, I), 175 | case {X > OldX, H#heap.mode} of 176 | {true, max} -> up_heapify(H, I, X, Ref); 177 | {false, min} -> up_heapify(H, I, X, Ref); 178 | {false, max} -> down_heapify(H, I, X, Ref); 179 | {true, min} -> down_heapify(H, I, X, Ref) 180 | end, 181 | true 182 | end. 183 | 184 | %% @doc Create a heap from a list of terms. 185 | %%

It returns the heap and a list of tuples {Key, Ref} 186 | %% where Key is the term that was added and Ref 187 | %% is its reference (used to change its priority).

188 | -spec from_list(mode(), [term()]) -> {heap(), [refterm()]}. 189 | 190 | from_list(M, L) when is_list(L), is_atom(M) -> 191 | HS = erlang:length(L), 192 | {H, Rs} = ets_from_elements(M, L, HS), 193 | I = HS div 2, 194 | construct_heap(H, I, HS), 195 | {H, Rs}; 196 | from_list(_M, _L) -> 197 | erlang:error(badarg). 198 | 199 | %% ======================================================================= 200 | %% Internal Functions 201 | %% ======================================================================= 202 | 203 | %% Re-arranges the heap in a bottom-up manner 204 | -spec insert_loop(heap(), pos_integer(), pos_integer()) -> ok. 205 | 206 | insert_loop(H, I, P) when I > 1 -> 207 | [{I, {X, _RefX}}] = ets:lookup(H#heap.htab, I), 208 | [{P, {Y, _RefY}}] = ets:lookup(H#heap.htab, P), 209 | case {Y < X, H#heap.mode} of 210 | {true, max} -> 211 | swap(H, P, I), 212 | NI = P, 213 | NP = NI div 2, 214 | insert_loop(H, NI, NP); 215 | {false, min} -> 216 | swap(H, P, I), 217 | NI = P, 218 | NP = NI div 2, 219 | insert_loop(H, NI, NP); 220 | {_, _} -> ok 221 | end; 222 | insert_loop(_H, _I, _P) -> ok. 223 | 224 | -spec up_heapify(heap(), pos_integer(), term(), reference()) -> ok. 225 | 226 | up_heapify(H, I, X, Ref) -> 227 | ets:insert(H#heap.htab, {I, {X, Ref}}), 228 | P = I div 2, 229 | insert_loop(H, I, P). 230 | 231 | %% Re-arranges the heap in a top-down manner 232 | -spec combine(heap(), pos_integer(), pos_integer()) -> ok. 233 | 234 | combine(H, I, HS) -> 235 | L = 2*I, 236 | R = 2*I + 1, 237 | MP = I, 238 | MP_L = combine_h1(H, L, MP, HS), 239 | MP_R = combine_h1(H, R, MP_L, HS), 240 | combine_h2(H, MP_R, I, HS). 241 | 242 | -spec combine_h1(heap(), pos_integer(), pos_integer(), pos_integer()) -> pos_integer(). 243 | 244 | combine_h1(H, W, MP, HS) when W =< HS -> 245 | [{W, {X, _RefX}}] = ets:lookup(H#heap.htab, W), 246 | [{MP, {Y, _RefY}}] = ets:lookup(H#heap.htab, MP), 247 | case {X > Y, H#heap.mode} of 248 | {true, max} -> W; 249 | {false, min} -> W; 250 | {_, _} -> MP 251 | end; 252 | combine_h1(_H, _W, MP, _HS) -> MP. 253 | 254 | -spec combine_h2(heap(), pos_integer(), pos_integer(), pos_integer()) -> ok. 255 | 256 | combine_h2(_H, MP, I, _HS) when MP =:= I -> ok; 257 | combine_h2(H, MP, I, HS) -> 258 | swap(H, I, MP), 259 | combine(H, MP, HS). 260 | 261 | -spec down_heapify(heap(), pos_integer(), term(), reference()) -> ok. 262 | 263 | down_heapify(H, I, X, Ref) -> 264 | ets:insert(H#heap.htab, {I, {X, Ref}}), 265 | HS = heap_size(H), 266 | combine(H, I, HS). 267 | 268 | %% Swaps two elements of the heap 269 | -spec swap(heap(), pos_integer(), pos_integer()) -> true. 270 | 271 | swap(H, I, J) -> 272 | [{I, {X, RX}}] = ets:lookup(H#heap.htab, I), 273 | [{J, {Y, RY}}] = ets:lookup(H#heap.htab, J), 274 | ets:insert(H#heap.htab, {I, {Y, RY}}), 275 | ets:insert(H#heap.htab, {RY, I}), 276 | ets:insert(H#heap.htab, {J, {X, RX}}), 277 | ets:insert(H#heap.htab, {RX, J}). 278 | 279 | %% Used for constructing a heap from a list 280 | -spec construct_heap(heap(), pos_integer(), pos_integer()) -> ok. 281 | 282 | construct_heap(H, I, HS) when I > 0 -> 283 | combine(H, I, HS), 284 | construct_heap(H, I-1, HS); 285 | construct_heap(_H, _I, _HS) -> ok. 286 | 287 | -spec ets_from_elements(mode(), [term()], non_neg_integer()) -> {heap(), [refterm()]}. 288 | 289 | ets_from_elements(M, L, HS) -> 290 | H = new(M), 291 | ets:insert(H#heap.htab, {size, HS}), 292 | Rs = add_elements(H, L, 1, []), 293 | {H, Rs}. 294 | 295 | -spec add_elements(heap(), [term()], pos_integer(), [refterm()]) -> [refterm()]. 296 | 297 | add_elements(_H, [], _N, Acc) -> 298 | lists:reverse(Acc); 299 | add_elements(H, [T|Ts], N, Acc) -> 300 | Ref = erlang:make_ref(), 301 | ets:insert(H#heap.htab, {N, {T, Ref}}), 302 | ets:insert(H#heap.htab, {Ref, N}), 303 | add_elements(H, Ts, N+1, [{T, Ref}|Acc]). 304 | 305 | 306 | -------------------------------------------------------------------------------- /src/kruskal.erl: -------------------------------------------------------------------------------- 1 | %% @doc Kruskal Algorithm 2 | %% 3 | %%

For examples you can check the kruskal_demo module.

4 | %% 5 | 6 | -module(kruskal). 7 | 8 | -export([run/1]). 9 | 10 | -type result_set() :: {non_neg_integer(), graph:weight(), [graph:edge()]}. 11 | 12 | %% ========================================================== 13 | %% Exported Functions 14 | %% ========================================================== 15 | 16 | %% @doc Runs the Kruskal algorithm on a graph Graph. 17 | %%

The result is a tuple {Cost, MST} where 18 | %% Cost :: term() is the cost of the 19 | %% MST.

20 | -spec run(graph:graph()) -> graph_lib:mst_info(). 21 | run(Graph) -> 22 | {UF, Es, N, R} = kruskal_init(Graph), 23 | {_Sz, Cost, MST} = kruskal_step(UF, Es, N, R), 24 | {Cost, MST}. 25 | 26 | %% ========================================================== 27 | %% Kruskal Functions 28 | %% ========================================================== 29 | 30 | %% Initialize data structures 31 | -spec kruskal_init(graph:graph()) -> {union_find:uf_forest(), [{graph:edge(), graph:weight()}], non_neg_integer(), result_set()}. 32 | kruskal_init(Graph) -> 33 | Vs = graph:vertices(Graph), 34 | N = length(Vs), 35 | WEs = graph:edges_with_weights(Graph), 36 | SEs = lists:sort(fun({_E1, W1}, {_E2, W2}) -> W1 < W2 end, WEs), 37 | UF = union_find:singletons_from_list(Vs), 38 | {UF, SEs, N, empty_result()}. 39 | 40 | %% Kruskal loop 41 | -spec kruskal_step(union_find:uf_forest(), [{graph:edge(), graph:weight()}], non_neg_integer(), result_set()) -> result_set(). 42 | kruskal_step(UF, [], _N, Res) -> 43 | union_find:delete(UF), 44 | Res; 45 | kruskal_step(UF, [{{X, Y}, _W}=E|Es], N, Res) -> 46 | case result_size(Res) < N-1 of 47 | true -> 48 | ParX = union_find:find(UF, X), 49 | ParY = union_find:find(UF, Y), 50 | case ParX =:= ParY of 51 | true -> 52 | kruskal_step(UF, Es, N, Res); 53 | false -> 54 | true = union_find:union(UF, ParX, ParY), 55 | kruskal_step(UF, Es, N, add_to_result(Res, E)) 56 | end; 57 | false -> 58 | union_find:delete(UF), 59 | Res 60 | end. 61 | 62 | %% ---------------------------------------------------------- 63 | %% Kruskal Abstractions 64 | %% ---------------------------------------------------------- 65 | 66 | %% Result Set Abstrations 67 | %% Result Set :: {Vertices, Cost, MST} 68 | empty_result() -> {0, 0, []}. 69 | add_to_result({Sz, Cost, Es}, {E, W}) -> {Sz+1, Cost+W, [E|Es]}. 70 | result_size({Sz, _Cost, _Es}) -> Sz. 71 | 72 | -------------------------------------------------------------------------------- /src/union_find.erl: -------------------------------------------------------------------------------- 1 | %% @doc Union / Find 2 | %% 3 | %%

This module implements the Union / Find data structure.

4 | %% 5 | %%

6 | %% The implementation is based on ETS tables for the O(1) lookup. 7 | %% It is optimized for the basic union / find operations: 8 | %%

23 | %%

24 | %% 25 | %%

For examples you can check the union_find_demo module.

26 | %% 27 | 28 | -module(union_find). 29 | 30 | -export([singletons_from_list/1, singletons_from_list/2, union/3, find/2, 31 | delete/1, number_of_sets/1, set_size/2, set_elements/2, pprint/1]). 32 | 33 | -export_type([uf_forest/0]). 34 | 35 | -define(UNDEF_SIZE, undef). 36 | 37 | %% 38 | %% @type uf_forest(). A forest of union-find sets. 39 | %% 40 | -type uf_forest() :: ets:tid(). 41 | -type uf_find() :: term() | uf_undef(). 42 | -type uf_size() :: pos_integer() | uf_undef(). 43 | -type uf_union() :: true | uf_undef() | {error, not_parent_elements}. 44 | -type uf_undef() :: {error, undef_element}. 45 | 46 | %% ======================================================================= 47 | %% External Exports 48 | %% ======================================================================= 49 | 50 | %% @doc Create a forest of singleton sets from a list of terms. 51 | -spec singletons_from_list([term(), ...]) -> uf_forest(). 52 | 53 | singletons_from_list([]) -> 54 | erlang:error(badarg); 55 | singletons_from_list(L) when is_list(L) -> 56 | singletons_from_list(fun id/1, L, ets:new(?MODULE, [ordered_set])); 57 | singletons_from_list(_L) -> 58 | erlang:error(badarg). 59 | 60 | %% @doc Create a forest of singleton sets from a list of terms. 61 | %%

Same as singletons_from_list/1 but it applies 62 | %% Fun to each term before adding it to the forest.

63 | -spec singletons_from_list(function(), [term(), ...]) -> uf_forest(). 64 | 65 | singletons_from_list(_Fun, []) -> 66 | erlang:error(badarg); 67 | singletons_from_list(Fun, L) when is_list(L), is_function(Fun) -> 68 | singletons_from_list(Fun, L, ets:new(?MODULE, [ordered_set])); 69 | singletons_from_list(_Fun, _L) -> 70 | erlang:error(badarg). 71 | 72 | %% Helper function singletons_from_list/3 73 | -spec singletons_from_list(function(), [term()], uf_forest()) -> uf_forest(). 74 | 75 | singletons_from_list(_Fun, [], Forest) -> 76 | Forest; 77 | singletons_from_list(Fun, [I|Is], Forest) -> 78 | ets:insert(Forest, {Fun(I), {root, 1}}), 79 | singletons_from_list(Fun, Is, Forest). 80 | 81 | %% @doc Union of two sets. 82 | %%

The parent elements of the two sets are needed.

83 | -spec union(uf_forest(), term(), term()) -> uf_union(). 84 | 85 | union(_Forest, _X, _X) -> true; 86 | union(Forest, X, Y) -> 87 | case {ets:lookup(Forest, X), ets:lookup(Forest, Y)} of 88 | {[{X, {root, SzX}}], [{Y, {root, SzY}}]} -> 89 | ets:insert(Forest, {Y, {X, ?UNDEF_SIZE}}), 90 | ets:insert(Forest, {X, {root, SzX + SzY}}); 91 | {[], _} -> 92 | {error, undef_element}; 93 | {_, []} -> 94 | {error, undef_element}; 95 | {_, _} -> 96 | {error, not_parent_elements} 97 | end. 98 | 99 | %% @doc Find the parent element of the set which a term belongs to. 100 | -spec find(uf_forest(), term()) -> uf_find(). 101 | 102 | find(Forest, X) -> find_and_compress(Forest, X, []). 103 | 104 | -spec find_and_compress(uf_forest(), term(), [term()]) -> uf_find(). 105 | 106 | find_and_compress(Forest, X, Es) -> 107 | case ets:lookup(Forest, X) of 108 | [] -> 109 | {error, undef_element}; 110 | [{X, {root, _SzX}}] -> 111 | lists:foreach(fun(E) -> ets:insert(Forest, {E, {X, ?UNDEF_SIZE}}) end, Es), 112 | X; 113 | [{X, {ParX, ?UNDEF_SIZE}}] -> 114 | find_and_compress(Forest, ParX, [X|Es]) 115 | end. 116 | 117 | %% @doc Delete a forest 118 | -spec delete(uf_forest()) -> true. 119 | 120 | delete(Forest) -> ets:delete(Forest). 121 | 122 | %% @doc Return the size of the set which an element belongs to 123 | -spec set_size(uf_forest(), term()) -> uf_size(). 124 | 125 | set_size(Forest, X) -> 126 | case ets:lookup(Forest, X) of 127 | [] -> {error, undef_element}; 128 | [{X, {root, Sz}}] -> Sz; 129 | [{X, {ParX, ?UNDEF_SIZE}}] -> set_size(Forest, ParX) 130 | end. 131 | 132 | %% @doc Return a list of all the elements of the set 133 | %% which an element belongs to. 134 | -spec set_elements(uf_forest(), term()) -> [term(), ...]. 135 | 136 | set_elements(Forest, X) -> 137 | Root = find(Forest, X), 138 | Es = lists:flatten(ets:match(Forest, {'$1', {'_', '_'}})), 139 | lists:filter(fun(E) -> Root =:= find(Forest, E) end, Es). 140 | 141 | %% @doc Return the number of sets that exist in a forest 142 | -spec number_of_sets(uf_forest()) -> non_neg_integer(). 143 | 144 | number_of_sets(Forest) -> length(ets:match_object(Forest, {'_', {root, '_'}})). 145 | 146 | %% @doc Pretty print the sets of a forest 147 | -spec pprint(uf_forest()) -> ok. 148 | 149 | pprint(Forest) -> 150 | Es = lists:flatten(ets:match(Forest, {'$1', {'_', '_'}})), 151 | pprint_sets(Forest, Es). 152 | 153 | -spec pprint_sets(uf_forest(), [term()]) -> ok. 154 | pprint_sets(_Forest, []) -> ok; 155 | pprint_sets(Forest, [E|_]=All) -> 156 | Root = find(Forest, E), 157 | io:format("Set with root : ~w~n", [Root]), 158 | SetEs = set_elements(Forest, E), 159 | io:format("Elements: ~w~n", [SetEs]), 160 | pprint_sets(Forest, All -- SetEs). 161 | 162 | -spec id(term()) -> term(). 163 | id(Arg) -> Arg. 164 | 165 | --------------------------------------------------------------------------------