{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# December 2017: Advent of Code Solutions\n",
    "\n",
    "<div align=right>Peter Norvig, December 2017</div>\n",
    "\n",
    "I'm doing the [Advent of Code](https://adventofcode.com) puzzles, just like [last year](https://github.com/norvig/pytudes/blob/master/ipynb/Advent%20of%20Code.ipynb). But this time, I won't repeat the write up my version of the puzzle description each time; you'll have to follow the links in the section headers (e.g. **[Day 1](https://adventofcode.com/2017/day/1)**) to read those. I just show my solutions.\n",
    "\n",
    "First, a set of imports and utility functions that might prove useful:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Python 3.x Utility Functions\n",
    "\n",
    "import re\n",
    "import numpy as np\n",
    "import math\n",
    "import random\n",
    "import urllib.request\n",
    "\n",
    "from collections import Counter, defaultdict, namedtuple, deque\n",
    "from functools   import lru_cache\n",
    "from itertools   import permutations, combinations, chain, cycle, product, islice, count as count_from\n",
    "from heapq       import heappop, heappush\n",
    "\n",
    "identity = lambda x: x\n",
    "letters  = 'abcdefghijklmnopqrstuvwxyz'\n",
    "\n",
    "cat = ''.join\n",
    "\n",
    "Ø   = frozenset() # Empty set\n",
    "inf = float('inf')\n",
    "BIG = 10 ** 999\n",
    "\n",
    "def Input(day, year=2017):\n",
    "    \"Open this day's input file.\"\n",
    "    filename = 'data/advent{}/input{}.txt'.format(year, day)\n",
    "    try:\n",
    "        return open(filename)\n",
    "    except FileNotFoundError:\n",
    "        return urllib.request.urlopen(\"http://norvig.com/ipython/\" + filename)\n",
    "    \n",
    "def array(lines):\n",
    "    \"Convert an iterable of lines into a 2-D array. If lines is a str, do splitlines.\"\n",
    "    if isinstance(lines, str): lines = lines.splitlines()\n",
    "    return [mapt(atom, line.split()) for line in lines]\n",
    "\n",
    "def atom(token):\n",
    "    try:\n",
    "        return int(token)\n",
    "    except ValueError:\n",
    "        try:\n",
    "            return float(token)\n",
    "        except ValueError:\n",
    "            return token\n",
    "\n",
    "################ Functions on Iterables\n",
    "\n",
    "def first(iterable, default=None): return next(iter(iterable), default)\n",
    "\n",
    "def first_true(iterable, pred=None, default=None):\n",
    "    \"\"\"Returns the first true value in the iterable.\n",
    "    If no true value is found, returns *default*\n",
    "    If *pred* is not None, returns the first item\n",
    "    for which pred(item) is true.\"\"\"\n",
    "    # first_true([a,b,c], default=x) --> a or b or c or x\n",
    "    # first_true([a,b], fn, x) --> a if fn(a) else b if fn(b) else x\n",
    "    return next(filter(pred, iterable), default)\n",
    "\n",
    "def nth(iterable, n, default=None):\n",
    "    \"Returns the nth item of iterable, or a default value\"\n",
    "    return next(islice(iterable, n, None), default)\n",
    "\n",
    "def upto(iterable, maxval):\n",
    "    \"From a monotonically increasing iterable, generate all the values <= maxval.\"\n",
    "    # Why <= maxval rather than < maxval? In part because that's how Ruby's upto does it.\n",
    "    return takewhile(lambda x: x <= maxval, iterable)\n",
    "\n",
    "def groupby(iterable, key=identity):\n",
    "    \"Return a dict of {key(item): [items...]} grouping all items in iterable by keys.\"\n",
    "    groups = defaultdict(list)\n",
    "    for item in iterable:\n",
    "        groups[key(item)].append(item)\n",
    "    return groups\n",
    "\n",
    "def grouper(iterable, n, fillvalue=None):\n",
    "    \"\"\"Collect data into fixed-length chunks:\n",
    "    grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx\"\"\"\n",
    "    args = [iter(iterable)] * n\n",
    "    return zip_longest(*args, fillvalue=fillvalue)\n",
    "\n",
    "def overlapping(iterable, n):\n",
    "    \"\"\"Generate all (overlapping) n-element subsequences of iterable.\n",
    "    overlapping('ABCDEFG', 3) --> ABC BCD CDE DEF EFG\"\"\"\n",
    "    if isinstance(iterable, abc.Sequence):\n",
    "        yield from (iterable[i:i+n] for i in range(len(iterable) + 1 - n))\n",
    "    else:\n",
    "        result = deque(maxlen=n)\n",
    "        for x in iterable:\n",
    "            result.append(x)\n",
    "            if len(result) == n:\n",
    "                yield tuple(result)\n",
    "                \n",
    "def pairwise(iterable):\n",
    "    \"s -> (s0,s1), (s1,s2), (s2, s3), ...\"\n",
    "    return overlapping(iterable, 2)\n",
    "\n",
    "def sequence(iterable, type=tuple):\n",
    "    \"Coerce iterable to sequence: leave it alone if it is already a sequence, else make it of type.\"\n",
    "    return iterable if isinstance(iterable, abc.Sequence) else type(iterable)\n",
    "\n",
    "def join(iterable, sep=''):\n",
    "    \"Join the items in iterable, converting each to a string first.\"\n",
    "    return sep.join(map(str, iterable))\n",
    "                \n",
    "def powerset(iterable):\n",
    "    \"Yield all subsets of items.\"\n",
    "    items = list(iterable)\n",
    "    for r in range(len(items)+1):\n",
    "        for c in combinations(items, r):\n",
    "            yield c\n",
    "            \n",
    "def quantify(iterable, pred=bool):\n",
    "    \"Count how many times the predicate is true.\"\n",
    "    return sum(map(pred, iterable))\n",
    "\n",
    "def shuffled(iterable):\n",
    "    \"Create a new list out of iterable, and shuffle it.\"\n",
    "    new = list(iterable)\n",
    "    random.shuffle(new)\n",
    "    return new\n",
    "    \n",
    "flatten = chain.from_iterable\n",
    "            \n",
    "class Set(frozenset):\n",
    "    \"A frozenset, but with a prettier printer.\"\n",
    "    def __repr__(self): return '{' + join(sorted(self), ', ') + '}'\n",
    "    \n",
    "def canon(items, typ=None):\n",
    "    \"Canonicalize these order-independent items into a hashable canonical form.\"\n",
    "    typ = (typ or (cat if isinstance(items, str) else tuple))\n",
    "    return typ(sorted(items))\n",
    "\n",
    "def mapt(fn, *args): \n",
    "    \"Do a map, and make the results into a tuple.\"\n",
    "    return tuple(map(fn, *args))\n",
    "            \n",
    "################ Math\n",
    "            \n",
    "def transpose(matrix): return tuple(zip(*matrix))\n",
    "\n",
    "def isqrt(n):\n",
    "    \"Integer square root (rounds down).\"\n",
    "    return int(n ** 0.5)\n",
    "\n",
    "def ints(start, end):\n",
    "    \"The integers from start to end, inclusive. Equivalent to range(start, end+1)\"\n",
    "    return range(start, end + 1)\n",
    "\n",
    "def floats(start, end, step=1.0):\n",
    "    \"Yields start, start+step, start+2*step, ... up to (maybe including) end.\"\n",
    "    m = (1.0 if step >= 0 else -1.0)\n",
    "    while start * m <= end * m:\n",
    "        yield start\n",
    "        start += step\n",
    "        \n",
    "def multiply(numbers):\n",
    "    \"Multiply all the numbers together.\"\n",
    "    result = 1\n",
    "    for n in numbers:\n",
    "        result *= n\n",
    "    return result\n",
    "\n",
    "################ 2-D points implemented using (x, y) tuples\n",
    "\n",
    "def X(point): x, y = point; return x\n",
    "def Y(point): x, y = point; return y\n",
    "\n",
    "def neighbors4(point): \n",
    "    \"The four neighboring squares.\"\n",
    "    x, y = point\n",
    "    return (          (x, y-1),\n",
    "            (x-1, y),           (x+1, y), \n",
    "                      (x, y+1))\n",
    "\n",
    "def neighbors8(point): \n",
    "    \"The eight neighboring squares.\"\n",
    "    x, y = point \n",
    "    return ((x-1, y-1), (x, y-1), (x+1, y-1),\n",
    "            (x-1, y),             (x+1, y),\n",
    "            (x+1, y+1), (x, y+1), (x+1, y+1))\n",
    "\n",
    "def cityblock_distance(p, q=(0, 0)): \n",
    "    \"Manhatten distance between two points.\"\n",
    "    return abs(X(p) - X(q)) + abs(Y(p) - Y(q))\n",
    "\n",
    "def distance(p, q=(0, 0)): \n",
    "    \"Hypotenuse distance between two points.\"\n",
    "    return math.hypot(X(p) - X(q), Y(p) - Y(q))\n",
    "\n",
    "################ Debugging \n",
    "\n",
    "def trace1(f):\n",
    "    \"Print a trace of the input and output of a function on one line.\"\n",
    "    def traced_f(*args):\n",
    "        result = f(*args)\n",
    "        print('{}({}) = {}'.format(f.__name__, ', '.join(map(str, args)), result))\n",
    "        return result\n",
    "    return traced_f\n",
    "\n",
    "def grep(pattern, iterable):\n",
    "    \"Print lines from iterable that match pattern.\"\n",
    "    for line in iterable:\n",
    "        if re.search(pattern, line):\n",
    "            print(line)\n",
    "\n",
    "################ A* and Breadth-First Search (we track states, not actions)\n",
    "\n",
    "def Astar(start, moves_func, h_func, cost_func=lambda s, s2: 1):\n",
    "    \"Find a shortest sequence of states from start to a goal state (a state s with h_func(s) == 0).\"\n",
    "    frontier  = [(h_func(start), start)] # A priority queue, ordered by path length, f = g + h\n",
    "    previous  = {start: None}  # start state has no previous state; other states will\n",
    "    path_cost = {start: 0}     # The cost of the best path to a state.\n",
    "    Path      = lambda s: ([] if (s is None) else Path(previous[s]) + [s])\n",
    "    while frontier:\n",
    "        (f, s) = heappop(frontier)\n",
    "        if h_func(s) == 0:\n",
    "            return Path(s)\n",
    "        for s2 in moves_func(s):\n",
    "            g = path_cost[s] + cost_func(s, s2)\n",
    "            if s2 not in path_cost or g < path_cost[s2]:\n",
    "                heappush(frontier, (g + h_func(s2), s2))\n",
    "                path_cost[s2] = g\n",
    "                previous[s2] = s\n",
    "\n",
    "def bfs(start, moves_func, goals):\n",
    "    \"Breadth-first search\"\n",
    "    goal_func = (goals if callable(goals) else lambda s: s in goals)\n",
    "    return Astar(start, moves_func, lambda s: (0 if goal_func(s) else 1))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# [Day 1](https://adventofcode.com/2017/day/1): Inverse Captcha\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "raw = '3294199471327195994824832197564859876682638188889768298894243832665654681412886862234525991553276578641265589959178414218389329361496673991614673626344552179413995562266818138372393213966143124914469397692587251112663217862879233226763533911128893354536353213847122251463857894159819828724827969576432191847787772732881266875469721189331882228146576832921314638221317393256471998598117289632684663355273845983933845721713497811766995367795857965222183668765517454263354111134841334631345111596131682726196574763165187889337599583345634413436165539744188866156771585647718555182529936669683581662398618765391487164715724849894563314426959348119286955144439452731762666568741612153254469131724137699832984728937865956711925592628456617133695259554548719328229938621332325125972547181236812263887375866231118312954369432937359357266467383318326239572877314765121844831126178173988799765218913178825966268816476559792947359956859989228917136267178571776316345292573489873792149646548747995389669692188457724414468727192819919448275922166321158141365237545222633688372891451842434458527698774342111482498999383831492577615154591278719656798277377363284379468757998373193231795767644654155432692988651312845433511879457921638934877557575241394363721667237778962455961493559848522582413748218971212486373232795878362964873855994697149692824917183375545192119453587398199912564474614219929345185468661129966379693813498542474732198176496694746111576925715493967296487258237854152382365579876894391815759815373319159213475555251488754279888245492373595471189191353244684697662848376529881512529221627313527441221459672786923145165989611223372241149929436247374818467481641931872972582295425936998535194423916544367799522276914445231582272368388831834437562752119325286474352863554693373718848649568451797751926315617575295381964426843625282819524747119726872193569785611959896776143539915299968276374712996485367853494734376257511273443736433464496287219615697341973131715166768916149828396454638596713572963686159214116763'\n",
    "\n",
    "digits = mapt(int, raw)\n",
    "N = len(digits)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1158"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sum(digits[i] \n",
    "    for i in range(N) \n",
    "    if digits[i] == digits[i - 1])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1132"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sum(digits[i] \n",
    "    for i in range(N) \n",
    "    if digits[i] == digits[i - N//2])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "I was able to do this warmup puzzle very quickly; too bad I started a few hours too late to make in on the leader board. I created a recurring calendar reminder so I'll be less likely to be late in the future."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# [Day 2](https://adventofcode.com/2017/day/2): Corruption Checksum\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "rows = array('''790\t99\t345\t1080\t32\t143\t1085\t984\t553\t98\t123\t97\t197\t886\t125\t947\n",
    "302\t463\t59\t58\t55\t87\t508\t54\t472\t63\t469\t419\t424\t331\t337\t72\n",
    "899\t962\t77\t1127\t62\t530\t78\t880\t129\t1014\t93\t148\t239\t288\t357\t424\n",
    "2417\t2755\t254\t3886\t5336\t3655\t5798\t3273\t5016\t178\t270\t6511\t223\t5391\t1342\t2377\n",
    "68\t3002\t3307\t166\t275\t1989\t1611\t364\t157\t144\t3771\t1267\t3188\t3149\t156\t3454\n",
    "1088\t1261\t21\t1063\t1173\t278\t1164\t207\t237\t1230\t1185\t431\t232\t660\t195\t1246\n",
    "49\t1100\t136\t1491\t647\t1486\t112\t1278\t53\t1564\t1147\t1068\t809\t1638\t138\t117\n",
    "158\t3216\t1972\t2646\t3181\t785\t2937\t365\t611\t1977\t1199\t2972\t201\t2432\t186\t160\n",
    "244\t86\t61\t38\t58\t71\t243\t52\t245\t264\t209\t265\t308\t80\t126\t129\n",
    "1317\t792\t74\t111\t1721\t252\t1082\t1881\t1349\t94\t891\t1458\t331\t1691\t89\t1724\n",
    "3798\t202\t3140\t3468\t1486\t2073\t3872\t3190\t3481\t3760\t2876\t182\t2772\t226\t3753\t188\n",
    "2272\t6876\t6759\t218\t272\t4095\t4712\t6244\t4889\t2037\t234\t223\t6858\t3499\t2358\t439\n",
    "792\t230\t886\t824\t762\t895\t99\t799\t94\t110\t747\t635\t91\t406\t89\t157\n",
    "2074\t237\t1668\t1961\t170\t2292\t2079\t1371\t1909\t221\t2039\t1022\t193\t2195\t1395\t2123\n",
    "8447\t203\t1806\t6777\t278\t2850\t1232\t6369\t398\t235\t212\t992\t7520\t7304\t7852\t520\n",
    "3928\t107\t3406\t123\t2111\t2749\t223\t125\t134\t146\t3875\t1357\t508\t1534\t4002\t4417''')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "46402"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sum(abs(max(row) - min(row)) for row in rows)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "265"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def evendiv(row): \n",
    "    return first(a // b for a in row for b in row if a > b and a // b == a / b)\n",
    "\n",
    "sum(map(evendiv, rows))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Today I remembered to start on time, but I was too slow to score any points; the best I could do was 112th place on Part Two. \n",
    "\n",
    "In Part One, my big mistake was typing `\"=\"` instead of `\"-\"` in `\"max(row) - min(row)\"`. I was confused by Python's misleading error message, which said `\"SyntaxError: keyword can't be an expression\"` and pointed at `\"max\"`."
   ]
  }
 ],
 "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": 2
}
