{"id":18400557,"url":"https://github.com/bogwi/musubi","last_synced_at":"2025-07-16T18:32:56.904Z","repository":{"id":191821444,"uuid":"685449373","full_name":"bogwi/Musubi","owner":"bogwi","description":"All purpose Graph in ZIG","archived":false,"fork":false,"pushed_at":"2024-01-17T00:40:29.000Z","size":93,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-02-16T03:24:10.364Z","etag":null,"topics":["data-structures","graph","graph-algorithms","zig","zig-package","ziglang"],"latest_commit_sha":null,"homepage":"","language":"Zig","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bogwi.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-08-31T08:55:33.000Z","updated_at":"2024-06-01T03:13:30.000Z","dependencies_parsed_at":"2024-01-17T02:35:20.058Z","dependency_job_id":"22619276-855d-4cbc-b07f-9c1c49df874b","html_url":"https://github.com/bogwi/Musubi","commit_stats":null,"previous_names":["bogwi/musubi"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bogwi%2FMusubi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bogwi%2FMusubi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bogwi%2FMusubi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bogwi%2FMusubi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bogwi","download_url":"https://codeload.github.com/bogwi/Musubi/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248601719,"owners_count":21131611,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["data-structures","graph","graph-algorithms","zig","zig-package","ziglang"],"created_at":"2024-11-06T02:34:38.270Z","updated_at":"2025-04-12T16:57:44.948Z","avatar_url":"https://github.com/bogwi.png","language":"Zig","funding_links":[],"categories":[],"sub_categories":[],"readme":"# All purpose Graph in ZIG.\n\n## Description\n\nAn implementation of an adjacency map graph, where all edges incident on a vertex are collected into a map, using the adjacent vertex as a key. \\\nThe graph can be initiated as one of four variants, subjects of graph type: `.directed,.undirected` and mode: `.weighted, .unweighted`.\n```zig\nconst Graph = Musubi(VertexId, EdgeId, EdgeWt, .undirected, .unweighted);\nvar graph: Graph = .{};\ngraph.init(allocator);\ndefer graph.deinit();\n``` \n`VertexId`, a vertex type, can be anything that can be hashed, such as numeric types, structs, arrays, snippets of code, or anything except floats and untagged enums. \\\n`EdgeId`, an edge type, can be anything. \\\n`EdgeWt`, the weight type of the edge, if the graph was initiated as weighted; any numeric type. If the graph is unweighted, the type is void. \n\nHere is the full API at the moment:\n\n*GENERAL API* \\\nA set of standard procedures typically found in graph ADTs.\n```\nVertex:\n    .id:                         VertexId\n    init:                            void\n\nEdge:\n    .origin:                       Vertex\n    .destination:                  Vertex\n    .id:                           EdgeId\n    .weight:                       EdgeWt\n    init:                            void\n    endpoints: PairV: \n                origin\n                destination\n    opposite:                      Vertex\n\n\n\ninit:                                void\ndeinit:                              void\n\nclearAndFree:                        void\nclearRetainingCapacity:              void\nensureTotalCapacity:                !void\n\ncloneIntoSelf:                      !void\ncloneIntoSelfWithAllocator:         !void\nmergeIntoSelf:                      !void\n\nmakeVertex:                        Vertex\ninsertVertex:                     !Vertex\ninsertVertexIfVertex:               !void\nremoveVertex:                        bool\ngotVertex:                           bool\nvertexCount:                          u64\nvertices:                       ?[]Vertex\nadjacentVertices:               ?[]Vertex\nverticesIntoSet:             !AllVertices\n    AllVertices:\n        .vertices:           ArrayHashMap\n        deinit:                      void\n        list:                    []Vertex\n        count:                      usize\n        gotVertex:                   bool\n        deleteVertex:                bool\n\nmakeEdge:                            Edge\ninsertEdge:                         !Edge\ninsertEdgeIfEdge:                   !void\nremoveEdge:                          bool\ngotEdge:                             bool\ngotEdgeIfEdge:                       bool\ngetEdge:                            ?Edge\nedgeCount:                          usize\ndegree:                             usize\nincidentEdges:                    ?[]Edge\nedgesIntoSet:                    AllEdges\n    AllEdges:\n        .edges:              ArrayHashMap\n        deinit:                      void\n        list:                      []Edge\n        count:                      usize\n        gotEdge:                     bool\n        deleteEdge:                  bool\n\n```\n*SPECIAL API* \n\n*tree Traversing*\n```\ntraverseTree:                  !ArrayList\ntraverseTreeIfTarget:          !ArrayList\n```\nThe tree traversing procedure supports four algorithms by passing a corresponding enum to the above function:\n\u003e`TreeTraverseAlg:`\n\n\u003e`.bfs`, breadth-first, iterative \\\n`.pre`, preorder, recursive \\\n`.post`, postorder, recursive \\\n`.ino`, inorder, recursive \n\n*graph Traversing*\n```\nconnectionTree:              !Connections\nconnectionTreeExcept:        !Connections\nconnectionTreeThrough:       !Connections\n\nconnectionTreeIfTarget:       !Connection\nconnectionTreeIfTargetExcept: !Connection\nconnectionTreeIfTargetThrough:!Connection\n```\nThe graph traversing procedure supports four + 1 algorithm by passing a corresponding enum to the above functions: \n\u003e`SearchAlg:`\n\n\u003e`.bfs`, breadth-first search, iterative \\\n`.dfsA`, depth-first search, iterative \\\n`.dfsB`, depth-first search, iterative, true recursion emulation \\\n`.dfsC`, depth-first search, pure recursive \\\n`.dij` , Dijkstra shortest path, iterative. \n\nWhich algorithm is better? That depends. `.bfs` and `.dij` are both shortest path algorithms. The only difference between them is that `.bfs` gives the shortest path based on how many edges it needs to travel to reach the goal, while `.dij` considers the weights of the edges, treating them like distances between vertices. If all edges have the same weight, then `.dij` will give the same result as `.bfsd`. \\\nThe depth-first group of algorithms is different and graph-dependent. If the graph is undirected, where all vertices are randomly connected, they will not necessarily produce the shortest paths from origin to destination. They explore the graph as a whole and are useful for finding the longest possible paths. If we have such an undirected tangly graph with 1M randomly connected vertices, and if it is possible to travel from the first vertex to the last vertex and visit all the nodes, the recursive `.dfsC` algorithm will find this path from 1M - 1 vertex. \\\n`.dfsB` is the author's iterative algorithm, which emulates true recursion to a large extent. In some scenarios, the paths it produces are identical to true recursion with an identical stack trace, but may differ in branches. It is only designed for undirected graphs as a `dfsC` replacement. \\\n`.dfsA` is a lazy iterative algorithm often found in books and used worldwide. It is an inversion of `.bfs` where the queue is substituted for the stack. In the case of an undirected, randomly connected graph, the paths it produces will be much shorter than those of the recursive `.dfsC`. \n\nAdditional parameters are \\\n`knockout`, a set of vertices to remove from the traversal or to traverse only \\\n`target`, the target of the traversal, the traversal will stop when the target is reached \\\n`depth`, the depth of the traversal, which has slightly different goals depending on the algorithm.\n\nThe traversal process computes a connection tree from the given starting vertex to all other vertices in the map. \\\nThe connection tree has its own documented API for working with the result:\n```\nConnection\n    .found:                          bool\n    .explore:                 Connections\n    deinit:                          void\n\nConnections:\n    .origin:                       Vertex\n    .path:                   ArrayHashMap\n    .discovered:             ArrayHashMap\n    .last_lookup:                  Vertex\n    deinit:                          void\n    connectedTo:                     bool\n    getAllConnected:             []Vertex  \n    getDistanceTo:                 EdgeWt          \n    getPathTo:                  ![]Vertex\n    walkPathTo:                 !WalkPath\n        WalkPath:\n            .cnt:            *Connections\n            .idx:                     u64\n            next:                 ?Vertex\n            reset:                   void\n    popPathTo:                   !PopPath\n        PopPath:\n            .cnt:            *Connections\n            .dest:                 Vertex\n            next:                 ?Vertex\n            reset:                   void\n```\n\n*Common-problems algorithms and their APIs* \\\n*topological sort*\n```\ntopologicalSort:                     TOPO\n    TOPO:\n        .topo:                  ArrayList\n        .acyclic:                    bool\n        getAll:                 ?[]Vertex\n        getPositions:             ?Vertex\n        getFirst:                 ?Vertex\n        getLast:                  ?Vertex\n        walk:                    WalkTopo   \n```\n*minimum spanning tree*\n```  \nprimJarnikMST:                        MST\nkruskalMST:                           MST\n    MST\n        .cost:                        u64\n        .tree:               ArrayHashMap          \n        .len:                       usize\n        getEdges:                  []Edge\n        getVertexPairs:           []PairV\n        gotVertexPair:               bool  \n```\n## Performance\nMusubi's underlying ADT is Zig's superior ArrayHashMap, which has unmatched iteration speed over keys and values, and can extract keys and values as a matter of course. This speeds up the graph routine considerably. For example, calling `vertices()` will give you an array of all the vertices in the graph without harvesting them all into a container and only then returning them to the user. The same goes for finding `incidentEdges()` of a vertex or its `adjacentVertices()`.\n\n### Testing \nApple M1 laptop with 32GB of RAM, \\\nReleaseFast optimization\n\n#### Complete Binary Tree\n```\n20M vertices: u64\n20M-1 edges: void\ncreation:            time: 7.667\n\nBFS                  time: 2.682\nPRE                  time: 3.020\nPOST                 time: 3.033\nINO                  time: 3.019\n```\nAlthough not advertised, Musubi remembers the insertion order and can be used as a general or binary tree for your projects. The only consequence is that broken links have to be repaired manually when vertices or edges are removed. The graph is not a linked tree and cannot behave as such. Nerveless tree traversal is implemented for directed graphs and is quite fast.\n\n#### Undirected, weighted, randomly connected, cobweb-looking graph\n\n```\n25k vertices: u64 \n500k edges:    u1\ncreation:            time: 0.105 sec\n\nTree - connection tree\nPaths - origin -\u003e others         25k\n\nBFS Tree                 time: 0.019\nBFS Paths                time: 0.001\nDFS A Tree               time: 0.021\nDFS A Paths              time: 0.342 a\nDFS B Tree               time: 0.031\nDFS B Paths              time: 5.056 a\nDFS C Tree               time: 0.020\nDFS C Paths              time: 6.181 a\nDIJ Tree                 time: 0.027\nDIJ Paths                time: 0.002\n\nMST:\nPrim-Jarnik: cost: 29751 time: 0.062, \nthroughput: 8.089\n\nKruskal:     cost: 29751 time: 0.082, \nthroughput: 6.108\n```\n(a) Constructing all 25k-1 paths computed by depth-first algorithms happens to be a costly task. As mentioned above, dfs algorithms on undirected randomly constructed graphs tend to produce the longest paths possible, with `.dfsC` as a true recursive algorithm producing the longest paths. Therefore, the *paths* test is omitted in the following results. However, such graphs are not real scenarios, but only benchmarking vessels. It also does not mean that DFS traversing should not be used at all to find a connection between two points of interest when working with such a tangled graph.\n\n```\n50k vertices: u64 \n1m edges:      u1\ncreation:            time: 0.313 sec\n\nTree - connection tree\nPaths - origin -\u003e others         50k \n\nBFS Tree                 time: 0.057\nBFS Paths                time: 0.004\nDFS A Tree               time: 0.057\nDFS A Paths                \nDFS B Tree               time: 0.086\nDFS B Paths              \nDFS C Tree               time: 0.056 a\nDFS C Paths              \nDIJ Tree                 time: 0.103\nDIJ Paths                time: 0.005\n\nMST:\nPrim-Jarnik: cost: 59264 time: 0.146 \nthroughput: 6.830\n\nKruskal:     cost: 59264 time: 0.226 \nthroughput: 4.418\n```\n(a) In an experiment, recursive `.dfsC` was found to break at about 1_200_000 edges for the graph described above, so there is no data for this algorithm implementation for larger graphs. For small undirected random graphs \u003c 1.2M edges, using a purely recursive `.dfsC` algorithm should be fine. \n\n```\n100k vertices: u64 \n2M edges:       u1\ncreation:            time: 0.785 sec\n\nTree - connection tree\nPaths - origin -\u003e others        100k\n\nBFS Tree                 time: 0.131\nBFS Paths                time: 0.009\nDFS A Tree               time: 0.129\nDFS A Paths              \nDFS B Tree               time: 0.199\nDFS B Paths              \nDFS C Tree               \nDFS C Paths              \nDIJ Tree                 time: 0.246\nDIJ Paths                time: 0.015\n\nMST:\nPrim-Jarnik: \ncost: 118512             time: 0.354 \nthroughput: 5.650\n\nKruskal:\ncost: 118512             time: 0.503 \nthroughput: 3.978\n```\n\n```\n1M vertices: u64 \n20M edges:    u1\ncreation:           time: 13.289 sec\n\nTree - connection tree\nPaths - origin -\u003e others          1M\n\nBFS Tree                 time: 3.213\nBFS Paths                time: 0.177\nDFS A Tree               time: 3.221\nDFS A Paths              \nDFS B Tree               time: 3.550\nDFS B Paths              \nDFS C Tree               \nDFS C Paths              \nDIJ Tree                 time: 6.335\nDIJ Paths                time: 0.347\n\nMST:\nPrim-Jarnik: \ncost: 1184658            time: 7.624\nthroughput: 2.623\n\nKruskal:\ncost: 1184658            time: 9.918\nthroughput: 2.017\n```\n\n#### Directed, weighted, acyclic, randomly connected graph\n```\n1M vertices: u64 \n20M+ edges:  u64\ncreation:            time: 7.083 sec\n\nTree - connection tree\nPaths - origin -\u003e others          1M\n\nBFS Tree                 time: 0.483\nBFS Paths                time: 0.105\nDFS A Tree               time: 0.446\nDFS A Paths              time: 0.138\nDFS B Tree            not applicable\nDFS B Paths           not applicable   \nDFS C Tree               time: 0.469\nDFS C Paths              time: 0.144\nDIJ Tree                 time: 1.582\nDIJ Paths                time: 0.239\n\nTopological Sort         time: 1.684\n```\n```\n5M vertices: u64 \n102M+ edges: u64\ncreation:           time: 46.741 sec\n\nTree - connection tree\nPaths - origin -\u003e others          5M\n\nBFS Tree                 time: 4.741\nBFS Paths                time: 0.765\nDFS A Tree               time: 3.743\nDFS A Paths              time: 0.914\nDFS B Tree            not applicable  \nDFS B Paths           not applicable  \nDFS C Tree               time: 4.026\nDFS C Paths              time: 0.907\nDIJ Tree                 time: 15.195\nDIJ Paths                time: 2.739\n\nTopological Sort         time: 20.585\n```\nIn the case of a directed graph, the results are very different. The cost of finding every path from the origin to every other vertex is very modest. Since there are no cycles, the recursive `.dfsC` algorithm that examines 102M edges works correctly and does not break.\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbogwi%2Fmusubi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbogwi%2Fmusubi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbogwi%2Fmusubi/lists"}