Delete AdventUtils.ipynb
This commit is contained in:
@@ -1,530 +0,0 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div style=\"text-align: right\" align=\"right\"><i>Peter Norvig<br>Decembers 2016–2021</i></div>\n",
|
||||
"\n",
|
||||
"# Advent of Code Utilities\n",
|
||||
"\n",
|
||||
"Stuff I might need for [Advent of Code](https://adventofcode.com). First, some imports that I have used in past AoC years:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from collections import Counter, defaultdict, namedtuple, deque, abc\n",
|
||||
"from dataclasses import dataclass\n",
|
||||
"from itertools import permutations, combinations, cycle, chain\n",
|
||||
"from itertools import count as count_from, product as cross_product\n",
|
||||
"from typing import *\n",
|
||||
"from statistics import mean, median\n",
|
||||
"from math import ceil, floor, factorial, gcd, log, log2, log10, sqrt, inf\n",
|
||||
"from functools import reduce\n",
|
||||
"\n",
|
||||
"import matplotlib.pyplot as plt\n",
|
||||
"import time\n",
|
||||
"import heapq\n",
|
||||
"import string\n",
|
||||
"import functools\n",
|
||||
"import pathlib\n",
|
||||
"import re"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Daily Input Parsing\n",
|
||||
"\n",
|
||||
"Each day's work will consist of three tasks, denoted by three sections in the notebook:\n",
|
||||
"- **Input**: Parse the day's input file. I will use the function `parse(day, parser, sep)`, which:\n",
|
||||
" - Reads the input file for `day`.\n",
|
||||
" - Breaks the file into a sequence of *items* separated by `sep` (default newline).\n",
|
||||
" - Applies `parser` to each item and returns the results as a tuple.\n",
|
||||
" - Useful parser functions include `ints`, `digits`, `atoms`, `words`, and the built-ins `int` and `str`.\n",
|
||||
" - Prints the first few input lines and output records. This is useful to me as a debugging tool, and to the reader.\n",
|
||||
"- **Part 1**: Understand the day's instructions and:\n",
|
||||
" - Write code to compute the answer to Part 1.\n",
|
||||
" - Once I have computed the answer and submitted it to the AoC site to verify it is correct, I record it with the `answer` function.\n",
|
||||
"- **Part 2**: Repeat the above steps for Part 2.\n",
|
||||
"- Occasionally I'll introduce a **Part 3** where I explore beyond the official instructions.\n",
|
||||
"\n",
|
||||
"Here is `parse` and some helper functions for it:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 18,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"current_year = 2022 # Subdirectory name for input files\n",
|
||||
"lines = '\\n' # For inputs where each record is a line\n",
|
||||
"paragraphs = '\\n\\n' # For inputs where each record is a paragraph \n",
|
||||
"\n",
|
||||
"def parse(day_or_text:Union[int, str], parser:Callable=str, sep:Callable=lines, show=6) -> tuple:\n",
|
||||
" \"\"\"Split the input text into items separated by `sep`, and apply `parser` to each.\n",
|
||||
" The first argument is either the text itself, or the day number of a text file.\"\"\"\n",
|
||||
" text = get_text(day_or_text)\n",
|
||||
" show_parse_items('Puzzle input', text.splitlines(), show, 'line')\n",
|
||||
" items = mapt(parser, text.rstrip().split(sep))\n",
|
||||
" if parser != str:\n",
|
||||
" show_parse_items('Parsed representation', items, show, f'{type(items[0]).__name__}')\n",
|
||||
" return items\n",
|
||||
"\n",
|
||||
"def get_text(day_or_text:Union[int, str]) -> str:\n",
|
||||
" \"\"\"The text used as input to the puzzle: either a string or the day number of a file.\"\"\"\n",
|
||||
" if isinstance(day_or_text, int):\n",
|
||||
" return pathlib.Path(f'AOC/{current_year}/input{day_or_text}.txt').read_text()\n",
|
||||
" else:\n",
|
||||
" return day_or_text\n",
|
||||
"\n",
|
||||
"def show_parse_items(source, items, show:int, name:str, sep=\"─\"*100):\n",
|
||||
" \"\"\"Show verbose output from `parse` for lines or items.\"\"\"\n",
|
||||
" if not show:\n",
|
||||
" return\n",
|
||||
" count = f'1 {name}' if len(items) == 1 else f'{len(items)} {name}s'\n",
|
||||
" for line in (sep, f'{source} ➜ {count}:', sep, *items[:show]):\n",
|
||||
" print(truncate(line, 100))\n",
|
||||
" if show < len(items):\n",
|
||||
" print('...')\n",
|
||||
" \n",
|
||||
"def truncate(object, width) -> str:\n",
|
||||
" \"\"\"Use elipsis to truncate `str(object)` to `width` characters, if necessary.\"\"\"\n",
|
||||
" string = str(object)\n",
|
||||
" return string if len(string) <= width else string[:width-4] + ' ...'\n",
|
||||
"\n",
|
||||
"def parse_sections(specs: Iterable) -> Callable:\n",
|
||||
" \"\"\"Return a parser that uses the first spec to parse the first section, the second for second, etc.\n",
|
||||
" Each spec is either parser or [parser, sep].\"\"\"\n",
|
||||
" specs = ([spec] if callable(spec) else spec for spec in specs)\n",
|
||||
" fns = ((lambda section: parse(section, *spec, show=0)) for spec in specs)\n",
|
||||
" return lambda section: next(fns)(section)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 38,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"## Functions that can be used by `parse`\n",
|
||||
"\n",
|
||||
"Char = str # Intended as the type of a one-character string\n",
|
||||
"Atom = Union[str, float, int] # The type of a string or number\n",
|
||||
"\n",
|
||||
"def ints(text: str) -> Tuple[int]:\n",
|
||||
" \"\"\"A tuple of all the integers in text, ignoring non-number characters.\"\"\"\n",
|
||||
" return mapt(int, re.findall(r'-?[0-9]+', text))\n",
|
||||
"\n",
|
||||
"def positive_ints(text: str) -> Tuple[int]:\n",
|
||||
" \"\"\"A tuple of all the integers in text, ignoring non-number characters.\"\"\"\n",
|
||||
" return mapt(int, re.findall(r'[0-9]+', text))\n",
|
||||
"\n",
|
||||
"def digits(text: str) -> Tuple[int]:\n",
|
||||
" \"\"\"A tuple of all the digits in text (as ints 0–9), ignoring non-digit characters.\"\"\"\n",
|
||||
" return mapt(int, re.findall(r'[0-9]', text))\n",
|
||||
"\n",
|
||||
"def words(text: str) -> Tuple[str]:\n",
|
||||
" \"\"\"A tuple of all the alphabetic words in text, ignoring non-letters.\"\"\"\n",
|
||||
" return tuple(re.findall(r'[a-zA-Z]+', text))\n",
|
||||
"\n",
|
||||
"def atoms(text: str) -> Tuple[Atom]:\n",
|
||||
" \"\"\"A tuple of all the atoms (numbers or identifiers) in text. Skip punctuation.\"\"\"\n",
|
||||
" return mapt(atom, re.findall(r'[_a-zA-Z0-9.+-]+', text))\n",
|
||||
"\n",
|
||||
"def atom(text: str) -> Atom:\n",
|
||||
" \"\"\"Parse text into a single float or int or str.\"\"\"\n",
|
||||
" try:\n",
|
||||
" x = float(text)\n",
|
||||
" return round(x) if x.is_integer() else x\n",
|
||||
" except ValueError:\n",
|
||||
" return text"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Daily Answers\n",
|
||||
"\n",
|
||||
"Here is the `answer` function:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# `answers` is a dict of {puzzle_number_id: message_about_results}\n",
|
||||
"answers = {} \n",
|
||||
"\n",
|
||||
"def answer(puzzle, correct, code: callable) -> None:\n",
|
||||
" \"\"\"Verify that calling `code` computes the `correct` answer for `puzzle`. \n",
|
||||
" Record results in the dict `answers`. Prints execution time.\"\"\"\n",
|
||||
" def pretty(x): return f'{x:,d}' if is_int(x) else str(x)\n",
|
||||
" start = time.time()\n",
|
||||
" got = code()\n",
|
||||
" dt = time.time() - start\n",
|
||||
" ans = pretty(got)\n",
|
||||
" msg = f'{dt:5.3f} seconds for ' + (\n",
|
||||
" f'correct answer: {ans}' if (got == correct) else\n",
|
||||
" f'WRONG!! answer: {ans}; expected {pretty(correct)}')\n",
|
||||
" answers[puzzle] = msg\n",
|
||||
" assert got == correct, msg\n",
|
||||
" print(msg)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Additional utility functions "
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 16,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def quantify(iterable, pred=bool) -> int:\n",
|
||||
" \"\"\"Count the number of items in iterable for which pred is true.\"\"\"\n",
|
||||
" return sum(1 for item in iterable if pred(item))\n",
|
||||
"\n",
|
||||
"def mapt(function: Callable, sequence) -> tuple:\n",
|
||||
" \"\"\"`map`, with the result as a tuple.\"\"\"\n",
|
||||
" return tuple(map(function, sequence))\n",
|
||||
"\n",
|
||||
"class multimap(defaultdict):\n",
|
||||
" \"\"\"A mapping of {key: [val1, val2, ...]}.\"\"\"\n",
|
||||
" def __init__(self, pairs: Iterable[tuple], symmetric=False):\n",
|
||||
" \"\"\"Given (key, val) pairs, return {key: [val, ...], ...}.\n",
|
||||
" If `symmetric` is True, treat (key, val) as (key, val) plus (val, key).\"\"\"\n",
|
||||
" self.default_factory = list\n",
|
||||
" for (key, val) in pairs:\n",
|
||||
" self[key].append(val)\n",
|
||||
" if symmetric:\n",
|
||||
" self[val].append(key)\n",
|
||||
"\n",
|
||||
"def prod(numbers) -> float: # Will be math.prod in Python 3.8\n",
|
||||
" \"\"\"The product formed by multiplying `numbers` together.\"\"\"\n",
|
||||
" result = 1\n",
|
||||
" for x in numbers:\n",
|
||||
" result *= x\n",
|
||||
" return result\n",
|
||||
"\n",
|
||||
"def total(counter: Counter) -> int: \n",
|
||||
" \"\"\"The sum of all the counts in a Counter.\"\"\"\n",
|
||||
" return sum(counter.values())\n",
|
||||
"\n",
|
||||
"def minmax(numbers) -> Tuple[int, int]:\n",
|
||||
" \"\"\"A tuple of the (minimum, maximum) of numbers.\"\"\"\n",
|
||||
" numbers = list(numbers)\n",
|
||||
" return min(numbers), max(numbers)\n",
|
||||
"\n",
|
||||
"def first(iterable) -> Optional[object]: \n",
|
||||
" \"\"\"The first element in an iterable, or None.\"\"\"\n",
|
||||
" return next(iter(iterable), None)\n",
|
||||
"\n",
|
||||
"def T(matrix: Sequence[Sequence]) -> List[Tuple]:\n",
|
||||
" \"\"\"The transpose of a matrix: T([(1,2,3), (4,5,6)]) == [(1,4), (2,5), (3,6)]\"\"\"\n",
|
||||
" return list(zip(*matrix))\n",
|
||||
"\n",
|
||||
"def cover(*integers) -> range:\n",
|
||||
" \"\"\"A `range` that covers all the given integers, and any in between them.\n",
|
||||
" cover(lo, hi) is a an inclusive (or closed) range, equal to range(lo, hi + 1).\"\"\"\n",
|
||||
" return range(min(integers), max(integers) + 1)\n",
|
||||
"\n",
|
||||
"def the(sequence) -> object:\n",
|
||||
" \"\"\"Return the one item in a sequence. Raise error if not exactly one.\"\"\"\n",
|
||||
" items = list(sequence)\n",
|
||||
" if not len(items) == 1:\n",
|
||||
" raise ValueError(f'Expected exactly one item in the sequence {items}')\n",
|
||||
" return items[0]\n",
|
||||
"\n",
|
||||
"def split_at(sequence, i) -> Tuple[Sequence, Sequence]:\n",
|
||||
" \"\"\"The sequence split into two pieces: (before position i, and i-and-after).\"\"\"\n",
|
||||
" return sequence[:i], sequence[i:]\n",
|
||||
"\n",
|
||||
"def batched(data, n) -> list:\n",
|
||||
" \"Batch data into lists of length n. The last batch may be shorter.\"\n",
|
||||
" # batched('ABCDEFG', 3) --> ABC DEF G\n",
|
||||
" return [data[i:i+n] for i in range(0, len(data), n)]\n",
|
||||
"\n",
|
||||
"def sliding_window(sequence, n) -> Iterable[Sequence]:\n",
|
||||
" \"\"\"All length-n subsequences of sequence.\"\"\"\n",
|
||||
" return (sequence[i:i+n] for i in range(len(sequence) + 1 - n))\n",
|
||||
"\n",
|
||||
"def ignore(*args) -> None: \"Just return None.\"; return None\n",
|
||||
"\n",
|
||||
"def is_int(x) -> bool: \"Is x an int?\"; return isinstance(x, int) \n",
|
||||
"\n",
|
||||
"def sign(x) -> int: \"0, +1, or -1\"; return (0 if x == 0 else +1 if x > 0 else -1)\n",
|
||||
"\n",
|
||||
"def append(sequences) -> Sequence: \"Append sequences into a list\"; return list(flatten(sequences))\n",
|
||||
"\n",
|
||||
"def union(sets) -> set: \"Union of several sets\"; return set().union(*sets)\n",
|
||||
"\n",
|
||||
"def intersection(sets):\n",
|
||||
" \"Intersection of several sets.\"\n",
|
||||
" first, *rest = sets\n",
|
||||
" return set(first).intersection(*rest)\n",
|
||||
"\n",
|
||||
"def square_plot(points, marker='o', size=12, extra=None, **kwds):\n",
|
||||
" \"\"\"Plot `points` in a square of given `size`, with no axis labels.\n",
|
||||
" Calls `extra()` to do more plt.* stuff if defined.\"\"\"\n",
|
||||
" plt.figure(figsize=(size, size))\n",
|
||||
" plt.plot(*T(points), marker, **kwds)\n",
|
||||
" if extra: extra()\n",
|
||||
" plt.axis('square'); plt.axis('off'); plt.gca().invert_yaxis()\n",
|
||||
" \n",
|
||||
"def clock_mod(i, m) -> int:\n",
|
||||
" \"\"\"i % m, but replace a result of 0 with m\"\"\"\n",
|
||||
" # This is like a clock, where 24 mod 12 is 12, not 0.\n",
|
||||
" return (i % m) or m\n",
|
||||
"\n",
|
||||
"flatten = chain.from_iterable # Yield items from each sequence in turn\n",
|
||||
"cat = ''.join\n",
|
||||
"cache = functools.lru_cache(None)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Points on a Grid"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"Point = Tuple[int, int] # (x, y) points on a grid\n",
|
||||
"\n",
|
||||
"def X_(point) -> int: \"X coordinate\"; return point[0]\n",
|
||||
"def Y_(point) -> int: \"Y coordinate\"; return point[1]\n",
|
||||
"\n",
|
||||
"def distance(p: Point, q: Point) -> float:\n",
|
||||
" \"\"\"Distance between two points.\"\"\"\n",
|
||||
" dx, dy = abs(X_(p) - X_(q)), abs(Y_(p) - Y_(q))\n",
|
||||
" return dx + dy if dx == 0 or dy == 0 else (dx ** 2 + dy ** 2) ** 0.5\n",
|
||||
"\n",
|
||||
"def manhatten_distance(p: Point, q: Point) -> int:\n",
|
||||
" \"\"\"Distance along grid lines between two points.\"\"\"\n",
|
||||
" return sum(abs(pi - qi) for pi, qi in zip(p, q))\n",
|
||||
"\n",
|
||||
"def add(p: Point, q: Point) -> Point:\n",
|
||||
" \"\"\"Add two points.\"\"\"\n",
|
||||
" return (X_(p) + X_(q), Y_(p) + Y_(q))\n",
|
||||
"\n",
|
||||
"def sub(p: Point, q: Point) -> Point:\n",
|
||||
" \"\"\"Subtract point q from point p.\"\"\"\n",
|
||||
" return (X_(p) - X_(q), Y_(p) - Y_(q))\n",
|
||||
"\n",
|
||||
"directions4 = North, South, East, West = ((0, -1), (0, 1), (1, 0), (-1, 0))\n",
|
||||
"directions8 = directions4 + ((1, 1), (1, -1), (-1, 1), (-1, -1))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 13,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class Grid(dict):\n",
|
||||
" \"\"\"A 2D grid, implemented as a mapping of {(x, y): cell_contents}.\"\"\"\n",
|
||||
" def __init__(self, mapping_or_rows, directions=directions4):\n",
|
||||
" \"\"\"Initialize with either (e.g.) `Grid({(0, 0): 1, (1, 0): 2, ...})`, or\n",
|
||||
" `Grid([(1, 2, 3), (4, 5, 6)]).\"\"\"\n",
|
||||
" self.update(mapping_or_rows if isinstance(mapping_or_rows, abc.Mapping) else\n",
|
||||
" {(x, y): val \n",
|
||||
" for y, row in enumerate(mapping_or_rows) \n",
|
||||
" for x, val in enumerate(row)})\n",
|
||||
" self.width = max(map(X_, self)) + 1\n",
|
||||
" self.height = max(map(Y_, self)) + 1\n",
|
||||
" self.directions = directions\n",
|
||||
" \n",
|
||||
" def copy(self): return Grid(self, directions=self.directions)\n",
|
||||
" \n",
|
||||
" def neighbors(self, point) -> List[Point]:\n",
|
||||
" \"\"\"Points on the grid that neighbor `point`.\"\"\"\n",
|
||||
" return [add(point, Δ) for Δ in self.directions if add(point, Δ) in self]\n",
|
||||
" \n",
|
||||
" def to_rows(self, default='.') -> List[List[object]]:\n",
|
||||
" \"\"\"The contents of the grid in a rectangular list of lists.\"\"\"\n",
|
||||
" return [[self.get((x, y), default) for x in range(self.width)]\n",
|
||||
" for y in range(self.height)]\n",
|
||||
" \n",
|
||||
" def to_picture(self, sep='', default='.') -> str:\n",
|
||||
" \"\"\"The contents of the grid as a picture.\"\"\"\n",
|
||||
" return '\\n'.join(map(cat, self.to_rows(default)))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# A* Search"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def A_star_search(problem, h=None):\n",
|
||||
" \"\"\"Search nodes with minimum f(n) = path_cost(n) + h(n) value first.\"\"\"\n",
|
||||
" h = h or problem.h\n",
|
||||
" return best_first_search(problem, f=lambda n: n.path_cost + h(n))\n",
|
||||
"\n",
|
||||
"def best_first_search(problem, f):\n",
|
||||
" \"Search nodes with minimum f(node) value first.\"\n",
|
||||
" node = Node(problem.initial)\n",
|
||||
" frontier = PriorityQueue([node], key=f)\n",
|
||||
" reached = {problem.initial: node}\n",
|
||||
" while frontier:\n",
|
||||
" node = frontier.pop()\n",
|
||||
" if problem.is_goal(node.state):\n",
|
||||
" return node\n",
|
||||
" for child in expand(problem, node):\n",
|
||||
" s = child.state\n",
|
||||
" if s not in reached or child.path_cost < reached[s].path_cost:\n",
|
||||
" reached[s] = child\n",
|
||||
" frontier.add(child)\n",
|
||||
" return search_failure"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class SearchProblem:\n",
|
||||
" \"\"\"The abstract class for a search problem. A new domain subclasses this,\n",
|
||||
" overriding `actions` and perhaps other methods.\n",
|
||||
" The default heuristic is 0 and the default action cost is 1 for all states.\n",
|
||||
" When you create an instance of a subclass, specify `initial`, and `goal` states \n",
|
||||
" (or give an `is_goal` method) and perhaps other keyword args for the subclass.\"\"\"\n",
|
||||
"\n",
|
||||
" def __init__(self, initial=None, goal=None, **kwds): \n",
|
||||
" self.__dict__.update(initial=initial, goal=goal, **kwds) \n",
|
||||
" \n",
|
||||
" def __str__(self):\n",
|
||||
" return '{}({!r}, {!r})'.format(type(self).__name__, self.initial, self.goal)\n",
|
||||
" \n",
|
||||
" def actions(self, state): raise NotImplementedError\n",
|
||||
" def result(self, state, action): return action # Simplest case: action is result state\n",
|
||||
" def is_goal(self, state): return state == self.goal\n",
|
||||
" def action_cost(self, s, a, s1): return 1\n",
|
||||
" def h(self, node): return 0 # Never overestimate!\n",
|
||||
" \n",
|
||||
"class GridProblem(SearchProblem):\n",
|
||||
" \"\"\"Problem for searching a grid from a start to a goal location.\n",
|
||||
" A states is just an (x, y) location in the grid.\"\"\"\n",
|
||||
" def actions(self, loc): return self.grid.neighbors(loc)\n",
|
||||
" def result(self, loc1, loc2): return loc2\n",
|
||||
" def action_cost(self, s1, a, s2): return self.grid[s2]\n",
|
||||
" def h(self, node): return manhatten_distance(node.state, self.goal) \n",
|
||||
"\n",
|
||||
"class Node:\n",
|
||||
" \"A Node in a search tree.\"\n",
|
||||
" def __init__(self, state, parent=None, action=None, path_cost=0):\n",
|
||||
" self.__dict__.update(state=state, parent=parent, action=action, path_cost=path_cost)\n",
|
||||
"\n",
|
||||
" def __repr__(self): return f'Node({self.state})'\n",
|
||||
" def __len__(self): return 0 if self.parent is None else (1 + len(self.parent))\n",
|
||||
" def __lt__(self, other): return self.path_cost < other.path_cost\n",
|
||||
" \n",
|
||||
"search_failure = Node('failure', path_cost=inf) # Indicates an algorithm couldn't find a solution.\n",
|
||||
" \n",
|
||||
"def expand(problem, node):\n",
|
||||
" \"Expand a node, generating the children nodes.\"\n",
|
||||
" s = node.state\n",
|
||||
" for action in problem.actions(s):\n",
|
||||
" s2 = problem.result(s, action)\n",
|
||||
" cost = node.path_cost + problem.action_cost(s, action, s2)\n",
|
||||
" yield Node(s2, node, action, cost)\n",
|
||||
" \n",
|
||||
"def path_actions(node):\n",
|
||||
" \"The sequence of actions to get to this node.\"\n",
|
||||
" if node.parent is None:\n",
|
||||
" return [] \n",
|
||||
" return path_actions(node.parent) + [node.action]\n",
|
||||
"\n",
|
||||
"def path_states(node):\n",
|
||||
" \"The sequence of states to get to this node.\"\n",
|
||||
" if node in (search_failure, None): \n",
|
||||
" return []\n",
|
||||
" return path_states(node.parent) + [node.state]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class PriorityQueue:\n",
|
||||
" \"\"\"A queue in which the item with minimum key(item) is always popped first.\"\"\"\n",
|
||||
"\n",
|
||||
" def __init__(self, items=(), key=lambda x: x): \n",
|
||||
" self.key = key\n",
|
||||
" self.items = [] # a heap of (score, item) pairs\n",
|
||||
" for item in items:\n",
|
||||
" self.add(item)\n",
|
||||
" \n",
|
||||
" def add(self, item):\n",
|
||||
" \"\"\"Add item to the queue.\"\"\"\n",
|
||||
" pair = (self.key(item), item)\n",
|
||||
" heapq.heappush(self.items, pair)\n",
|
||||
"\n",
|
||||
" def pop(self):\n",
|
||||
" \"\"\"Pop and return the item with min f(item) value.\"\"\"\n",
|
||||
" return heapq.heappop(self.items)[1]\n",
|
||||
" \n",
|
||||
" def top(self): return self.items[0][1]\n",
|
||||
"\n",
|
||||
" def __len__(self): return len(self.items)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.7.6"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 4
|
||||
}
|
||||
Reference in New Issue
Block a user