{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "import re\n",
    "from itertools import product, chain, permutations\n",
    "from collections import defaultdict\n",
    "from functools import lru_cache as cache\n",
    "from math import factorial"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# How to Count Things\n",
    "\n",
    "This notebook contains problems designed to show how to count things. So far there are five example problems.\n",
    "\n",
    "# (1) Student Records: Late, Absent, Present\n",
    "\n",
    "Consider this problem:\n",
    "\n",
    "> (1) Students at a school must meet with the guidance counselor if they have two total absences, or three consecutive late days. Each student's attendance record consists of a string of 'A' for absent, 'L' for late, or 'P' for present. For example: \"LAPLPA\" requires a meeting (because there are two absences), and \"LAPLPL\" is OK (there are three late days, but they are not consecutive). Write a function that takes such a string as input and returns `True` if the student's record is OK. \n",
    "\n",
    "> (2) Write a function to calculate the number of attendance records of length N that are OK.\n",
    "\n",
    "For part (1), the simplest approach is to use `re.search`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def ok(record: str) -> bool: return not re.search(r'LLL|A.*A', record)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'pass'"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def test_ok():\n",
    "    assert     ok(\"LAPLLP\")\n",
    "    assert not ok(\"LAPLLL\")   # 3 Ls in a row\n",
    "    assert not ok(\"LAPLLA\")   # 2 As overall\n",
    "    assert     ok(\"APLLPLLP\")\n",
    "    assert not ok(\"APLLPLLL\") # 3 Ls in a row\n",
    "    assert not ok(\"APLLPLLA\") # 2 As overall\n",
    "    return 'pass'\n",
    "    \n",
    "test_ok()  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For part (2), I'll start with a simple (but slow) solution called `total_ok_slow` that enumerates `all_strings` (using `itertools.product`) and counts how many are `ok`. I use the `quantify` recipe ([from `itertools`](https://docs.python.org/3.6/library/itertools.html#itertools-recipes)) to count them:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def total_ok_slow(N: int) -> int:\n",
    "    \"How many strings over 'LAP' of length N are ok?\"\n",
    "    return quantify(all_strings('LAP', N), ok)\n",
    "\n",
    "def all_strings(alphabet, N): \n",
    "    \"All length-N strings over the given alphabet.\"\n",
    "    return map(cat, product(alphabet, repeat=N))\n",
    "\n",
    "def quantify(iterable, pred=bool) -> int:\n",
    "    \"Count how many times the predicate is true of items in iterable.\"\n",
    "    return sum(map(pred, iterable))\n",
    "\n",
    "cat = ''.join"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{0: 1,\n",
       " 1: 3,\n",
       " 2: 8,\n",
       " 3: 19,\n",
       " 4: 43,\n",
       " 5: 94,\n",
       " 6: 200,\n",
       " 7: 418,\n",
       " 8: 861,\n",
       " 9: 1753,\n",
       " 10: 3536}"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "{N: total_ok_slow(N) for N in range(11)}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This looks good, but\n",
    "I will need a more efficient algorithm to handle large values of *N*. Here's how I think about it:\n",
    "\n",
    "* I can't enumerate all the strings; there are too many of them, 3<sup>N</sup>. \n",
    "* Even if I only enumerate the ok strings, there are still too many, O(2<sup>N</sup>).\n",
    "* Instead, I'll want to keep track of a *summary* of all the ok strings of length *N*, and use that to quickly compute a summary of the ok strings of length *N*+1. I recognize this as a *[dynamic programming](https://en.wikipedia.org/wiki/Dynamic_programming)* approach.\n",
    "\n",
    "* What is in the summary? A list of all ok strings is too much. A count of the number of ok strings is not enough. Instead, I will group together the strings that have the same number of `'A'` characters in them, and the same number of consecutive `'L'` characters at the end of the string, and count them.  I don't need to count strings that have two or more `'A'` characters, or 3 consecutive `'L'` characters anywhere in the string. And I don't need to worry about runs of 1 or 2 `'L'` characters embedded in the middle of the string. So the summary is a mapping of the form `{(A, L): count, ...}`. \n",
    "\n",
    "Here is a function to create the summary for `N+1`, given the summary for `N`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def next_summary(prev_summary: dict) -> dict:\n",
    "    \"Given a summary of the form {(A, L): count}, return summary for one char more.\"\n",
    "    summary = defaultdict(int)\n",
    "    for (A, L), c in prev_summary.items():\n",
    "            if A < 1: summary[A+1, 0] += c # transition with 'A'\n",
    "            if L < 2: summary[A, L+1] += c # transition with 'L'\n",
    "            summary[A, 0] += c             # transition with 'P'\n",
    "    return summary"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For `N = 0`, the summary is `{(0, 0): 1}`, because there is one string, the empty string, which has no `'A'` nor `'L'`. From there we can proceed in a \"bottom-up\" fashion to compute the total number of OK strings for any value of `N`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "defaultdict(int, {(0, 0): 1, (0, 1): 1, (1, 0): 1})"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "next_summary({(0, 0): 1})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "defaultdict(int, {(0, 0): 2, (0, 1): 1, (0, 2): 1, (1, 0): 3, (1, 1): 1})"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "next_summary(_)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "I can annotate that result with the two-letter strings that form each count:\n",
    "\n",
    "      {(0, 0): 2, # LP, PP\n",
    "       (0, 1): 1, # PL\n",
    "       (0, 2): 1, # LL\n",
    "       (1, 0): 1, # AP, LA, PA\n",
    "       (1, 1): 1} # AL\n",
    "\n",
    "\n",
    "Here's an implementation of `total_ok`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def total_ok(N) -> int:\n",
    "    \"How many strings of length N are ok?\"\n",
    "    summary = {(0, 0): 1}\n",
    "    for _ in range(N):\n",
    "        summary = next_summary(summary)\n",
    "    return sum(summary.values()) "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can use this to go way beyond what we could do with `total_ok_slow`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 1.21 ms, sys: 111 µs, total: 1.32 ms\n",
      "Wall time: 1.35 ms\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "5261545087067582125179062608958232695543100705754634272071166414871321070487675367"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "%time total_ok(300)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1.3689147905858837e+143"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "3. ** 300"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "There are over 10<sup>80</sup> ok strings of length 300; more than the number of atoms in the universe. But it only took around a millisecond to count them (while ignoring the 3<sup>300</sup> = 10<sup>143</sup> not-ok strings of length 300).\n",
    "\n",
    "Dynamic programming can also be done top-down (where we start at `N` and work down to `0`):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def total_ok(N) -> int:\n",
    "    \"How many strings of length N are ok?\"\n",
    "    return sum(summary_for(N).values())\n",
    "    \n",
    "def summary_for(N) -> dict: \n",
    "    \"The {(A, L): count} summary for strings of length N.\"\n",
    "    return ({(0, 0): 1} if N == 0 else next_summary(summary_for(N - 1)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 1.82 ms, sys: 46 µs, total: 1.87 ms\n",
      "Wall time: 1.88 ms\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "5261545087067582125179062608958232695543100705754634272071166414871321070487675367"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "%time total_ok(300)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We get the same answer in about the same amount of time.\n",
    "\n",
    "Let's verify our results against the slow, reliable `total_ok_slow`, and look at the summaries for the first few values of `N`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " N   ok summary(N)\n",
      "-- ---- ----------\n",
      " 0    1 {(0, 0): 1}\n",
      " 1    3 {(0, 1): 1, (1, 0): 1, (0, 0): 1}\n",
      " 2    8 {(0, 1): 1, (1, 0): 3, (0, 0): 2, (0, 2): 1, (1, 1): 1}\n",
      " 3   19 {(0, 1): 2, (1, 2): 1, (0, 0): 4, (1, 0): 8, (0, 2): 1, (1, 1): 3}\n",
      " 4   43 {(0, 1): 4, (1, 2): 3, (0, 0): 7, (1, 0): 19, (0, 2): 2, (1, 1): 8}\n",
      " 5   94 {(0, 1): 7, (1, 2): 8, (0, 0): 13, (1, 0): 43, (0, 2): 4, (1, 1): 19}\n",
      " 6  200 {(0, 1): 13, (1, 2): 19, (0, 0): 24, (1, 0): 94, (0, 2): 7, (1, 1): 43}\n",
      " 7  418 {(0, 1): 24, (1, 2): 43, (0, 0): 44, (1, 0): 200, (0, 2): 13, (1, 1): 94}\n",
      " 8  861 {(0, 1): 44, (1, 2): 94, (0, 0): 81, (1, 0): 418, (0, 2): 24, (1, 1): 200}\n",
      " 9 1753 {(0, 1): 81, (1, 2): 200, (0, 0): 149, (1, 0): 861, (0, 2): 44, (1, 1): 418}\n",
      "10 3536 {(0, 1): 149, (1, 2): 418, (0, 0): 274, (1, 0): 1753, (0, 2): 81, (1, 1): 861}\n"
     ]
    }
   ],
   "source": [
    "print(' N   ok summary(N)')\n",
    "print('-- ---- ----------')\n",
    "for N in range(11): \n",
    "    assert total_ok(N) == total_ok_slow(N)\n",
    "    print('{:2} {:4} {}'.format(N, total_ok(N), dict(summary_for(N))))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# (2) Count Strings with Alphabetic First Occurences\n",
    "\n",
    "Here's another problem:\n",
    "\n",
    "> Given an alphabet of length k, how many strings of length k can be formed such that the first occurrences of each character in the string are a prefix of the alphabet?\n",
    "\n",
    "Let's first make sure we understand the problem. Since *k* could go well beyond 26, I will choose as my alphabet the integers, not the letters `'abc...'`. An alphabet of length *k* is `range(k)`, and a valid string of length 3 could be\n",
    "`[0, 1, 2]` or `[0, 0, 1]` (or other possibilities). These are valid because the first occurrence of each character for these strings are `[0, 1, 2]` and `[0, 1]`, respectively, and these are prefixes of `range(3)`. But `[0, 0, 2]` is not valid, because the first occurrences are `[0, 2]`, and this is not a prefix (because it is missing the `1`). \n",
    "\n",
    "I'll define four key concepts:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def valid(s) -> bool: \n",
    "    \"A string is valid if its first occurrences are a prefix of the alphabet.\"\n",
    "    return is_prefix(first_occurrences(s))\n",
    "\n",
    "def is_prefix(s) -> bool: \n",
    "    \"A string is a valid prefix if it is consecutive integers starting from 0.\"\n",
    "    return s == list(range(len(s)))\n",
    "\n",
    "def first_occurrences(s) -> list:\n",
    "    \"The unique elements of s, in the order they first appear.\" \n",
    "    firsts = []\n",
    "    for x in s:\n",
    "        if x not in firsts: firsts.append(x)\n",
    "    return firsts \n",
    "\n",
    "def all_strings(k): \n",
    "    \"All strings of length k over an alphabet of k ints.\"\n",
    "    return product(range(k), repeat=k)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'ok'"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def test(): \n",
    "    assert valid([0, 1, 2]) and valid([0, 0, 1])\n",
    "    assert not valid([0, 0, 2])\n",
    "    assert is_prefix([0, 1, 2])\n",
    "    assert first_occurrences([0, 0, 2]) == [0, 2]\n",
    "    assert set(all_strings(2)) == {(0, 0), (0, 1), (1, 0), (1, 1)}\n",
    "    #            s             first_occurrences(s) valid(s)\n",
    "    assert test1([0, 1, 2],    [0, 1, 2],           True)  \n",
    "    assert test1([0, 0, 0],    [0],                 True)      \n",
    "    assert test1([1],          [1],                 False)      \n",
    "    assert test1([0, 1, 3],    [0, 1, 3],           False)\n",
    "    assert test1([0, 1, 3, 2], [0, 1, 3, 2],        False)\n",
    "    assert test1([0, 1, 0, 1, 0, 2, 1], [0, 1, 2],  True)\n",
    "    assert test1([0, 1, 0, 2, 1, 3, 1, 2, 5, 4, 3], [0, 1, 2, 3, 5, 4], False)\n",
    "    assert test1([0, 1, 0, 2, 1, 3, 1, 2, 4, 5, 3], [0, 1, 2, 3, 4, 5], True)\n",
    "    return 'ok'\n",
    "\n",
    "def test1(s, firsts, is_valid):\n",
    "    return first_occurrences(s) == firsts and valid(s) == is_valid\n",
    "    \n",
    "test()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "First, I will solve the problem in a slow but sure way: generate all possible strings, then count the number that are valid. The complexity of this algorithm is $O(k^{k+1})$, because there are $k^k$ strings, and to validate a string requires looking at all $k$ characters."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[1, 1, 2, 5, 15, 52, 203]"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def how_many_slow(k) -> int: \n",
    "    \"\"\"Count the number of valid strings. (Try all possible strings.)\"\"\"\n",
    "    return quantify(all_strings(k), valid)\n",
    "\n",
    "[how_many_slow(k) for k in range(7)]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now let's think about how to speed that up. I don't want to have to consider every possible string, because there are too many ($k^k$) of them. Can I group together many strings and just count the number of them, without enumerating each one? For example, if I knew there were 52 valid strings of length $k-1$ (and didn't know anything else about them), can I tell how many valid strings of length $k$ there are? I don't see a way to do this directly, because the number of ways to extend a valid string is dependent on the number of distinct characters in the string. If a string has $m$ distinct characters, then I can extend it in $m$ waysby repeating any of those $m$ characters, or I can introduce a first occurrence of character number $m+1$ in just 1 way.\n",
    "\n",
    "So I need to keep track of the number of valid strings of length $k$ that have exactly $m$ distinct characters (those characters must be exactly `range(m)`). I'll call that number `C(k, m)`. Because I can reach a recursive call to `C(k, m)` by many paths, I will use the `cache` decorator to keep track of the computations that I have already done. Then I can define `how_many(k)` as the sum over all values of `m` of `C(k, m)`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "@cache()\n",
    "def C(k, m) -> int:\n",
    "    \"Count the number of valid strings of length k, that use m distinct characters.\"\n",
    "    return (1 if k == 0 == m else\n",
    "            0 if k == 0 != m else\n",
    "            C(k-1, m) * m + C(k-1, m-1)) # m ways to add an old character; 1 way to add new\n",
    "\n",
    "def how_many(k): return sum(C(k, m) for m in range(k+1))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "47585391276764833658790768841387207826363669686825611466616334637559114497892442622672724044217756306953557882560751"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "how_many(100)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "assert all(how_many(k) == how_many_slow(k) for k in range(7))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "  0             1\n",
      "  1             1\n",
      "  2             2\n",
      "  3             5\n",
      "  4            15\n",
      "  5            52\n",
      "  6           203\n",
      "  7           877\n",
      "  8          4140\n",
      "  9         21147\n",
      " 10        115975\n",
      " 20   5.17242e+13\n",
      " 30   8.46749e+23\n",
      " 40   1.57451e+35\n",
      " 50   1.85724e+47\n",
      " 60   9.76939e+59\n",
      " 70    1.8075e+73\n",
      " 80   9.91268e+86\n",
      " 90   1.4158e+101\n",
      "100  4.75854e+115\n",
      "110  3.46846e+130\n",
      "120   5.1263e+145\n"
     ]
    }
   ],
   "source": [
    "for k in chain(range(10), range(10, 121, 10)):\n",
    "    print('{:3}  {:12g}'.format(k,  how_many(k)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# (3) Sol Golomb’s Rectangle Puzzle\n",
    "\n",
    "This problem is covered in depth in [another notebook](Golomb-puzzle.ipynb), so here I present just the part that has to do with counting things:\n",
    "\n",
    "> *Say you’re given the following challenge: create a set of five rectangles that have sides of length 1, 2, 3, 4, 5, 6, 7, 8, 9 and 10 units. You can combine sides in a variety of ways: for example, you could create a set of rectangles with dimensions 1 x 3, 2 x 4, 5 x 7, 6 x 8 and 9 x 10. How many different sets of five rectangles are possible?*\n",
    "\n",
    "This is a basic [combinatorics](http://en.wikipedia.org/wiki/Combinatorics) or counting problem. I will present *three* methods to count the sets. If all goes well they will give the same answer. The example set of rectangles given in the problem was\n",
    "\n",
    "> {1 &times; 3, 2 &times; 4, 5 &times; 7, 6 &times; 8, 9 &times; 10}\n",
    "    \n",
    "and in general it would be\n",
    "\n",
    "> {A &times; B, C &times; D, E &times; F, G &times; H, I &times; J}\n",
    "\n",
    "The question is: how many distinct ways can we assign the integers 1 through 10 to the variables A through J?\n",
    "    \n",
    "**Method 1: Count all permutations and divide by repetitions:** There are 10 variables to be filled, so there are 10! = 3,628,800 permutations.  But if we fill the first two variables with 1 &times; 3, that is the same rectangle as 3 &times; 1. So divide 10! by 2<sup>5</sup> to account for the fact that each of 5 rectangles can appear 2 ways.  Similarly, if we fill A and B with 1 &times; 3, that yields the same set as if we filled C and D with 1 &times; 3.  So divide again by 5! (the number of permutations of 5 things) to account for this.\n",
    "That gives us:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "945.0"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "factorial(10) / 2 ** 5 / factorial(5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "(It is always a relief when this \"count and divide\" method comes out to a whole number.)\n",
    "\n",
    "**Method 2: Count without repetitions**: in each rectangle of the example set the smaller component is listed first, and in each set, the rectangles with smaller first components are listed first.  An alternate to \"count and divide\" is to count directly how many sets there are that respect this ordering.  We'll work from left to right.  How many choices are there for variable A?  Only one: A must always be 1, because we agreed that the smallest number comes first. Then, given A, there are 9 remaining choices for B.  For C, given A and B, there is again only one choice: C must be the smallest of the remaining 8 numbers (it will be 3 if the first rectangle was 1 &times; 2; otherwise it will be 2, but either way there is only one choice).  That leaves 7 choices for D, 5 for F, 3 for H and 1 for J. So:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "945"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "9 * 7 * 5 * 3 * 1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "(It is always a relief when two methods give the same answer.)\n",
    "          \n",
    "**Method 3: Write a program to enumerate the sets:** We'll represent the 1 &times; 3 rectangle as the tuple `(1, 3)` and the example set of rectangles as the set\n",
    "\n",
    "    {(1, 3), (2, 4), (5, 7), (6, 8), (9, 10)}\n",
    "\n",
    "We'll write a program to generate all possible sets of rectangles, following method 2, and then just count how many there are. To implement method 2, the minimum side will always be the first element, A, in an (A, B) pair. We iterate through all possible values for B, and then join that pair with all possible rectangles made from the remaining sides. We also have to handle the case when there are no sides; then there is one possible set of rectangles: the empty set."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "945"
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def rectangle_sets(sides):\n",
    "    \"Given a set of sides, list all distinct sets of rectangles that can be made.\"\n",
    "    if not sides:\n",
    "        return [ set() ]\n",
    "    else:\n",
    "        A = min(sides)\n",
    "        return [ {(A, B)} | other_rects\n",
    "                for B in sides if B is not A\n",
    "                for other_rects in rectangle_sets(sides - {A, B}) ]\n",
    "    \n",
    "len(rectangle_sets({1, 2, 3, 4, 5, 6, 7, 8, 9, 0}))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "(It is a relief that once again we get the same answer, 945.)  \n",
    "\n",
    "Here is a list of the rectangle sets with just 6 sides:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{(1, 2), (3, 4), (5, 6)},\n",
       " {(1, 2), (3, 5), (4, 6)},\n",
       " {(1, 2), (3, 6), (4, 5)},\n",
       " {(1, 3), (2, 4), (5, 6)},\n",
       " {(1, 3), (2, 5), (4, 6)},\n",
       " {(1, 3), (2, 6), (4, 5)},\n",
       " {(1, 4), (2, 3), (5, 6)},\n",
       " {(1, 4), (2, 5), (3, 6)},\n",
       " {(1, 4), (2, 6), (3, 5)},\n",
       " {(1, 5), (2, 3), (4, 6)},\n",
       " {(1, 5), (2, 4), (3, 6)},\n",
       " {(1, 5), (2, 6), (3, 4)},\n",
       " {(1, 6), (2, 3), (4, 5)},\n",
       " {(1, 6), (2, 4), (3, 5)},\n",
       " {(1, 6), (2, 5), (3, 4)}]"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "rectangle_sets({1, 2, 3, 4, 5, 6})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# (4) Counting Positions in Fischerandom Chess\n",
    "\n",
    "In this [variant](https://en.wikipedia.org/wiki/Chess960) of chess, the pieces are set up in a random but restricted fashion. The pawns are in their regular positions, and the major white pieces are placed randomly on the first rank, with two restrictions: the bishops must be placed on opposite-color squares, and the king must be placed between the rooks. The black pieces are set up to mirror the white pieces. How many starting positions are there?\n",
    "\n",
    "We can answer by generating all distinct permutations of the eight pieces and quantifying (counting) the number of permutations that are legal according to the two restrictions:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "960"
      ]
     },
     "execution_count": 26,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from statistics import median\n",
    "\n",
    "def legal(pieces):\n",
    "    B, R, K = map(pieces.index, 'BRK')\n",
    "    b, r    = map(cat(pieces).rindex, 'BR')\n",
    "    return (B % 2 != b % 2) and median([R, K, r]) == K\n",
    "\n",
    "quantify(set(permutations('RNBKQBNR')), legal)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*Note:* initially I wrote `pieces.rindex`, because I forgot that while tuples, lists and strings all have an `index` method, only strings have `rindex`.  How annoying! In Ruby, both strings and arrays have `index` and `rindex`. In  Java and Javascript, both strings and lists/arrays have both `indexOf` and `lastIndexOf`. What's wrong with Python?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# (5) Counting Paths on a Grid\n",
    "\n",
    "Consider the following grid, where the goal is to find a path from `S` to `G`, making only \"right\" or \"down\" moves:\n",
    "\n",
    "     S..........\n",
    "     ...........\n",
    "     ...........\n",
    "     ...........\n",
    "     ...........\n",
    "     ..........G\n",
    "     \n",
    "One solution path would be to go right 10 times, then go down 5 times. But you could also go down 3 times, then right 10 times, then down 2 times; or take many other paths. How many paths are there? We can use the same three methods we used for the previous puzzle:\n",
    "\n",
    "**Method 1: Count all permutations and divide by repetitions:** Any path must consist of 10 right and 5 down moves, but they can appear in any order. Arranging 15 things in any order gives 15! = 1,307,674,368,000 possible paths. But that counts all the moves as being distinct, when actually the 10 right moves are indistinguishable, as are the 5 down moves, so we need to divide by the number of ways that they can be arranged. That gives us:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "3003.0"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "factorial(15) / factorial(10) / factorial(5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Method 2: Count without repetitions**: Another way to look at it is that there will be 15 total moves, so start with all 15 being \"right\" moves and  then choose 5 of them to become \"down\" moves. So the answer is (15 choose 5), which leads to the same formula we just used.\n",
    "\n",
    "**Method 3: Write a program to count the paths:** We can define the function `paths(start, goal)` to count the number of paths from start location to goal location, where a location is a `(column, row)` pair of integers.\n",
    "In general, the number of paths to the goal is the number of paths to the location just to the left of the goal, plus the number of paths to the location just above the goal. But there are two special cases: there is only one path (the empty path) when the start is equal to the goal, and there are zero paths when the goal is off the board."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "3003"
      ]
     },
     "execution_count": 28,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "@cache()\n",
    "def paths(start, goal):\n",
    "    \"Number of paths to goal, using only 'right' and 'down' moves.\"\n",
    "    (col, row) = goal\n",
    "    return (1 if goal == start else\n",
    "            0 if col < 0 or row < 0 else\n",
    "            paths(start, (col - 1, row)) + paths(start, (col, row - 1)))\n",
    "    \n",
    "paths((0, 0), (5, 10))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can handle much larger grids (while checking the time taken, and then verifying that the answer agrees with the factorial formula):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 222 ms, sys: 2.81 ms, total: 225 ms\n",
      "Wall time: 225 ms\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "4158251463258564744783383526326405580280466005743648708663033657304756328324008620"
      ]
     },
     "execution_count": 29,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "goal = (C, R) = (100, 200)\n",
    "\n",
    "%time paths((0, 0), goal)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 30,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "_ == factorial(C + R) // factorial(C) // factorial(R)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Why bother with the recursive function when the formula works so well? Good question. One reason is that the two different approaches validate each other by giving the same answer. Another reason is that we can modify the `paths` function to handle grids that have obstacles in them. I'll define a `Grid` constructor, and any cell in the grid that is not a `'.'` will be considered an impassible barrier."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def Grid(text): return tuple(text.split())\n",
    "\n",
    "@cache()\n",
    "def paths2(grid, start=(0, 0), goal=None):\n",
    "    \"Number of paths to goal, using only 'right' and 'down' moves.\"\n",
    "    goal = goal or bottom_right(grid)\n",
    "    (col, row) = goal\n",
    "    return (1 if goal == start else\n",
    "            0 if col < 0 or row < 0 or grid[col][row] != '.' else\n",
    "            paths2(grid, start, (col - 1, row)) + \n",
    "            paths2(grid, start, (col, row - 1)))\n",
    "\n",
    "def bottom_right(grid): return (len(grid) - 1, len(grid[0]) - 1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can verify that we get the same answer on the 11 by 6 empty grid:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "3003"
      ]
     },
     "execution_count": 32,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "paths2(Grid(\"\"\"\n",
    "...........\n",
    "...........\n",
    "...........\n",
    "...........\n",
    "...........\n",
    "...........\n",
    "\"\"\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here's a grid where there should be only two paths:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "2"
      ]
     },
     "execution_count": 33,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "paths2(Grid(\"\"\"\n",
    "...........\n",
    ".........|.\n",
    ".........|.\n",
    ".........|.\n",
    ".--------+.\n",
    "...........\n",
    "\"\"\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If we tear down that wall, there should be many paths (but less than 3003 because some of the wall remains):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "992"
      ]
     },
     "execution_count": 34,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "paths2(Grid(\"\"\"\n",
    "...........\n",
    ".........|.\n",
    ".........|.\n",
    "...........\n",
    ".-------...\n",
    "...........\n",
    "\"\"\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here's a bigger, and a much bigger example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "58975"
      ]
     },
     "execution_count": 35,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "paths2(Grid(r\"\"\"\n",
    "................\\---\n",
    "../......|..........\n",
    "./..()...|.().|...\\.\n",
    ".\\............|.....\n",
    "..\\----....|..|.....\n",
    ".......\\...|........\n",
    "\\.......\\...........\n",
    "-\\.............()...\n",
    "--\\.................\n",
    "---\\....../\\........\n",
    "\"\"\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "121480689204"
      ]
     },
     "execution_count": 38,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "paths2(Grid(r\"\"\"\n",
    "....................http://www.ascii-art-generator.org/.................\n",
    "........................................................................\n",
    ".......................WWNK0OkxdoooolooddxkO0KXW........................\n",
    ".................WX0kdolc::::::::::cc::::::::::clodk0XW.................\n",
    "............WXOdl::::cldxkO0KKKKKKXXXXKKKKKK0Okxdlc:;;:ldOXW............\n",
    ".........N0dc;;coxkxxdxKXXXXXXXKddKXXKxdKXXXXXXXKxdxxxxoc;;cd0N.........\n",
    "........d:,:oxkdl:,..'xXXXXXXXX0:.,;;;.:0XXXXXXXXx'..':ldkxo:,:d0W......\n",
    "....W0l.;okxl;.......cKXXXXXXXXO,......,kXXXXXXXXKc.......;lxko;,l0W....\n",
    "...Xo';.Od;..........;OXXXXXXXKl........lKXXXXXXXO:..........;dkd;,oX...\n",
    "..K:'lO.,.............;dOKKK0x:..........:x0KKKOd;.............,xOl':K..\n",
    ".Xc.o0o..................,,,'...............,,,..................o0o.cX.\n",
    ".k';0k'..........................................................'k0;'k.\n",
    ".d.cKd............................................................dKc.d.\n",
    ".k';Ok,...........................................................kO;'k.\n",
    ".Xl.l0d'.........''..................................''...........0l.cX.\n",
    "..Kc'cOk;......;x000ko;..,okOOd;........;dOOko,..;ok000x;......;x..'cK..\n",
    "...Xd,,dkd:....oXXXXXXKkx0XXXXXKd'....'dKXXXXX0xkKXXXXXXo....;dkd;.dX...\n",
    "....WKo,;lxxo;':OXXXXXXXXXXXXXXXXx,..'xXNXXXXXXXXXXXXXXO:'.oxxl;,l.W....\n",
    "......WKx:,:lxxxOXXXXXXXXXXXXXXXXXx::xXXXXXXXXXXXXXXXXXOxx..:,:dKW......\n",
    ".........WKxl;;:ldk0KXXXXNNXXXXXXXXKKXXXXXXXXXXXXXXK0kdl:;.cxKW.........\n",
    "............WN0xoc:;:clodxkO00KKKXXXXXXKKK00Okxdol::;:cox0.W............\n",
    ".................WNKOxdlcc::::::::::::::::::::ccldxOKNW.................\n",
    "........................WWXK0OkkxxddddxxkkO0KXNW........................\n",
    "........................................................................\n",
    "\"\"\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Can you verify that these last three answers are correct?"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
