Add files via upload
This commit is contained in:
parent
d64b68ac42
commit
4928f96bec
@ -4,7 +4,7 @@
|
|||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"<div style=\"text-align:right\">*Peter Norvig<br>Updated Sept 2018*</div>\n",
|
"<div style=\"text-align:right\">Peter Norvig<br>Updated Sept 2018</div>\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
@ -33,7 +33,7 @@
|
|||||||
"\n",
|
"\n",
|
||||||
"First let's get some Python preliminaries out of the way: imports, and three utility functions:\n",
|
"First let's get some Python preliminaries out of the way: imports, and three utility functions:\n",
|
||||||
"- `cat` concatenates strings into one big string\n",
|
"- `cat` concatenates strings into one big string\n",
|
||||||
"- `quantify` (from [the `itertools` module recipes](https://docs.python.org/3/library/itertools.html) counts how many things a predicate is true for.\n",
|
"- `quantify` (from [the `itertools` module recipes](https://docs.python.org/3/library/itertools.html)) counts how many things a predicate is true for.\n",
|
||||||
"- `totalcount` totals up all the values in a Counter."
|
"- `totalcount` totals up all the values in a Counter."
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -47,20 +47,20 @@
|
|||||||
"source": [
|
"source": [
|
||||||
"# Python 3.x imports, and some utilities\n",
|
"# Python 3.x imports, and some utilities\n",
|
||||||
"\n",
|
"\n",
|
||||||
"import re\n",
|
|
||||||
"from itertools import product, permutations, combinations\n",
|
|
||||||
"from collections import Counter\n",
|
"from collections import Counter\n",
|
||||||
"from functools import lru_cache\n",
|
"from functools import lru_cache\n",
|
||||||
"from math import factorial, inf # inf = infinity\n",
|
"from itertools import product, permutations, combinations\n",
|
||||||
|
"from math import factorial, inf # infinity\n",
|
||||||
|
"import re\n",
|
||||||
"\n",
|
"\n",
|
||||||
"cat = ''.join\n",
|
"cat = ''.join\n",
|
||||||
"\n",
|
"\n",
|
||||||
"def quantify(iterable, pred=bool) -> int: \n",
|
"def quantify(iterable, predicate=bool) -> int: \n",
|
||||||
" \"Count the number of items in iterable for which the predicate is true.\"\n",
|
" \"Count the number of items in iterable for which the predicate is true.\"\n",
|
||||||
" return sum(map(pred, iterable)) \n",
|
" return sum(map(predicate, iterable)) \n",
|
||||||
"\n",
|
"\n",
|
||||||
"def totalcount(counter) -> int:\n",
|
"def totalcount(counter) -> int:\n",
|
||||||
" \"The sum of all the values in the Counter (or mapping).\"\n",
|
" \"The sum of all the values in a Counter (or mapping).\"\n",
|
||||||
" return sum(counter.values())"
|
" return sum(counter.values())"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -68,7 +68,7 @@
|
|||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"Here is an **enumeration** strategy and a **calculation** strategy for cube counting:"
|
"Here is an **enumeration** strategy and a **calculation** strategy for sub-cube counting:"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -106,14 +106,14 @@
|
|||||||
"name": "stdout",
|
"name": "stdout",
|
||||||
"output_type": "stream",
|
"output_type": "stream",
|
||||||
"text": [
|
"text": [
|
||||||
"CPU times: user 3min 50s, sys: 1.92 s, total: 3min 52s\n",
|
"CPU times: user 6.4 s, sys: 79.7 ms, total: 6.48 s\n",
|
||||||
"Wall time: 4min 2s\n"
|
"Wall time: 7.77 s\n"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
"text/plain": [
|
"text/plain": [
|
||||||
"1000000000"
|
"27000000"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"execution_count": 3,
|
"execution_count": 3,
|
||||||
@ -122,7 +122,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"source": [
|
"source": [
|
||||||
"%time enumerate_subcubes(1000)"
|
"%time enumerate_subcubes(300)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -136,14 +136,14 @@
|
|||||||
"name": "stdout",
|
"name": "stdout",
|
||||||
"output_type": "stream",
|
"output_type": "stream",
|
||||||
"text": [
|
"text": [
|
||||||
"CPU times: user 7 µs, sys: 1 µs, total: 8 µs\n",
|
"CPU times: user 5 µs, sys: 1 µs, total: 6 µs\n",
|
||||||
"Wall time: 11.9 µs\n"
|
"Wall time: 9.06 µs\n"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
"text/plain": [
|
"text/plain": [
|
||||||
"1000000000"
|
"27000000"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"execution_count": 4,
|
"execution_count": 4,
|
||||||
@ -152,7 +152,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"source": [
|
"source": [
|
||||||
"%time calculate_subcubes(1000)"
|
"%time calculate_subcubes(300)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -208,7 +208,7 @@
|
|||||||
" return map(cat, product(alphabet, repeat=n))\n",
|
" return map(cat, product(alphabet, repeat=n))\n",
|
||||||
"\n",
|
"\n",
|
||||||
"def enumerate_ok(n) -> int:\n",
|
"def enumerate_ok(n) -> int:\n",
|
||||||
" \"How many strings over 'LAP' of length n are ok?\"\n",
|
" \"How many attendance records of length n are ok?\"\n",
|
||||||
" return quantify(all_strings('LAP', n), ok)\n",
|
" return quantify(all_strings('LAP', n), ok)\n",
|
||||||
"\n",
|
"\n",
|
||||||
"{n: enumerate_ok(n) for n in range(11)}"
|
"{n: enumerate_ok(n) for n in range(11)}"
|
||||||
@ -221,13 +221,15 @@
|
|||||||
"This looks good, but for large values of *n*\n",
|
"This looks good, but for large values of *n*\n",
|
||||||
"I will need an efficient **calculation** strategy. Here's how I think about it:\n",
|
"I will need an efficient **calculation** strategy. Here's how I think about it:\n",
|
||||||
"\n",
|
"\n",
|
||||||
"* I can't enumerate all the strings; there are too many of them, 3<sup>*n*</sup>. \n",
|
"* I can't enumerate all the strings; there are too many of them. \n",
|
||||||
"* Instead, I want to **divide and conquer**: divide the problem up into simpler subproblems, solve the subproblems, and combine them. A good way to divide this problem is to solve the case for *n* by first solving the case for *n*-1. - With the enumeration strategy, going from *n*-1 to *n* multiplies the amount of work by 3 (because you have to consider any of three letters tacked on to the end of every possible record). But with the right divide and conquer strategy, we can get from *n*-1 to *n* with a constant amount of work. So the total complexity can be *O*(*n*) instead of *O*(3<sup>*n*</sup>). This divide and conquer strategy is sometimes called *[dynamic programming](https://en.wikipedia.org/wiki/Dynamic_programming)* (although some writers reserve the term *dynamic programming* for algorithms that explicitly store partial results in a table).\n",
|
"* Instead, I want to **divide and conquer**: divide the problem up into simpler subproblems, solve the subproblems, and combine them. A good way to divide this problem is to solve the case for *n* by first solving the case for *n*-1.\n",
|
||||||
"* How do I get from *n*-1 to *n*? I will keep track of a *summary* of all the `ok` strings of length *n*-1, and use that to quickly compute a summary of the `ok` strings of length *n*. \n",
|
"* With the enumeration strategy, going from *n*-1 to *n* multiplies the amount of work by 3 (because you have to consider any of three letters tacked on to the end of every possible record). \n",
|
||||||
|
"* With the right divide and conquer strategy, we can get from *n*-1 to *n* with a constant amount of work. So the total complexity can be *O*(*n*) instead of *O*(3<sup>*n*</sup>). \n",
|
||||||
|
"* (This divide and conquer strategy is sometimes called *[dynamic programming](https://en.wikipedia.org/wiki/Dynamic_programming)*, although some writers reserve the term *dynamic programming* for algorithms that explicitly store partial results in a table.)\n",
|
||||||
|
"* How do I get from *n*-1 to *n*? I will keep track of a *summary* of all the `ok` strings of length *n*-1, and use that to quickly (in constant time) compute a summary of the `ok` strings of length *n*. \n",
|
||||||
"* What is in the summary? A list of all `ok` strings is too much. A simple count of the number of `ok` strings is not enough. Instead, I need several different counts, for several different classes of strings. Each class is defined by the number of `'A'` characters in the string, and the number of consecutive `'L'` characters at the *end* of the string (because these are the two things that determine whether the string will be `ok` or not `ok` when I add a letter to the end). So the summary can be represented as a `Counter` of the form `{(A, L): count, ...}`. \n",
|
"* What is in the summary? A list of all `ok` strings is too much. A simple count of the number of `ok` strings is not enough. Instead, I need several different counts, for several different classes of strings. Each class is defined by the number of `'A'` characters in the string, and the number of consecutive `'L'` characters at the *end* of the string (because these are the two things that determine whether the string will be `ok` or not `ok` when I add a letter to the end). So the summary can be represented as a `Counter` of the form `{(A, L): count, ...}`. \n",
|
||||||
"\n",
|
"\n",
|
||||||
"For *n* = 0, the summary is `{(0, 0): 1}`, because there is one string of length zero, the empty string, which has no `'A'` nor `'L'` in it. From there we can proceed in a \"bottom-up\" fashion to compute the total number of `ok` strings for the next value of `n`, using the function `next_summary`, which says that we can add an `'A'` to any string that doesn't already have one; we can add an `L` to anystring that doesn't already end in 2 `'L'`s; and we can add a `'P'` to any string.\n",
|
"For *n* = 0, the summary is `{(0, 0): 1}`, because there is one string of length zero, the empty string, which has no `'A'` nor `'L'` in it. From there we can proceed in a \"bottom-up\" fashion to compute the total number of `ok` strings for the next value of `n`, using the function `next_summary`, which says that we can add an `'A'` to any string that doesn't already have one; we can add an `L` to any string that doesn't already end in 2 `'L'`s; and we can add a `'P'` to any string."
|
||||||
"\n"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -240,11 +242,11 @@
|
|||||||
"source": [
|
"source": [
|
||||||
"summary0 = Counter({(0, 0): 1})\n",
|
"summary0 = Counter({(0, 0): 1})\n",
|
||||||
"\n",
|
"\n",
|
||||||
"def next_summary(prev_summary: dict) -> dict:\n",
|
"def next_summary(prev_summary: Counter) -> Counter:\n",
|
||||||
" \"Given a summary of the form {(A, L): count}, return summary for one letter more.\"\n",
|
" \"Given a summary of the form {(A, L): count}, return summary for one letter more.\"\n",
|
||||||
" summary = Counter()\n",
|
" summary = Counter()\n",
|
||||||
" for (A, L), count in prev_summary.items():\n",
|
" for (A, L), count in prev_summary.items():\n",
|
||||||
" if A < 1: summary[A+1, 0] += count # add an 'A'\n",
|
" if not A: summary[A+1, 0] += count # add an 'A'\n",
|
||||||
" if L < 2: summary[A, L+1] += count # add an 'L'\n",
|
" if L < 2: summary[A, L+1] += count # add an 'L'\n",
|
||||||
" summary[A, 0] += count # add a 'P'\n",
|
" summary[A, 0] += count # add a 'P'\n",
|
||||||
" return summary"
|
" return summary"
|
||||||
@ -320,7 +322,7 @@
|
|||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"Here's an implementation of `calculate_ok`:"
|
"Here's an implementation of `calculate_ok`, and a demonstration that it gets the same results as `enumerate_ok`:"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -375,7 +377,7 @@
|
|||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"We can use this to go way beyond what we could do with `enumerate_ok`:"
|
"But we can go way beyond what we could do with `enumerate_ok`:"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -406,7 +408,7 @@
|
|||||||
"source": [
|
"source": [
|
||||||
"## Alternative: Abstraction with `iterate`\n",
|
"## Alternative: Abstraction with `iterate`\n",
|
||||||
"\n",
|
"\n",
|
||||||
"This pattern of repeatedly calling `next_summary` *n* times will become a common pattern in this notebook; we can encapsulate it in a function called `iterate` (after the Haskell function of that name) allowing us to implement a version of `calculate_ok` in one line:"
|
"This pattern of repeatedly calling `next_summary` *n* times will be common in this notebook; we can encapsulate it in a function called `iterate` (after the Haskell function of that name) allowing us to implement a version of `calculate_ok` in one line:"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -467,7 +469,7 @@
|
|||||||
"source": [
|
"source": [
|
||||||
"def calculate_ok3(n) -> int: return totalcount(nth_summary(n))\n",
|
"def calculate_ok3(n) -> int: return totalcount(nth_summary(n))\n",
|
||||||
" \n",
|
" \n",
|
||||||
"def nth_summary(n) -> dict: \n",
|
"def nth_summary(n) -> Counter: \n",
|
||||||
" \"The {(A, L): count} summary for strings of length n.\"\n",
|
" \"The {(A, L): count} summary for strings of length n.\"\n",
|
||||||
" return (summary0 if n == 0 else next_summary(nth_summary(n - 1)))"
|
" return (summary0 if n == 0 else next_summary(nth_summary(n - 1)))"
|
||||||
]
|
]
|
||||||
@ -512,10 +514,10 @@
|
|||||||
"name": "stdout",
|
"name": "stdout",
|
||||||
"output_type": "stream",
|
"output_type": "stream",
|
||||||
"text": [
|
"text": [
|
||||||
"The slowest run took 5.89 times longer than the fastest. This could mean that an intermediate result is being cached.\n",
|
"The slowest run took 4.34 times longer than the fastest. This could mean that an intermediate result is being cached.\n",
|
||||||
"100 loops, best of 3: 5.77 ms per loop\n",
|
"100 loops, best of 3: 6.26 ms per loop\n",
|
||||||
"100 loops, best of 3: 5.57 ms per loop\n",
|
"100 loops, best of 3: 9.52 ms per loop\n",
|
||||||
"100 loops, best of 3: 5.87 ms per loop\n"
|
"100 loops, best of 3: 5.52 ms per loop\n"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -541,7 +543,7 @@
|
|||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"There are over 10<sup>134</sup> ok strings of length 500, but it only takes a couple milliseconds to count them; any of the three methods works equally well."
|
"There are over 10<sup>134</sup> ok strings of length 500, but it only takes a couple milliseconds to count them. Any of the three methods works equally well; use the approach you feel comfortable with."
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -556,7 +558,7 @@
|
|||||||
"\n",
|
"\n",
|
||||||
"Let's first make sure we understand the problem, because the statement is a bit abstract and tricky. Given a string we have to pick out the first occurrences of characters (letters). For example, for the string `\"abba\"`, we read left-to-right, recording each time we see a letter for the first time; that gives us `\"ab\"` (the subsequent occurrences of `'a'` and `'b'` don't matter). Is that a prefix of the alphabet, `\"abcd...\"`? Yes it is. \n",
|
"Let's first make sure we understand the problem, because the statement is a bit abstract and tricky. Given a string we have to pick out the first occurrences of characters (letters). For example, for the string `\"abba\"`, we read left-to-right, recording each time we see a letter for the first time; that gives us `\"ab\"` (the subsequent occurrences of `'a'` and `'b'` don't matter). Is that a prefix of the alphabet, `\"abcd...\"`? Yes it is. \n",
|
||||||
"\n",
|
"\n",
|
||||||
"Here are some test cases—each case is a triple consisting of a string, the first occurrences of characters from that string, and a Boolean saying whether or not the first occurrences are a prefix of the alphabet:"
|
"Here are some test cases—pairs of strings along with their first occurrences—labelled with `True` for valid strings and `False` for invalid:"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -567,19 +569,20 @@
|
|||||||
},
|
},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"alpha_test_cases = [\n",
|
"alpha_test_cases = {\n",
|
||||||
" ('abba', 'ab', True),\n",
|
" True: {('abba', 'ab'),\n",
|
||||||
" ('abbott', 'abot', False), # Because 'o' is out of place\n",
|
" ('', ''),\n",
|
||||||
" ('', '', True),\n",
|
" ('a', 'a'),\n",
|
||||||
" ('a', 'a', True),\n",
|
" ('aaa', 'a'),\n",
|
||||||
" ('b', 'b', False), # Because 'a' is missing\n",
|
" ('abc', 'abc'),\n",
|
||||||
" ('abc', 'abc', True),\n",
|
" ('abac', 'abc'),\n",
|
||||||
" ('cab', 'cab', False), # Because 'c' is before 'ab'\n",
|
" ('abbacabbadabba', 'abcd')},\n",
|
||||||
" ('abd', 'abd', False), # Because 'c' is missing\n",
|
" False: {('b', 'b'), # 'a' is missing\n",
|
||||||
" ('abac', 'abc', True),\n",
|
" ('cab', 'cab'), # 'c' is before 'ab'\n",
|
||||||
" ('badcafe','badcfe', False), # Because 'b' is before 'a'\n",
|
" ('abd', 'abd'), # 'c' is missing\n",
|
||||||
" ('abecedarian', 'abecdrin', False), # Because 'e' is before 'cd'\n",
|
" ('aback', 'abck'), # 'k' is before 'd-j'\n",
|
||||||
"]"
|
" ('badcafe','badcfe'), # 'b' is before 'a', etc.\n",
|
||||||
|
" ('abecedarian', 'abecdrin')}} # 'e' is before 'cd', etc."
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -593,7 +596,7 @@
|
|||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 19,
|
"execution_count": 19,
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"collapsed": true
|
"collapsed": false
|
||||||
},
|
},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
@ -607,14 +610,14 @@
|
|||||||
"\n",
|
"\n",
|
||||||
"def firsts(s) -> list: \n",
|
"def firsts(s) -> list: \n",
|
||||||
" \"The first occurrences of each character, in the order they appear.\"\n",
|
" \"The first occurrences of each character, in the order they appear.\"\n",
|
||||||
" return sorted(set(s), key=lambda x: s.index(x)) "
|
" return sorted(set(s), key=lambda x: s.index(x)) "
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"I'll test this code on the examples I showed above by defining the function `test1`:"
|
"I'll test this code on the examples I showed above by defining the function `alpha_test`:"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -638,9 +641,10 @@
|
|||||||
"source": [
|
"source": [
|
||||||
"def alpha_test(cases=alpha_test_cases):\n",
|
"def alpha_test(cases=alpha_test_cases):\n",
|
||||||
" \"Verify all the test cases.\"\n",
|
" \"Verify all the test cases.\"\n",
|
||||||
" for (s, s_firsts, validity) in cases:\n",
|
" for validity in cases:\n",
|
||||||
" assert firsts(s) == list(s_firsts)\n",
|
" for (s, s_firsts) in cases[validity]:\n",
|
||||||
" assert is_alpha_firsts(s, letters) == validity\n",
|
" assert firsts(s) == list(s_firsts)\n",
|
||||||
|
" assert is_alpha_firsts(s, letters) == validity\n",
|
||||||
" return 'pass'\n",
|
" return 'pass'\n",
|
||||||
"\n",
|
"\n",
|
||||||
"alpha_test()"
|
"alpha_test()"
|
||||||
@ -684,8 +688,8 @@
|
|||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"Now let's think about a **calculation** that would be faster than the **enumeration** strategy.\n",
|
"Now let's think about a **calculation** strategy that would be faster than the **enumeration** strategy.\n",
|
||||||
"As with previous problems, I need a *summary* of the relevant information for strings of length *k*, to help me calculate the relevant information for length *k*+1. I know that if I have a valid string of length *k* with *m* distinct characters in it, then I can extend it by one character in *m* ways by repeating any of those *m* characters, or I can introduce a first occurrence of character number *m+1* (but I can do that in just 1 way). I can't validly introduce any other character. So a good summary would be a dict of `{`*m*`: `*count*`}`, and we get this:"
|
"As with previous problems, I need a *summary* of the relevant information for strings of length *k*, to help me calculate the relevant information for length *k*+1. I know that if I have a valid string of length *k* with *m* distinct characters in it, then I can extend it by one character in *m* ways by repeating any of those *m* characters, or I can introduce a first occurrence of character number *m+1* (but I can do that in just 1 way). I can't validly introduce any other character. So a good summary would be a Counter of `{`*m*`: `*count*`}`, and we get this:"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -700,7 +704,7 @@
|
|||||||
" \"Count the number of strings of length k that have alphabetic first character occurrences.\"\n",
|
" \"Count the number of strings of length k that have alphabetic first character occurrences.\"\n",
|
||||||
" return totalcount(iterate(next_alpha_summary, {0: 1}, k))\n",
|
" return totalcount(iterate(next_alpha_summary, {0: 1}, k))\n",
|
||||||
"\n",
|
"\n",
|
||||||
"def next_alpha_summary(prev_summary) -> dict:\n",
|
"def next_alpha_summary(prev_summary) -> Counter:\n",
|
||||||
" \"Given a summary of the form {m: count}, return summary for one character more.\"\n",
|
" \"Given a summary of the form {m: count}, return summary for one character more.\"\n",
|
||||||
" summary = Counter()\n",
|
" summary = Counter()\n",
|
||||||
" for m, c in prev_summary.items():\n",
|
" for m, c in prev_summary.items():\n",
|
||||||
@ -840,7 +844,7 @@
|
|||||||
"source": [
|
"source": [
|
||||||
"# Problem 4: Counting Barcodes\n",
|
"# Problem 4: Counting Barcodes\n",
|
||||||
"\n",
|
"\n",
|
||||||
"> Consider the barcode pictured below. A valid barcode consists of alternating black and white stripes, where each stripe (black or white) is either 1, 2, or 3 units wide. The question is: for a box that is *n* units wide, how many different valid barcodes are there?\n",
|
"> Consider the barcode pictured below. A valid barcode consists of alternating black and white stripes, where each stripe is either 1, 2, or 3 units wide. The question is: for a box that is *n* units wide, how many different valid barcodes are there?\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
@ -909,7 +913,7 @@
|
|||||||
"source": [
|
"source": [
|
||||||
"We can see that the table starts out with the powers of two: 1, 2, 4, 8. That makes sense: each of the *n* positions in the barcode could be either black or white, so that's 2<sup>*n*</sup> barcodes. But barcodes with 4 or more of the same color in a row are invalid, so for *n* = 4, `'BBBB'` and `'WWWW'` are invalid, and we get 14 (not 16) valid barcodes. For *n* = 5 and up, the difference between 2<sup>*n*</sup> and the count of valid barcodes becomes larger.\n",
|
"We can see that the table starts out with the powers of two: 1, 2, 4, 8. That makes sense: each of the *n* positions in the barcode could be either black or white, so that's 2<sup>*n*</sup> barcodes. But barcodes with 4 or more of the same color in a row are invalid, so for *n* = 4, `'BBBB'` and `'WWWW'` are invalid, and we get 14 (not 16) valid barcodes. For *n* = 5 and up, the difference between 2<sup>*n*</sup> and the count of valid barcodes becomes larger.\n",
|
||||||
"\n",
|
"\n",
|
||||||
"Now for a fast calculation. As before, we need a representation that summarizes the valid barcodes of length *n*. The key thing we need to know is how many barcode units of the same color are at the *end* of the barcode: if it is 1 or 2, then we can add another instance of the same color to the end; no matter what it is we can always add the opposite color (and then the barcode will end in just one unit of the same color)."
|
"Now for a fast **calculation**. As before, we need a representation that summarizes the valid barcodes of length *n*. The key thing we need to know is how many barcode units of the same color are at the *end* of the barcode: if it is 1 or 2, then we can add another instance of the same color to the end; no matter what it is we can always add the opposite color (and then the barcode will end in just one unit of the same color)."
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -945,7 +949,7 @@
|
|||||||
"source": [
|
"source": [
|
||||||
"def calculate_barcodes(k): return totalcount(iterate(next_barcode_summary, {0: 1}, k))\n",
|
"def calculate_barcodes(k): return totalcount(iterate(next_barcode_summary, {0: 1}, k))\n",
|
||||||
"\n",
|
"\n",
|
||||||
"def next_barcode_summary(prev_summary) -> dict:\n",
|
"def next_barcode_summary(prev_summary) -> Counter:\n",
|
||||||
" \"Given a summary of the form {end: count}, return summary for one unit more.\"\n",
|
" \"Given a summary of the form {end: count}, return summary for one unit more.\"\n",
|
||||||
" summary = Counter()\n",
|
" summary = Counter()\n",
|
||||||
" for end, c in prev_summary.items():\n",
|
" for end, c in prev_summary.items():\n",
|
||||||
@ -1375,7 +1379,7 @@
|
|||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"If we tear down part of that wall, there should be many paths (but less than 3003 because most of the wall remains):"
|
"If we put a hole in the wall, there should be many paths (but less than 3003 because most of the wall remains):"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1388,7 +1392,7 @@
|
|||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
"text/plain": [
|
"text/plain": [
|
||||||
"497"
|
"167"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"execution_count": 43,
|
"execution_count": 43,
|
||||||
@ -1402,7 +1406,7 @@
|
|||||||
".........|.\n",
|
".........|.\n",
|
||||||
".........|.\n",
|
".........|.\n",
|
||||||
".........|.\n",
|
".........|.\n",
|
||||||
".-------...\n",
|
".-------.+.\n",
|
||||||
"...........\n",
|
"...........\n",
|
||||||
"\"\"\"))"
|
"\"\"\"))"
|
||||||
]
|
]
|
||||||
@ -1591,7 +1595,7 @@
|
|||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"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?]"
|
"[*Note:* initially I wrote `pieces.rindex` instead of `cat(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?]"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1600,13 +1604,13 @@
|
|||||||
"source": [
|
"source": [
|
||||||
"# Problem 8: Counting Change\n",
|
"# Problem 8: Counting Change\n",
|
||||||
"\n",
|
"\n",
|
||||||
"> How many ways are there to select coins that add up to a specified amount of money? For example, to make 11 cents with an unlimited number of coins of denomination 10, 5, and 1 cents, there are four ways: {10+1, 5+5+1, 5+1+1+1+1+1+1, 1+1+1+1+1+1+1+1+1+1+1}. \n",
|
"> How many ways are there to select coins that add up to a specified amount of money? For example, to make 10 cents with an unlimited number of coins of denomination 10, 5, and 1 cents, there are four ways: {10, 5+5, 5+1+1+1+1+1, 1+1+1+1+1+1+1+1+1+1}. \n",
|
||||||
"\n",
|
"\n",
|
||||||
"We can count by breaking the possibilities down into cases:\n",
|
"For this problem there is no sense of advanccing from $k$ to $k$+1; instead we will need a recursive breakdown. Here are the cases:\n",
|
||||||
"- It is possible to add up to zero cents in exactly one way (with no coins).\n",
|
"- There is one way to add up to zero cents (with no coins).\n",
|
||||||
"- It is not possible to add up to a negative amount (because there are no negative coins).\n",
|
"- It is not possible to add up to a negative amount (because there are no negative coins).\n",
|
||||||
"- It is not possible to add up to a positive amount if you don't have any coin denominations to do it.\n",
|
"- It is not possible to add up to a positive amount if you don't have any coin denominations to do it.\n",
|
||||||
"- In the general case, you should add up the number of ways you can make change by using one of the first denomination, plus the number of ways you can do it by skipping the first denomination. If you use one coin of a denomination you are free to use another one, but once you skip a denomination, you can't go back to it later. That way, assuming we are looking at the largest denominations first, we will count 10+1, but we will not also count 1+10, which is as it should be."
|
"- In the general case, you should add up the number of ways you can make change by using one coin of the first denomination, plus the number of ways you can do it by skipping the first denomination. If you use one coin of a denomination you are free to use another one, but once you skip a denomination, you can't go back to it later. That way, assuming we are looking at the largest denominations first, we will count 10+1, but we will not also count 1+10, which is as it should be."
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1744,7 +1748,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"source": [
|
"source": [
|
||||||
"calculate_limited_change(11, (10, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1))"
|
"calculate_limited_change(10, (10, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1))"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1757,7 +1761,7 @@
|
|||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
"text/plain": [
|
"text/plain": [
|
||||||
"1"
|
"4"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"execution_count": 53,
|
"execution_count": 53,
|
||||||
@ -1766,7 +1770,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"source": [
|
"source": [
|
||||||
"calculate_limited_change(10, (10, 5, 1, 1, 1, 1))"
|
"calculate_limited_change(10, (50, 25, 10, 10, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1))"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1779,7 +1783,7 @@
|
|||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
"text/plain": [
|
"text/plain": [
|
||||||
"9"
|
"1"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"execution_count": 54,
|
"execution_count": 54,
|
||||||
@ -1788,7 +1792,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"source": [
|
"source": [
|
||||||
"calculate_limited_change(25, (25, 10, 10, 5, 5, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)) "
|
"calculate_limited_change(10, (10, 5, 1, 1, 1, 1))"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1801,7 +1805,7 @@
|
|||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
"text/plain": [
|
"text/plain": [
|
||||||
"2"
|
"9"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"execution_count": 55,
|
"execution_count": 55,
|
||||||
@ -1809,6 +1813,28 @@
|
|||||||
"output_type": "execute_result"
|
"output_type": "execute_result"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"source": [
|
||||||
|
"calculate_limited_change(25, (25, 10, 10, 5, 5, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)) "
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 56,
|
||||||
|
"metadata": {
|
||||||
|
"collapsed": false
|
||||||
|
},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"2"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 56,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
"source": [
|
"source": [
|
||||||
"calculate_limited_change(25, (25, 10, 5, 5, 5, 5, 1, 1, 1, 1)) "
|
"calculate_limited_change(25, (25, 10, 5, 5, 5, 5, 1, 1, 1, 1)) "
|
||||||
]
|
]
|
||||||
@ -1824,17 +1850,17 @@
|
|||||||
"> I was recently traveling in Europe and struck by the number of coins the euro uses. They have 2 euro, 1 euro, 50 cent, 20 cent, 10 cent, 5 cent, 2 cent and 1 cent coins. This got me thinking: If Riddler Nation needed to make change (anywhere from 0.01 to 0.99) and was establishing its own denomination, what values of coins would be ideal to yield the smallest number of coins in any transaction? When picking values, let’s say we’re ditching the Europeans and limiting our denomination to four different coin denominations — replacing the current common American ones of penny, nickel, dime and quarter. \n",
|
"> I was recently traveling in Europe and struck by the number of coins the euro uses. They have 2 euro, 1 euro, 50 cent, 20 cent, 10 cent, 5 cent, 2 cent and 1 cent coins. This got me thinking: If Riddler Nation needed to make change (anywhere from 0.01 to 0.99) and was establishing its own denomination, what values of coins would be ideal to yield the smallest number of coins in any transaction? When picking values, let’s say we’re ditching the Europeans and limiting our denomination to four different coin denominations — replacing the current common American ones of penny, nickel, dime and quarter. \n",
|
||||||
"\n",
|
"\n",
|
||||||
"This is an optimization problem, not a counting problem, but it is related to the other coin/denomination problems here. Here's how I address the problem:\n",
|
"This is an optimization problem, not a counting problem, but it is related to the other coin/denomination problems here. Here's how I address the problem:\n",
|
||||||
"- The function `totalcost(denominations)` will give the total number of coins (taken from those denominations) that are\n",
|
"- The function `totalcoins(denominations)` will give the total number of coins (taken from those denominations) that are\n",
|
||||||
"required to make each amount of change from 1 to 99 cents (assuming optimal change choices).\n",
|
"required to make each amount of change from 1 to 99 cents (assuming optimal change choices).\n",
|
||||||
"- The function `mincoins(amount, denominations)` computes this optimal number of coins.\n",
|
"- The function `mincoins(amount, denominations)` computes this optimal number of coins for a given amount, or returns infinity if the amount cannot be made.\n",
|
||||||
"- I know I'm going to need a 1 cent piece; otherwise I can't make 1 cent total.\n",
|
"- I know I'm going to need a 1 cent piece; otherwise I can't make 1 cent total.\n",
|
||||||
"- That leaves 3 coins that could be anywhere from 2 to 99 cents; let's try all combinations. We'll call that `candidates`.\n",
|
"- That leaves 3 coins that could be anywhere from 2 to 99 cents; let's try all combinations (even though it will take a minute or two). \n",
|
||||||
"- We'll report the candidate that has the minimum total cost.\n"
|
"- We'll report the candidate combination that has the minimum total number of coins.\n"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 56,
|
"execution_count": 57,
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"collapsed": false
|
"collapsed": false
|
||||||
},
|
},
|
||||||
@ -1843,8 +1869,8 @@
|
|||||||
"name": "stdout",
|
"name": "stdout",
|
||||||
"output_type": "stream",
|
"output_type": "stream",
|
||||||
"text": [
|
"text": [
|
||||||
"CPU times: user 1min 46s, sys: 8.12 s, total: 1min 54s\n",
|
"CPU times: user 1min 40s, sys: 5.93 s, total: 1min 46s\n",
|
||||||
"Wall time: 2min 4s\n"
|
"Wall time: 1min 55s\n"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1853,27 +1879,27 @@
|
|||||||
"(25, 18, 5, 1)"
|
"(25, 18, 5, 1)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"execution_count": 56,
|
"execution_count": 57,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"output_type": "execute_result"
|
"output_type": "execute_result"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"source": [
|
"source": [
|
||||||
"def totalcost(denominations) -> int: \n",
|
"def totalcoins(denominations) -> int: \n",
|
||||||
" \"The total number of coins needed to make change for all amounts up to 99 cents.\"\n",
|
" \"The total number of coins needed to make change for all amounts up to 99 cents.\"\n",
|
||||||
" return sum(mincoins(a, denominations) for a in range(1, 100))\n",
|
" return sum(mincoins(a, denominations) for a in range(1, 100))\n",
|
||||||
"\n",
|
"\n",
|
||||||
"@lru_cache(None)\n",
|
"@lru_cache(None)\n",
|
||||||
"def mincoins(amount, denominations) -> int:\n",
|
"def mincoins(amount, denominations) -> int:\n",
|
||||||
" \"The minimum number of coins, taken from denominations, that add to amount.\"\n",
|
" \"The minimum number of coins, taken from denominations, that add to amount.\"\n",
|
||||||
" return (0 if amount == 0 else\n",
|
" return (0 if amount == 0 else\n",
|
||||||
" inf if not denominations or amount < min(denominations) else\n",
|
" inf if not denominations or amount < min(denominations) else\n",
|
||||||
" min(mincoins(amount, denominations[1:]),\n",
|
" min(mincoins(amount, denominations[1:]),\n",
|
||||||
" mincoins(amount - denominations[0], denominations) + 1))\n",
|
" mincoins(amount - denominations[0], denominations) + 1))\n",
|
||||||
"\n",
|
"\n",
|
||||||
"candidates = ((L, M, S, 1) for S, M, L in combinations(range(2, 100), 3))\n",
|
"candidates = ((L, M, S, 1) for S, M, L in combinations(range(2, 100), 3))\n",
|
||||||
"\n",
|
"\n",
|
||||||
"%time min(candidates, key=totalcost) "
|
"%time min(candidates, key=totalcoins) "
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user