Thicket data repository for the EEG
at main 5.1 kB view raw
1{ 2 "id": "https://www.tunbury.org/2025/03/25/topological-sort", 3 "title": "Topological Sort of Packages", 4 "link": "https://www.tunbury.org/2025/03/25/topological-sort/", 5 "updated": "2025-03-25T00:00:00", 6 "published": "2025-03-25T00:00:00", 7 "summary": "Given a list of packages and their dependencies, what order should those packages be installed in?", 8 "content": "<p>Given a list of packages and their dependencies, what order should those packages be installed in?</p>\n\n<p>The above graph gives a simple example of the dependencies of the package <code>dune</code> nicely ordered right to left.</p>\n\n<p>We might choose to model this in OCaml using a map with the package name as the key and a set of the dependent packages:</p>\n\n<div><div><pre><code><span>module</span> <span>PackageSet</span> <span>=</span> <span>Set</span><span>.</span><span>Make</span> <span>(</span><span>String</span><span>);;</span>\n<span>module</span> <span>PackageMap</span> <span>=</span> <span>Map</span><span>.</span><span>Make</span> <span>(</span><span>String</span><span>);;</span>\n</code></pre></div></div>\n\n<p>Thus, the <code>dune</code> example could be defined like this.</p>\n\n<div><div><pre><code><span>let</span> <span>dune</span> <span>=</span> <span>PackageMap</span><span>.(</span><span>empty</span> <span>|&gt;</span>\n <span>add</span> <span>\"ocaml\"</span> <span>(</span><span>PackageSet</span><span>.(</span><span>empty</span> <span>|&gt;</span> <span>add</span> <span>\"ocaml-config\"</span> <span>|&gt;</span> <span>add</span> <span>\"ocaml-variants\"</span><span>))</span> <span>|&gt;</span>\n <span>add</span> <span>\"ocaml-config\"</span> <span>(</span><span>PackageSet</span><span>.(</span><span>empty</span> <span>|&gt;</span> <span>add</span> <span>\"ocaml-variants\"</span><span>))</span> <span>|&gt;</span>\n <span>add</span> <span>\"dune\"</span> <span>(</span><span>PackageSet</span><span>.(</span><span>empty</span> <span>|&gt;</span> <span>add</span> <span>\"ocaml\"</span> <span>|&gt;</span> <span>add</span> <span>\"base-unix.base\"</span> <span>|&gt;</span> <span>add</span> <span>\"base-threads.base\"</span><span>))</span> <span>|&gt;</span>\n <span>add</span> <span>\"ocaml-variants\"</span> <span>(</span><span>PackageSet</span><span>.</span><span>empty</span><span>)</span> <span>|&gt;</span>\n <span>add</span> <span>\"base-unix.base\"</span> <span>(</span><span>PackageSet</span><span>.</span><span>empty</span><span>)</span> <span>|&gt;</span>\n <span>add</span> <span>\"base-threads.base\"</span> <span>(</span><span>PackageSet</span><span>.</span><span>empty</span><span>)</span>\n <span>);;</span>\n</code></pre></div></div>\n\n<p>We can create a topological sort by first choosing any package with an empty set of dependencies. This package should then be removed from the map of packages and also removed as a dependency from any of the sets. This can be written concisely in OCaml</p>\n\n<div><div><pre><code><span>let</span> <span>rec</span> <span>topological_sort</span> <span>pkgs</span> <span>=</span>\n <span>match</span> <span>PackageMap</span><span>.</span><span>is_empty</span> <span>pkgs</span> <span>with</span>\n <span>|</span> <span>true</span> <span>-&gt;</span> <span>[]</span>\n <span>|</span> <span>false</span> <span>-&gt;</span>\n <span>let</span> <span>installable</span> <span>=</span> <span>PackageMap</span><span>.</span><span>filter</span> <span>(</span><span>fun</span> <span>_</span> <span>deps</span> <span>-&gt;</span> <span>PackageSet</span><span>.</span><span>is_empty</span> <span>deps</span><span>)</span> <span>pkgs</span> <span>in</span>\n <span>let</span> <span>()</span> <span>=</span> <span>assert</span> <span>(</span><span>not</span> <span>(</span><span>PackageMap</span><span>.</span><span>is_empty</span> <span>installable</span><span>))</span> <span>in</span>\n <span>let</span> <span>i</span> <span>=</span> <span>PackageMap</span><span>.</span><span>choose</span> <span>installable</span> <span>|&gt;</span> <span>fst</span> <span>in</span>\n <span>let</span> <span>pkgs</span> <span>=</span> <span>PackageMap</span><span>.</span><span>remove</span> <span>i</span> <span>pkgs</span> <span>|&gt;</span> <span>PackageMap</span><span>.</span><span>map</span> <span>(</span><span>fun</span> <span>deps</span> <span>-&gt;</span> <span>PackageSet</span><span>.</span><span>remove</span> <span>i</span> <span>deps</span><span>)</span> <span>in</span>\n <span>i</span> <span>::</span> <span>topological_sort</span> <span>pkgs</span>\n</code></pre></div></div>\n\n<p>This gives us the correct installation order:</p>\n\n<div><div><pre><code># topological_sort dune;;\n- : PackageMap.key list =\n[\"base-threads.base\"; \"base-unix.base\"; \"ocaml-variants\"; \"ocaml-config\"; \"ocaml\"; \"dune\"]\n</code></pre></div></div>", 9 "content_type": "html", 10 "author": { 11 "name": "Mark Elvers", 12 "email": "mark.elvers@tunbury.org", 13 "uri": null 14 }, 15 "categories": [ 16 "OCaml", 17 "tunbury.org" 18 ], 19 "source": "https://www.tunbury.org/atom.xml" 20}