{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "If we take each intersection as a node and each direction as an edge, then we can see that this is just a [DAG](https://en.wikipedia.org/wiki/Directed_acyclic_graph).\n", "To count the possible paths from start to end we can observe than the number of paths of a node is the sum of the number of paths of its children. We do a [topological sort](https://en.wikipedia.org/wiki/Topological_sorting) of the graph and starting from the bottom right corner we go back calculating the number of paths. Since for each $v$ the paths of its children have already been calculated, we need to just sum them." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "137846528820" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import networkx as nx\n", "\n", "\n", "def build_graph(n):\n", " g = nx.DiGraph()\n", "\n", " def i(col, row):\n", " return n * (row - 1) + col\n", "\n", " for c in range(1, n + 1):\n", " for r in range(1, n + 1):\n", " if c != n:\n", " g.add_edge(i(c, r), i(c + 1, r))\n", " if r != n:\n", " g.add_edge(i(c, r), i(c, r + 1))\n", " return g\n", "\n", "\n", "# Kahn's algorithm\n", "def topological_sort(g):\n", " g = g.copy()\n", " sorted_nodes = []\n", " without_incoming_edges = [1]\n", " while without_incoming_edges:\n", " n = without_incoming_edges.pop()\n", " sorted_nodes.append(n)\n", " edges_to_remove = []\n", " for s in g.successors(n):\n", " edges_to_remove.append((n, s))\n", " if g.in_degree(s) == 1: # the edge n -> s will be removed, so we check for 1 instead of 0\n", " without_incoming_edges.append(s)\n", " for (n, s) in edges_to_remove:\n", " g.remove_edge(n, s)\n", " return sorted_nodes\n", "\n", "\n", "def count_paths(g, f, t):\n", " sorted_nodes = topological_sort(g)\n", " cache = {t: 1}\n", " for n in reversed(sorted_nodes[:-1]):\n", " count = 0\n", " for s in g.successors(n):\n", " count += cache[s]\n", " cache[n] = count\n", " return cache[f]\n", "\n", "\n", "nodes = 20 + 1\n", "g = build_graph(nodes)\n", "count_paths(g, 1, nodes * nodes)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" }, "title": "Lattice paths" }, "nbformat": 4, "nbformat_minor": 2 }