├── .gitignore ├── .gitmodules ├── LICENSE ├── Readme.md ├── generate_graph.py ├── protostar.toml ├── pyproject.toml ├── setup.cfg ├── setup.py ├── src └── cairo_graphs │ ├── data_types │ └── data_types.cairo │ ├── graph │ ├── dfs_all_paths.cairo │ ├── dijkstra.cairo │ └── graph.cairo │ └── utils │ └── array_utils.cairo └── tests ├── test_array_utils.cairo ├── test_dfs_all_paths.cairo ├── test_dijkstra.cairo └── test_graph.cairo /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | hubble.iml 3 | graph.gv -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enitrat/cairo-graphs/72cfd914090e3ac58561919e0c7af5da296288fc/.gitmodules -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Mathieu Saugier 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Cairo-graphs: A graph library in Cairo 2 | 3 | ## Introduction 4 | 5 | Cairo-Graphs is a graph library written in Cairo that allows you to create and interact with graphs, entirely in Cairo, without state persistence. 6 | 7 | ## Graph implementation 8 | 9 | The graph is implemented as an array of `Vertex`. A vertex is composed of three elements : 10 | 11 | - Its index in the graph array. 12 | - Its identifier (it can be a token address, a hash, a number, a string, etc.) 13 | - An array of adjacent vertices. 14 | 15 | Since the memory is immutable in cairo, I had to make a choice when building the graph. 16 | Since I store in each vertex an array containing the neighboring vertices, I had to track how many neighbors each vertex had (`adjacent_vertices_count[i]`). 17 | But since the memory is immutable in cairo, to avoid having complex operations requiring rebuilding the whole graph on each change 18 | I introduced an external array that tracks how many neighbors each vertex has. 19 | That way, when I add a neighbor to a vertex, I can just append it to the `adjacent_vertices_count` array of the current vertex, and I can update the number of neighbors it has by 20 | rebuilding the `adjacent_vertices_count` array. 21 | 22 | This implementation is probably not the most efficient, and one should expect modifications - 23 | I have to study the benefits of using another data structure, for example, a dict, to track how many neighbors each vertex has. 24 | 25 | Here is how the Graph struct is built : 26 | 27 | - a member length tracks the amount of vertices 28 | - a member `vertices` represents the array of vertices. 29 | - a member `adj_vertices_count`, an array of integers, tracks how many neighbors each vertex has. 30 | 31 | ## How to use it 32 | 33 | ### Create a graph 34 | 35 | To create a graph, import the `GraphMethods` namespace from `cairo_graphs.graph.graph` that exposes several methods : 36 | 37 | - `new_graph` returns an empty graph that you can fill manually 38 | - `build_undirected_graph_from_edges` : Given an array of Edges (Represented by a src_identifier, dst_identifier and weight), returns an undirected graph built from the edges. 39 | - `build_directed_graph_from_edges` : Given an array of Edges (Represented by a src_identifier, dst_identifier and weight), returns a directed graph built from the edges. 40 | - `add_edge`: Given an existing graph and an edge to add, updates the graph and returns the updated data. 41 | - `add_vertex_to_graph` : Given an existing graph, adds a new vertex with a specific identifier. 42 | 43 | The easiest way is to have an array of `Edge`, Edge being a struct with the following fields : 44 | 45 | - `src_identifier` : The identifier of the source vertex. 46 | - `dst_identifier` : The identifier of the destination vertex. 47 | - `weight` : The weight of the edge. 48 | 49 | You can for example simply call `build_directed_graph_from_edges(edges_len,edges)` to create a directed graph from an array of edges. 50 | 51 | ### Graph algorithms 52 | 53 | For now, two algorithms are implemented : 54 | 55 | - The Dijkstra algorithm. You can use it once you have built a valid graph. 56 | To do so, import `Dijkstra` from `cairo_graphs.graph.dijkstra` and call `Dijkstra.shortest_path`, which will return the distance between the two vertices as well as the path itself. 57 | You will need to provide the actual `graph` as parameter. 58 | - A DFS algorithm that returns all the possible paths to go from a vertex A to a vertex B given a maximum number of hops. 59 | The lower the number of hops allowed, the less computing-intensive the algorithm will be. 60 | 61 | ## Testing 62 | 63 | To run the tests, install protostar and run : 64 | 65 | ``` 66 | protostar test 67 | ``` 68 | -------------------------------------------------------------------------------- /generate_graph.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import os 3 | 4 | def run_protostar(): 5 | tempfile = open(os.path.join(script_path,"temp_output.gv"), "w") 6 | subprocess.run(['protostar','--no-color', 'test','::test_generate_graphviz','--no-progress-bar'], stdout=tempfile) 7 | tempfile.close() 8 | 9 | def generate_graph(): 10 | graph_file = open(os.path.join(script_path,"graph.gv"), "w") 11 | r_tempfile = open(os.path.join(script_path,"temp_output.gv"), "r") 12 | contents = r_tempfile.readlines() 13 | found = False 14 | for i, line in enumerate(contents): 15 | if "[test]" in line: 16 | found = True 17 | graph_file.write("digraph {\n") 18 | continue 19 | if found and line!="\n": 20 | print(line) 21 | graph_file.write(line) 22 | graph_file.write("}") 23 | graph_file.close() 24 | 25 | 26 | script_path = os.path.dirname(os.path.realpath(__file__)) 27 | run_protostar() 28 | generate_graph() 29 | os.remove(os.path.join(script_path,"temp_output.gv")) 30 | 31 | 32 | -------------------------------------------------------------------------------- /protostar.toml: -------------------------------------------------------------------------------- 1 | ["protostar.config"] 2 | protostar_version = "0.4.2" 3 | 4 | ["protostar.project"] 5 | libs_path = "lib" 6 | 7 | ["protostar.contracts"] 8 | 9 | ["protostar.shared_command_configs"] 10 | cairo_path = ["./src"] 11 | 12 | [profile.devnet.protostar.deploy] 13 | gateway-url = "http://127.0.0.1:5050/" 14 | 15 | [profile.testnet.protostar.deploy] 16 | network="alpha-goerli" 17 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | # AVOID CHANGING REQUIRES: IT WILL BE UPDATED BY PYSCAFFOLD! 3 | requires = ["setuptools>=46.1.0", "setuptools_scm[toml]>=5", "wheel"] 4 | build-backend = "setuptools.build_meta" 5 | 6 | [tool.setuptools_scm] 7 | # See configuration details in https://github.com/pypa/setuptools_scm 8 | version_scheme = "no-guess-dev" 9 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = cairo-graphs 3 | version = 1.0.0 4 | description = Library for graphs written in Cairo 5 | author = msaug 6 | author_email = msaug@protonmail.com 7 | license = MIT 8 | long_description = file: README.md 9 | long_description_content_type = text/markdown; charset=UTF-8 10 | url = https://github.com/msaug/cairo-graphs 11 | platforms = any 12 | classifiers = 13 | Operating System :: OS Independent 14 | 15 | [options] 16 | zip_safe = False 17 | packages = find_namespace: 18 | include_package_data = True 19 | package_dir = 20 | =src 21 | 22 | install_requires = 23 | importlib-metadata>=4.0 24 | 25 | [options.packages.find] 26 | where = src 27 | exclude = 28 | tests 29 | 30 | [options.package_data] 31 | openzeppelin = "*.cairo" 32 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | if __name__ == "__main__": 4 | try: 5 | setup(use_scm_version={"version_scheme": "no-guess-dev"}) 6 | except: # noqa 7 | print( 8 | "\n\nAn error occurred while building the project, " 9 | "please ensure you have the most updated version of setuptools, " 10 | "setuptools_scm and wheel with:\n" 11 | " pip install -U setuptools setuptools_scm wheel\n\n" 12 | ) 13 | raise 14 | -------------------------------------------------------------------------------- /src/cairo_graphs/data_types/data_types.cairo: -------------------------------------------------------------------------------- 1 | // A vertex has an index (its position in the graph DS), 2 | // a unique identifier and a list of adjacent vertices. 3 | struct Vertex { 4 | index: felt, 5 | identifier: felt, 6 | adjacent_vertices: AdjacentVertex*, 7 | } 8 | 9 | // An edge is represented by its source and destination identifiers and a weight. 10 | struct Edge { 11 | src_identifier: felt, 12 | dst_identifier: felt, 13 | weight: felt, 14 | } 15 | 16 | // dst represents the adjacent vertex in the graph 17 | // weight is the distance from the current vertex to dst. 18 | struct AdjacentVertex { 19 | dst: Vertex, 20 | weight: felt, 21 | } 22 | 23 | struct Graph { 24 | length:felt, 25 | vertices: Vertex*, 26 | adjacent_vertices_count: felt*, 27 | } 28 | -------------------------------------------------------------------------------- /src/cairo_graphs/graph/dfs_all_paths.cairo: -------------------------------------------------------------------------------- 1 | from starkware.cairo.common.default_dict import default_dict_new, default_dict_finalize 2 | from starkware.cairo.common.dict_access import DictAccess 3 | from starkware.cairo.common.alloc import alloc 4 | from starkware.cairo.common.math_cmp import is_le 5 | from starkware.cairo.common.memcpy import memcpy 6 | from starkware.cairo.common.dict import dict_write, dict_update, dict_read 7 | 8 | from cairo_graphs.data_types.data_types import Edge, Vertex, Graph 9 | from cairo_graphs.graph.graph import GraphMethods 10 | from cairo_graphs.utils.array_utils import Stack 11 | 12 | const MAX_FELT = 2 ** 251 - 1; 13 | const MAX_HOPS = 4; 14 | 15 | func init_dict() -> (dict_ptr: DictAccess*) { 16 | alloc_locals; 17 | 18 | let (local dict_start) = default_dict_new(default_value=0); 19 | let dict_end = dict_start; 20 | return (dict_end,); 21 | } 22 | 23 | func init_dfs{range_check_ptr}( 24 | graph: Graph, start_identifier: felt, dst_identifier: felt, max_hops: felt 25 | ) -> (saved_paths_len: felt, saved_paths: felt*) { 26 | alloc_locals; 27 | let (dict_ptr: DictAccess*) = init_dict(); 28 | let (saved_paths: felt*) = alloc(); 29 | let (current_path: felt*) = alloc(); 30 | 31 | let start_vertex_index = GraphMethods.get_vertex_index{ 32 | graph=graph, identifier=start_identifier 33 | }(0); 34 | let dst_vertex_index = GraphMethods.get_vertex_index{graph=graph, identifier=dst_identifier}(0); 35 | 36 | let (saved_paths_len, _, _) = DFS_rec{dict_ptr=dict_ptr}( 37 | graph=graph, 38 | current_node=graph.vertices[start_vertex_index], 39 | destination_node=graph.vertices[dst_vertex_index], 40 | max_hops=max_hops, 41 | current_path_len=0, 42 | current_path=current_path, 43 | saved_paths_len=0, 44 | saved_paths=saved_paths, 45 | ); 46 | 47 | // stores the identifiers instead of the indexes in the path 48 | let (identifiers_path: felt*) = alloc(); 49 | get_identifiers_from_path( 50 | graph, saved_paths_len, saved_paths, current_index=0, identifiers_path=identifiers_path 51 | ); 52 | return (saved_paths_len, identifiers_path); 53 | } 54 | 55 | func DFS_rec{dict_ptr: DictAccess*, range_check_ptr}( 56 | graph: Graph, 57 | current_node: Vertex, 58 | destination_node: Vertex, 59 | max_hops: felt, 60 | current_path_len: felt, 61 | current_path: felt*, 62 | saved_paths_len: felt, 63 | saved_paths: felt*, 64 | ) -> (saved_paths_len: felt, current_path_len: felt, current_path: felt*) { 65 | alloc_locals; 66 | dict_write{dict_ptr=dict_ptr}(key=current_node.index, new_value=1); 67 | 68 | let (current_path_len, current_path) = Stack.put( 69 | current_path_len, current_path, current_node.index 70 | ); 71 | 72 | // When we return from this recursive function, we want to: 73 | // 1. Update the saved_paths array with the current path if it is a valid path. Since we're working with a pointer 74 | // to the saved_paths array that never changes, we just need to update its length 75 | // 2. Update the current_path array, after trimming the last elem. 76 | // 3. Update the current_path_len, after trimming the last elem. 77 | // 5. Incrementing the remaining_hops since we're going up in the recursion stack 78 | 79 | if (current_node.identifier == destination_node.identifier) { 80 | // store current path length inside saved_paths 81 | assert saved_paths[saved_paths_len] = current_path_len; 82 | let (saved_paths_len) = save_path( 83 | current_path_len, current_path, saved_paths_len + 1, saved_paths 84 | ); 85 | tempvar current_path_len = current_path_len; 86 | tempvar current_path = current_path; 87 | tempvar saved_paths_len = saved_paths_len; 88 | } else { 89 | tempvar current_path_len = current_path_len; 90 | tempvar current_path = current_path; 91 | tempvar saved_paths_len = saved_paths_len; 92 | } 93 | 94 | let (saved_paths_len, current_path_len, current_path, _, _) = visit_successors{ 95 | dict_ptr=dict_ptr 96 | }( 97 | graph=graph, 98 | current_node=current_node, 99 | destination_node=destination_node, 100 | remaining_hops=max_hops, 101 | successors_len=graph.adjacent_vertices_count[current_node.index], 102 | current_path_len=current_path_len, 103 | current_path=current_path, 104 | saved_paths_len=saved_paths_len, 105 | saved_paths=saved_paths, 106 | ); 107 | return (saved_paths_len, current_path_len, current_path); 108 | } 109 | 110 | func visit_successors{dict_ptr: DictAccess*, range_check_ptr}( 111 | graph: Graph, 112 | current_node: Vertex, 113 | destination_node: Vertex, 114 | remaining_hops: felt, 115 | successors_len: felt, 116 | current_path_len: felt, 117 | current_path: felt*, 118 | saved_paths_len: felt, 119 | saved_paths: felt*, 120 | ) -> ( 121 | saved_paths_len: felt, 122 | current_path_len: felt, 123 | current_path: felt*, 124 | successors_len: felt, 125 | remaining_hops: felt, 126 | ) { 127 | alloc_locals; 128 | 129 | // 130 | // Return conditions 131 | // 132 | 133 | // No more successors 134 | if (successors_len == 0) { 135 | // dict_write{dict_ptr=dict_ptr}(key=current_node.index, new_value=2) 136 | let (current_path_len, current_path, _) = Stack.pop(current_path_len, current_path); 137 | // explore previous_node's next_successor 138 | return ( 139 | saved_paths_len, current_path_len, current_path, successors_len - 1, remaining_hops 140 | ); 141 | } 142 | 143 | // Hops greater than limit 144 | if (remaining_hops == 0) { 145 | let (current_path_len, current_path, _) = Stack.pop(current_path_len, current_path); 146 | // explore previous_node's next_successor 147 | return ( 148 | saved_paths_len, current_path_len, current_path, successors_len - 1, remaining_hops 149 | ); 150 | } 151 | 152 | // Already visited successor, avoid cycles 153 | let successor = current_node.adjacent_vertices[successors_len - 1].dst; 154 | let successor_index = successor.index; 155 | let (is_already_visited) = is_in_path(current_path_len, current_path, successor_index); 156 | if (is_already_visited == 1) { 157 | return visit_successors( 158 | graph=graph, 159 | current_node=current_node, 160 | destination_node=destination_node, 161 | remaining_hops=remaining_hops, 162 | successors_len=successors_len - 1, 163 | current_path_len=current_path_len, 164 | current_path=current_path, 165 | saved_paths_len=saved_paths_len, 166 | saved_paths=saved_paths, 167 | ); 168 | } 169 | 170 | // 171 | // Go deeper in the recursion (do DFSrec from current node) 172 | // 173 | 174 | 175 | let (successor_visit_state) = dict_read{dict_ptr=dict_ptr}(key=successor_index); 176 | 177 | local saved_paths_len_updated: felt; 178 | local current_path_updated: felt*; 179 | local current_path_len_updated: felt; 180 | 181 | let is_state_1_or_0 = is_le(successor_visit_state, 1); 182 | if (is_state_1_or_0 == 1) { 183 | // assert current_path[current_path_len] = successor_index 184 | let (saved_paths_len, current_path_len, current_path) = DFS_rec( 185 | graph=graph, 186 | current_node=successor, 187 | destination_node=destination_node, 188 | max_hops=remaining_hops - 1, 189 | current_path_len=current_path_len, 190 | current_path=current_path, 191 | saved_paths_len=saved_paths_len, 192 | saved_paths=saved_paths, 193 | ); 194 | saved_paths_len_updated = saved_paths_len; 195 | current_path_len_updated = current_path_len; 196 | current_path_updated = current_path; 197 | tempvar dict_ptr = dict_ptr; 198 | tempvar range_check_ptr = range_check_ptr; 199 | } else { 200 | saved_paths_len_updated = saved_paths_len; 201 | current_path_len_updated = current_path_len; 202 | current_path_updated = current_path; 203 | tempvar dict_ptr = dict_ptr; 204 | tempvar range_check_ptr = range_check_ptr; 205 | } 206 | 207 | // Visit next successor (decrement successors_len) 208 | 209 | return visit_successors( 210 | graph=graph, 211 | current_node=current_node, 212 | destination_node=destination_node, 213 | remaining_hops=remaining_hops, 214 | successors_len=successors_len - 1, 215 | current_path_len=current_path_len_updated, 216 | current_path=current_path_updated, 217 | saved_paths_len=saved_paths_len_updated, 218 | saved_paths=saved_paths, 219 | ); 220 | } 221 | 222 | // @notice returns the index of the node in the graph 223 | // @returns -1 if it's not in the graph 224 | // @returns array index otherwise 225 | func is_in_path(current_path_len: felt, current_path: felt*, index: felt) -> (boolean: felt) { 226 | if (current_path_len == 0) { 227 | return (0,); 228 | } 229 | 230 | let current_index: felt = [current_path]; 231 | if (current_index == index) { 232 | return (1,); 233 | } 234 | 235 | return is_in_path(current_path_len - 1, current_path + 1, index); 236 | } 237 | 238 | func save_path( 239 | current_path_len: felt, current_path: felt*, saved_paths_len: felt, saved_paths: felt* 240 | ) -> (new_saved_paths_len: felt) { 241 | let new_saved_paths_len = saved_paths_len + current_path_len; 242 | memcpy(saved_paths + saved_paths_len, current_path, current_path_len); 243 | return (new_saved_paths_len,); 244 | } 245 | 246 | // @notice Return with an array composed by (path_len,path) subarrays identified by identifiers. 247 | func get_identifiers_from_path( 248 | graph: Graph, 249 | saved_paths_len: felt, 250 | saved_paths: felt*, 251 | current_index: felt, 252 | identifiers_path: felt*, 253 | ) { 254 | if (current_index == saved_paths_len) { 255 | return (); 256 | } 257 | let subarray_length = saved_paths[current_index]; 258 | assert [identifiers_path] = subarray_length; 259 | 260 | parse_array_segment( 261 | graph=graph, 262 | saved_paths=saved_paths, 263 | i=current_index + 1, 264 | j=current_index + 1 + subarray_length, 265 | identifiers_path=identifiers_path + 1, 266 | ); 267 | return get_identifiers_from_path( 268 | graph=graph, 269 | saved_paths_len=saved_paths_len, 270 | saved_paths=saved_paths, 271 | current_index=current_index + subarray_length + 1, 272 | identifiers_path=identifiers_path + 1 + subarray_length, 273 | ); 274 | } 275 | 276 | // @notice parses the identifiers for all vertices between indexes i and j in the indexes array 277 | func parse_array_segment( 278 | graph: Graph, saved_paths: felt*, i: felt, j: felt, identifiers_path: felt* 279 | ) { 280 | if (i == j) { 281 | return (); 282 | } 283 | let index_in_graph = saved_paths[i]; 284 | assert [identifiers_path] = graph.vertices[index_in_graph].identifier; 285 | return parse_array_segment(graph, saved_paths, i + 1, j, identifiers_path + 1); 286 | } 287 | -------------------------------------------------------------------------------- /src/cairo_graphs/graph/dijkstra.cairo: -------------------------------------------------------------------------------- 1 | from starkware.cairo.common.default_dict import default_dict_new, default_dict_finalize 2 | from starkware.cairo.common.dict_access import DictAccess 3 | from starkware.cairo.common.alloc import alloc 4 | from starkware.cairo.common.math_cmp import is_le_felt 5 | from starkware.cairo.common.memcpy import memcpy 6 | from starkware.cairo.common.dict import dict_write, dict_read 7 | 8 | from cairo_graphs.data_types.data_types import Vertex, Graph 9 | from cairo_graphs.graph.graph import GraphMethods 10 | from cairo_graphs.utils.array_utils import Array 11 | 12 | const MAX_FELT = 2 ** 251 - 1; 13 | 14 | // Glossary : 15 | // Unvisited vertices = white vertices. It means that they have never been visited. 16 | // Visited vertices = grey vertices. It means that they have been visited once, but they are not in their final state yet. 17 | // Finished vertices = black vertices. It means that we have found the shortest path to them. 18 | 19 | // @notice creates a new dictionary with a default value set to 0. 20 | func init_dict() -> (dict_ptr: DictAccess*) { 21 | alloc_locals; 22 | 23 | let (local dict_start) = default_dict_new(default_value=0); 24 | let dict_end = dict_start; 25 | return (dict_end,); 26 | } 27 | 28 | // @notice Sets all the values in the array to MAX_FELT. 29 | // @param remaining_len : remaining array length. 30 | // @param current_index : pointer to the current array address. 31 | func set_array_to_max_felt(remaining_len: felt, current_index: felt*) { 32 | if (remaining_len == 0) { 33 | return (); 34 | } 35 | assert [current_index] = MAX_FELT; 36 | return set_array_to_max_felt(remaining_len - 1, current_index + 1); 37 | } 38 | 39 | namespace Dijkstra { 40 | // @notice starts a Dijkstra algorith to find the shortest path from the source to the destination vertex. 41 | // @dev only works if all weights are positive. 42 | // @param graph the graph 43 | // @param start_identifier identifier of the starting vertex of the algorithm 44 | func run{range_check_ptr}(graph: Graph, start_identifier: felt) -> ( 45 | graph: Graph, predecessors: felt*, distances: felt* 46 | ) { 47 | alloc_locals; 48 | 49 | let start_vertex_index = GraphMethods.get_vertex_index{ 50 | graph=graph, identifier=start_identifier 51 | }(0); 52 | 53 | // 54 | // Data structures 55 | // 56 | 57 | // stores all of the vertices being currently visited 58 | let (grey_vertices: felt*) = alloc(); 59 | // stores the predecessor of each node. at index i you have the predecessor of graph[i] 60 | let (predecessors: felt*) = alloc(); 61 | // stores the distance from origin of each node. at index i you have the distance of graph[i] from origin. 62 | let (distances: felt*) = alloc(); 63 | 64 | // 65 | // Initial state 66 | // 67 | 68 | // Set all nodes to unvisited state 69 | let (dict_ptr: DictAccess*) = init_dict(); 70 | tempvar dict_ptr_start = dict_ptr; 71 | // Set all initial distances to MAX_FELT 72 | set_array_to_max_felt(graph.length, distances); 73 | // predecessor = max_felt means that there is no predecessor 74 | set_array_to_max_felt(graph.length, predecessors); 75 | // Set initial node as visited : pushed in the visited vertices, updates its value in the dict. 76 | let (grey_vertices_len) = DijkstraUtils.set_visited{dict_ptr=dict_ptr}( 77 | start_vertex_index, 0, grey_vertices 78 | ); 79 | // Set initial vertex distance to 0 80 | let (distances) = DijkstraUtils.set_distance( 81 | start_vertex_index, graph.length, distances, 0 82 | ); 83 | 84 | // cant use the values as implicit args without binding them to an identifier 85 | let (predecessors, distances) = visit_grey_vertices{ 86 | dict_ptr=dict_ptr, range_check_ptr=range_check_ptr, graph=graph 87 | }(predecessors, distances, grey_vertices_len, grey_vertices); 88 | 89 | default_dict_finalize(dict_ptr_start, dict_ptr, 0); 90 | 91 | return (graph, predecessors, distances); 92 | } 93 | 94 | func shortest_path{range_check_ptr}( 95 | graph: Graph, start_vertex_id: felt, end_vertex_id: felt 96 | ) -> (path_len: felt, path: felt*, distance: felt) { 97 | alloc_locals; 98 | let (graph, predecessors, distances) = run(graph, start_vertex_id); 99 | 100 | let end_vertex_index = GraphMethods.get_vertex_index{graph=graph, identifier=end_vertex_id}( 101 | 0 102 | ); 103 | let start_vertex_index = GraphMethods.get_vertex_index{ 104 | graph=graph, identifier=start_vertex_id 105 | }(0); 106 | 107 | let (shortest_path_indexes: felt*) = alloc(); 108 | let total_distance = distances[end_vertex_index]; 109 | 110 | // Read all the predecessors from end_vertex to start_vertex 111 | // and store the path in an array 112 | // TODO optimize these steps. 113 | assert [shortest_path_indexes] = end_vertex_index; 114 | let (shortest_path_len) = build_shortest_path{ 115 | graph=graph, 116 | predecessors=predecessors, 117 | start_vertex_index=start_vertex_index, 118 | }( 119 | current_vertex_index=end_vertex_index, 120 | shortest_path_len=1, 121 | shortest_path_indexes=shortest_path_indexes + 1, 122 | ); 123 | 124 | let (correct_order_path) = Array.inverse(shortest_path_len, shortest_path_indexes); 125 | // stores the token addresses instead of the graph indexes in the path 126 | let (identifiers: felt*) = alloc(); 127 | get_identifiers_from_indexes( 128 | graph.vertices, shortest_path_len, correct_order_path, identifiers 129 | ); 130 | 131 | return (shortest_path_len, identifiers, total_distance); 132 | } 133 | } 134 | 135 | func visit_grey_vertices{dict_ptr: DictAccess*, range_check_ptr, graph: Graph}( 136 | predecessors: felt*, distances: felt*, grey_vertices_len: felt, grey_vertices: felt* 137 | ) -> (predecessors: felt*, distances: felt*) { 138 | alloc_locals; 139 | 140 | let (closest_distance, closest_vertex_index) = DijkstraUtils.get_closest_visited_vertex( 141 | graph.length, distances, grey_vertices_len, grey_vertices, MAX_FELT, MAX_FELT 142 | ); 143 | 144 | if (closest_distance == MAX_FELT) { 145 | return (predecessors, distances); 146 | // done, it means that there are no more grey vertices pending. 147 | } 148 | 149 | let current_vertex = graph.vertices[closest_vertex_index]; 150 | let nb_adj_vertices = graph.adjacent_vertices_count[closest_vertex_index]; 151 | let (predecessors, distances) = visit_successors{ 152 | dict_ptr=dict_ptr, 153 | graph=graph, 154 | grey_vertices_len=grey_vertices_len, 155 | grey_vertices=grey_vertices, 156 | }( 157 | current_vertex=current_vertex, 158 | successors_len=nb_adj_vertices, 159 | predecessors=predecessors, 160 | distances=distances, 161 | ); 162 | 163 | let (grey_vertices_len, grey_vertices) = DijkstraUtils.set_finished( 164 | current_vertex.index, grey_vertices_len, grey_vertices 165 | ); 166 | return visit_grey_vertices(predecessors, distances, grey_vertices_len, grey_vertices); 167 | } 168 | 169 | // @notice Visits the successors of a vertex. 170 | func visit_successors{ 171 | dict_ptr: DictAccess*, range_check_ptr, graph: Graph, grey_vertices_len, grey_vertices: felt* 172 | }(current_vertex: Vertex, successors_len: felt, predecessors: felt*, distances: felt*) -> ( 173 | predecessors: felt*, distances: felt* 174 | ) { 175 | alloc_locals; 176 | 177 | // No more successors -> stop current successor loop 178 | if (successors_len == 0) { 179 | return (predecessors, distances); 180 | } 181 | 182 | // We release the current edge if s[j] is not black 183 | let successor_edge = current_vertex.adjacent_vertices[successors_len - 1]; 184 | let successor = successor_edge.dst; 185 | let weight = successor_edge.weight; 186 | let (is_successor_finished) = DijkstraUtils.is_finished(successor.index); 187 | 188 | // Visit next node if the successor is black 189 | if (is_successor_finished == 1) { 190 | return visit_successors(current_vertex, successors_len - 1, predecessors, distances); 191 | } 192 | 193 | relax_edge{predecessors=predecessors, distances=distances}(current_vertex, successor, weight); 194 | 195 | let (is_not_visited) = DijkstraUtils.is_not_visited(successor.index); 196 | 197 | if (is_not_visited == 1) { 198 | let (grey_vertices_len) = DijkstraUtils.set_visited( 199 | successor.index, grey_vertices_len, grey_vertices 200 | ); 201 | tempvar dict_ptr = dict_ptr; 202 | tempvar grey_vertices_len = grey_vertices_len; 203 | } else { 204 | tempvar dict_ptr = dict_ptr; 205 | tempvar grey_vertices_len = grey_vertices_len; 206 | } 207 | 208 | return visit_successors(current_vertex, successors_len - 1, predecessors, distances); 209 | } 210 | 211 | func relax_edge{ 212 | dict_ptr: DictAccess*, graph: Graph, predecessors: felt*, distances: felt*, range_check_ptr 213 | }(src: Vertex, dst: Vertex, weight: felt) { 214 | alloc_locals; 215 | let current_disctance = distances[dst.index]; 216 | let new_distance = distances[src.index] + weight; 217 | let is_new_distance_better = is_le_felt(new_distance, current_disctance); 218 | if (is_new_distance_better == 1) { 219 | let (distances) = DijkstraUtils.set_distance( 220 | dst.index, graph.length, distances, new_distance 221 | ); 222 | let (predecessors) = DijkstraUtils.set_predecessor( 223 | dst.index, graph.length, predecessors, src.index 224 | ); 225 | return (); 226 | } 227 | return (); 228 | } 229 | 230 | // @notice: This function is called recursively. It reads the predecessor from the current vertex and 231 | // stores it in the path array. 232 | // @param currrent_vertex_index : currently analysed vertex index. 233 | // @param shortest_path_len : length of the array that holds the path. 234 | // @param shortest_path_indexes : array that holds the indexes of the vertices in the shortes path. 235 | // @returns shortest_path_len : length of the array that holds the shortest path. 236 | func build_shortest_path{ 237 | graph:Graph, predecessors: felt*, start_vertex_index: felt 238 | }(current_vertex_index: felt, shortest_path_len: felt, shortest_path_indexes: felt*) -> ( 239 | shortest_path_len: felt 240 | ) { 241 | let prev_vertex_index = predecessors[current_vertex_index]; 242 | if (prev_vertex_index == MAX_FELT) { 243 | return (0,); 244 | } 245 | assert [shortest_path_indexes] = prev_vertex_index; 246 | if (prev_vertex_index == start_vertex_index) { 247 | return (shortest_path_len + 1,); 248 | } 249 | 250 | return build_shortest_path( 251 | current_vertex_index=graph.vertices[prev_vertex_index].index, 252 | shortest_path_len=shortest_path_len + 1, 253 | shortest_path_indexes=shortest_path_indexes + 1, 254 | ); 255 | } 256 | 257 | // @notice Returns an array composed by graph identifiers instead of graph indexes. 258 | func get_identifiers_from_indexes( 259 | vertices: Vertex*, source_array_len: felt, source_array: felt*, res: felt* 260 | ) -> (identifiers_predecessors: felt*) { 261 | alloc_locals; 262 | if (source_array_len == 0) { 263 | return (res,); 264 | } 265 | local current_vertex_index = source_array[source_array_len - 1]; 266 | 267 | // origin vertex has MAX_FELT as predecessor, which is not a valid graph index. 268 | if (current_vertex_index == MAX_FELT) { 269 | assert res[source_array_len - 1] = MAX_FELT; 270 | } else { 271 | assert res[source_array_len - 1] = vertices[current_vertex_index].identifier; 272 | } 273 | 274 | return get_identifiers_from_indexes( 275 | vertices=vertices, source_array_len=source_array_len - 1, source_array=source_array, res=res 276 | ); 277 | } 278 | 279 | // Util functions used in our algorithm. 280 | namespace DijkstraUtils { 281 | func get_state{dict_ptr: DictAccess*}(vertex_index: felt) { 282 | let (state) = dict_read{dict_ptr=dict_ptr}(key=vertex_index); 283 | return (state,); 284 | } 285 | 286 | func is_not_visited{dict_ptr: DictAccess*}(vertex_index: felt) -> (visited: felt) { 287 | let (state) = dict_read{dict_ptr=dict_ptr}(key=vertex_index); 288 | if (state == 0) { 289 | return (1,); 290 | } 291 | return (0,); 292 | } 293 | 294 | func set_visited{dict_ptr: DictAccess*}( 295 | vertex_index: felt, grey_vertices_len: felt, grey_vertices: felt* 296 | ) -> (grey_vertices_len: felt) { 297 | dict_write{dict_ptr=dict_ptr}(key=vertex_index, new_value=1); 298 | assert grey_vertices[grey_vertices_len] = vertex_index; 299 | return (grey_vertices_len + 1,); 300 | } 301 | 302 | func is_visited{dict_ptr: DictAccess*}(vertex) -> (visited: felt) { 303 | let (state) = dict_read{dict_ptr=dict_ptr}(key=vertex.index); 304 | if (state - 1 == 0) { 305 | return (1,); 306 | } 307 | return (0,); 308 | } 309 | 310 | func set_finished{dict_ptr: DictAccess*}( 311 | vertex_index: felt, grey_vertices_len: felt, grey_vertices: felt* 312 | ) -> (grey_vertices_len: felt, grey_vertices: felt*) { 313 | alloc_locals; 314 | dict_write{dict_ptr=dict_ptr}(key=vertex_index, new_value=2); 315 | let (index_to_remove) = Array.get_value_index( 316 | grey_vertices_len, grey_vertices, vertex_index, current_index=0 317 | ); 318 | let (grey_vertices_len, grey_vertices) = Array.remove_value_at_index( 319 | grey_vertices_len, grey_vertices, index_to_remove 320 | ); 321 | return (grey_vertices_len, grey_vertices); 322 | } 323 | 324 | func is_finished{dict_ptr: DictAccess*}(vertex_index: felt) -> (finished: felt) { 325 | let (state) = dict_read{dict_ptr=dict_ptr}(key=vertex_index); 326 | if (state - 2 == 0) { 327 | return (1,); 328 | } 329 | return (0,); 330 | } 331 | 332 | func set_distance(vertex_index: felt, distances_len: felt, distances: felt*, new_distance) -> ( 333 | new_distances: felt* 334 | ) { 335 | let (new_distances) = Array.update_value_at_index( 336 | distances_len, distances, vertex_index, new_distance 337 | ); 338 | return (new_distances,); 339 | } 340 | 341 | func set_predecessor{dict_ptr: DictAccess*}( 342 | vertex_index: felt, predecessors_len: felt, predecessors: felt*, new_predecessor_index: felt 343 | ) -> (new_predecessors: felt*) { 344 | let (new_predecessors) = Array.update_value_at_index( 345 | predecessors_len, predecessors, vertex_index, new_predecessor_index 346 | ); 347 | return (new_predecessors,); 348 | } 349 | 350 | // Returns the distance of index of the closest vertex 351 | func get_closest_visited_vertex{range_check_ptr}( 352 | distances_len: felt, 353 | distances: felt*, 354 | grey_vertices_len: felt, 355 | grey_vertices: felt*, 356 | closest_distance: felt, 357 | closest_vertex: felt, 358 | ) -> (closest_distance: felt, closest_vertex_index: felt) { 359 | alloc_locals; 360 | if (grey_vertices_len == 0) { 361 | return (closest_distance, closest_vertex); 362 | } 363 | tempvar current_vertex_index = [grey_vertices]; 364 | tempvar current_distance = distances[current_vertex_index]; 365 | 366 | let is_new_distance_better = is_le_felt(current_distance, closest_distance); 367 | if (is_new_distance_better == 1) { 368 | return get_closest_visited_vertex( 369 | distances_len, 370 | distances, 371 | grey_vertices_len - 1, 372 | grey_vertices + 1, 373 | current_distance, 374 | current_vertex_index, 375 | ); 376 | } 377 | 378 | return get_closest_visited_vertex( 379 | distances_len, 380 | distances, 381 | grey_vertices_len - 1, 382 | grey_vertices + 1, 383 | closest_distance, 384 | closest_vertex, 385 | ); 386 | } 387 | } 388 | -------------------------------------------------------------------------------- /src/cairo_graphs/graph/graph.cairo: -------------------------------------------------------------------------------- 1 | from starkware.cairo.common.memcpy import memcpy 2 | from starkware.cairo.common.alloc import alloc 3 | from starkware.cairo.common.math import assert_not_equal 4 | 5 | from cairo_graphs.data_types.data_types import Vertex, Edge, AdjacentVertex, Graph 6 | from cairo_graphs.utils.array_utils import Array 7 | 8 | // # Adjancency list graph implementation 9 | 10 | namespace GraphMethods { 11 | // @notice Creates a new empty graph. 12 | func new_graph() -> Graph { 13 | let (graph: Vertex*) = alloc(); 14 | let (adj_vertices_count: felt*) = alloc(); 15 | tempvar res: Graph = Graph(0, graph, adj_vertices_count); 16 | return res; 17 | } 18 | 19 | // @notice Builds an undirected graph 20 | // @param edges_len : The length of the array of edges 21 | // @param edges : The array of edges 22 | // @returns graph : The graph built from the array of edges 23 | func build_undirected_graph_from_edges(edges_len: felt, edges: Edge*) -> Graph { 24 | alloc_locals; 25 | let graph = GraphMethods.new_graph(); 26 | return build_undirected_graph_from_edges_internal(edges_len, edges, graph); 27 | } 28 | 29 | // @notice Builds a directed graph 30 | // @param edges_len : The length of the array of edges 31 | // @param edges : The array of edges 32 | // @returns graph : The graph built from the array of edges 33 | func build_directed_graph_from_edges(edges_len: felt, edges: Edge*) -> Graph { 34 | let graph = GraphMethods.new_graph(); 35 | return build_directed_graph_from_edges_internal(edges_len, edges, graph); 36 | } 37 | 38 | // @notice Adds an edge between two graph vertices 39 | // @dev if the vertices don't exist yet, adds them to the graph 40 | // @param graph : The graph we're adding the edges to 41 | // @param edge : Edge to add to the graph. Holds the info about the src_identifier, dst_identifier and weight. 42 | // @returns the updated Graph 43 | func add_edge(graph: Graph, edge: Edge) -> Graph { 44 | alloc_locals; 45 | let src_identifier = edge.src_identifier; 46 | let dst_identifier = edge.dst_identifier; 47 | let vertices = graph.vertices; 48 | let weight = edge.weight; 49 | 50 | let graph_len = graph.length; 51 | let vertices = graph.vertices; 52 | let adjacent_vertices_count: felt* = graph.adjacent_vertices_count; 53 | 54 | assert_not_equal(src_identifier, dst_identifier); // can't add two nodes with the same identifier 55 | let src_vertex_index = get_vertex_index{graph=graph, identifier=src_identifier}(0); 56 | let dst_vertex_index = get_vertex_index{graph=graph, identifier=dst_identifier}(0); 57 | 58 | // Add both vertices to the graph if they aren't already there 59 | if (src_vertex_index == -1) { 60 | let graph = add_vertex_to_graph(graph, src_identifier); 61 | tempvar src_vertex_index = graph.length - 1; 62 | tempvar graph = graph; 63 | } else { 64 | tempvar src_vertex_index = src_vertex_index; 65 | tempvar graph = graph; 66 | } 67 | 68 | tempvar src_vertex_index = src_vertex_index; 69 | 70 | if (dst_vertex_index == -1) { 71 | let graph = add_vertex_to_graph(graph, dst_identifier); 72 | tempvar dst_vertex_index = graph.length - 1; 73 | tempvar graph = graph; 74 | } else { 75 | tempvar dst_vertex_index = dst_vertex_index; 76 | tempvar graph = graph; 77 | } 78 | 79 | tempvar dst_vertex_index = dst_vertex_index; 80 | 81 | // Add the edge from src to dst to the graph, stored as an adjacent vertex in the adjacency lists of the source. 82 | let res = add_neighbor( 83 | vertices[src_vertex_index], 84 | vertices[dst_vertex_index], 85 | graph, 86 | src_vertex_index, 87 | weight, 88 | ); 89 | tempvar dst_vertex_index = dst_vertex_index; 90 | return res; 91 | } 92 | 93 | // @notice Adds a vertex to the graph. 94 | // @dev Creates a new vertex stored at graph.vertices[graph_len]. 95 | // @param graph : The graph we want to add the vertex to. 96 | // @param identifier : Unique identifier of the vertex to add. 97 | // @returns graph_len : The updated graph. 98 | func add_vertex_to_graph(graph: Graph, identifier: felt) -> Graph { 99 | let adj_vertices: AdjacentVertex* = alloc(); 100 | tempvar vertex: Vertex = Vertex(graph.length, identifier, adj_vertices); 101 | assert graph.vertices[graph.length] = vertex; 102 | assert graph.adjacent_vertices_count[graph.length] = 0; 103 | let new_graph_len = graph.length + 1; 104 | tempvar res: Graph = Graph(new_graph_len, graph.vertices, graph.adjacent_vertices_count); 105 | return res; 106 | } 107 | 108 | // @notice Recursive function returning the index of the node in the graph 109 | // @param graph : The graph we're working with. 110 | // @param identifier, The unique identifier of the vertex. 111 | // @returns -1 if it's not in the graph, the index in the graph.vertices array otherwise 112 | func get_vertex_index{graph: Graph, identifier: felt}(current_index) -> felt { 113 | alloc_locals; 114 | if (graph.length == current_index) { 115 | return -1; 116 | } 117 | local current_identifier: felt = graph.vertices[current_index].identifier; 118 | if (current_identifier == identifier) { 119 | return graph.vertices[current_index].index; 120 | } 121 | 122 | return get_vertex_index(current_index + 1); 123 | } 124 | } 125 | 126 | // @notice adds a neighbor to the adjacent vertices of a vertex 127 | // @param vertex : The vertex to add the neighbor to. 128 | // @param new_neighbor : The neighbor to add to the vertex. 129 | // @param adj_vertices_count_len : The length of the adj_vertices_count array. 130 | // @param adj_vertices_count : Array that tracks how many adjacent vertices each vertex has. 131 | // @param vertex_index_in_graph : The index of the vertex in the graph. 132 | // @return the updated adj_vertices_count 133 | func add_neighbor( 134 | vertex: Vertex, 135 | new_neighbor: Vertex, 136 | graph:Graph, 137 | vertex_index_in_graph: felt, 138 | weight: felt, 139 | ) -> Graph { 140 | let current_count = graph.adjacent_vertices_count[vertex_index_in_graph]; 141 | tempvar adjacent_vertex = AdjacentVertex(new_neighbor, weight); 142 | assert vertex.adjacent_vertices[current_count] = adjacent_vertex; 143 | // update neighbors_len 144 | let new_count = current_count + 1; 145 | let (new_adj_vertices_count: felt*) = Array.update_value_at_index( 146 | graph.length, graph.adjacent_vertices_count, vertex_index_in_graph, new_count 147 | ); 148 | tempvar res = Graph(graph.length, graph.vertices, new_adj_vertices_count); 149 | return res; 150 | } 151 | 152 | // @notice internal function to build the graph recursively 153 | // @dev 154 | // @param pairs_len : The length of the pairs edges_len 155 | // @param edges : The edges array 156 | // @param graph : The graph 157 | func build_undirected_graph_from_edges_internal( 158 | edges_len: felt, edges: Edge*, graph: Graph 159 | ) -> Graph { 160 | alloc_locals; 161 | 162 | if (edges_len == 0) { 163 | return graph; 164 | } 165 | 166 | let graph = GraphMethods.add_edge(graph, [edges]); 167 | 168 | let graph = GraphMethods.add_edge( 169 | graph, Edge([edges].dst_identifier, [edges].src_identifier, [edges].weight) 170 | ); 171 | 172 | return build_undirected_graph_from_edges_internal(edges_len - 1, edges + Edge.SIZE, graph); 173 | } 174 | 175 | // @notice internal function to build the graph recursively 176 | // @dev 177 | // @param pairs_len : The length of the pairs array 178 | // @param pairs : The pairs array 179 | // @param graph : The graph 180 | func build_directed_graph_from_edges_internal( 181 | edges_len: felt, edges: Edge*, graph: Graph 182 | ) -> Graph { 183 | alloc_locals; 184 | 185 | if (edges_len == 0) { 186 | return graph; 187 | } 188 | 189 | let graph = GraphMethods.add_edge(graph, [edges]); 190 | 191 | return build_directed_graph_from_edges_internal(edges_len - 1, edges + Edge.SIZE, graph); 192 | } 193 | -------------------------------------------------------------------------------- /src/cairo_graphs/utils/array_utils.cairo: -------------------------------------------------------------------------------- 1 | from starkware.cairo.common.alloc import alloc 2 | from starkware.cairo.common.memcpy import memcpy 3 | from starkware.cairo.lang.compiler.lib.registers import get_fp_and_pc 4 | 5 | namespace Stack { 6 | // Removes the last element from an array and returns it 7 | func pop(stack_len: felt, stack: felt*) -> ( 8 | new_stack_len: felt, new_stack: felt*, last_elem: felt 9 | ) { 10 | alloc_locals; 11 | 12 | let (local res: felt*) = alloc(); 13 | memcpy(res, stack, stack_len - 1); 14 | return (stack_len - 1, res, stack[stack_len - 1]); 15 | } 16 | 17 | func put(stack_len: felt, stack: felt*, element: felt) -> ( 18 | new_stack_len: felt, new_stack: felt* 19 | ) { 20 | alloc_locals; 21 | 22 | assert stack[stack_len] = element; 23 | let new_stack_len = stack_len + 1; 24 | return (new_stack_len, stack); 25 | } 26 | } 27 | 28 | namespace Array { 29 | // @notice increments the neighbors_len of a node by re-writing the entire 30 | func update_value_at_index( 31 | array_len: felt, array: felt*, elem_index: felt, new_value: felt 32 | ) -> (new_array: felt*) { 33 | alloc_locals; 34 | let (__fp__, _) = get_fp_and_pc(); 35 | let (local res: felt*) = alloc(); 36 | memcpy(res, array, elem_index); // copy elem_index elements from array to res 37 | memcpy(res + elem_index, &new_value, 1); // store new_value at memory cell [res+member_index] 38 | 39 | // first memory address to copy in 40 | // first memory address to copy from 41 | // number of values to copy 42 | memcpy(res + elem_index + 1, array + elem_index + 1, array_len - elem_index - 1); 43 | 44 | return (res,); 45 | } 46 | 47 | func copy(array_len: felt, array: felt*) -> (new_array: felt*) { 48 | alloc_locals; 49 | let (local res: felt*) = alloc(); 50 | memcpy(res, array, array_len); // copy array_len elems from array to res 51 | 52 | return (res,); 53 | } 54 | 55 | func remove_value_at_index(array_len: felt, array: felt*, elem_index: felt) -> ( 56 | new_array_len: felt, new_array: felt* 57 | ) { 58 | alloc_locals; 59 | let (__fp__, _) = get_fp_and_pc(); 60 | let (local res: felt*) = alloc(); 61 | local new_value = array[elem_index] + 1; 62 | memcpy(res, array, elem_index); // copy elem_index elements from array to res 63 | // copy the rest of the array from elem_index+1 to the end of the array 64 | memcpy(res + elem_index, array + elem_index + 1, array_len - elem_index - 1); 65 | 66 | return (array_len - 1, res); 67 | } 68 | 69 | func get_value_index(array_len: felt, array: felt*, value: felt, current_index: felt) -> ( 70 | index: felt 71 | ) { 72 | if (array_len == 0) { 73 | assert 1 = 0; // fail if it's not in the array 74 | } 75 | 76 | let current_value: felt = [array]; 77 | if (current_value == value) { 78 | return (current_index,); 79 | } 80 | 81 | return get_value_index(array_len - 1, array + 1, value, current_index + 1); 82 | } 83 | 84 | func inverse(array_len: felt, array: felt*) -> (inv_array: felt*) { 85 | alloc_locals; 86 | let (local inv_array: felt*) = alloc(); 87 | 88 | _inverse_internal(array_len, array, inv_array); 89 | return (inv_array,); 90 | } 91 | } 92 | 93 | func _inverse_internal(array_len: felt, array: felt*, inv_array: felt*) { 94 | if (array_len == 0) { 95 | return (); 96 | } 97 | assert inv_array[array_len - 1] = [array]; 98 | return _inverse_internal(array_len - 1, array + 1, inv_array); 99 | } 100 | -------------------------------------------------------------------------------- /tests/test_array_utils.cairo: -------------------------------------------------------------------------------- 1 | %lang starknet 2 | 3 | from starkware.cairo.common.alloc import alloc 4 | from starkware.cairo.common.memcpy import memcpy 5 | 6 | from cairo_graphs.utils.array_utils import Stack, Array 7 | 8 | @external 9 | func test_pop_stack() { 10 | let (my_stack: felt*) = alloc(); 11 | assert my_stack[0] = 1; 12 | assert my_stack[1] = 2; 13 | assert my_stack[2] = 3; 14 | let stack_len = 3; 15 | let (stack_len, my_stack, last_elem) = Stack.pop(stack_len, my_stack); 16 | assert last_elem = 3; 17 | assert stack_len = 2; 18 | assert my_stack[0] = 1; 19 | tempvar memcell_allocated = my_stack[1]; 20 | assert memcell_allocated = 2; 21 | %{ expect_revert() %} 22 | tempvar memcell_unallocated = my_stack[2]; // last elem was popped -> should revert 23 | assert memcell_unallocated = 3; 24 | return (); 25 | } 26 | 27 | @external 28 | func test_put_stack() { 29 | let (my_stack: felt*) = alloc(); 30 | assert my_stack[0] = 1; 31 | assert my_stack[1] = 2; 32 | assert my_stack[2] = 3; 33 | let stack_len = 3; 34 | let (stack_len, my_stack) = Stack.put(stack_len, my_stack, 4); 35 | assert stack_len = 4; 36 | tempvar last_value = my_stack[stack_len - 1]; 37 | assert last_value = 4; 38 | return (); 39 | } 40 | 41 | @external 42 | func test_update_value_at_index() { 43 | alloc_locals; 44 | let (local my_array: felt*) = alloc(); 45 | assert my_array[0] = 1; 46 | assert my_array[1] = 2; 47 | assert my_array[2] = 3; 48 | let array_len = 3; 49 | let (new_array: felt*) = Array.update_value_at_index(array_len, my_array, 1, 20); 50 | tempvar value = new_array[0]; 51 | assert value = my_array[0]; 52 | tempvar value = new_array[1]; 53 | assert value = 20; 54 | tempvar value = new_array[2]; 55 | assert value = my_array[2]; 56 | %{ expect_revert() %} 57 | tempvar value = new_array[3]; 58 | return (); 59 | } 60 | 61 | @external 62 | func test_copy() { 63 | alloc_locals; 64 | let (local my_array: felt*) = alloc(); 65 | assert my_array[0] = 1; 66 | assert my_array[1] = 2; 67 | assert my_array[2] = 3; 68 | let array_len = 3; 69 | let (new_array) = Array.copy(array_len, my_array); 70 | tempvar value = new_array[0]; 71 | assert value = my_array[0]; 72 | tempvar value = new_array[1]; 73 | assert value = my_array[1]; 74 | tempvar value = new_array[2]; 75 | assert value = my_array[2]; 76 | %{ expect_revert() %} 77 | tempvar value = new_array[3]; 78 | return (); 79 | } 80 | 81 | @external 82 | func test_remove_value_at_index() { 83 | alloc_locals; 84 | let (local my_array: felt*) = alloc(); 85 | assert my_array[0] = 1; 86 | assert my_array[1] = 2; 87 | assert my_array[2] = 3; 88 | let array_len = 3; 89 | let (new_array_len: felt, new_array: felt*) = Array.remove_value_at_index( 90 | array_len, my_array, 1 91 | ); 92 | assert new_array_len = 2; 93 | tempvar value = new_array[0]; 94 | assert value = my_array[0]; 95 | tempvar value = new_array[1]; 96 | assert value = my_array[2]; 97 | %{ expect_revert() %} 98 | tempvar value = new_array[2]; 99 | return (); 100 | } 101 | 102 | @external 103 | func test_get_value_index() { 104 | alloc_locals; 105 | let (local my_array: felt*) = alloc(); 106 | assert my_array[0] = 1; 107 | assert my_array[1] = 2; 108 | assert my_array[2] = 3; 109 | let array_len = 3; 110 | let (index) = Array.get_value_index(array_len, my_array, 3, 0); 111 | assert index = 2; 112 | return (); 113 | } 114 | 115 | @external 116 | func test_inv_array() { 117 | alloc_locals; 118 | let (local my_array: felt*) = alloc(); 119 | assert my_array[0] = 1; 120 | assert my_array[1] = 2; 121 | assert my_array[2] = 3; 122 | let array_len = 3; 123 | let (inv_array) = Array.inverse(array_len, my_array); 124 | tempvar value = inv_array[2]; 125 | assert value = my_array[0]; 126 | tempvar value = inv_array[1]; 127 | assert value = my_array[1]; 128 | tempvar value = inv_array[0]; 129 | assert value = my_array[2]; 130 | %{ expect_revert() %} 131 | tempvar value = inv_array[3]; 132 | return (); 133 | } 134 | -------------------------------------------------------------------------------- /tests/test_dfs_all_paths.cairo: -------------------------------------------------------------------------------- 1 | %lang starknet 2 | 3 | from starkware.cairo.common.alloc import alloc 4 | 5 | from cairo_graphs.graph.graph import GraphMethods 6 | from cairo_graphs.data_types.data_types import Edge, Vertex 7 | from cairo_graphs.graph.dfs_all_paths import init_dfs 8 | 9 | const TOKEN_A = 123; 10 | const TOKEN_B = 456; 11 | const TOKEN_C = 990; 12 | const TOKEN_D = 982; 13 | 14 | @external 15 | func test_dfs{range_check_ptr}() { 16 | let edges: Edge* = alloc(); 17 | assert edges[0] = Edge(TOKEN_A, TOKEN_B, 1); 18 | assert edges[1] = Edge(TOKEN_A, TOKEN_C, 1); 19 | assert edges[2] = Edge(TOKEN_B, TOKEN_C, 1); 20 | 21 | let graph = GraphMethods.build_undirected_graph_from_edges(3, edges); 22 | assert graph.length = 3; 23 | 24 | // graph is [vertex_a,vertex_b,noce_c] 25 | // neighbors : [2,2,2] 26 | // we run dfs like this : vertex_a -> vertex_c -> vertex_b => save path 27 | // then we pop back to vertex_a and find vertex_a -> vertex_b => save_path 28 | // so we have 2 possible paths. 29 | // The length of the saved_paths array is : 1(length of path_1) + path_1_len + 1(length of path_2) + path_2_len = 1+3+2+1 = 7 30 | let (saved_paths_len, saved_paths) = init_dfs(graph, TOKEN_A, TOKEN_B, 4); 31 | // %{ 32 | // print(ids.saved_paths_len) 33 | // for i in range(ids.saved_paths_len): 34 | // print(memory[ids.saved_paths+i]) 35 | // %} 36 | assert saved_paths_len = 7; 37 | assert saved_paths[0] = 3; // path 1 length 38 | assert saved_paths[4] = 2; // path 2 length 39 | assert saved_paths[1] = TOKEN_A; 40 | assert saved_paths[2] = TOKEN_C; 41 | assert saved_paths[3] = TOKEN_B; 42 | assert saved_paths[5] = TOKEN_A; 43 | assert saved_paths[6] = TOKEN_B; 44 | 45 | return (); 46 | } 47 | 48 | @external 49 | func test_dfs_2{range_check_ptr}() { 50 | let edges: Edge* = alloc(); 51 | assert edges[0] = Edge(TOKEN_A, TOKEN_B, 1); 52 | assert edges[1] = Edge(TOKEN_A, TOKEN_C, 1); 53 | assert edges[2] = Edge(TOKEN_B, TOKEN_C, 1); 54 | assert edges[3] = Edge(TOKEN_D, TOKEN_C, 1); 55 | assert edges[4] = Edge(TOKEN_D, TOKEN_B, 1); 56 | // graph is [vertex_a,vertex_b,noce_c,vertex_d] 57 | // neighbors : [2,3,3,2] 58 | // we want all paths from TOKEN_A to TOKEN_C 59 | // we run dfs like this : vertex_a -> vertex_c => save path 60 | // then we pop back to vertex_a and find vertex_a -> vertex_b -> vertex_d -> vertex_c => save_path 61 | // then we pop back to vertex_b and find vertex_a -> vertex_b -> vertex_c => save_path 62 | // so we have 3 possible paths. 63 | // The length of the saved_paths array is 1 + 2 + 1 + 4 + 1 + 3 = 12 64 | 65 | let graph = GraphMethods.build_undirected_graph_from_edges(5, edges); 66 | 67 | let (saved_paths_len, saved_paths) = init_dfs(graph, TOKEN_A, TOKEN_C, 4); 68 | // %{ 69 | // print(ids.saved_paths_len) 70 | // for i in range(ids.saved_paths_len): 71 | // print(memory[ids.saved_paths+i]) 72 | // %} 73 | assert saved_paths_len = 12; 74 | assert saved_paths[0] = 2; // path 1 length 75 | assert saved_paths[3] = 4; // path 2 length 76 | assert saved_paths[8] = 3; // path 3 length 77 | assert saved_paths[1] = TOKEN_A; 78 | assert saved_paths[2] = TOKEN_C; 79 | assert saved_paths[4] = TOKEN_A; 80 | assert saved_paths[5] = TOKEN_B; 81 | assert saved_paths[6] = TOKEN_D; 82 | assert saved_paths[7] = TOKEN_C; 83 | assert saved_paths[9] = TOKEN_A; 84 | assert saved_paths[10] = TOKEN_B; 85 | assert saved_paths[11] = TOKEN_C; 86 | 87 | return (); 88 | } 89 | -------------------------------------------------------------------------------- /tests/test_dijkstra.cairo: -------------------------------------------------------------------------------- 1 | %lang starknet 2 | 3 | from starkware.cairo.common.alloc import alloc 4 | 5 | from cairo_graphs.graph.graph import GraphMethods 6 | from cairo_graphs.graph.dijkstra import Dijkstra 7 | from cairo_graphs.data_types.data_types import Vertex, Graph, Edge 8 | 9 | func before_undirected_weighted() -> Graph { 10 | alloc_locals; 11 | let input_data: Edge* = alloc(); 12 | assert input_data[0] = Edge(1, 2, 1); 13 | assert input_data[1] = Edge(1, 3, 3); 14 | assert input_data[2] = Edge(1, 4, 4); 15 | assert input_data[3] = Edge(2, 3, 1); 16 | assert input_data[4] = Edge(3, 4, 1); 17 | assert input_data[5] = Edge(4, 5, 1); 18 | assert input_data[6] = Edge(5, 6, 1); 19 | assert input_data[7] = Edge(5, 8, 3); 20 | assert input_data[8] = Edge(6, 7, 1); 21 | assert input_data[9] = Edge(7, 8, 7); 22 | assert input_data[10] = Edge(8, 9, 1); 23 | assert input_data[11] = Edge(12, 9, 1); 24 | 25 | let graph = GraphMethods.build_undirected_graph_from_edges( 26 | 12, input_data 27 | ); 28 | return graph; 29 | } 30 | 31 | func before_directed_weighted() -> Graph { 32 | alloc_locals; 33 | let input_data: Edge* = alloc(); 34 | 35 | // 0, 0, 3, 0, 0, 3, 0, 0, 0, 0, 36 | // 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 37 | // 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 38 | // 0, 1, 0, 0, 0, 0, 3, 0, 1, 0, 39 | // 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40 | // 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41 | // 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42 | // 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 43 | // 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 44 | // 0, 0, 0, 0, 0, 3, 1, 0, 0, 0, 45 | assert input_data[0] = Edge(1, 3, 3); 46 | assert input_data[1] = Edge(1, 6, 3); 47 | assert input_data[2] = Edge(3, 2, 2); 48 | assert input_data[3] = Edge(2, 4, 1); 49 | assert input_data[4] = Edge(4, 7, 3); 50 | assert input_data[5] = Edge(4, 9, 1); 51 | assert input_data[6] = Edge(5, 1, 3); 52 | assert input_data[7] = Edge(7, 1, 1); 53 | assert input_data[8] = Edge(8, 12, 1); 54 | assert input_data[9] = Edge(9, 5, 1); 55 | assert input_data[10] = Edge(12, 7, 1); 56 | 57 | let graph = GraphMethods.build_directed_graph_from_edges( 58 | 11, input_data 59 | ); 60 | return graph; 61 | } 62 | 63 | @external 64 | func test_dijkstra_undirected_weighted{range_check_ptr}() { 65 | alloc_locals; 66 | let graph = before_undirected_weighted(); 67 | 68 | // Test 1 : from 1 to 9 69 | let (path_len, path, distance) = Dijkstra.shortest_path( 70 | graph, start_vertex_id=1, end_vertex_id=9 71 | ); 72 | 73 | assert path_len = 7; 74 | assert distance = 8; 75 | assert path[0] = 1; 76 | assert path[1] = 2; 77 | assert path[2] = 3; 78 | assert path[3] = 4; 79 | assert path[4] = 5; 80 | assert path[5] = 8; 81 | assert path[6] = 9; 82 | 83 | // Test 1 : from 8 to 7 84 | 85 | let (path_len, path, distance) = Dijkstra.shortest_path( 86 | graph, start_vertex_id=8, end_vertex_id=7 87 | ); 88 | 89 | assert path_len = 4; 90 | assert distance = 5; 91 | assert path[0] = 8; 92 | assert path[1] = 5; 93 | assert path[2] = 6; 94 | assert path[3] = 7; 95 | return (); 96 | } 97 | 98 | @external 99 | func test_dijkstra_directed_weighted{range_check_ptr}() { 100 | alloc_locals; 101 | let graph = before_directed_weighted(); 102 | 103 | // Test 1 : from 1 to 5 104 | 105 | let (path_len, path, distance) = Dijkstra.shortest_path( 106 | graph, start_vertex_id=1, end_vertex_id=5 107 | ); 108 | 109 | assert path_len = 6; 110 | assert distance = 8; 111 | assert path[0] = 1; 112 | assert path[1] = 3; 113 | assert path[2] = 2; 114 | assert path[3] = 4; 115 | assert path[4] = 9; 116 | assert path[5] = 5; 117 | 118 | // Test 2 : from 5 to 1 is different than 1 to 5 119 | let (path_len, path, distance) = Dijkstra.shortest_path( 120 | graph, start_vertex_id=5, end_vertex_id=1 121 | ); 122 | 123 | assert path_len = 2; 124 | assert distance = 3; 125 | assert path[0] = 5; 126 | assert path[1] = 1; 127 | 128 | // Test 3 : from 1 to 12 is unreachable 129 | let (path_len, path, distance) = Dijkstra.shortest_path( 130 | graph, start_vertex_id=1, end_vertex_id=12 131 | ); 132 | 133 | assert path_len = 0; 134 | assert distance = 2 ** 251 - 1; 135 | 136 | // Test 3 : from 12 to 1 is reachable 137 | let (path_len, path, distance) = Dijkstra.shortest_path( 138 | graph, start_vertex_id=12, end_vertex_id=1 139 | ); 140 | 141 | assert path_len = 3; 142 | assert distance = 2; 143 | assert path[0] = 12; 144 | assert path[1] = 7; 145 | assert path[2] = 1; 146 | return (); 147 | } 148 | -------------------------------------------------------------------------------- /tests/test_graph.cairo: -------------------------------------------------------------------------------- 1 | %lang starknet 2 | 3 | from starkware.cairo.common.alloc import alloc 4 | 5 | from cairo_graphs.graph.graph import add_neighbor, GraphMethods 6 | from cairo_graphs.data_types.data_types import Edge, Vertex, AdjacentVertex, Graph 7 | 8 | const TOKEN_A = 123; 9 | const TOKEN_B = 456; 10 | const TOKEN_C = 990; 11 | const TOKEN_D = 982; 12 | 13 | func build_graph_before_each() -> Graph { 14 | alloc_locals; 15 | let graph = GraphMethods.new_graph(); 16 | 17 | let (vertex_a_neighbors: AdjacentVertex*) = alloc(); 18 | let (vertex_b_neighbors: AdjacentVertex*) = alloc(); 19 | let (vertex_c_neighbors: AdjacentVertex*) = alloc(); 20 | 21 | local vertex_a: Vertex = Vertex(0, TOKEN_A, vertex_a_neighbors); 22 | local vertex_b: Vertex = Vertex(1, TOKEN_B, vertex_b_neighbors); 23 | local vertex_c: Vertex = Vertex(2, TOKEN_C, vertex_c_neighbors); 24 | 25 | // populate graph 26 | assert graph.vertices[0] = vertex_a; 27 | assert graph.adjacent_vertices_count[0] = 0; 28 | assert graph.vertices[1] = vertex_b; 29 | assert graph.adjacent_vertices_count[1] = 0; 30 | assert graph.vertices[2] = vertex_c; 31 | assert graph.adjacent_vertices_count[2] = 0; 32 | let graph_len = 3; 33 | tempvar res: Graph = Graph(graph_len, graph.vertices, graph.adjacent_vertices_count); 34 | return res; 35 | } 36 | 37 | @external 38 | func test_add_node_to_graph() { 39 | let (vertices: Vertex*) = alloc(); 40 | let (adj_vertices_count: felt*) = alloc(); // array that tracks the number of neighbor_nodes 41 | let graph_len: felt = 0; 42 | 43 | tempvar graph: Graph = Graph(graph_len, vertices, adj_vertices_count); 44 | let graph = GraphMethods.add_vertex_to_graph(graph, TOKEN_A); 45 | assert graph.length = 1; 46 | assert vertices[0].identifier = TOKEN_A; 47 | assert adj_vertices_count[0] = 0; 48 | tempvar graph: Graph = Graph(graph.length, graph.vertices, graph.adjacent_vertices_count); 49 | 50 | let graph = GraphMethods.add_vertex_to_graph(graph, TOKEN_B); 51 | assert graph.length = 2; 52 | assert graph.vertices[1].identifier = TOKEN_B; 53 | assert graph.adjacent_vertices_count[1] = 0; 54 | 55 | return (); 56 | } 57 | 58 | @external 59 | func test_add_neighbor() { 60 | alloc_locals; 61 | let graph = build_graph_before_each(); 62 | assert graph.vertices[0].identifier = TOKEN_A; 63 | assert graph.vertices[1].identifier = TOKEN_B; 64 | assert graph.vertices[2].identifier = TOKEN_C; 65 | assert graph.length = 3; // graph_len is 3 because we have 3 nodes in our graph 66 | 67 | // add TOKEN_B as neighbor of TOKEN_A 68 | let graph = add_neighbor( 69 | graph.vertices[0], graph.vertices[1], graph, 0, 0 70 | ); 71 | 72 | assert graph.vertices[0].adjacent_vertices[0].dst.identifier = TOKEN_B; 73 | assert graph.adjacent_vertices_count[0] = 1; // TOKEN_A has 1 neighbor, which is TOKEN_B 74 | assert graph.adjacent_vertices_count[1] = 0; // TOKEN_B still has 0 neighbors 75 | 76 | // now add TOKEN_A as neighbor of TOKEN_B 77 | let graph = add_neighbor( 78 | graph.vertices[1], graph.vertices[0], graph, 1, 0 79 | ); 80 | assert graph.vertices[1].adjacent_vertices[0].dst.identifier = TOKEN_A; 81 | assert graph.adjacent_vertices_count[1] = 1; // TOKEN_B now has 1 neighbor 82 | 83 | // add TOKEN_C as neighbor of TOKEN_A 84 | let graph = add_neighbor( 85 | graph.vertices[0], graph.vertices[2], graph, 0, 0 86 | ); 87 | assert graph.vertices[0].adjacent_vertices[1].dst.identifier = TOKEN_C; 88 | assert graph.adjacent_vertices_count[0] = 2; // TOKEN_A now has 2 neighbors 89 | 90 | return (); 91 | } 92 | 93 | @external 94 | func test_add_edge() { 95 | alloc_locals; 96 | let graph = build_graph_before_each(); 97 | let token_c = TOKEN_C; 98 | let token_d = TOKEN_D; 99 | 100 | assert graph.length = 3; 101 | assert graph.vertices[0].identifier = TOKEN_A; 102 | // add C<>D 103 | let graph = GraphMethods.add_edge(graph, Edge(TOKEN_C, TOKEN_D, 0)); 104 | 105 | let res = GraphMethods.get_vertex_index{graph=graph, identifier=token_c}(0); 106 | assert res = 2; 107 | let res = GraphMethods.get_vertex_index{graph=graph, identifier=token_d}(0); 108 | assert res = 3; 109 | 110 | assert graph.vertices[3].adjacent_vertices[0].dst.identifier = TOKEN_C; 111 | 112 | return (); 113 | } 114 | 115 | @external 116 | func test_get_node_index() { 117 | alloc_locals; 118 | let graph = build_graph_before_each(); 119 | let token_a = TOKEN_A; 120 | let token_b = TOKEN_B; 121 | 122 | let res = GraphMethods.get_vertex_index{graph=graph, identifier=token_a}(0); 123 | assert res = 0; 124 | let res = GraphMethods.get_vertex_index{graph=graph, identifier=token_b}(0); 125 | assert res = 1; 126 | 127 | return (); 128 | } 129 | 130 | @external 131 | func test_build_graph_undirected() { 132 | alloc_locals; 133 | let input_data: Edge* = alloc(); 134 | assert input_data[0] = Edge(TOKEN_A, TOKEN_B, 1); 135 | assert input_data[1] = Edge(TOKEN_A, TOKEN_C, 1); 136 | assert input_data[2] = Edge(TOKEN_B, TOKEN_C, 1); 137 | 138 | // the node at graph[i] has adj_vertices_count[i] adjacent vertices. 139 | // that allows us to dynamically modify the number of neighbors to a vertex, without the need 140 | // to rebuild the graph (since memory is write-once, we can't update a property of a struct already stored.) 141 | let graph = GraphMethods.build_undirected_graph_from_edges(3, input_data); 142 | 143 | assert graph.length = 3; 144 | assert graph.vertices[0].identifier = TOKEN_A; 145 | assert graph.vertices[1].identifier = TOKEN_B; 146 | assert graph.vertices[2].identifier = TOKEN_C; 147 | assert graph.adjacent_vertices_count[0] = 2; 148 | assert graph.adjacent_vertices_count[1] = 2; 149 | assert graph.adjacent_vertices_count[2] = 2; 150 | return (); 151 | } 152 | 153 | @external 154 | func test_generate_graphviz() { 155 | alloc_locals; 156 | let input_data: Edge* = alloc(); 157 | assert input_data[0] = Edge(TOKEN_A, TOKEN_B, 1); 158 | assert input_data[1] = Edge(TOKEN_A, TOKEN_C, 1); 159 | assert input_data[2] = Edge(TOKEN_B, TOKEN_C, 1); 160 | 161 | let graph = GraphMethods.build_undirected_graph_from_edges(3, input_data); 162 | let graph_len = graph.length; 163 | let vertices = graph.vertices; 164 | let adjacent_vertices_count = graph.adjacent_vertices_count; 165 | 166 | %{ 167 | IDENTIFIER_INDEX = 1 168 | ADJACENT_VERTICES_INDEX = 2 169 | for i in range(ids.graph_len): 170 | neighbours_len = memory[ids.adjacent_vertices_count+i] 171 | vertex_id = memory[ids.vertices.address_+i*ids.Vertex.SIZE+IDENTIFIER_INDEX] 172 | adjacent_vertices_pointer = memory[ids.vertices.address_+i*ids.Vertex.SIZE+ADJACENT_VERTICES_INDEX] 173 | print(f"{vertex_id} -> {{",end='') 174 | for j in range (neighbours_len): 175 | adjacent_vertex = memory[adjacent_vertices_pointer+j*ids.AdjacentVertex.SIZE+IDENTIFIER_INDEX] 176 | print(f"{adjacent_vertex} ",end='') 177 | print('}',end='') 178 | print() 179 | %} 180 | 181 | assert graph_len = 3; 182 | assert vertices[0].identifier = TOKEN_A; 183 | assert vertices[1].identifier = TOKEN_B; 184 | assert vertices[2].identifier = TOKEN_C; 185 | assert adjacent_vertices_count[0] = 2; 186 | assert adjacent_vertices_count[1] = 2; 187 | assert adjacent_vertices_count[2] = 2; 188 | return (); 189 | } 190 | --------------------------------------------------------------------------------