{"title":{},"link":[{"@attributes":{"rel":"self","type":"application\/atom+xml","href":"https:\/\/ddejohn.dev\/atom.xml"}},{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/ddejohn.dev"}}],"generator":"Zola","updated":"2024-09-12T00:00:00+00:00","id":"https:\/\/ddejohn.dev\/atom.xml","entry":[{"title":"Distinct string types with Python","published":"2024-09-12T00:00:00+00:00","updated":"2024-09-12T00:00:00+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/ddejohn.dev\/distinct-types\/"}},"id":"https:\/\/ddejohn.dev\/distinct-types\/","content":"<h2 id=\"writing-type-safe-code-in-python\">Writing type-safe code in Python<\/h2>\n<p>This post is one of many I plan to write detailing techniques you can use to write \"type-safe\" code in Python. I use quotes there because types in Python are not enforced. Despite this, they can make your code <em>much<\/em> safer and easier to develop against, reason about, test, and maintain.<\/p>\n<h2 id=\"everything-is-a-string\">Everything is a string<\/h2>\n<p>If you've ever developed or maintained an HTTP\/JSON API, you've likely experienced this issue: you've got some <code>POST<\/code> endpoint that accepts a large JSON payload which gets dumped into your lap as an arbitrarily nested <code>dict<\/code> of strings. This isn't particularly useful to us, the maintainer of this endpoint. Most web frameworks, I'm assuming, will marshall the data into native Python objects, but this is only half the battle:<\/p>\n<blockquote>\n<p>What is the difference between <code>\"123-456-7890\"<\/code> and <code>\"first.last@email.com\"<\/code>?<\/p>\n<\/blockquote>\n<p>In Python (virtually any programming language, really), these two pieces of information are merely strings. Of course, you, the reader know that one is a phone number and the other is an email address -- but Python <em>doesn't<\/em>. All Python sees is two strings. Python wouldn't think you were crazy for trying to store that email address in a database table called <code>phone_numbers<\/code>.<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"font-style:italic;color:#5f697a;\"># my_database.py\n<\/span><span style=\"color:#cd74e8;\">from <\/span><span style=\"color:#abb2bf;\">typing <\/span><span style=\"color:#cd74e8;\">import <\/span><span style=\"color:#abb2bf;\">Protocol\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#cd74e8;\">class <\/span><span style=\"color:#f0c678;\">Database<\/span><span style=\"color:#adb7c9;\">(<\/span><span style=\"color:#9acc76;\">Protocol<\/span><span style=\"color:#adb7c9;\">):\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"color:#cd74e8;\">def <\/span><span style=\"color:#5cb3fa;\">persist<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#eb6772;\">self<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#eb6772;\">value<\/span><span style=\"color:#abb2bf;\">: str, <\/span><span style=\"color:#eb6772;\">table<\/span><span style=\"color:#abb2bf;\">: str) -&gt; <\/span><span style=\"color:#db9d63;\">None<\/span><span style=\"color:#abb2bf;\">: <\/span><span style=\"color:#db9d63;\">...\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#cd74e8;\">def <\/span><span style=\"color:#5cb3fa;\">persist_phone_number<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#eb6772;\">phone<\/span><span style=\"color:#abb2bf;\">: str, <\/span><span style=\"color:#eb6772;\">db<\/span><span style=\"color:#abb2bf;\">: Database) -&gt; <\/span><span style=\"color:#db9d63;\">None<\/span><span style=\"color:#abb2bf;\">:\n<\/span><span style=\"color:#abb2bf;\">    db.<\/span><span style=\"color:#eb6772;\">persist<\/span><span style=\"color:#abb2bf;\">(phone, <\/span><span style=\"color:#eb6772;\">table<\/span><span style=\"color:#adb7c9;\">=<\/span><span style=\"color:#9acc76;\">&quot;phone_number&quot;<\/span><span style=\"color:#abb2bf;\">)\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#cd74e8;\">def <\/span><span style=\"color:#5cb3fa;\">persist_email<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#eb6772;\">email<\/span><span style=\"color:#abb2bf;\">: str, <\/span><span style=\"color:#eb6772;\">db<\/span><span style=\"color:#abb2bf;\">: Database) -&gt; <\/span><span style=\"color:#db9d63;\">None<\/span><span style=\"color:#abb2bf;\">:\n<\/span><span style=\"color:#abb2bf;\">    db.<\/span><span style=\"color:#eb6772;\">persist<\/span><span style=\"color:#abb2bf;\">(email, <\/span><span style=\"color:#eb6772;\">table<\/span><span style=\"color:#adb7c9;\">=<\/span><span style=\"color:#9acc76;\">&quot;email_address&quot;<\/span><span style=\"color:#abb2bf;\">)\n<\/span><\/code><\/pre>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"font-style:italic;color:#5f697a;\"># my_service.py\n<\/span><span style=\"color:#cd74e8;\">import <\/span><span style=\"color:#abb2bf;\">my_database\n<\/span><span style=\"color:#cd74e8;\">from <\/span><span style=\"color:#abb2bf;\">typing <\/span><span style=\"color:#cd74e8;\">import <\/span><span style=\"color:#abb2bf;\">Tuple\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"font-style:italic;color:#5f697a;\"># UserInfo is a tuple consisting of a phone number and an email address\n<\/span><span style=\"color:#abb2bf;\">UserInfo <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#abb2bf;\">Tuple[str, str]\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#cd74e8;\">def <\/span><span style=\"color:#5cb3fa;\">customer_info<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#eb6772;\">data<\/span><span style=\"color:#abb2bf;\">: UserInfo, <\/span><span style=\"color:#eb6772;\">db<\/span><span style=\"color:#abb2bf;\">: my_database.Database) -&gt; <\/span><span style=\"color:#db9d63;\">None<\/span><span style=\"color:#abb2bf;\">:\n<\/span><span style=\"color:#abb2bf;\">    email, phone <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#abb2bf;\">data\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"color:#cd74e8;\">if not <\/span><span style=\"color:#5ebfcc;\">isinstance<\/span><span style=\"color:#abb2bf;\">(phone, str) <\/span><span style=\"color:#cd74e8;\">or <\/span><span style=\"color:#abb2bf;\">phone <\/span><span style=\"color:#adb7c9;\">== <\/span><span style=\"color:#9acc76;\">&quot;&quot;<\/span><span style=\"color:#abb2bf;\">:\n<\/span><span style=\"color:#abb2bf;\">        <\/span><span style=\"color:#cd74e8;\">raise <\/span><span style=\"color:#eb6772;\">ValueError<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#9acc76;\">&quot;Phone number must be a non-empty string&quot;<\/span><span style=\"color:#abb2bf;\">)\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"color:#cd74e8;\">if not <\/span><span style=\"color:#5ebfcc;\">isinstance<\/span><span style=\"color:#abb2bf;\">(email, str) <\/span><span style=\"color:#cd74e8;\">or <\/span><span style=\"color:#abb2bf;\">email <\/span><span style=\"color:#adb7c9;\">== <\/span><span style=\"color:#9acc76;\">&quot;&quot;<\/span><span style=\"color:#abb2bf;\">:\n<\/span><span style=\"color:#abb2bf;\">        <\/span><span style=\"color:#cd74e8;\">raise <\/span><span style=\"color:#eb6772;\">ValueError<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#9acc76;\">&quot;Email address must be a non-empty string&quot;<\/span><span style=\"color:#abb2bf;\">)\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#abb2bf;\">    my_database.<\/span><span style=\"color:#eb6772;\">persist_phone_number<\/span><span style=\"color:#abb2bf;\">(phone, db)\n<\/span><span style=\"color:#abb2bf;\">    my_database.<\/span><span style=\"color:#eb6772;\">persist_email<\/span><span style=\"color:#abb2bf;\">(email, db)\n<\/span><\/code><\/pre>\n<p>Can you spot the problem with this code? It's a little bit of a trick question; this code would run just fine -- there are no syntax errors, we are validating that <code>phone<\/code> and <code>email<\/code> are both non-empty strings, which means we're calling the persistence methods with the correct types -- so what's wrong?<\/p>\n<p>We've unpacked the <code>UserInfo<\/code> tuple incorrectly. That line of code should read <code>phone, email = data<\/code>.<\/p>\n<p>Best case scenario: your data team has some mechanism of validating the input at the database layer, and returns a <code>DatabaseError<\/code>, crashing your program. Worst case scenario: there is no further validation and you've just stuffed an email into the <code>phone_number<\/code> table, and a phone number into the <code>email_address<\/code> table -- everything worked fine because <em>you didn't violate the API contract anywhere along the line<\/em>. You performed a perfectly valid task in your Python program, but now you have a data integrity problem!<\/p>\n<h2 id=\"everything-is-a-string-and-that-s-kind-of-bad\">Everything is a string, and that's kind of bad<\/h2>\n<p>How can we prevent this? We <em>could<\/em> implement custom classes for <code>EmailAddress<\/code> and <code>PhoneNumber<\/code>, but that'll get old real quick when you also need to implement classes for <code>UserName<\/code>, <code>Password<\/code>, <code>GivenName<\/code>, <code>FamilyName<\/code>, <code>PreferredName<\/code>, <code>Pronouns<\/code>, etc. Another issue is that not all of these inputs can even be validated. This also wouldn't solve the issue of having to deal with two inputs that are the same \"type\" (e.g., mailing addresses -- you definitely don't want to mix up sending and receiving addresses!).<\/p>\n<p>You would also need to construct instances of each of those classes whenever you receive a payload, and deal with all the overhead of maintaining a custom class definition. You may have also taken already care of data validation in the frontend, or at the webserver ingress or proxy layer, so writing <em>another<\/em> step of data validation is both wasteful and adds technical debt.<\/p>\n<h2 id=\"different-types-of-string\">Different types of string<\/h2>\n<p>This is where <code>typing.NewType<\/code> comes in! It's only a type definition so it'll be completely ignored at runtime, but it allows you to define different \"variants\" of a given data type -- in our case, <code>str<\/code> -- which our type checker will treat as completely different types:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"font-style:italic;color:#5f697a;\"># my_database.py\n<\/span><span style=\"color:#cd74e8;\">from <\/span><span style=\"color:#abb2bf;\">typing <\/span><span style=\"color:#cd74e8;\">import <\/span><span style=\"color:#abb2bf;\">NewType, Tuple, Protocol\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#abb2bf;\">EmailAddress <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#eb6772;\">NewType<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#9acc76;\">&quot;EmailAddress&quot;<\/span><span style=\"color:#abb2bf;\">, str)\n<\/span><span style=\"color:#abb2bf;\">PhoneNumber <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#eb6772;\">NewType<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#9acc76;\">&quot;PhoneNumber&quot;<\/span><span style=\"color:#abb2bf;\">, str)\n<\/span><span style=\"color:#abb2bf;\">UserInfo <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#abb2bf;\">Tuple[PhoneNumber, EmailAddress]\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#cd74e8;\">class <\/span><span style=\"color:#f0c678;\">Database<\/span><span style=\"color:#adb7c9;\">(<\/span><span style=\"color:#9acc76;\">Protocol<\/span><span style=\"color:#adb7c9;\">):\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"color:#cd74e8;\">def <\/span><span style=\"color:#5cb3fa;\">persist<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#eb6772;\">self<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#eb6772;\">value<\/span><span style=\"color:#abb2bf;\">: PhoneNumber <\/span><span style=\"color:#adb7c9;\">| <\/span><span style=\"color:#abb2bf;\">EmailAddress, <\/span><span style=\"color:#eb6772;\">table<\/span><span style=\"color:#abb2bf;\">: str) -&gt; <\/span><span style=\"color:#db9d63;\">None<\/span><span style=\"color:#abb2bf;\">: <\/span><span style=\"color:#db9d63;\">...\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#cd74e8;\">def <\/span><span style=\"color:#5cb3fa;\">persist_phone_number<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#eb6772;\">phone<\/span><span style=\"color:#abb2bf;\">: PhoneNumber, <\/span><span style=\"color:#eb6772;\">db<\/span><span style=\"color:#abb2bf;\">: Database) -&gt; <\/span><span style=\"color:#db9d63;\">None<\/span><span style=\"color:#abb2bf;\">:\n<\/span><span style=\"color:#abb2bf;\">    db.<\/span><span style=\"color:#eb6772;\">persist<\/span><span style=\"color:#abb2bf;\">(phone, <\/span><span style=\"color:#eb6772;\">table<\/span><span style=\"color:#adb7c9;\">=<\/span><span style=\"color:#9acc76;\">&quot;phone_number&quot;<\/span><span style=\"color:#abb2bf;\">)\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#cd74e8;\">def <\/span><span style=\"color:#5cb3fa;\">persist_email<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#eb6772;\">email<\/span><span style=\"color:#abb2bf;\">: EmailAddress, <\/span><span style=\"color:#eb6772;\">db<\/span><span style=\"color:#abb2bf;\">: Database) -&gt; <\/span><span style=\"color:#db9d63;\">None<\/span><span style=\"color:#abb2bf;\">:\n<\/span><span style=\"color:#abb2bf;\">    db.<\/span><span style=\"color:#eb6772;\">persist<\/span><span style=\"color:#abb2bf;\">(email, <\/span><span style=\"color:#eb6772;\">table<\/span><span style=\"color:#adb7c9;\">=<\/span><span style=\"color:#9acc76;\">&quot;email_address&quot;<\/span><span style=\"color:#abb2bf;\">)\n<\/span><\/code><\/pre>\n<p>Using the same <code>customer_info<\/code> function, but with a <code>UserInfo<\/code> tuple that uses our <code>NewType<\/code> types, the editor now shows a type error with our previous code:<\/p>\n<p align=\"center\"><img src=\"type-error.png\"><\/p>\n<p>Note that you cannot get this behavior with just type aliases!<\/p>\n<p>This is obviously a contrived example, but using <code>NewType<\/code> can really help you and the other developers reading and maintaining your code, or your users if you're developing a library! Remember that you really need to be validating your data somewhere along the line -- Python's types are not enforced, they are merely a tool for keeping developers from making simple mistakes.<\/p>\n<p>My next post will go into more detail on using <code>typing.Protocol<\/code>. I'm going to try to keep these posts short and sweet for now -- there's tons more to say about <code>NewType<\/code>, but you can also just go RTFM!<\/p>\n"},{"title":"String Shenanigans","published":"2021-09-26T00:00:00+00:00","updated":"2021-09-26T00:00:00+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/ddejohn.dev\/overlapping-strings\/"}},"id":"https:\/\/ddejohn.dev\/overlapping-strings\/","content":"<p>This post is an extension of <a href=\"https:\/\/stackoverflow.com\/questions\/69337251\/concatenate-strings-with-a-common-substring-in-python\/69337268#69337268\">one of my recent Stack Overflow answers<\/a>.<\/p>\n<p>I'll show two pure Python solutions for concatenating an arbitrary list of unique strings based on suffix-prefix pairs, e.g.: <code>\"helloworld\"<\/code> and <code>\"worldfoo\"<\/code> would combine to <code>\"helloworldfoo\"<\/code>. Here's an example of a (short) list of strings for which we can start working on a solution:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#abb2bf;\">strings <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#abb2bf;\">{<\/span><span style=\"color:#9acc76;\">&quot;lazydog&quot;<\/span><span style=\"color:#abb2bf;\">,\n<\/span><span style=\"color:#abb2bf;\">           <\/span><span style=\"color:#9acc76;\">&quot;Thequick&quot;<\/span><span style=\"color:#abb2bf;\">,\n<\/span><span style=\"color:#abb2bf;\">           <\/span><span style=\"color:#9acc76;\">&quot;thelazy&quot;<\/span><span style=\"color:#abb2bf;\">,\n<\/span><span style=\"color:#abb2bf;\">           <\/span><span style=\"color:#9acc76;\">&quot;overthe&quot;<\/span><span style=\"color:#abb2bf;\">,\n<\/span><span style=\"color:#abb2bf;\">           <\/span><span style=\"color:#9acc76;\">&quot;foxjumps&quot;<\/span><span style=\"color:#abb2bf;\">,\n<\/span><span style=\"color:#abb2bf;\">           <\/span><span style=\"color:#9acc76;\">&quot;jumpsover&quot;<\/span><span style=\"color:#abb2bf;\">,\n<\/span><span style=\"color:#abb2bf;\">           <\/span><span style=\"color:#9acc76;\">&quot;brownfox&quot;<\/span><span style=\"color:#abb2bf;\">,\n<\/span><span style=\"color:#abb2bf;\">           <\/span><span style=\"color:#9acc76;\">&quot;quickbrown&quot;<\/span><span style=\"color:#abb2bf;\">}\n<\/span><\/code><\/pre>\n<p>Notice that if the strings were in order, e.g., <code>\"Thequick\", \"quickbrown\", \"brownfox\", ...<\/code>, then it'd be pretty straight forward to join the strings and remove the overlaps to get the desired result <code>\"Thequickbrownfox...\"<\/code>. Of course, we can't make that assumption about an ordered list, so we'll have to figure out the ordering manually.<\/p>\n<p>Since we need to check every pair of words, and since we can't assume <em>any<\/em> pair is ordered, we also have to take the reverse of every pair:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#abb2bf;\">pairs <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#5ebfcc;\">set<\/span><span style=\"color:#abb2bf;\">()\n<\/span><span style=\"color:#cd74e8;\">for <\/span><span style=\"color:#abb2bf;\">a <\/span><span style=\"color:#cd74e8;\">in <\/span><span style=\"color:#abb2bf;\">strings:\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"color:#cd74e8;\">for <\/span><span style=\"color:#abb2bf;\">b <\/span><span style=\"color:#cd74e8;\">in <\/span><span style=\"color:#abb2bf;\">strings:\n<\/span><span style=\"color:#abb2bf;\">        <\/span><span style=\"color:#cd74e8;\">if <\/span><span style=\"color:#abb2bf;\">a <\/span><span style=\"color:#adb7c9;\">!= <\/span><span style=\"color:#abb2bf;\">b:\n<\/span><span style=\"color:#abb2bf;\">            pairs.<\/span><span style=\"color:#eb6772;\">add<\/span><span style=\"color:#abb2bf;\">((a, b))\n<\/span><span style=\"color:#abb2bf;\">            pairs.<\/span><span style=\"color:#eb6772;\">add<\/span><span style=\"color:#abb2bf;\">((b, a))\n<\/span><\/code><\/pre>\n<p>Output:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#abb2bf;\">{(<\/span><span style=\"color:#9acc76;\">&#39;Thequick&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;brownfox&#39;<\/span><span style=\"color:#abb2bf;\">),\n<\/span><span style=\"color:#abb2bf;\"> (<\/span><span style=\"color:#9acc76;\">&#39;Thequick&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;foxjumps&#39;<\/span><span style=\"color:#abb2bf;\">),\n<\/span><span style=\"color:#abb2bf;\"> (<\/span><span style=\"color:#9acc76;\">&#39;Thequick&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;jumpsover&#39;<\/span><span style=\"color:#abb2bf;\">),\n<\/span><span style=\"color:#abb2bf;\"> (<\/span><span style=\"color:#9acc76;\">&#39;Thequick&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;lazydog&#39;<\/span><span style=\"color:#abb2bf;\">),\n<\/span><span style=\"color:#abb2bf;\"> (<\/span><span style=\"color:#9acc76;\">&#39;Thequick&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;overthe&#39;<\/span><span style=\"color:#abb2bf;\">),\n<\/span><span style=\"color:#abb2bf;\"> (<\/span><span style=\"color:#9acc76;\">&#39;Thequick&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;quickbrown&#39;<\/span><span style=\"color:#abb2bf;\">),\n<\/span><span style=\"color:#abb2bf;\"> (<\/span><span style=\"color:#9acc76;\">&#39;Thequick&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;thelazy&#39;<\/span><span style=\"color:#abb2bf;\">),\n<\/span><span style=\"color:#abb2bf;\"> (<\/span><span style=\"color:#9acc76;\">&#39;brownfox&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;Thequick&#39;<\/span><span style=\"color:#abb2bf;\">),\n<\/span><span style=\"color:#abb2bf;\"> (<\/span><span style=\"color:#9acc76;\">&#39;brownfox&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;foxjumps&#39;<\/span><span style=\"color:#abb2bf;\">),\n<\/span><span style=\"color:#abb2bf;\"> (<\/span><span style=\"color:#9acc76;\">&#39;brownfox&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;jumpsover&#39;<\/span><span style=\"color:#abb2bf;\">),\n<\/span><span style=\"color:#abb2bf;\"> (<\/span><span style=\"color:#9acc76;\">&#39;brownfox&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;lazydog&#39;<\/span><span style=\"color:#abb2bf;\">),\n<\/span><span style=\"color:#abb2bf;\"> (<\/span><span style=\"color:#9acc76;\">&#39;brownfox&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;overthe&#39;<\/span><span style=\"color:#abb2bf;\">),\n<\/span><span style=\"color:#abb2bf;\"> .\n<\/span><span style=\"color:#abb2bf;\"> .\n<\/span><span style=\"color:#abb2bf;\"> .\n<\/span><\/code><\/pre>\n<p>For this task, we can use permutations instead, which tends to be a bit quicker than the cartesian product:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#cd74e8;\">import <\/span><span style=\"color:#abb2bf;\">itertools\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#5ebfcc;\">set<\/span><span style=\"color:#abb2bf;\">(itertools.<\/span><span style=\"color:#eb6772;\">permutations<\/span><span style=\"color:#abb2bf;\">(strings, <\/span><span style=\"color:#eb6772;\">r<\/span><span style=\"color:#adb7c9;\">=<\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">))\n<\/span><\/code><\/pre>\n<p>Output:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#abb2bf;\">{(<\/span><span style=\"color:#9acc76;\">&#39;Thequick&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;brownfox&#39;<\/span><span style=\"color:#abb2bf;\">),\n<\/span><span style=\"color:#abb2bf;\"> (<\/span><span style=\"color:#9acc76;\">&#39;Thequick&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;foxjumps&#39;<\/span><span style=\"color:#abb2bf;\">),\n<\/span><span style=\"color:#abb2bf;\"> (<\/span><span style=\"color:#9acc76;\">&#39;Thequick&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;jumpsover&#39;<\/span><span style=\"color:#abb2bf;\">),\n<\/span><span style=\"color:#abb2bf;\"> (<\/span><span style=\"color:#9acc76;\">&#39;Thequick&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;lazydog&#39;<\/span><span style=\"color:#abb2bf;\">),\n<\/span><span style=\"color:#abb2bf;\"> (<\/span><span style=\"color:#9acc76;\">&#39;Thequick&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;overthe&#39;<\/span><span style=\"color:#abb2bf;\">),\n<\/span><span style=\"color:#abb2bf;\"> (<\/span><span style=\"color:#9acc76;\">&#39;Thequick&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;quickbrown&#39;<\/span><span style=\"color:#abb2bf;\">),\n<\/span><span style=\"color:#abb2bf;\"> (<\/span><span style=\"color:#9acc76;\">&#39;Thequick&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;thelazy&#39;<\/span><span style=\"color:#abb2bf;\">),\n<\/span><span style=\"color:#abb2bf;\"> (<\/span><span style=\"color:#9acc76;\">&#39;brownfox&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;Thequick&#39;<\/span><span style=\"color:#abb2bf;\">),\n<\/span><span style=\"color:#abb2bf;\"> (<\/span><span style=\"color:#9acc76;\">&#39;brownfox&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;foxjumps&#39;<\/span><span style=\"color:#abb2bf;\">),\n<\/span><span style=\"color:#abb2bf;\"> (<\/span><span style=\"color:#9acc76;\">&#39;brownfox&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;jumpsover&#39;<\/span><span style=\"color:#abb2bf;\">),\n<\/span><span style=\"color:#abb2bf;\"> (<\/span><span style=\"color:#9acc76;\">&#39;brownfox&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;lazydog&#39;<\/span><span style=\"color:#abb2bf;\">),\n<\/span><span style=\"color:#abb2bf;\"> (<\/span><span style=\"color:#9acc76;\">&#39;brownfox&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;overthe&#39;<\/span><span style=\"color:#abb2bf;\">),\n<\/span><span style=\"color:#abb2bf;\"> .\n<\/span><span style=\"color:#abb2bf;\"> .\n<\/span><span style=\"color:#abb2bf;\"> .\n<\/span><\/code><\/pre>\n<p>Now for every pair, we need to find whether or not they overlap. Take, for example, the pair <code>('brownfox', 'foxjumps')<\/code>. Let's \"slide\" the string <code>'foxjumps'<\/code> under the string <code>\"brownfox\"<\/code> one character at a time to see if we get any overlaps:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#abb2bf;\">brownfox\n<\/span><span style=\"color:#abb2bf;\">       foxjumps\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#abb2bf;\">brownfox\n<\/span><span style=\"color:#abb2bf;\">      foxjumps\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#abb2bf;\">brownfox\n<\/span><span style=\"color:#abb2bf;\">     foxjumps  <\/span><span style=\"font-style:italic;color:#5f697a;\"># Found an overlap!\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#abb2bf;\">brownfox\n<\/span><span style=\"color:#abb2bf;\">    foxjumps\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#abb2bf;\">brownfox\n<\/span><span style=\"color:#abb2bf;\">   foxjumps\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#abb2bf;\">brownfox\n<\/span><span style=\"color:#abb2bf;\">  foxjumps\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#abb2bf;\">brownfox\n<\/span><span style=\"color:#abb2bf;\"> foxjumps\n<\/span><\/code><\/pre>\n<p>Note that we do not continue further than this because we are assuming that all words in the original list are unique, so there cannot be an overlap equal to the length of either string in a given pair.<\/p>\n<p>Now let's repeat the process with another pair, <code>('brownfox', 'jumpsover')<\/code>:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#abb2bf;\">brownfox\n<\/span><span style=\"color:#abb2bf;\">       jumpsover\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#abb2bf;\">brownfox\n<\/span><span style=\"color:#abb2bf;\">      jumpsover\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#abb2bf;\">brownfox\n<\/span><span style=\"color:#abb2bf;\">     jumpsover\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#abb2bf;\">brownfox\n<\/span><span style=\"color:#abb2bf;\">    jumpsover\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#abb2bf;\">brownfox\n<\/span><span style=\"color:#abb2bf;\">   jumpsover\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#abb2bf;\">brownfox\n<\/span><span style=\"color:#abb2bf;\">  jumpsover\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#abb2bf;\">brownfox\n<\/span><span style=\"color:#abb2bf;\"> jumpsover  <\/span><span style=\"font-style:italic;color:#5f697a;\"># No point in checking any further than this\n<\/span><\/code><\/pre>\n<p>We'll use this function to find a suffix-prefix match:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#cd74e8;\">def <\/span><span style=\"color:#5cb3fa;\">get_match<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#eb6772;\">pair<\/span><span style=\"color:#abb2bf;\">: Tuple[str, str], <\/span><span style=\"color:#eb6772;\">min_overlap<\/span><span style=\"color:#abb2bf;\">: int <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#db9d63;\">3<\/span><span style=\"color:#abb2bf;\">) -&gt; str:\n<\/span><span style=\"color:#abb2bf;\">    a, b <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#abb2bf;\">pair\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"color:#cd74e8;\">for <\/span><span style=\"color:#abb2bf;\">i <\/span><span style=\"color:#cd74e8;\">in <\/span><span style=\"color:#5ebfcc;\">range<\/span><span style=\"color:#abb2bf;\">(min_overlap, <\/span><span style=\"color:#5ebfcc;\">min<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#5ebfcc;\">map<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#5ebfcc;\">len<\/span><span style=\"color:#abb2bf;\">, pair)) <\/span><span style=\"color:#adb7c9;\">+ <\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">):\n<\/span><span style=\"color:#abb2bf;\">        <\/span><span style=\"color:#cd74e8;\">if <\/span><span style=\"color:#abb2bf;\">a[<\/span><span style=\"color:#adb7c9;\">-<\/span><span style=\"color:#abb2bf;\">i:] <\/span><span style=\"color:#adb7c9;\">== <\/span><span style=\"color:#abb2bf;\">b[:i]:\n<\/span><span style=\"color:#abb2bf;\">            <\/span><span style=\"color:#cd74e8;\">return <\/span><span style=\"color:#abb2bf;\">b[:i]\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"color:#cd74e8;\">return <\/span><span style=\"color:#9acc76;\">&quot;&quot;\n<\/span><\/code><\/pre>\n<p>What this function does is essentially what is shown in the diagrams above: the second string <code>b<\/code> from <code>pair<\/code> is \"slid\" over the \"top\" of the first string <code>a<\/code> from <code>pair<\/code> until a match is found at which point the function returns immediately. The <code>min_overlap<\/code> parameter is optional and allows us to enforce that a match must be at least <code>min_overlap<\/code> number of characters, e.g., if <code>min_overlap == 2<\/code>, then <code>\"hello\", \"one\"<\/code> shouldn't match since the only suffix-prefix match is <code>\"o\"<\/code>.<\/p>\n<p>Now, we need to iterate over every possible pairing, and record all pairs which match:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#cd74e8;\">for <\/span><span style=\"color:#abb2bf;\">pair <\/span><span style=\"color:#cd74e8;\">in <\/span><span style=\"color:#abb2bf;\">itertools.<\/span><span style=\"color:#eb6772;\">permutations<\/span><span style=\"color:#abb2bf;\">(strings, <\/span><span style=\"color:#eb6772;\">r<\/span><span style=\"color:#adb7c9;\">=<\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">):\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"color:#cd74e8;\">if <\/span><span style=\"color:#abb2bf;\">(match <\/span><span style=\"color:#adb7c9;\">:= <\/span><span style=\"color:#eb6772;\">get_match<\/span><span style=\"color:#abb2bf;\">(pair)):\n<\/span><span style=\"color:#abb2bf;\">        <\/span><span style=\"color:#5ebfcc;\">print<\/span><span style=\"color:#abb2bf;\">(pair)\n<\/span><\/code><\/pre>\n<p>Output:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#9acc76;\">&#39;Thequick&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;quickbrown&#39;<\/span><span style=\"color:#abb2bf;\">)\n<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#9acc76;\">&#39;thelazy&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;lazydog&#39;<\/span><span style=\"color:#abb2bf;\">)\n<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#9acc76;\">&#39;overthe&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;thelazy&#39;<\/span><span style=\"color:#abb2bf;\">)\n<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#9acc76;\">&#39;foxjumps&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;jumpsover&#39;<\/span><span style=\"color:#abb2bf;\">)\n<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#9acc76;\">&#39;jumpsover&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;overthe&#39;<\/span><span style=\"color:#abb2bf;\">)\n<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#9acc76;\">&#39;brownfox&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;foxjumps&#39;<\/span><span style=\"color:#abb2bf;\">)\n<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#9acc76;\">&#39;quickbrown&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;brownfox&#39;<\/span><span style=\"color:#abb2bf;\">)\n<\/span><\/code><\/pre>\n<p>Now here's where the two solutions diverge.<\/p>\n<h2 id=\"first-solution-constructing-a-dag\">First solution: constructing a DAG<\/h2>\n<p>So we know that the solution for our particular example should be <code>Thequickbrownfoxjumpsoverthelazydog<\/code>. This can be represented by a (boring) <a href=\"https:\/\/en.wikipedia.org\/wiki\/Directed_acyclic_graph\">Directed Acyclic Graph<\/a>:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#abb2bf;\">Thequick <\/span><span style=\"color:#adb7c9;\">-&gt; <\/span><span style=\"color:#abb2bf;\">quickbrown <\/span><span style=\"color:#adb7c9;\">-&gt; <\/span><span style=\"color:#abb2bf;\">brownfox <\/span><span style=\"color:#adb7c9;\">-&gt; <\/span><span style=\"color:#abb2bf;\">foxjumps <\/span><span style=\"color:#adb7c9;\">-&gt; <\/span><span style=\"color:#abb2bf;\">jumpsover <\/span><span style=\"color:#adb7c9;\">-&gt; <\/span><span style=\"color:#abb2bf;\">overthe <\/span><span style=\"color:#adb7c9;\">-&gt; <\/span><span style=\"color:#abb2bf;\">thelazy <\/span><span style=\"color:#adb7c9;\">-&gt; <\/span><span style=\"color:#abb2bf;\">lazydog\n<\/span><\/code><\/pre>\n<p>This first solution uses a dictionary to track the adjacencies:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#cd74e8;\">def <\/span><span style=\"color:#5cb3fa;\">links_joiners<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#eb6772;\">strings<\/span><span style=\"color:#abb2bf;\">: List[str]) -&gt; Tuple[Dict[str, str], Set[str]]:\n<\/span><span style=\"color:#abb2bf;\">    links <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#5ebfcc;\">dict<\/span><span style=\"color:#abb2bf;\">()\n<\/span><span style=\"color:#abb2bf;\">    joiners <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#5ebfcc;\">set<\/span><span style=\"color:#abb2bf;\">()\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"color:#cd74e8;\">for <\/span><span style=\"color:#abb2bf;\">pair <\/span><span style=\"color:#cd74e8;\">in <\/span><span style=\"color:#abb2bf;\">itertools.<\/span><span style=\"color:#eb6772;\">permutations<\/span><span style=\"color:#abb2bf;\">(strings, <\/span><span style=\"color:#eb6772;\">r<\/span><span style=\"color:#adb7c9;\">=<\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">):\n<\/span><span style=\"color:#abb2bf;\">        <\/span><span style=\"color:#cd74e8;\">if <\/span><span style=\"color:#abb2bf;\">(match <\/span><span style=\"color:#adb7c9;\">:= <\/span><span style=\"color:#eb6772;\">get_match<\/span><span style=\"color:#abb2bf;\">(pair)):\n<\/span><span style=\"color:#abb2bf;\">            joiners.<\/span><span style=\"color:#eb6772;\">add<\/span><span style=\"color:#abb2bf;\">(match)\n<\/span><span style=\"color:#abb2bf;\">            links.<\/span><span style=\"color:#eb6772;\">update<\/span><span style=\"color:#abb2bf;\">((pair,))\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"color:#cd74e8;\">return <\/span><span style=\"color:#abb2bf;\">links, joiners\n<\/span><\/code><\/pre>\n<p>Output:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#abb2bf;\">links, joiners <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#eb6772;\">links_joiners<\/span><span style=\"color:#abb2bf;\">(strings)\n<\/span><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#abb2bf;\">links\n<\/span><span style=\"color:#abb2bf;\">{<\/span><span style=\"color:#9acc76;\">&#39;Thequick&#39;<\/span><span style=\"color:#abb2bf;\">: <\/span><span style=\"color:#9acc76;\">&#39;quickbrown&#39;<\/span><span style=\"color:#abb2bf;\">,\n<\/span><span style=\"color:#abb2bf;\"> <\/span><span style=\"color:#9acc76;\">&#39;thelazy&#39;<\/span><span style=\"color:#abb2bf;\">: <\/span><span style=\"color:#9acc76;\">&#39;lazydog&#39;<\/span><span style=\"color:#abb2bf;\">,\n<\/span><span style=\"color:#abb2bf;\"> <\/span><span style=\"color:#9acc76;\">&#39;overthe&#39;<\/span><span style=\"color:#abb2bf;\">: <\/span><span style=\"color:#9acc76;\">&#39;thelazy&#39;<\/span><span style=\"color:#abb2bf;\">,\n<\/span><span style=\"color:#abb2bf;\"> <\/span><span style=\"color:#9acc76;\">&#39;foxjumps&#39;<\/span><span style=\"color:#abb2bf;\">: <\/span><span style=\"color:#9acc76;\">&#39;jumpsover&#39;<\/span><span style=\"color:#abb2bf;\">,\n<\/span><span style=\"color:#abb2bf;\"> <\/span><span style=\"color:#9acc76;\">&#39;jumpsover&#39;<\/span><span style=\"color:#abb2bf;\">: <\/span><span style=\"color:#9acc76;\">&#39;overthe&#39;<\/span><span style=\"color:#abb2bf;\">,\n<\/span><span style=\"color:#abb2bf;\"> <\/span><span style=\"color:#9acc76;\">&#39;brownfox&#39;<\/span><span style=\"color:#abb2bf;\">: <\/span><span style=\"color:#9acc76;\">&#39;foxjumps&#39;<\/span><span style=\"color:#abb2bf;\">,\n<\/span><span style=\"color:#abb2bf;\"> <\/span><span style=\"color:#9acc76;\">&#39;quickbrown&#39;<\/span><span style=\"color:#abb2bf;\">: <\/span><span style=\"color:#9acc76;\">&#39;brownfox&#39;<\/span><span style=\"color:#abb2bf;\">}\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#abb2bf;\">joiners\n<\/span><span style=\"color:#abb2bf;\">{<\/span><span style=\"color:#9acc76;\">&#39;brown&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;fox&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;jumps&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;lazy&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;over&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;quick&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;the&#39;<\/span><span style=\"color:#abb2bf;\">}\n<\/span><\/code><\/pre>\n<p>For now, we'll focus on the <code>links<\/code> dictionary, which shows us our adjacencies. Recall our target:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#abb2bf;\">Thequick <\/span><span style=\"color:#adb7c9;\">-&gt; <\/span><span style=\"color:#abb2bf;\">quickbrown <\/span><span style=\"color:#adb7c9;\">-&gt; <\/span><span style=\"color:#abb2bf;\">brownfox <\/span><span style=\"color:#adb7c9;\">-&gt; <\/span><span style=\"color:#abb2bf;\">foxjumps <\/span><span style=\"color:#adb7c9;\">-&gt; <\/span><span style=\"color:#abb2bf;\">jumpsover <\/span><span style=\"color:#adb7c9;\">-&gt; <\/span><span style=\"color:#abb2bf;\">overthe <\/span><span style=\"color:#adb7c9;\">-&gt; <\/span><span style=\"color:#abb2bf;\">thelazy <\/span><span style=\"color:#adb7c9;\">-&gt; <\/span><span style=\"color:#abb2bf;\">lazydog\n<\/span><\/code><\/pre>\n<p>Now pick a random key in <code>links<\/code> and take its associated value. If that value is <em>also<\/em> a key, repeat that last step with the new key. If you get a value that <em>isn't<\/em> also a key in <code>links<\/code>, you've reached the end of the DAG. For example, consider an intial starting key, <code>\"jumpsover\"<\/code>.<\/p>\n<ul>\n<li><code>\"jumpsover\"<\/code> points to <code>\"overthe\"<\/code><\/li>\n<li><code>\"overthe\"<\/code> points to <code>\"thelazy\"<\/code><\/li>\n<li><code>\"thelazy\"<\/code> points to <code>\"lazydog\"<\/code><\/li>\n<li><code>\"lazydog\"<\/code> is not a key in <code>links<\/code>, therefore we've reached the end of the DAG<\/li>\n<\/ul>\n<p>This algorithm gives us the number of steps from any given key in the DAG (or \"node\") to the end. Our goal is to connect the nodes in the correct order without having to rerun another <code>get_match()<\/code> function on all the adjacent pairs of pairs. With that in mind, we can recursively traverse over <code>links<\/code>, finding the number of steps to the end of the DAG from every node. Since there's only one sequence which results in the longest list, and that will be achieved by ordering the nodes by their number of steps to the end of the DAG, in descending order:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#abb2bf;\"> nodes:   Thequick <\/span><span style=\"color:#adb7c9;\">-&gt; <\/span><span style=\"color:#abb2bf;\">quickbrown <\/span><span style=\"color:#adb7c9;\">-&gt; <\/span><span style=\"color:#abb2bf;\">brownfox <\/span><span style=\"color:#adb7c9;\">-&gt; <\/span><span style=\"color:#abb2bf;\">foxjumps <\/span><span style=\"color:#adb7c9;\">-&gt; <\/span><span style=\"color:#abb2bf;\">jumpsover <\/span><span style=\"color:#adb7c9;\">-&gt; <\/span><span style=\"color:#abb2bf;\">overthe <\/span><span style=\"color:#adb7c9;\">-&gt; <\/span><span style=\"color:#abb2bf;\">thelazy <\/span><span style=\"color:#adb7c9;\">-&gt; <\/span><span style=\"color:#abb2bf;\">lazydog\n<\/span><span style=\"color:#abb2bf;\"> steps:       <\/span><span style=\"color:#db9d63;\">7           6            5           4            3           2          1          0\n<\/span><\/code><\/pre>\n<p>Here's the code:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#cd74e8;\">def <\/span><span style=\"color:#5cb3fa;\">sort_nodes<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#eb6772;\">nodes<\/span><span style=\"color:#abb2bf;\">: List[str], <\/span><span style=\"color:#eb6772;\">links<\/span><span style=\"color:#abb2bf;\">: Dict[str, str]) -&gt; List[str]:\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"color:#cd74e8;\">def <\/span><span style=\"color:#5cb3fa;\">dist<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#eb6772;\">node<\/span><span style=\"color:#abb2bf;\">: str) -&gt; int:\n<\/span><span style=\"color:#abb2bf;\">        <\/span><span style=\"color:#cd74e8;\">if <\/span><span style=\"color:#abb2bf;\">node <\/span><span style=\"color:#cd74e8;\">not in <\/span><span style=\"color:#abb2bf;\">links:\n<\/span><span style=\"color:#abb2bf;\">            <\/span><span style=\"color:#cd74e8;\">return <\/span><span style=\"color:#db9d63;\">0\n<\/span><span style=\"color:#abb2bf;\">        <\/span><span style=\"color:#cd74e8;\">return <\/span><span style=\"color:#db9d63;\">1 <\/span><span style=\"color:#adb7c9;\">+ <\/span><span style=\"color:#eb6772;\">dist<\/span><span style=\"color:#abb2bf;\">(links[node])\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"color:#cd74e8;\">return <\/span><span style=\"color:#5ebfcc;\">sorted<\/span><span style=\"color:#abb2bf;\">(nodes, <\/span><span style=\"color:#eb6772;\">key<\/span><span style=\"color:#adb7c9;\">=<\/span><span style=\"color:#abb2bf;\">dist, <\/span><span style=\"color:#eb6772;\">reverse<\/span><span style=\"color:#adb7c9;\">=<\/span><span style=\"color:#db9d63;\">True<\/span><span style=\"color:#abb2bf;\">)\n<\/span><\/code><\/pre>\n<p>The inner function <code>dist()<\/code> is what's known as a <a href=\"https:\/\/en.wikipedia.org\/wiki\/Closure_(computer_programming)\">closure<\/a>, which means that <code>dist()<\/code> encloses or \"captures\" any variables which are local to the scope of the closure's parent and which are used inside the closure. Here, the two variables in the parent scope are <code>nodes<\/code>, and <code>links<\/code>, the two parameters of the <code>sort_nodes()<\/code> function.<\/p>\n<p>Notice that inside the closure <code>dist()<\/code>, the variable <code>links<\/code> is accessed even though <code>links<\/code> isn't within the scope of <code>dist()<\/code>. This is taking advantage of the fact that a function in Python will jump up the call stack to its parent's scope for a variable reference if it can't find it locally first.<\/p>\n<p>The reason for using a closure here is so <code>dist<\/code> only needs to take one parameter. This is so we can use it as the sorting method for the built-in <code>sorted()<\/code> function. Under the hood, Python will take each <code>node<\/code> in the list of <code>nodes<\/code>, and use <code>dist<\/code> to determine its ordinality.<\/p>\n<p>Let's look at the closure more closely:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#cd74e8;\">def <\/span><span style=\"color:#5cb3fa;\">dist<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#eb6772;\">node<\/span><span style=\"color:#abb2bf;\">: str) -&gt; int:\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"color:#cd74e8;\">if <\/span><span style=\"color:#abb2bf;\">node <\/span><span style=\"color:#cd74e8;\">not in <\/span><span style=\"color:#abb2bf;\">links:\n<\/span><span style=\"color:#abb2bf;\">        <\/span><span style=\"color:#cd74e8;\">return <\/span><span style=\"color:#db9d63;\">0\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"color:#cd74e8;\">return <\/span><span style=\"color:#db9d63;\">1 <\/span><span style=\"color:#adb7c9;\">+ <\/span><span style=\"color:#eb6772;\">dist<\/span><span style=\"color:#abb2bf;\">(links[node])\n<\/span><\/code><\/pre>\n<p>Take a minute to convince yourself that this function implements the algorithm outlined above. Let's do an example. Recall <code>links<\/code>:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#abb2bf;\">{<\/span><span style=\"color:#9acc76;\">&#39;Thequick&#39;<\/span><span style=\"color:#abb2bf;\">: <\/span><span style=\"color:#9acc76;\">&#39;quickbrown&#39;<\/span><span style=\"color:#abb2bf;\">,\n<\/span><span style=\"color:#abb2bf;\"> <\/span><span style=\"color:#9acc76;\">&#39;thelazy&#39;<\/span><span style=\"color:#abb2bf;\">: <\/span><span style=\"color:#9acc76;\">&#39;lazydog&#39;<\/span><span style=\"color:#abb2bf;\">,\n<\/span><span style=\"color:#abb2bf;\"> <\/span><span style=\"color:#9acc76;\">&#39;overthe&#39;<\/span><span style=\"color:#abb2bf;\">: <\/span><span style=\"color:#9acc76;\">&#39;thelazy&#39;<\/span><span style=\"color:#abb2bf;\">,\n<\/span><span style=\"color:#abb2bf;\"> <\/span><span style=\"color:#9acc76;\">&#39;foxjumps&#39;<\/span><span style=\"color:#abb2bf;\">: <\/span><span style=\"color:#9acc76;\">&#39;jumpsover&#39;<\/span><span style=\"color:#abb2bf;\">,\n<\/span><span style=\"color:#abb2bf;\"> <\/span><span style=\"color:#9acc76;\">&#39;jumpsover&#39;<\/span><span style=\"color:#abb2bf;\">: <\/span><span style=\"color:#9acc76;\">&#39;overthe&#39;<\/span><span style=\"color:#abb2bf;\">,\n<\/span><span style=\"color:#abb2bf;\"> <\/span><span style=\"color:#9acc76;\">&#39;brownfox&#39;<\/span><span style=\"color:#abb2bf;\">: <\/span><span style=\"color:#9acc76;\">&#39;foxjumps&#39;<\/span><span style=\"color:#abb2bf;\">,\n<\/span><span style=\"color:#abb2bf;\"> <\/span><span style=\"color:#9acc76;\">&#39;quickbrown&#39;<\/span><span style=\"color:#abb2bf;\">: <\/span><span style=\"color:#9acc76;\">&#39;brownfox&#39;<\/span><span style=\"color:#abb2bf;\">}\n<\/span><\/code><\/pre>\n<p>Now trace an example call, <code>dist(\"jumpsover\")<\/code>:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#eb6772;\">dist<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#9acc76;\">&quot;jumpsover&quot;<\/span><span style=\"color:#abb2bf;\">) <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#db9d63;\">1 <\/span><span style=\"color:#adb7c9;\">+ <\/span><span style=\"color:#eb6772;\">dist<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#9acc76;\">&quot;overthe&quot;<\/span><span style=\"color:#abb2bf;\">)  <\/span><span style=\"font-style:italic;color:#5f697a;\"># recurse, because &quot;jumpsover&quot; is a key in `links`\n<\/span><span style=\"color:#abb2bf;\">                  <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#db9d63;\">1 <\/span><span style=\"color:#adb7c9;\">+ <\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">1 <\/span><span style=\"color:#adb7c9;\">+ <\/span><span style=\"color:#eb6772;\">dist<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#9acc76;\">&quot;thelazy&quot;<\/span><span style=\"color:#abb2bf;\">))\n<\/span><span style=\"color:#abb2bf;\">                  <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#db9d63;\">1 <\/span><span style=\"color:#adb7c9;\">+ <\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">1 <\/span><span style=\"color:#adb7c9;\">+ <\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">1 <\/span><span style=\"color:#adb7c9;\">+ <\/span><span style=\"color:#eb6772;\">dist<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#9acc76;\">&quot;lazydog&quot;<\/span><span style=\"color:#abb2bf;\">)))  <\/span><span style=\"font-style:italic;color:#5f697a;\"># base case; &quot;lazydog&quot; is not a key in `links`\n<\/span><span style=\"color:#abb2bf;\">                  <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#db9d63;\">1 <\/span><span style=\"color:#adb7c9;\">+ <\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">1 <\/span><span style=\"color:#adb7c9;\">+ <\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">1 <\/span><span style=\"color:#adb7c9;\">+ <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">))\n<\/span><span style=\"color:#abb2bf;\">                  <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#db9d63;\">3\n<\/span><\/code><\/pre>\n<p>Now, by passing all of our <code>strings<\/code>, or \"nodes\", to <code>sorted()<\/code>, we will end up with a properly sorted list:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#abb2bf;\">strings <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#abb2bf;\">[<\/span><span style=\"color:#9acc76;\">&quot;lazydog&quot;<\/span><span style=\"color:#abb2bf;\">,\n<\/span><span style=\"color:#abb2bf;\">           <\/span><span style=\"color:#9acc76;\">&quot;Thequick&quot;<\/span><span style=\"color:#abb2bf;\">,\n<\/span><span style=\"color:#abb2bf;\">           <\/span><span style=\"color:#9acc76;\">&quot;thelazy&quot;<\/span><span style=\"color:#abb2bf;\">,\n<\/span><span style=\"color:#abb2bf;\">           <\/span><span style=\"color:#9acc76;\">&quot;overthe&quot;<\/span><span style=\"color:#abb2bf;\">,\n<\/span><span style=\"color:#abb2bf;\">           <\/span><span style=\"color:#9acc76;\">&quot;foxjumps&quot;<\/span><span style=\"color:#abb2bf;\">,\n<\/span><span style=\"color:#abb2bf;\">           <\/span><span style=\"color:#9acc76;\">&quot;jumpsover&quot;<\/span><span style=\"color:#abb2bf;\">,\n<\/span><span style=\"color:#abb2bf;\">           <\/span><span style=\"color:#9acc76;\">&quot;brownfox&quot;<\/span><span style=\"color:#abb2bf;\">,\n<\/span><span style=\"color:#abb2bf;\">           <\/span><span style=\"color:#9acc76;\">&quot;quickbrown&quot;<\/span><span style=\"color:#abb2bf;\">]\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#abb2bf;\">nodes_in_order <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#eb6772;\">sort_nodes<\/span><span style=\"color:#abb2bf;\">(strings, links)\n<\/span><\/code><\/pre>\n<p>Output:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#abb2bf;\">nodes_in_order\n<\/span><span style=\"color:#abb2bf;\">[<\/span><span style=\"color:#9acc76;\">&#39;Thequick&#39;<\/span><span style=\"color:#abb2bf;\">,\n<\/span><span style=\"color:#abb2bf;\"> <\/span><span style=\"color:#9acc76;\">&#39;quickbrown&#39;<\/span><span style=\"color:#abb2bf;\">,\n<\/span><span style=\"color:#abb2bf;\"> <\/span><span style=\"color:#9acc76;\">&#39;brownfox&#39;<\/span><span style=\"color:#abb2bf;\">,\n<\/span><span style=\"color:#abb2bf;\"> <\/span><span style=\"color:#9acc76;\">&#39;foxjumps&#39;<\/span><span style=\"color:#abb2bf;\">,\n<\/span><span style=\"color:#abb2bf;\"> <\/span><span style=\"color:#9acc76;\">&#39;jumpsover&#39;<\/span><span style=\"color:#abb2bf;\">,\n<\/span><span style=\"color:#abb2bf;\"> <\/span><span style=\"color:#9acc76;\">&#39;overthe&#39;<\/span><span style=\"color:#abb2bf;\">,\n<\/span><span style=\"color:#abb2bf;\"> <\/span><span style=\"color:#9acc76;\">&#39;thelazy&#39;<\/span><span style=\"color:#abb2bf;\">,\n<\/span><span style=\"color:#abb2bf;\"> <\/span><span style=\"color:#9acc76;\">&#39;lazydog&#39;<\/span><span style=\"color:#abb2bf;\">]\n<\/span><\/code><\/pre>\n<p>Now, recall <code>joiners<\/code> from earlier:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#abb2bf;\">{<\/span><span style=\"color:#9acc76;\">&#39;brown&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;fox&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;jumps&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;lazy&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;over&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;quick&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;the&#39;<\/span><span style=\"color:#abb2bf;\">}\n<\/span><\/code><\/pre>\n<p>Our sorted list, together with <code>joiners<\/code> are all we need in order to finally put the pieces together. However, there's one last hurdle, when we join <code>nodes_in_order<\/code>, all of the overlapping strings are present:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#9acc76;\">&quot;&quot;<\/span><span style=\"color:#abb2bf;\">.<\/span><span style=\"color:#eb6772;\">join<\/span><span style=\"color:#abb2bf;\">(nodes_in_order)\n<\/span><span style=\"color:#9acc76;\">&#39;Thequickquickbrownbrownfoxfoxjumpsjumpsoveroverthethelazylazydog&#39;\n<\/span><\/code><\/pre>\n<p>So instead of leaving it at that, we iterate over <code>joiners<\/code> and for each joiner <code>j<\/code>, we replace one copy of <code>j<\/code> with the empty string so as to get rid of the duplicates formed by joining:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#cd74e8;\">def <\/span><span style=\"color:#5cb3fa;\">join_strings<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#eb6772;\">strings<\/span><span style=\"color:#abb2bf;\">: List[str], <\/span><span style=\"color:#eb6772;\">joiners<\/span><span style=\"color:#abb2bf;\">: Set[str]) -&gt; str:\n<\/span><span style=\"color:#abb2bf;\">    result <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#9acc76;\">&quot;&quot;<\/span><span style=\"color:#abb2bf;\">.<\/span><span style=\"color:#eb6772;\">join<\/span><span style=\"color:#abb2bf;\">(strings)\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"color:#cd74e8;\">for <\/span><span style=\"color:#abb2bf;\">j <\/span><span style=\"color:#cd74e8;\">in <\/span><span style=\"color:#abb2bf;\">joiners:\n<\/span><span style=\"color:#abb2bf;\">        result <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#abb2bf;\">result.<\/span><span style=\"color:#eb6772;\">replace<\/span><span style=\"color:#abb2bf;\">(j, <\/span><span style=\"color:#9acc76;\">&quot;&quot;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">)\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"color:#cd74e8;\">return <\/span><span style=\"color:#abb2bf;\">result\n<\/span><\/code><\/pre>\n<p>Output:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#eb6772;\">join_strings<\/span><span style=\"color:#abb2bf;\">(nodes_in_order, joiners)\n<\/span><span style=\"color:#9acc76;\">&#39;Thequickbrownfoxjumpsoverthelazydog&#39;\n<\/span><\/code><\/pre>\n<p>Done!<\/p>\n<h2 id=\"second-solution-pure-unfettered-recursion\">Second solution: pure, unfettered recursion<\/h2>\n<p>This solution reuses <code>join_strings()<\/code> and <code>get_match()<\/code> from above. This solution foregoes collecting pairs, creating a dictionary of adjacency \"links\", finding the correct ordering -- and skips straight to joining pairs of strings:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#cd74e8;\">def <\/span><span style=\"color:#5cb3fa;\">join_overlapping_pairs<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#eb6772;\">strings<\/span><span style=\"color:#abb2bf;\">: Set[str]) -&gt; str:\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"color:#cd74e8;\">if <\/span><span style=\"color:#5ebfcc;\">len<\/span><span style=\"color:#abb2bf;\">(strings) <\/span><span style=\"color:#adb7c9;\">== <\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">:\n<\/span><span style=\"color:#abb2bf;\">        <\/span><span style=\"color:#cd74e8;\">return <\/span><span style=\"color:#abb2bf;\">strings.<\/span><span style=\"color:#eb6772;\">pop<\/span><span style=\"color:#abb2bf;\">()\n<\/span><span style=\"color:#abb2bf;\">    matches <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#5ebfcc;\">set<\/span><span style=\"color:#abb2bf;\">()\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"color:#cd74e8;\">for <\/span><span style=\"color:#abb2bf;\">pair <\/span><span style=\"color:#cd74e8;\">in <\/span><span style=\"color:#abb2bf;\">itertools.<\/span><span style=\"color:#eb6772;\">permutations<\/span><span style=\"color:#abb2bf;\">(strings, <\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">):\n<\/span><span style=\"color:#abb2bf;\">        <\/span><span style=\"color:#cd74e8;\">if <\/span><span style=\"color:#abb2bf;\">(match <\/span><span style=\"color:#adb7c9;\">:= <\/span><span style=\"color:#eb6772;\">get_match<\/span><span style=\"color:#abb2bf;\">(pair)):\n<\/span><span style=\"color:#abb2bf;\">            matches.<\/span><span style=\"color:#eb6772;\">add<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#eb6772;\">join_strings<\/span><span style=\"color:#abb2bf;\">(pair, (match,)))\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"color:#cd74e8;\">return <\/span><span style=\"color:#eb6772;\">join_overlapping_pairs<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#eb6772;\">filter_substrings<\/span><span style=\"color:#abb2bf;\">(matches))\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#cd74e8;\">def <\/span><span style=\"color:#5cb3fa;\">filter_substrings<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#eb6772;\">strings<\/span><span style=\"color:#abb2bf;\">: Set[str]) -&gt; Set[str]:\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"color:#cd74e8;\">if <\/span><span style=\"color:#5ebfcc;\">len<\/span><span style=\"color:#abb2bf;\">(strings) <\/span><span style=\"color:#adb7c9;\">== <\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">:\n<\/span><span style=\"color:#abb2bf;\">        <\/span><span style=\"color:#cd74e8;\">return <\/span><span style=\"color:#abb2bf;\">strings\n<\/span><span style=\"color:#abb2bf;\">    largest <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#5ebfcc;\">set<\/span><span style=\"color:#abb2bf;\">()\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"color:#cd74e8;\">for <\/span><span style=\"color:#abb2bf;\">s1 <\/span><span style=\"color:#cd74e8;\">in <\/span><span style=\"color:#abb2bf;\">strings:\n<\/span><span style=\"color:#abb2bf;\">        <\/span><span style=\"color:#cd74e8;\">for <\/span><span style=\"color:#abb2bf;\">s2 <\/span><span style=\"color:#cd74e8;\">in <\/span><span style=\"color:#abb2bf;\">strings <\/span><span style=\"color:#adb7c9;\">- <\/span><span style=\"color:#abb2bf;\">{s1,}:\n<\/span><span style=\"color:#abb2bf;\">            <\/span><span style=\"color:#cd74e8;\">if <\/span><span style=\"color:#abb2bf;\">(s1 <\/span><span style=\"color:#cd74e8;\">in <\/span><span style=\"color:#abb2bf;\">s2):\n<\/span><span style=\"color:#abb2bf;\">                <\/span><span style=\"color:#cd74e8;\">break\n<\/span><span style=\"color:#abb2bf;\">        <\/span><span style=\"color:#cd74e8;\">else<\/span><span style=\"color:#abb2bf;\">:\n<\/span><span style=\"color:#abb2bf;\">            largest.<\/span><span style=\"color:#eb6772;\">add<\/span><span style=\"color:#abb2bf;\">(s1)\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"color:#cd74e8;\">if <\/span><span style=\"color:#abb2bf;\">largest <\/span><span style=\"color:#adb7c9;\">== <\/span><span style=\"color:#abb2bf;\">strings:\n<\/span><span style=\"color:#abb2bf;\">        <\/span><span style=\"color:#cd74e8;\">return <\/span><span style=\"color:#abb2bf;\">strings\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"color:#cd74e8;\">return <\/span><span style=\"color:#abb2bf;\">largest <\/span><span style=\"color:#adb7c9;\">| <\/span><span style=\"color:#eb6772;\">filter_substrings<\/span><span style=\"color:#abb2bf;\">(largest)\n<\/span><\/code><\/pre>\n<p>By adding <code>print(matches)<\/code> right before the recursive call of <code>join_overlapping_pairs()<\/code>, we can trace the behavior of the function:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#eb6772;\">join_overlapping_pairs<\/span><span style=\"color:#abb2bf;\">(strings)\n<\/span><span style=\"color:#abb2bf;\">{<\/span><span style=\"color:#9acc76;\">&#39;brownfoxjumps&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;foxjumpsover&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;jumpsoverthe&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;quickbrownfox&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;overthelazy&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;thelazydog&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;Thequickbrown&#39;<\/span><span style=\"color:#abb2bf;\">}\n<\/span><span style=\"color:#abb2bf;\">{<\/span><span style=\"color:#9acc76;\">&#39;Thequickbrownfoxjumps&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;quickbrownfoxjumpsover&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;jumpsoverthelazydog&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;foxjumpsoverthelazy&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;brownfoxjumpsoverthe&#39;<\/span><span style=\"color:#abb2bf;\">}\n<\/span><span style=\"color:#abb2bf;\">{<\/span><span style=\"color:#9acc76;\">&#39;Thequickbrownfoxjumpsoverthelazydog&#39;<\/span><span style=\"color:#abb2bf;\">}\n<\/span><\/code><\/pre>\n<p>The <code>filter_substrings()<\/code> function isn't wholly necessary, but it does reduce the size of the search space <code>join_overlapping_pairs()<\/code> has to work with. Without it, here's what the <code>matches<\/code> set looks like after the second iteration of <code>join_overlapping_pairs()<\/code>:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#abb2bf;\">{<\/span><span style=\"color:#9acc76;\">&#39;overthelazydog&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;brownfoxjumpsoverthe&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;quickbrownfoxjumps&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;jumpsoverthelazydog&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;jumpsoverthelazy&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;Thequickbrownfox&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;foxjumpsoverthelazy&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;Thequickbrownfoxjumps&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;quickbrownfoxjumpsover&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;foxjumpsoverthe&#39;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#9acc76;\">&#39;brownfoxjumpsover&#39;<\/span><span style=\"color:#abb2bf;\">}\n<\/span><\/code><\/pre>\n<p>But if you look closely, many of these strings are actually substrings of others (e.g., <code>\"jumpsoverthelazy\"<\/code> and <code>\"foxjumpsoverthelazy\"<\/code>), which means we can get rid of a lot of redundancy. If we print <code>len(matches)<\/code> instead:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#db9d63;\">7 42\n<\/span><span style=\"color:#db9d63;\">11 110\n<\/span><span style=\"color:#db9d63;\">15 210\n<\/span><span style=\"color:#db9d63;\">10 90\n<\/span><span style=\"color:#db9d63;\">6 30\n<\/span><span style=\"color:#db9d63;\">3 6\n<\/span><span style=\"color:#db9d63;\">1 0\n<\/span><\/code><\/pre>\n<p>The numbers on the left are how many strings were passed to <code>join_overlapping_pairs()<\/code>. The numbers on the right are the size of the search space. Notice that the number of strings and the size of the search space (the paired permutations of every string, which is <code>n * (n - 1)<\/code>) actually <em>increases<\/em> before it decreases. This isn't a good sign if we want to use this for larger sets of strings...<\/p>\n<p>By first filtering out the substrings before recursing, we can not only reduce the search space for successive iterations, but even reduce the number of steps it takes to find a solution:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#db9d63;\">7 42\n<\/span><span style=\"color:#db9d63;\">5 20\n<\/span><span style=\"color:#db9d63;\">1 0\n<\/span><\/code><\/pre>\n<p>The <code>filter_substrings()<\/code> function is pretty straightforward. It recursively iterates over the set, checking each string in the set to determine if it's a substring of any other string in the set.<\/p>\n<h2 id=\"first-solution-source-code\">First solution: source code<\/h2>\n<p>Here's the source code for the first solution:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#cd74e8;\">import <\/span><span style=\"color:#abb2bf;\">itertools\n<\/span><span style=\"color:#cd74e8;\">from <\/span><span style=\"color:#abb2bf;\">typing <\/span><span style=\"color:#cd74e8;\">import <\/span><span style=\"color:#abb2bf;\">Set, Tuple, Dict, List\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#cd74e8;\">def <\/span><span style=\"color:#5cb3fa;\">get_match<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#eb6772;\">pair<\/span><span style=\"color:#abb2bf;\">: Tuple[str, str], <\/span><span style=\"color:#eb6772;\">min_overlap<\/span><span style=\"color:#abb2bf;\">: int <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#db9d63;\">3<\/span><span style=\"color:#abb2bf;\">) -&gt; str:\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"font-style:italic;color:#5f697a;\">&quot;&quot;&quot;\n<\/span><span style=\"font-style:italic;color:#5f697a;\">    For `a`, `b` in `pair`, this &#39;slides&#39; `b` over the end of\n<\/span><span style=\"font-style:italic;color:#5f697a;\">    `a` from the left, starting at `min_overlap`. Returns as\n<\/span><span style=\"font-style:italic;color:#5f697a;\">    soon as a match is found, or the empty string otherwise.\n<\/span><span style=\"font-style:italic;color:#5f697a;\">    &quot;&quot;&quot;\n<\/span><span style=\"color:#abb2bf;\">    a, b <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#abb2bf;\">pair\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"color:#cd74e8;\">for <\/span><span style=\"color:#abb2bf;\">i <\/span><span style=\"color:#cd74e8;\">in <\/span><span style=\"color:#5ebfcc;\">range<\/span><span style=\"color:#abb2bf;\">(min_overlap, <\/span><span style=\"color:#5ebfcc;\">min<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#5ebfcc;\">map<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#5ebfcc;\">len<\/span><span style=\"color:#abb2bf;\">, pair)) <\/span><span style=\"color:#adb7c9;\">+ <\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">):\n<\/span><span style=\"color:#abb2bf;\">        <\/span><span style=\"color:#cd74e8;\">if <\/span><span style=\"color:#abb2bf;\">a[<\/span><span style=\"color:#adb7c9;\">-<\/span><span style=\"color:#abb2bf;\">i:] <\/span><span style=\"color:#adb7c9;\">== <\/span><span style=\"color:#abb2bf;\">b[:i]:\n<\/span><span style=\"color:#abb2bf;\">            <\/span><span style=\"color:#cd74e8;\">return <\/span><span style=\"color:#abb2bf;\">b[:i]\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"color:#cd74e8;\">return <\/span><span style=\"color:#9acc76;\">&quot;&quot;\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#cd74e8;\">def <\/span><span style=\"color:#5cb3fa;\">join_strings<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#eb6772;\">strings<\/span><span style=\"color:#abb2bf;\">: List[str], <\/span><span style=\"color:#eb6772;\">joiners<\/span><span style=\"color:#abb2bf;\">: Set[str]) -&gt; str:\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"font-style:italic;color:#5f697a;\">&quot;&quot;&quot;\n<\/span><span style=\"font-style:italic;color:#5f697a;\">    Joins an ordered sequence of `strings`, removing the extraneous `joiner`\n<\/span><span style=\"font-style:italic;color:#5f697a;\">    between each joined pair.\n<\/span><span style=\"font-style:italic;color:#5f697a;\">    &quot;&quot;&quot;\n<\/span><span style=\"color:#abb2bf;\">    result <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#9acc76;\">&quot;&quot;<\/span><span style=\"color:#abb2bf;\">.<\/span><span style=\"color:#eb6772;\">join<\/span><span style=\"color:#abb2bf;\">(strings)\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"color:#cd74e8;\">for <\/span><span style=\"color:#abb2bf;\">j <\/span><span style=\"color:#cd74e8;\">in <\/span><span style=\"color:#abb2bf;\">joiners:\n<\/span><span style=\"color:#abb2bf;\">        result <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#abb2bf;\">result.<\/span><span style=\"color:#eb6772;\">replace<\/span><span style=\"color:#abb2bf;\">(j, <\/span><span style=\"color:#9acc76;\">&quot;&quot;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">)\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"color:#cd74e8;\">return <\/span><span style=\"color:#abb2bf;\">result\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#cd74e8;\">def <\/span><span style=\"color:#5cb3fa;\">links_joiners<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#eb6772;\">strings<\/span><span style=\"color:#abb2bf;\">: List[str]) -&gt; Tuple[Dict[str, str], Set[str]]:\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"font-style:italic;color:#5f697a;\">&quot;&quot;&quot;\n<\/span><span style=\"font-style:italic;color:#5f697a;\">    Links will be key:value pairs of words that have a common\n<\/span><span style=\"font-style:italic;color:#5f697a;\">    substring where the end of `s1` overlaps with the start of `s2`.\n<\/span><span style=\"font-style:italic;color:#5f697a;\">    &quot;&quot;&quot;\n<\/span><span style=\"color:#abb2bf;\">    links <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#5ebfcc;\">dict<\/span><span style=\"color:#abb2bf;\">()\n<\/span><span style=\"color:#abb2bf;\">    joiners <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#5ebfcc;\">set<\/span><span style=\"color:#abb2bf;\">()\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"color:#cd74e8;\">for <\/span><span style=\"color:#abb2bf;\">pair <\/span><span style=\"color:#cd74e8;\">in <\/span><span style=\"color:#abb2bf;\">itertools.<\/span><span style=\"color:#eb6772;\">permutations<\/span><span style=\"color:#abb2bf;\">(strings, <\/span><span style=\"color:#eb6772;\">r<\/span><span style=\"color:#adb7c9;\">=<\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">):\n<\/span><span style=\"color:#abb2bf;\">        <\/span><span style=\"color:#cd74e8;\">if <\/span><span style=\"color:#abb2bf;\">(match <\/span><span style=\"color:#adb7c9;\">:= <\/span><span style=\"color:#eb6772;\">get_match<\/span><span style=\"color:#abb2bf;\">(pair)):\n<\/span><span style=\"color:#abb2bf;\">            joiners.<\/span><span style=\"color:#eb6772;\">add<\/span><span style=\"color:#abb2bf;\">(match)\n<\/span><span style=\"color:#abb2bf;\">            links.<\/span><span style=\"color:#eb6772;\">update<\/span><span style=\"color:#abb2bf;\">((pair,))\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"color:#cd74e8;\">return <\/span><span style=\"color:#abb2bf;\">links, joiners\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#cd74e8;\">def <\/span><span style=\"color:#5cb3fa;\">sort_nodes<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#eb6772;\">nodes<\/span><span style=\"color:#abb2bf;\">: List[str], <\/span><span style=\"color:#eb6772;\">links<\/span><span style=\"color:#abb2bf;\">: Dict[str, str]) -&gt; List[str]:\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"font-style:italic;color:#5f697a;\">&quot;&quot;&quot;\n<\/span><span style=\"font-style:italic;color:#5f697a;\">    A recursive closure which determines the length of each sequence\n<\/span><span style=\"font-style:italic;color:#5f697a;\">    starting at `node` and traversing the `links` dictionary.\n<\/span><span style=\"font-style:italic;color:#5f697a;\">    &quot;&quot;&quot;\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"color:#cd74e8;\">def <\/span><span style=\"color:#5cb3fa;\">dist<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#eb6772;\">node<\/span><span style=\"color:#abb2bf;\">: str) -&gt; int:\n<\/span><span style=\"color:#abb2bf;\">        <\/span><span style=\"color:#cd74e8;\">if <\/span><span style=\"color:#abb2bf;\">node <\/span><span style=\"color:#cd74e8;\">not in <\/span><span style=\"color:#abb2bf;\">links:\n<\/span><span style=\"color:#abb2bf;\">            <\/span><span style=\"color:#cd74e8;\">return <\/span><span style=\"color:#db9d63;\">0\n<\/span><span style=\"color:#abb2bf;\">        <\/span><span style=\"color:#cd74e8;\">return <\/span><span style=\"color:#db9d63;\">1 <\/span><span style=\"color:#adb7c9;\">+ <\/span><span style=\"color:#eb6772;\">dist<\/span><span style=\"color:#abb2bf;\">(links[node])\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"color:#cd74e8;\">return <\/span><span style=\"color:#5ebfcc;\">sorted<\/span><span style=\"color:#abb2bf;\">(nodes, <\/span><span style=\"color:#eb6772;\">key<\/span><span style=\"color:#adb7c9;\">=<\/span><span style=\"color:#abb2bf;\">dist, <\/span><span style=\"color:#eb6772;\">reverse<\/span><span style=\"color:#adb7c9;\">=<\/span><span style=\"color:#db9d63;\">True<\/span><span style=\"color:#abb2bf;\">)\n<\/span><\/code><\/pre>\n<h2 id=\"second-solution-source-code\">Second solution: source code<\/h2>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#cd74e8;\">import <\/span><span style=\"color:#abb2bf;\">itertools\n<\/span><span style=\"color:#cd74e8;\">from <\/span><span style=\"color:#abb2bf;\">typing <\/span><span style=\"color:#cd74e8;\">import <\/span><span style=\"color:#abb2bf;\">Set, Tuple, List, Union\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#cd74e8;\">def <\/span><span style=\"color:#5cb3fa;\">get_match<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#eb6772;\">pair<\/span><span style=\"color:#abb2bf;\">: Tuple[str, str], <\/span><span style=\"color:#eb6772;\">min_overlap<\/span><span style=\"color:#abb2bf;\">: int <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#db9d63;\">3<\/span><span style=\"color:#abb2bf;\">) -&gt; str:\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"font-style:italic;color:#5f697a;\">&quot;&quot;&quot;\n<\/span><span style=\"font-style:italic;color:#5f697a;\">    For `a`, `b` in `pair`, this &#39;slides&#39; `b` over the end of\n<\/span><span style=\"font-style:italic;color:#5f697a;\">    `a` from the left, starting at `min_overlap`. Returns as\n<\/span><span style=\"font-style:italic;color:#5f697a;\">    soon as a match is found, or the empty string otherwise.\n<\/span><span style=\"font-style:italic;color:#5f697a;\">    &quot;&quot;&quot;\n<\/span><span style=\"color:#abb2bf;\">    a, b <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#abb2bf;\">pair\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"color:#cd74e8;\">for <\/span><span style=\"color:#abb2bf;\">i <\/span><span style=\"color:#cd74e8;\">in <\/span><span style=\"color:#5ebfcc;\">range<\/span><span style=\"color:#abb2bf;\">(min_overlap, <\/span><span style=\"color:#5ebfcc;\">min<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#5ebfcc;\">map<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#5ebfcc;\">len<\/span><span style=\"color:#abb2bf;\">, pair)) <\/span><span style=\"color:#adb7c9;\">+ <\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">):\n<\/span><span style=\"color:#abb2bf;\">        <\/span><span style=\"color:#cd74e8;\">if <\/span><span style=\"color:#abb2bf;\">a[<\/span><span style=\"color:#adb7c9;\">-<\/span><span style=\"color:#abb2bf;\">i:] <\/span><span style=\"color:#adb7c9;\">== <\/span><span style=\"color:#abb2bf;\">b[:i]:\n<\/span><span style=\"color:#abb2bf;\">            <\/span><span style=\"color:#cd74e8;\">return <\/span><span style=\"color:#abb2bf;\">b[:i]\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"color:#cd74e8;\">return <\/span><span style=\"color:#9acc76;\">&quot;&quot;\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#cd74e8;\">def <\/span><span style=\"color:#5cb3fa;\">join_strings<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#eb6772;\">strings<\/span><span style=\"color:#abb2bf;\">: List[str], <\/span><span style=\"color:#eb6772;\">joiners<\/span><span style=\"color:#abb2bf;\">: Set[str]) -&gt; str:\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"font-style:italic;color:#5f697a;\">&quot;&quot;&quot;\n<\/span><span style=\"font-style:italic;color:#5f697a;\">    Joins an ordered sequence of `strings`, removing the extraneous `joiner`\n<\/span><span style=\"font-style:italic;color:#5f697a;\">    between each joined pair.\n<\/span><span style=\"font-style:italic;color:#5f697a;\">    &quot;&quot;&quot;\n<\/span><span style=\"color:#abb2bf;\">    result <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#9acc76;\">&quot;&quot;<\/span><span style=\"color:#abb2bf;\">.<\/span><span style=\"color:#eb6772;\">join<\/span><span style=\"color:#abb2bf;\">(strings)\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"color:#cd74e8;\">for <\/span><span style=\"color:#abb2bf;\">j <\/span><span style=\"color:#cd74e8;\">in <\/span><span style=\"color:#abb2bf;\">joiners:\n<\/span><span style=\"color:#abb2bf;\">        result <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#abb2bf;\">result.<\/span><span style=\"color:#eb6772;\">replace<\/span><span style=\"color:#abb2bf;\">(j, <\/span><span style=\"color:#9acc76;\">&quot;&quot;<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">)\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"color:#cd74e8;\">return <\/span><span style=\"color:#abb2bf;\">result\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#cd74e8;\">def <\/span><span style=\"color:#5cb3fa;\">join_overlapping_pairs<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#eb6772;\">strings<\/span><span style=\"color:#abb2bf;\">: Set[str]) -&gt; str:\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"color:#cd74e8;\">if <\/span><span style=\"color:#5ebfcc;\">len<\/span><span style=\"color:#abb2bf;\">(strings) <\/span><span style=\"color:#adb7c9;\">== <\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">:\n<\/span><span style=\"color:#abb2bf;\">        <\/span><span style=\"color:#cd74e8;\">return <\/span><span style=\"color:#abb2bf;\">strings.<\/span><span style=\"color:#eb6772;\">pop<\/span><span style=\"color:#abb2bf;\">()\n<\/span><span style=\"color:#abb2bf;\">    matches <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#5ebfcc;\">set<\/span><span style=\"color:#abb2bf;\">()\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"color:#cd74e8;\">for <\/span><span style=\"color:#abb2bf;\">pair <\/span><span style=\"color:#cd74e8;\">in <\/span><span style=\"color:#abb2bf;\">itertools.<\/span><span style=\"color:#eb6772;\">permutations<\/span><span style=\"color:#abb2bf;\">(strings, <\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">):\n<\/span><span style=\"color:#abb2bf;\">        <\/span><span style=\"color:#cd74e8;\">if <\/span><span style=\"color:#abb2bf;\">(match <\/span><span style=\"color:#adb7c9;\">:= <\/span><span style=\"color:#eb6772;\">get_match<\/span><span style=\"color:#abb2bf;\">(pair)):\n<\/span><span style=\"color:#abb2bf;\">            matches.<\/span><span style=\"color:#eb6772;\">add<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#eb6772;\">join_strings<\/span><span style=\"color:#abb2bf;\">(pair, (match,)))\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"color:#cd74e8;\">return <\/span><span style=\"color:#eb6772;\">join_overlapping_pairs<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#eb6772;\">filter_substrings<\/span><span style=\"color:#abb2bf;\">(matches))\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#cd74e8;\">def <\/span><span style=\"color:#5cb3fa;\">filter_substrings<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#eb6772;\">strings<\/span><span style=\"color:#abb2bf;\">: Set[str]) -&gt; Set[str]:\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"color:#cd74e8;\">if <\/span><span style=\"color:#5ebfcc;\">len<\/span><span style=\"color:#abb2bf;\">(strings) <\/span><span style=\"color:#adb7c9;\">== <\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">:\n<\/span><span style=\"color:#abb2bf;\">        <\/span><span style=\"color:#cd74e8;\">return <\/span><span style=\"color:#abb2bf;\">strings\n<\/span><span style=\"color:#abb2bf;\">    largest <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#5ebfcc;\">set<\/span><span style=\"color:#abb2bf;\">()\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"color:#cd74e8;\">for <\/span><span style=\"color:#abb2bf;\">s1 <\/span><span style=\"color:#cd74e8;\">in <\/span><span style=\"color:#abb2bf;\">strings:\n<\/span><span style=\"color:#abb2bf;\">        <\/span><span style=\"color:#cd74e8;\">for <\/span><span style=\"color:#abb2bf;\">s2 <\/span><span style=\"color:#cd74e8;\">in <\/span><span style=\"color:#abb2bf;\">strings <\/span><span style=\"color:#adb7c9;\">- <\/span><span style=\"color:#abb2bf;\">{s1,}:\n<\/span><span style=\"color:#abb2bf;\">            <\/span><span style=\"color:#cd74e8;\">if <\/span><span style=\"color:#abb2bf;\">s1 <\/span><span style=\"color:#cd74e8;\">in <\/span><span style=\"color:#abb2bf;\">s2:\n<\/span><span style=\"color:#abb2bf;\">                <\/span><span style=\"color:#cd74e8;\">break\n<\/span><span style=\"color:#abb2bf;\">        <\/span><span style=\"color:#cd74e8;\">else<\/span><span style=\"color:#abb2bf;\">:\n<\/span><span style=\"color:#abb2bf;\">            largest.<\/span><span style=\"color:#eb6772;\">add<\/span><span style=\"color:#abb2bf;\">(s1)\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"color:#cd74e8;\">if <\/span><span style=\"color:#abb2bf;\">largest <\/span><span style=\"color:#adb7c9;\">== <\/span><span style=\"color:#abb2bf;\">strings:\n<\/span><span style=\"color:#abb2bf;\">        <\/span><span style=\"color:#cd74e8;\">return <\/span><span style=\"color:#abb2bf;\">strings\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"color:#cd74e8;\">return <\/span><span style=\"color:#abb2bf;\">largest <\/span><span style=\"color:#adb7c9;\">| <\/span><span style=\"color:#eb6772;\">filter_substrings<\/span><span style=\"color:#abb2bf;\">(largest)\n<\/span><\/code><\/pre>\n"},{"title":"Conway's Game of Life with Numpy","published":"2021-08-20T00:00:00+00:00","updated":"2021-08-20T00:00:00+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/ddejohn.dev\/life\/"}},"id":"https:\/\/ddejohn.dev\/life\/","content":"<p>A NumPy-oriented implementation of Conway's Game of Life with options for fixed or periodic boundary conditions. See the source code <a href=\"https:\/\/github.com\/ddejohn\/life\">here<\/a>.<\/p>\n<p align=\"center\"><img src=\"banner.gif\"><\/p>\n<!-- \n  \n  \n    \n    \n  \n  <img src=\"https:\/\/ddejohn.devbanner.gif\" class=\"center\" decoding=\"async\" loading=\"lazy\"\/>\n\n -->\n<p><em>From left to right: <a href=\"https:\/\/www.conwaylife.com\/wiki\/Kok%27s_galaxy\">Kok's galaxy<\/a>, a period-8 oscillator; <a href=\"https:\/\/www.conwaylife.com\/wiki\/68P9\">68P9<\/a>, a period-9 oscillator; and <a href=\"https:\/\/www.conwaylife.com\/wiki\/2.2.6\">2.2.6<\/a>, a period-2 oscillator. Kok's galaxy and 68P9 are then reflected horizontally on the right.<\/em><\/p>\n<ul>\n<li><a href=\"https:\/\/ddejohn.dev\/life\/#the-algorithm\">The algorithm<\/a>\n<ul>\n<li><a href=\"https:\/\/ddejohn.dev\/life\/#each-cell-is-a-finite-automaton\">Each cell is a finite automaton!<\/a><\/li>\n<\/ul>\n<\/li>\n<li><a href=\"https:\/\/ddejohn.dev\/life\/#implementation\">Implementation<\/a>\n<ul>\n<li><a href=\"https:\/\/ddejohn.dev\/life\/#encoding-the-rules\">Encoding the rules<\/a><\/li>\n<li><a href=\"https:\/\/ddejohn.dev\/life\/#neighborhoods-and-boundary-conditions\">Neighborhoods and boundary conditions<\/a><\/li>\n<li><a href=\"https:\/\/ddejohn.dev\/life\/#checking-neighbors\">Checking neighbors<\/a><\/li>\n<li><a href=\"https:\/\/ddejohn.dev\/life\/#stride-tricks\">Stride tricks<\/a><\/li>\n<li><a href=\"https:\/\/ddejohn.dev\/life\/#sliding-windows\">Sliding windows<\/a><\/li>\n<li><a href=\"https:\/\/ddejohn.dev\/life\/#counting-neighbors\">Counting neighbors<\/a><\/li>\n<li><a href=\"https:\/\/ddejohn.dev\/life\/#example\">Example<\/a><\/li>\n<li><a href=\"https:\/\/ddejohn.dev\/life\/#tile-and-slice\">Tile and slice<\/a><\/li>\n<\/ul>\n<\/li>\n<li><a href=\"https:\/\/ddejohn.dev\/life\/#fixing-the-bottleneck\">Fixing the bottleneck<\/a><\/li>\n<li><a href=\"https:\/\/ddejohn.dev\/life\/#seed-generation\">Seed generation<\/a><\/li>\n<\/ul>\n<h1 id=\"conway-s-game-of-life\">Conway's Game of Life<\/h1>\n<p>I had always been very intrigued by <a href=\"https:\/\/en.m.wikipedia.org\/wiki\/Conway%27s_Game_of_Life\">Conway's Game of Life<\/a> but had never actually implemented it myself. When I looked up the ruleset, I realized I had no excuse.<\/p>\n<p>For those unfamiliar, Conway's Game of Life is what is known as a <a href=\"https:\/\/en.m.wikipedia.org\/wiki\/Cellular_automaton\">cellular automaton<\/a>. The Game of Life is a 2-dimensional array of cells which observe a binary state: either on or off, alive or dead. Going forward I'll be referring to this collection of cells as <strong>cells<\/strong> and the overall state of the cells at any given time as <strong>state<\/strong>.<\/p>\n<p>The state at time <code>t<\/code> is entirely determined by the state at time <code>t - 1<\/code>, and is governed by this simple rule set:<\/p>\n<ol>\n<li><strong>Underpopulation:<\/strong> a live cell with zero or one live neighbors dies<\/li>\n<li><strong>Survival:<\/strong> a live cell with two or three neighbors survives<\/li>\n<li><strong>Overpopulation:<\/strong> a live cell with four or more live neighbors dies<\/li>\n<li><strong>Reproduction:<\/strong> a dead cell with exactly three neighbors is reborn<\/li>\n<\/ol>\n<p>Those are the original rules, but they can actually be consolidated to only three rules:<\/p>\n<ol>\n<li>Any live cell with two or three live neighbors survives<\/li>\n<li>Any dead cell with exactly three live neighbors becomes a live cell<\/li>\n<li>All other live cells die in the next generation (all dead cells stay dead)<\/li>\n<\/ol>\n<p><a href=\"https:\/\/en.m.wikipedia.org\/wiki\/Conway%27s_Game_of_Life#Rules\">(source)<\/a><\/p>\n<h2 id=\"the-algorithm\">The algorithm<\/h2>\n<p>The basic algorithm for implementing Life is as follows:<\/p>\n<ol>\n<li>For a given cell, count how many living neighbors it has<\/li>\n<li>Determine whether that cell should die, survive, or be reborn<\/li>\n<li>Repeat for all cells<\/li>\n<li>Update all cells to their new value<\/li>\n<\/ol>\n<p>Each cell's <strong>neighborhood<\/strong> consists of the eight cells adjacent to it (up, down, left, right, and the diagonals). Here's a cell outlined in yellow and its eight neighbors and their statuses:<\/p>\n<p align=\"center\"><img src=\"neighbors_alive_dead.png\"><\/p>\n<h3 id=\"each-cell-is-a-finite-automaton\">Each cell is a finite automaton!<\/h3>\n<p>The word <strong>state<\/strong> in this section will specifically refer to the status (alive or dead) of an individual cell. Familiarity with <a href=\"https:\/\/en.m.wikipedia.org\/wiki\/Finite-state_machine\">discrete finite state machines<\/a> would be helpful here, but is not necessary.<\/p>\n<p>Since there are two states in which a cell can be observed and anywhere from 0 to 8 living neighbors, it's entirely possible to enumerate all the possible state transitions, which is the cartesian product of <code>{0, 1} x {0, 1, ..., 8}<\/code> for a total of 18 possible state transitions:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">) <\/span><span style=\"color:#adb7c9;\">-&gt; <\/span><span style=\"color:#db9d63;\">0\n<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">) <\/span><span style=\"color:#adb7c9;\">-&gt; <\/span><span style=\"color:#db9d63;\">0\n<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">) <\/span><span style=\"color:#adb7c9;\">-&gt; <\/span><span style=\"color:#db9d63;\">0\n<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">3<\/span><span style=\"color:#abb2bf;\">) <\/span><span style=\"color:#adb7c9;\">-&gt; <\/span><span style=\"color:#db9d63;\">1\n<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">4<\/span><span style=\"color:#abb2bf;\">) <\/span><span style=\"color:#adb7c9;\">-&gt; <\/span><span style=\"color:#db9d63;\">0\n<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">5<\/span><span style=\"color:#abb2bf;\">) <\/span><span style=\"color:#adb7c9;\">-&gt; <\/span><span style=\"color:#db9d63;\">0\n<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">6<\/span><span style=\"color:#abb2bf;\">) <\/span><span style=\"color:#adb7c9;\">-&gt; <\/span><span style=\"color:#db9d63;\">0\n<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">7<\/span><span style=\"color:#abb2bf;\">) <\/span><span style=\"color:#adb7c9;\">-&gt; <\/span><span style=\"color:#db9d63;\">0\n<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">8<\/span><span style=\"color:#abb2bf;\">) <\/span><span style=\"color:#adb7c9;\">-&gt; <\/span><span style=\"color:#db9d63;\">0\n<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">) <\/span><span style=\"color:#adb7c9;\">-&gt; <\/span><span style=\"color:#db9d63;\">0\n<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">) <\/span><span style=\"color:#adb7c9;\">-&gt; <\/span><span style=\"color:#db9d63;\">0\n<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">) <\/span><span style=\"color:#adb7c9;\">-&gt; <\/span><span style=\"color:#db9d63;\">1\n<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">3<\/span><span style=\"color:#abb2bf;\">) <\/span><span style=\"color:#adb7c9;\">-&gt; <\/span><span style=\"color:#db9d63;\">1\n<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">4<\/span><span style=\"color:#abb2bf;\">) <\/span><span style=\"color:#adb7c9;\">-&gt; <\/span><span style=\"color:#db9d63;\">0\n<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">5<\/span><span style=\"color:#abb2bf;\">) <\/span><span style=\"color:#adb7c9;\">-&gt; <\/span><span style=\"color:#db9d63;\">0\n<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">6<\/span><span style=\"color:#abb2bf;\">) <\/span><span style=\"color:#adb7c9;\">-&gt; <\/span><span style=\"color:#db9d63;\">0\n<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">7<\/span><span style=\"color:#abb2bf;\">) <\/span><span style=\"color:#adb7c9;\">-&gt; <\/span><span style=\"color:#db9d63;\">0\n<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">8<\/span><span style=\"color:#abb2bf;\">) <\/span><span style=\"color:#adb7c9;\">-&gt; <\/span><span style=\"color:#db9d63;\">0\n<\/span><\/code><\/pre>\n<p>The way to read these state transitions is from left to right: on the left hand side is a 2-tuple containing the current state of any given cell and the number of <em>living<\/em> neighbors it has, and the right hand side is the next state of that cell. So for instance, the state transition <code>(0, 5) -&gt; 0<\/code> applies to any cell which is dead (the <code>0<\/code> in the 2-tuple) and which has five living neighbors (the <code>5<\/code> in the 2-tuple). As you can see, the next state of that cell is 0, meaning the cell stays dead.<\/p>\n<p>Notice that the vast majority of the state transitions are equivalent based on those three rules from earlier. There's no functional difference between <code>(0, 5) -&gt; 0<\/code> and <code>(0, 7) -&gt; 0<\/code>, for example. Taking this into account, there are actually only three state transitions which we really care about:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">) <\/span><span style=\"color:#adb7c9;\">-&gt; <\/span><span style=\"color:#db9d63;\">1\n<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">3<\/span><span style=\"color:#abb2bf;\">) <\/span><span style=\"color:#adb7c9;\">-&gt; <\/span><span style=\"color:#db9d63;\">1\n<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">3<\/span><span style=\"color:#abb2bf;\">) <\/span><span style=\"color:#adb7c9;\">-&gt; <\/span><span style=\"color:#db9d63;\">1\n<\/span><\/code><\/pre>\n<p>The first two transitions reflect the first rule:<\/p>\n<blockquote>\n<p>Any live cell with two or three live neighbors survives<\/p>\n<\/blockquote>\n<p>And the third transition corresponds to the second rule:<\/p>\n<blockquote>\n<p>Any dead cell with exactly three live neighbors becomes a live cell<\/p>\n<\/blockquote>\n<p>Every other possible state (again, a 2-tuple <code>(alive or dead, number of neighbors)<\/code>) results in either a live cell dying or a dead cell staying dead.<\/p>\n<h2 id=\"implementation\">Implementation<\/h2>\n<p>Conventionally, one might take a looping approach where each cell is checked and updated individually, cell-by-cell. This approach has several pitfalls: manual indexing (a nightmare), and the necessity for manually updating a new array (you can't update any of the cells until you've checked <em>all of them<\/em> and have determined their next state, since updating one cell could potentially change the number of neighbors of the next cell).<\/p>\n<p>I decided to take advantage of NumPy to simplify the calculations with the tradeoff being that the initial set up was a little bit more complicated to figure out. It may help to have <code>life.py<\/code> open somewhere to which you can refer during the following sections, however the relevant code will also be shown.<\/p>\n<h3 id=\"encoding-the-rules\">Encoding the rules<\/h3>\n<p>The easiest place to start is in encoding the rules of Life. The three state transitions we care about become<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#eb6772;\">RULES <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#abb2bf;\">{(<\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">): <\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, (<\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">3<\/span><span style=\"color:#abb2bf;\">): <\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, (<\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">3<\/span><span style=\"color:#abb2bf;\">): <\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">}\n<\/span><\/code><\/pre>\n<p>Choosing this particular data structure meant I needed to write a function which could accept a 2-tuple of the elements from two arrays: the <code>state<\/code> array and the <code>neighbors<\/code> array. For this, I used <code>np.vectorize()<\/code>:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"font-style:italic;color:#5f697a;\"># Relevant type aliases\n<\/span><span style=\"color:#abb2bf;\">StateArray <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#abb2bf;\">Tuple[np.ndarray, np.ndarray]\n<\/span><span style=\"color:#abb2bf;\">StateUpdater <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#abb2bf;\">Callable[[StateArray], np.ndarray]\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#eb6772;\">NEXT_STATE<\/span><span style=\"color:#abb2bf;\">: StateUpdater <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#abb2bf;\">np.<\/span><span style=\"color:#eb6772;\">vectorize<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#cd74e8;\">lambda <\/span><span style=\"color:#eb6772;\">x<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#eb6772;\">y<\/span><span style=\"color:#abb2bf;\">: RULES.<\/span><span style=\"color:#eb6772;\">get<\/span><span style=\"color:#abb2bf;\">((x, y), <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">))\n<\/span><\/code><\/pre>\n<p>Let's focus on the type hints in order to break down how the <code>NEXT_STATE()<\/code> function works: the <code>StateArray<\/code> type is an alias for <code>Tuple[np.ndarray, np.ndarray]<\/code>, or a 2-tuple of NumPy arrays, and the <code>StateUpdater<\/code> type is a <code>Callable<\/code> (Python's type hint for functions) which takes as its input a <code>StateArray<\/code> and returns a NumPy array. In other words, the <code>np.vectorize()<\/code> function returns a new function of the type <code>StateUpdater<\/code> and is assigned to <code>NEXT_STATE()<\/code>.<\/p>\n<p>The <code>NEXT_STATE()<\/code> function now takes the <code>state<\/code> and <code>neighbors<\/code> arrays as arguments and constructs a new array by getting values based on the <code>RULES<\/code> dictionary lookup. This means that <code>NEXT_STATE()<\/code> is a closed binary operation on 2D NumPy arrays of the same shape and all we need to do in order to get the next <strong>generation<\/strong> of Life is call <code>NEXT_STATE(state, neighbors)<\/code>.<\/p>\n<p>Unfortunately, <code>np.vectorize()<\/code> is a only convenience method and doesn't truly take advantage of <a href=\"https:\/\/en.wikipedia.org\/wiki\/SIMD\">SIMD<\/a> as far as I can tell, so this particular technique is actually going to end up being a pretty significant bottleneck! We'll come back to it later but for now, this hefty layer of abstraction allows us to focus on the next piece of the puzzle: calculating each cell's number of living neighbors.<\/p>\n<h3 id=\"neighborhoods-and-boundary-conditions\">Neighborhoods and boundary conditions<\/h3>\n<p>The rules of the Game of Life entirely revolve around how many living neighbors a given cell has. Typically, a <strong>neighborhood<\/strong> is considered to be the 8 cells surrounding any given cell. For cells not on the edge of the array finding the number of neighbors is trivial: simply look at each adjacent cell in all eight directions and count how many of those cells are alive. In the following visualizations, each cell will be annotated with either a 1 or a 0, indicating whether that cell is alive or dead, respectively. Here is an example cell outlined in yellow, and its neighborhood outlined in pink:<\/p>\n<p align=\"center\"><img src=\"basic_heatmap.png\"><\/p>\n<p>The tricky part comes in how to deal with cells on the edge of the array. These are what are known as <strong>boundary conditions<\/strong> and there are two variants: the first is the <strong>fixed<\/strong> boundary condition wherein all the cells adjacent to those on the edge of the array are considered dead (think of it as a wall of zeros surrounding the entire array, which is outlined in turquoise):<\/p>\n<p align=\"center\"><img src=\"fixed_heatmap.png\"><\/p>\n<p>The other is the <strong>periodic<\/strong> boundary condition which means that each edge of the array essentially \"wraps\" around to the other side so that, for instance, cells on left edge of the array are considered to be adjacent to the cells on the right edge:<\/p>\n<p align=\"center\"><img src=\"periodic_heatmap.png\"><\/p>\n<h3 id=\"checking-neighbors\">Checking neighbors<\/h3>\n<p>Now, you could write a nested <code>for<\/code> loop over the <code>r, c<\/code> (row, column) coordinates of each neighbor of a given cell:<\/p>\n<p align=\"center\"><img src=\"neighbors_indexing.png\"><\/p>\n<p>But that in and of itself is a nightmare, let alone having to deal with the boundary conditions. Here's what one corner's neighbors look like with periodic boundary conditions:<\/p>\n<p align=\"center\"><img src=\"periodic_indexing_nightmare.png\"><\/p>\n<p>No thanks! Instead, we'll \"pad\" the <code>state<\/code> array with a 1-cell-wide border and use a <code>(3, 3)<\/code> sliding window!<\/p>\n<h3 id=\"stride-tricks\">Stride tricks<\/h3>\n<p>NumPy's arrays are truly remarkable. Because they are homogeneous, each element in any given array requires the exact same amount memory as every other element in that array. For example, a six-element vector of <code>int8<\/code> (integers represented using only 8 bits, of which there are <code>2^8 = 256<\/code>) would take up exactly 48 bits (or six bytes) in memory. In the following examples I'll use <code>dtype=\"uint8\"<\/code> which is an <strong>unsigned<\/strong> integer, so the possible values are 0-255.<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#cd74e8;\">import <\/span><span style=\"color:#abb2bf;\">numpy <\/span><span style=\"color:#cd74e8;\">as <\/span><span style=\"color:#abb2bf;\">np\n<\/span><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#abb2bf;\">x <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#abb2bf;\">np.random.<\/span><span style=\"color:#eb6772;\">randint<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">255<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#eb6772;\">size<\/span><span style=\"color:#adb7c9;\">=<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">6<\/span><span style=\"color:#abb2bf;\">,), <\/span><span style=\"color:#eb6772;\">dtype<\/span><span style=\"color:#adb7c9;\">=<\/span><span style=\"color:#9acc76;\">&quot;uint8&quot;<\/span><span style=\"color:#abb2bf;\">)\n<\/span><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#abb2bf;\">x\n<\/span><span style=\"color:#eb6772;\">array<\/span><span style=\"color:#abb2bf;\">([<\/span><span style=\"color:#db9d63;\">133<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">195<\/span><span style=\"color:#abb2bf;\">,  <\/span><span style=\"color:#db9d63;\">41<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">201<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">208<\/span><span style=\"color:#abb2bf;\">,  <\/span><span style=\"color:#db9d63;\">60<\/span><span style=\"color:#abb2bf;\">], <\/span><span style=\"color:#eb6772;\">dtype<\/span><span style=\"color:#adb7c9;\">=<\/span><span style=\"color:#abb2bf;\">uint8)\n<\/span><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#abb2bf;\">x.size <\/span><span style=\"color:#adb7c9;\">* <\/span><span style=\"color:#abb2bf;\">x.itemsize  <\/span><span style=\"font-style:italic;color:#5f697a;\"># itemsize is in units of bytes\n<\/span><span style=\"color:#db9d63;\">6\n<\/span><\/code><\/pre>\n<p>This fixed-memory-per-element constraint allows NumPy to carve out a contiguous chunk of memory in which to store a given array. This in turn makes accessing the elements of an array much faster than, say, Python's <code>list<\/code> type because NumPy doesn't have do any wonky pointer manipulation to jump around your computer's memory in order to grab elements. We can get a feel for this by checking <code>np.ndarray.strides<\/code>:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#abb2bf;\">x.strides\n<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">,)\n<\/span><\/code><\/pre>\n<p>For a 1D array, this is telling us that in order to \"travel\" to the next element in the array, we simply need to increment the memory pointer by one byte. This pattern follows for higher-dimensional arrays:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#abb2bf;\">x <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#abb2bf;\">np.random.<\/span><span style=\"color:#eb6772;\">randint<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">255<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#eb6772;\">size<\/span><span style=\"color:#adb7c9;\">=<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">3<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">3<\/span><span style=\"color:#abb2bf;\">), <\/span><span style=\"color:#eb6772;\">dtype<\/span><span style=\"color:#adb7c9;\">=<\/span><span style=\"color:#9acc76;\">&quot;uint8&quot;<\/span><span style=\"color:#abb2bf;\">)\n<\/span><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#abb2bf;\">x.strides\n<\/span><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">3<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">)\n<\/span><\/code><\/pre>\n<p>This is telling us that in order to travel to the next <em>row<\/em> we need to increment our pointer by three bytes. Columnar jumps still only require one byte because data in NumPy is stored in <a href=\"https:\/\/en.wikipedia.org\/wiki\/Row-_and_column-major_order\">row-major order<\/a> by default (and one byte specifically in this case because I'm using <code>dtype=\"uint8\"<\/code>). Take this array for example:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#abb2bf;\">x\n<\/span><span style=\"color:#eb6772;\">array<\/span><span style=\"color:#abb2bf;\">([[<\/span><span style=\"color:#db9d63;\">218<\/span><span style=\"color:#abb2bf;\">,  <\/span><span style=\"color:#db9d63;\">19<\/span><span style=\"color:#abb2bf;\">,  <\/span><span style=\"color:#db9d63;\">18<\/span><span style=\"color:#abb2bf;\">],\n<\/span><span style=\"color:#abb2bf;\">       [ <\/span><span style=\"color:#db9d63;\">32<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">227<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">144<\/span><span style=\"color:#abb2bf;\">],\n<\/span><span style=\"color:#abb2bf;\">       [<\/span><span style=\"color:#db9d63;\">152<\/span><span style=\"color:#abb2bf;\">,  <\/span><span style=\"color:#db9d63;\">44<\/span><span style=\"color:#abb2bf;\">,  <\/span><span style=\"color:#db9d63;\">64<\/span><span style=\"color:#abb2bf;\">]], <\/span><span style=\"color:#eb6772;\">dtype<\/span><span style=\"color:#adb7c9;\">=<\/span><span style=\"color:#abb2bf;\">uint8)\n<\/span><\/code><\/pre>\n<p>Moving down one row from <code>19<\/code> takes three bytes; one byte to reach <code>18<\/code>, one byte to reach <code>32<\/code>, and finally one more byte to reach <code>227<\/code> which is directly below <code>19<\/code>. What does any of this have to do with Conway's Game of Life? Well, the way I got around dealing with that indexing nightmare was by padding the original <code>state<\/code> array with a 1-cell-wide border using <code>np.pad()<\/code> and then using a <code>(3, 3)<\/code> sliding window view to check each cell's neighbors!<\/p>\n<h3 id=\"sliding-windows\">Sliding windows<\/h3>\n<p>The <code>np.lib.stride_tricks.sliding_window_view()<\/code> function provides a sequence of views of the input array via striding. The implementation details aren't super important, the real key here is that this function returns an array of a very special shape: <code>(n, n, 3, 3)<\/code> where <code>n<\/code> is the original size of the array (I've restricted my implementation to square arrays for a couple reasons I'll talk about in the next article); in general, for an original array of size <code>(n, m)<\/code>, after padding, the sliding window views will be a <code>(n, m, p, q)<\/code> array, where <code>(p, q)<\/code> is the shape of the sliding window you want, assuming that the window shape and the original array shape <a href=\"https:\/\/numpy.org\/devreference\/generated\/numpy.lib.stride_tricks.sliding_window_view.html#numpy-lib-stride-tricks-sliding-window-view\">work well<\/a> together).<\/p>\n<p>For example, if you have a <code>state<\/code> array which is <code>(3, 3)<\/code> then after padding with a fixed or periodic border of one cell, the sliding window function returns a <code>(3, 3)<\/code> array of <code>(3, 3)<\/code> arrays! See where I'm going with this?<\/p>\n<p align=\"center\"><img src=\"fixed_sliding_window_animation.gif\"><\/p>\n<p>In the example gif above, the original <code>state<\/code> array of shape <code>(3, 3)<\/code> is outlined in pink on the left. Each cell in the original array is outlined in yellow. The sliding blue window is an animated depiction of iterating through each <code>(3, 3)<\/code> neighborhood of the corresponding cell outlined in yellow. The subarrays on the right are the individual <code>(3, 3)<\/code> neighborhood views produced by the <code>np.lib.stride_tricks.sliding_window_view()<\/code> function, conveniently arranged into a 3x3 shape!<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#abb2bf;\">windows <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#abb2bf;\">np.lib.stride_tricks.<\/span><span style=\"color:#eb6772;\">sliding_window_view<\/span><span style=\"color:#abb2bf;\">(np.<\/span><span style=\"color:#eb6772;\">pad<\/span><span style=\"color:#abb2bf;\">(x, <\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">), (<\/span><span style=\"color:#db9d63;\">3<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">3<\/span><span style=\"color:#abb2bf;\">))\n<\/span><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#abb2bf;\">windows.shape\n<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">3<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">3<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">3<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">3<\/span><span style=\"color:#abb2bf;\">)\n<\/span><\/code><\/pre>\n<p>This is made possible with some fancy striding under the hood by NumPy, leaving us to focus on more pressing matters.<\/p>\n<h3 id=\"counting-neighbors\">Counting neighbors<\/h3>\n<p>The whole point of this was to figure out how many living neighbors a given cell has. Once we've gotten our <code>windows<\/code> array (remember, its shape is <code>(n, n, 3, 3)<\/code>), we can call <code>np.sum()<\/code> to take the sum of each neighborhood subarray:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#abb2bf;\">windows.<\/span><span style=\"color:#eb6772;\">sum<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#eb6772;\">axis<\/span><span style=\"color:#adb7c9;\">=<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">3<\/span><span style=\"color:#abb2bf;\">)) <\/span><span style=\"color:#adb7c9;\">- <\/span><span style=\"color:#abb2bf;\">state\n<\/span><\/code><\/pre>\n<p>The <code>axis<\/code> argument here specifies that we're summing over the third and fourth axes (remember that Python is zero-indexed). One way to think of it is that that each <code>(3, 3)<\/code> neighborhood is \"collapsed\" down to a scalar which is the sum of the nine elements in that neighborhood (we're dealing with binary data, so summing the values is equivalent to counting the number of ones). By collapsing those third and fourth axes down to a scalar, we end up with an <code>(n, n)<\/code> array of neighbor counts:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#abb2bf;\">windows.<\/span><span style=\"color:#eb6772;\">sum<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#eb6772;\">axis<\/span><span style=\"color:#adb7c9;\">=<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">3<\/span><span style=\"color:#abb2bf;\">)).shape\n<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">3<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">3<\/span><span style=\"color:#abb2bf;\">)\n<\/span><\/code><\/pre>\n<p>This result will always be a positive integer between 0 and 9, since each of the nine cells in a <code>(3, 3)<\/code> binary array can be 0 or 1. Next, we subtract <code>state<\/code> so that we're not counting the value of any given cell itself when counting its neighbors. Again, we can perform this subtraction because the sum over the third and fourth axes collapses the <code>(n, n, 3, 3)<\/code> array back down to <code>(n, n)<\/code> and the <code>state<\/code> array here is <code>(n, n)<\/code>.<\/p>\n<p><em>Et voila!<\/em> We now have two arrays: the <code>state<\/code>, a binary array representing dead and alive cells, and a <code>neighbors<\/code> array of values between 0 and 8. Remember the <code>NEXT_STATE()<\/code> function from before, the vectorized function made out of a dictionary lookup? We can now pass these two <code>(n, n)<\/code> arrays to that function and NumPy will perform the <code>dictionary<\/code> lookups for us and return a new binary array which represents our new <code>state<\/code>.<\/p>\n<h3 id=\"example\">Example<\/h3>\n<p>Here's a REPL example using a <code>(2, 2)<\/code> array representing the <code>state<\/code> of a given instance of <code>Life<\/code> with the <code>\"fixed\"<\/code> boundary condition. Here are the padding, sliding window, sum, and subtraction steps encapsulated by the <code>LifeFactory.neighbors()<\/code> function for that <code>state<\/code> array:<\/p>\n<p>First, we'll instantiate a random <code>(2, 2)<\/code> binary array:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#cd74e8;\">import <\/span><span style=\"color:#abb2bf;\">numpy <\/span><span style=\"color:#cd74e8;\">as <\/span><span style=\"color:#abb2bf;\">np\n<\/span><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#abb2bf;\">state <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#abb2bf;\">np.random.<\/span><span style=\"color:#eb6772;\">randint<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">, (<\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">))\n<\/span><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#abb2bf;\">state\n<\/span><span style=\"color:#eb6772;\">array<\/span><span style=\"color:#abb2bf;\">([[<\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">],\n<\/span><span style=\"color:#abb2bf;\">       [<\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">]])\n<\/span><\/code><\/pre>\n<p>Next, we'll pad this <code>state<\/code> array:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#abb2bf;\">padded <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#abb2bf;\">np.<\/span><span style=\"color:#eb6772;\">pad<\/span><span style=\"color:#abb2bf;\">(state, <\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">)\n<\/span><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#abb2bf;\">padded\n<\/span><span style=\"color:#eb6772;\">array<\/span><span style=\"color:#abb2bf;\">([[<\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">],\n<\/span><span style=\"color:#abb2bf;\">       [<\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">],\n<\/span><span style=\"color:#abb2bf;\">       [<\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">],\n<\/span><span style=\"color:#abb2bf;\">       [<\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">]])\n<\/span><\/code><\/pre>\n<p>Now obtain the sliding window view:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#abb2bf;\">windows <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#abb2bf;\">np.lib.stride_tricks.<\/span><span style=\"color:#eb6772;\">sliding_window_view<\/span><span style=\"color:#abb2bf;\">(padded, (<\/span><span style=\"color:#db9d63;\">3<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">3<\/span><span style=\"color:#abb2bf;\">))\n<\/span><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#abb2bf;\">windows\n<\/span><span style=\"color:#eb6772;\">array<\/span><span style=\"color:#abb2bf;\">([[[[<\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">],\n<\/span><span style=\"color:#abb2bf;\">         [<\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">],\n<\/span><span style=\"color:#abb2bf;\">         [<\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">]],\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#abb2bf;\">        [[<\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">],\n<\/span><span style=\"color:#abb2bf;\">         [<\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">],\n<\/span><span style=\"color:#abb2bf;\">         [<\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">]]],\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#abb2bf;\">       [[[<\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">],\n<\/span><span style=\"color:#abb2bf;\">         [<\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">],\n<\/span><span style=\"color:#abb2bf;\">         [<\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">]],\n<\/span><span style=\"color:#abb2bf;\">\n<\/span><span style=\"color:#abb2bf;\">        [[<\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">],\n<\/span><span style=\"color:#abb2bf;\">         [<\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">],\n<\/span><span style=\"color:#abb2bf;\">         [<\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">]]]])\n<\/span><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#abb2bf;\">windows.shape\n<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">3<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">3<\/span><span style=\"color:#abb2bf;\">)\n<\/span><\/code><\/pre>\n<p>Next, we sum over the third and fourth axes:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#abb2bf;\">window_sum <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#abb2bf;\">windows.<\/span><span style=\"color:#eb6772;\">sum<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#eb6772;\">axis<\/span><span style=\"color:#adb7c9;\">=<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">3<\/span><span style=\"color:#abb2bf;\">))\n<\/span><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#abb2bf;\">window_sum\n<\/span><span style=\"color:#eb6772;\">array<\/span><span style=\"color:#abb2bf;\">([[<\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">],\n<\/span><span style=\"color:#abb2bf;\">       [<\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">]])\n<\/span><\/code><\/pre>\n<p>Now, take a second to make sure you understand this result. It may be helpful to look back at the original <code>state<\/code> array directly next to its <code>padded<\/code> version:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#abb2bf;\">state\n<\/span><span style=\"color:#eb6772;\">array<\/span><span style=\"color:#abb2bf;\">([[<\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">],\n<\/span><span style=\"color:#abb2bf;\">       [<\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">]])\n<\/span><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#abb2bf;\">padded\n<\/span><span style=\"color:#eb6772;\">array<\/span><span style=\"color:#abb2bf;\">([[<\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">],\n<\/span><span style=\"color:#abb2bf;\">       [<\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">],\n<\/span><span style=\"color:#abb2bf;\">       [<\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">],\n<\/span><span style=\"color:#abb2bf;\">       [<\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">]])\n<\/span><\/code><\/pre>\n<p>In each <code>(3, 3)<\/code> neighborhood of the four cells in the <code>state<\/code> array, there are always at least two ones, hence our result<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#abb2bf;\">window_sum\n<\/span><span style=\"color:#eb6772;\">array<\/span><span style=\"color:#abb2bf;\">([[<\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">],\n<\/span><span style=\"color:#abb2bf;\">       [<\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">]])\n<\/span><\/code><\/pre>\n<p>Finally, by subtracting this collapsed summation by the original state, we arrive at the proper neighbor count:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#abb2bf;\">neighbors <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#abb2bf;\">window_sum <\/span><span style=\"color:#adb7c9;\">- <\/span><span style=\"color:#abb2bf;\">state\n<\/span><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#abb2bf;\">neighbors\n<\/span><span style=\"color:#eb6772;\">array<\/span><span style=\"color:#abb2bf;\">([[<\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">],\n<\/span><span style=\"color:#abb2bf;\">       [<\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">]])\n<\/span><\/code><\/pre>\n<p>Take another second to verify this result:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#abb2bf;\">state\n<\/span><span style=\"color:#eb6772;\">array<\/span><span style=\"color:#abb2bf;\">([[<\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">],\n<\/span><span style=\"color:#abb2bf;\">       [<\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">]])\n<\/span><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#abb2bf;\">neighbors\n<\/span><span style=\"color:#eb6772;\">array<\/span><span style=\"color:#abb2bf;\">([[<\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">],\n<\/span><span style=\"color:#abb2bf;\">       [<\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">]])\n<\/span><\/code><\/pre>\n<p>The top-left cell only has one living neighbor (to its southeast), the top-right cell has <em>two<\/em> living neighbors (one directly west, one directly south), and so on.<\/p>\n<p>Let's revisit the rules of Conway's Game of Life and determine the next generation of this <code>state<\/code> array. For this small example, we'll manually loop through the <code>state<\/code> and <code>neighbors<\/code> in parallel in order to determine the next state:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#eb6772;\">RULES <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#abb2bf;\">{(<\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">): <\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, (<\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">3<\/span><span style=\"color:#abb2bf;\">): <\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, (<\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">3<\/span><span style=\"color:#abb2bf;\">): <\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">}\n<\/span><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#abb2bf;\">new_state <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#abb2bf;\">[]\n<\/span><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#cd74e8;\">for <\/span><span style=\"color:#abb2bf;\">i <\/span><span style=\"color:#cd74e8;\">in <\/span><span style=\"color:#5ebfcc;\">range<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">):\n<\/span><span style=\"color:#db9d63;\">...     <\/span><span style=\"color:#abb2bf;\">new_row <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#abb2bf;\">[]\n<\/span><span style=\"color:#db9d63;\">...     <\/span><span style=\"color:#cd74e8;\">for <\/span><span style=\"color:#abb2bf;\">j <\/span><span style=\"color:#cd74e8;\">in <\/span><span style=\"color:#5ebfcc;\">range<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">):\n<\/span><span style=\"color:#db9d63;\">...             <\/span><span style=\"color:#abb2bf;\">rules_input_tuple <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#abb2bf;\">state[i, j], neighbors[i, j]\n<\/span><span style=\"color:#db9d63;\">...             <\/span><span style=\"color:#abb2bf;\">rules_lookup <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#abb2bf;\">RULES.<\/span><span style=\"color:#eb6772;\">get<\/span><span style=\"color:#abb2bf;\">(rules_input_tuple, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">)\n<\/span><span style=\"color:#db9d63;\">...             <\/span><span style=\"color:#abb2bf;\">new_row.<\/span><span style=\"color:#eb6772;\">append<\/span><span style=\"color:#abb2bf;\">(rules_lookup)\n<\/span><span style=\"color:#db9d63;\">...             <\/span><span style=\"color:#5ebfcc;\">print<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#9acc76;\">&quot;tuple key:&quot;<\/span><span style=\"color:#abb2bf;\">, rules_input_tuple)\n<\/span><span style=\"color:#db9d63;\">...     <\/span><span style=\"color:#abb2bf;\">new_state.<\/span><span style=\"color:#eb6772;\">append<\/span><span style=\"color:#abb2bf;\">(new_row)\n<\/span><span style=\"color:#db9d63;\">...\n<\/span><span style=\"color:#abb2bf;\">tuple key: (<\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">)\n<\/span><span style=\"color:#abb2bf;\">tuple key: (<\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">)\n<\/span><span style=\"color:#abb2bf;\">tuple key: (<\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">)\n<\/span><span style=\"color:#abb2bf;\">tuple key: (<\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">)\n<\/span><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#abb2bf;\">np.<\/span><span style=\"color:#eb6772;\">array<\/span><span style=\"color:#abb2bf;\">(new_state)\n<\/span><span style=\"color:#eb6772;\">array<\/span><span style=\"color:#abb2bf;\">([[<\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">],\n<\/span><span style=\"color:#abb2bf;\">       [<\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">0<\/span><span style=\"color:#abb2bf;\">]])\n<\/span><\/code><\/pre>\n<p>Oops! Both live cells died due to underpopulation, and neither of the previously-dead cells had enough living neighbors in order to be reborn. There you have it though, that's the core logic behind my implementation.<\/p>\n<p>But wait, there's more! We forgot to talk about <strong>periodic boundaries,<\/strong> and the bottleneck introduced by that dictionary lookup.<\/p>\n<h3 id=\"tile-and-slice\">Tile and slice<\/h3>\n<p>In retrospect, the logic behind padding <code>state<\/code> with a periodic boundary border is actually shockingly straight-forward. However, I did not have the benefit of hindsight and it took me an afternoon to come up with a more elegant way to figure out this padding method besides a brute force mashing and <em>frankensteining<\/em> of array creation, stacking, and blocking (which is extremely slow and memory-intensive).<\/p>\n<p>For a while I couldn't really wrap my mind around what to do with the corners of each array, but then it dawned on me: caddy-corner cells <em>must<\/em> be adjacent! The way I figured this out was by fiddling with the <code>np.tile()<\/code> function, which takes as input an array <code>x<\/code>, and a desired shape, and <em>tiles<\/em> <code>x<\/code> in a repeating pattern such that it fills out an array of shape <code>(n*p, m*q)<\/code> where <code>(n, m)<\/code> is the shape of <code>x<\/code> and <code>(p, q)<\/code> is the desired shape passed as an argument to <code>np.tile()<\/code>.<\/p>\n<p>Let's take a look at an example. It'll be easier to see what <code>np.tile()<\/code> is doing with a non-binary array:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#abb2bf;\">x <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#abb2bf;\">np.<\/span><span style=\"color:#eb6772;\">array<\/span><span style=\"color:#abb2bf;\">([[<\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">3<\/span><span style=\"color:#abb2bf;\">], [<\/span><span style=\"color:#db9d63;\">4<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">5<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">6<\/span><span style=\"color:#abb2bf;\">]])\n<\/span><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#abb2bf;\">x\n<\/span><span style=\"color:#eb6772;\">array<\/span><span style=\"color:#abb2bf;\">([[<\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">3<\/span><span style=\"color:#abb2bf;\">],\n<\/span><span style=\"color:#abb2bf;\">       [<\/span><span style=\"color:#db9d63;\">4<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">5<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">6<\/span><span style=\"color:#abb2bf;\">]])\n<\/span><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#abb2bf;\">pattern <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">4<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">)\n<\/span><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#abb2bf;\">tiles <\/span><span style=\"color:#adb7c9;\">= <\/span><span style=\"color:#abb2bf;\">np.<\/span><span style=\"color:#eb6772;\">tile<\/span><span style=\"color:#abb2bf;\">(x, pattern)\n<\/span><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#abb2bf;\">tiles\n<\/span><span style=\"color:#eb6772;\">array<\/span><span style=\"color:#abb2bf;\">([[<\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">3<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">3<\/span><span style=\"color:#abb2bf;\">],\n<\/span><span style=\"color:#abb2bf;\">       [<\/span><span style=\"color:#db9d63;\">4<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">5<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">6<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">4<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">5<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">6<\/span><span style=\"color:#abb2bf;\">],\n<\/span><span style=\"color:#abb2bf;\">       [<\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">3<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">3<\/span><span style=\"color:#abb2bf;\">],\n<\/span><span style=\"color:#abb2bf;\">       [<\/span><span style=\"color:#db9d63;\">4<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">5<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">6<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">4<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">5<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">6<\/span><span style=\"color:#abb2bf;\">],\n<\/span><span style=\"color:#abb2bf;\">       [<\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">3<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">3<\/span><span style=\"color:#abb2bf;\">],\n<\/span><span style=\"color:#abb2bf;\">       [<\/span><span style=\"color:#db9d63;\">4<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">5<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">6<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">4<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">5<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">6<\/span><span style=\"color:#abb2bf;\">],\n<\/span><span style=\"color:#abb2bf;\">       [<\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">3<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">3<\/span><span style=\"color:#abb2bf;\">],\n<\/span><span style=\"color:#abb2bf;\">       [<\/span><span style=\"color:#db9d63;\">4<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">5<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">6<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">4<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">5<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">6<\/span><span style=\"color:#abb2bf;\">]])\n<\/span><span style=\"color:#adb7c9;\">&gt;&gt;&gt; <\/span><span style=\"color:#5ebfcc;\">print<\/span><span style=\"color:#abb2bf;\">(x.shape); <\/span><span style=\"color:#5ebfcc;\">print<\/span><span style=\"color:#abb2bf;\">(pattern); <\/span><span style=\"color:#5ebfcc;\">print<\/span><span style=\"color:#abb2bf;\">(tiles.shape)\n<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">3<\/span><span style=\"color:#abb2bf;\">)\n<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">4<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">2<\/span><span style=\"color:#abb2bf;\">)\n<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">8<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">6<\/span><span style=\"color:#abb2bf;\">)\n<\/span><\/code><\/pre>\n<p>As you can see, we've simply repeated <code>x<\/code> array with shape <code>(2, 3)<\/code> in a <code>(4, 2)<\/code> pattern (two copies of <code>x<\/code> repeating horizontally, and four copies repeating vertically), resulting in an <code>(8, 6)<\/code> array.<\/p>\n<p>Let's return to binary arrays and look at an example of a <code>(5, 5)<\/code> instance of <code>Life<\/code> (with the original <code>(5, 5)<\/code> array outlined in pink):<\/p>\n<p align=\"center\"><img src=\"tiled_heatmap.png\"><\/p>\n<p>Now we take a slice of the array such that we capture both the original <code>state<\/code> and its periodic border:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#abb2bf;\">np.<\/span><span style=\"color:#eb6772;\">tile<\/span><span style=\"color:#abb2bf;\">(state, (<\/span><span style=\"color:#db9d63;\">3<\/span><span style=\"color:#abb2bf;\">, <\/span><span style=\"color:#db9d63;\">3<\/span><span style=\"color:#abb2bf;\">))[(s <\/span><span style=\"color:#adb7c9;\">:= <\/span><span style=\"color:#abb2bf;\">n<\/span><span style=\"color:#adb7c9;\">-<\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">):<\/span><span style=\"color:#adb7c9;\">-<\/span><span style=\"color:#abb2bf;\">s, s:<\/span><span style=\"color:#adb7c9;\">-<\/span><span style=\"color:#abb2bf;\">s]\n<\/span><\/code><\/pre>\n<p>Here <code>s<\/code> is the size of the <code>state<\/code> array minus 1. Here's what the slice looks like (outlined in yellow):<\/p>\n<p align=\"center\"><img src=\"tiled_and_sliced_heatmap.png\"><\/p>\n<p>This tiled and sliced array is now the <code>padded<\/code> array on which we can perform the sliding window view function, count neighbors, and update <code>state<\/code>.<\/p>\n<p>Of course, I soon realized all of this was unnecessary as there's actually a <code>mode<\/code> parameter for the <code>np.pad()<\/code> function which does all this heavy lifting for you... Oh well, it was a nice exercise.<\/p>\n<h2 id=\"fixing-the-bottleneck\">Fixing the bottleneck<\/h2>\n<p>As mentioned, our dictionary lookup is actually a pretty severe bottleneck on this program. It's not <em>that<\/em> big a deal since the code I'm using to actually create the animated gifs takes quite a while to do so anyway for arrays of shape <code>(100, 100)<\/code> or greater. But there was still noticeable lag when running Life up to the max allowed number of generations (1000).<\/p>\n<p>The issue here is that <code>np.vectorize()<\/code> is a little bit of a misnomer; it's really just a convenience method which allows you to apply a function to two arrays without having to write the <code>for<\/code> loop logic yourself. At the end of the day it's still quite slow in the context of the greater NumPy library. As far as I can tell, <code>np.vectorize()<\/code> doesn't actually <strong>vectorize<\/strong> your function for you, which is the process by which an algorithm is written in or converted to such a form as to take advantage of your CPU's SIMD capabilities.<\/p>\n<p>Realizing this, and knowing that arithmetic operations <em>are<\/em> properly vectorized in NumPy, I set out to find a closed formula which could calculate the next state of the cells array without resorting to a <code>dict<\/code> lookup. Here's what I came up with:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#cd74e8;\">def <\/span><span style=\"color:#5cb3fa;\">next_state<\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#eb6772;\">s<\/span><span style=\"color:#abb2bf;\">: np.ndarray, <\/span><span style=\"color:#eb6772;\">n<\/span><span style=\"color:#abb2bf;\">: np.ndarray) -&gt; np.ndarray:\n<\/span><span style=\"color:#abb2bf;\">    <\/span><span style=\"color:#cd74e8;\">return <\/span><span style=\"color:#abb2bf;\">(n <\/span><span style=\"color:#adb7c9;\">&lt; <\/span><span style=\"color:#db9d63;\">4<\/span><span style=\"color:#abb2bf;\">) <\/span><span style=\"color:#adb7c9;\">* <\/span><span style=\"color:#abb2bf;\">(<\/span><span style=\"color:#db9d63;\">1 <\/span><span style=\"color:#adb7c9;\">- <\/span><span style=\"color:#abb2bf;\">s <\/span><span style=\"color:#adb7c9;\">* <\/span><span style=\"color:#abb2bf;\">(n <\/span><span style=\"color:#adb7c9;\">% <\/span><span style=\"color:#db9d63;\">2 <\/span><span style=\"color:#adb7c9;\">- <\/span><span style=\"color:#db9d63;\">1<\/span><span style=\"color:#abb2bf;\">) <\/span><span style=\"color:#adb7c9;\">+ <\/span><span style=\"color:#abb2bf;\">n) <\/span><span style=\"color:#adb7c9;\">\/\/ <\/span><span style=\"color:#db9d63;\">4\n<\/span><\/code><\/pre>\n<p>While it certainly isn't pretty (and I wouldn't be surprised if it can be reduced), it does an excellent job of opening up that bottleneck:<\/p>\n<pre data-lang=\"python\" style=\"background-color:#2b303b;color:#6c7079;\" class=\"language-python \"><code class=\"language-python\" data-lang=\"python\"><span style=\"color:#abb2bf;\">vectorized dict: <\/span><span style=\"color:#db9d63;\">14.12 <\/span><span style=\"color:#abb2bf;\">s\n<\/span><span style=\"color:#abb2bf;\">formula: <\/span><span style=\"color:#db9d63;\">0.91 <\/span><span style=\"color:#abb2bf;\">s\n<\/span><\/code><\/pre>\n<p>That test was run on a <code>(1000, 1000)<\/code> array, which is <em>much<\/em> larger than my program will even allow the user to input, but it's clear that when possible it's always a good idea to try to find a vectorize-able function!<\/p>\n<h2 id=\"seed-generation\">Seed generation<\/h2>\n<p>I'll write another post about how I decided to deal with seed generation. Originally, the seeds were just monolithic tiles of uniformly distributed binary noise and while they're interesting in their own right, they can also become quite mundane after a couple dozen runs of Life, so I set out (again with trusty NumPy by my side) to come up with more options for random seed generation. Here are a few examples:<\/p>\n<p align=\"center\"><img width=\"700\" src=\"120x120_symmetries.gif\"><\/p>\n<p>Thanks for reading!<\/p>\n<p>Just kidding, here's an example of Life running on one of these symmetric seeds:<\/p>\n<p align=\"center\"><img width=\"700\" src=\"symmetric_seed_example.gif\"><\/p>\n<p>Okay, thanks for sticking around!<\/p>\n"}]}