{ "cells": [ { "cell_type": "markdown", "id": "b95f5544-6519-45d3-a3f2-a58350abd3ce", "metadata": {}, "source": [ "##### Algorithms and Data Structures (Winter - Spring 2022)\n", "\n", "\n", "* [Table of Contents](ADS_TOC.ipynb)\n", "* \"Open\n", "* [![nbviewer](https://raw.githubusercontent.com/jupyter/design/master/logos/Badges/nbviewer_badge.svg)](https://nbviewer.org/github/4dsolutions/elite_school/blob/master/ADS_sandbox_9.ipynb)\n", "\n", "\n", "### Simulations\n", "\n", "As an exercise in revisioning what an applied digital mathematics curriculum might look like, I came up with my [Silicon Forest Digital Mathematics](https://wikieducator.org/Digital_Math), which I've been sharing with teachers, and testing with students.\n", "\n", "The Silicon Forest is a great place for distilled maths and computer science. The original Wiki was invented here.\n", "\n", "DM distills into four areas:\n", "\n", "* Martian Math, a space for science fiction and futuristic thinking\n", "* Neolithic Math, a space of appreciating the saga of math's evolution\n", "* Supermarket Math, dealing with the everyday logistics of planetary maintenance, including resource distribution, manufacturing, keep inventories stockedd and supply chains going.\n", "* Casino Math, the space of combinatorics, games of chance, cost versus risk versus benefit analysis.\n", "\n", "One would not need to adopt the above as a complete replacement for anything. As an outline and self-contained rubric, it's a source of organizing ideas, or \"heuristics for teachers\" as I call it." ] }, { "cell_type": "markdown", "id": "8cc85760-0fb9-47fa-a46d-2fa3bbdac0ab", "metadata": {}, "source": [ "## Supermarket Math\n", "\n", "In this Sandbox I want to look at a Supermarket simulation. A goal here is to demonstrate object oriented thinking, by making the Supermarket, Shopper and Inventory be three distinct objects." ] }, { "cell_type": "markdown", "id": "5de693a6-1ce6-497c-a124-99c51cfdd8f1", "metadata": {}, "source": [ "The first code cell demonstrates a best practice, which is to clone the Exception type to define subclasses of error specific to the model at hand.\n", "\n", "The two errors we seek to handle are:\n", "\n", "* a shopper not having enough funds to complete a purchase\n", "* a supermarket no having enough in inventory to satisfy a customer purchase." ] }, { "cell_type": "code", "execution_count": 1, "id": "fe93adb3-bd70-48f4-bb7d-b21d34d5f23f", "metadata": {}, "outputs": [], "source": [ "\"\"\"\n", "Created on Fri Sep 30 10:10:41 2016\n", "\n", "@author: Kirby Urner\n", "\n", "Show some types working together to simulate a shopper\n", "in a Supermarket, with a fixed starting amount of money.\n", "\n", "Inventory keeps track of how much of a product remains.\n", "\n", "Supermarket keeps track of income, should equal sum of purchases.\n", "\"\"\"\n", "\n", "import json\n", "\n", "class NoMoney(Exception):\n", " pass\n", "\n", "class OutOfStock(Exception):\n", " pass\n" ] }, { "cell_type": "markdown", "id": "7e502f72-3b62-4778-8811-c22930e655f1", "metadata": {}, "source": [ "The Supermarket class is a designed as a context manager meaning it has `__enter__` and `__exit__` methods triggered by the keyword `with`.\n", "\n", "See the simulation function below, wherein the shopper's transactions happen within a try block, under which any exceptions get handled." ] }, { "cell_type": "code", "execution_count": 2, "id": "97c9edb4-25a5-41ff-a363-2c902097f8db", "metadata": {}, "outputs": [], "source": [ "class SuperMarket:\n", " \"\"\"\n", " Persists buyable items in a json file.\n", " Initializes with 0 cash\n", " \"\"\"\n", " def __init__(self, source):\n", " self.source = source\n", " \n", " def __enter__(self):\n", " self.inventory = Inventory(self.source)\n", " self.cash = 0\n", " return self\n", " \n", " def buy(self, shopper, item, how_many):\n", " \"\"\"\n", " remove money from shopper wallet, add qty of item\n", " to basket, abort if customer short on cash\n", " \"\"\"\n", " if item in self.inventory.wares: # check keys\n", " price = self.inventory.wares[item][0]\n", " try:\n", " self.inventory.remove_item(item, how_many)\n", " shopper.add_item(item, price, how_many)\n", " self.cash += price * how_many\n", " except NoMoney:\n", " # print(\"Customer out of money\")\n", " raise # re-raise exception\n", " except OutOfStock:\n", " # print(\"Don't have enough in stock\")\n", " raise\n", "\n", " def __exit__(self, *oops):\n", " \"\"\"\n", " write json file\n", " \"\"\"\n", " self.inventory.save_items()\n", " \n", " def __repr__(self):\n", " return \"SuperMarket with cash: {}\".format(self.cash)\n", " " ] }, { "cell_type": "markdown", "id": "19a8e8b3-1187-4954-8726-4dc10d10054d", "metadata": {}, "source": [ "The shopper's name is somewhat incidental and would like be a primary key id number pointing to another database of all known shoppers. \n", "\n", "A contemporary supermarket might work with each shopper through a smartphone app, offering custom discounts based on previous buying patterns. Our model here makes no attempt to take these added wrinkles into account." ] }, { "cell_type": "markdown", "id": "3dd1b065-a4fc-4383-99a5-0f3419030153", "metadata": {}, "source": [ "The SuperMarket class contains the core method: `buy`. The shopper is but an argument to this method, however the shopper has an `add_item` method the `Supermarket.buy` method will, in turn, call. " ] }, { "cell_type": "markdown", "id": "5a91461b-dbbc-4605-9e91-f81ae39e6cf7", "metadata": {}, "source": [ "Note that when an Exception is raised, as in the case of `raise NoMoney`, the remainder of the method in question is not executed. The flow of execution, in case of an exception, is to escape the surrounding try block immediately, and to fall through to the first matching Exception." ] }, { "cell_type": "code", "execution_count": 7, "id": "8b7fb050-84cd-4865-a051-d7d083412617", "metadata": {}, "outputs": [], "source": [ "class Shopper:\n", " \n", " def __init__(self, name, budget):\n", " self.name = name\n", " self.basket = { }\n", " self.wallet = budget # budgeted allowance\n", "\n", " def add_item(self, item, price, qty):\n", " \"\"\"\n", " add qty of item to basket and pay, if money available\n", " \"\"\"\n", " if self.wallet - qty * price < 0:\n", " raise NoMoney\n", " self.basket[item] = self.basket.get(item, 0) + qty\n", " self.wallet -= qty * price\n", " \n", " def __repr__(self):\n", " return \"{}: {}; {}\".format(self.name, self.wallet, self.basket)" ] }, { "cell_type": "markdown", "id": "27336a7f-ed24-41cb-9e61-a6b4f7fda545", "metadata": {}, "source": [ "Just as the Shopper has the ability to raise an `OutOfMoney` exception, so does the `Inventory` type get to raise an `OutOfStock`." ] }, { "cell_type": "code", "execution_count": 4, "id": "80633e5e-d73e-46f1-8594-ff56e691c750", "metadata": {}, "outputs": [], "source": [ "class Inventory:\n", " \"\"\"\n", " Supermarket brings inventory instance on board upon\n", " initialization, increments / decrements items, reads\n", " and writes to json file. Does not track cash.\n", " \"\"\"\n", " \n", " def __init__(self, the_file):\n", " print(\"Loading inventory...\")\n", " self.storage = the_file\n", " with open(the_file, 'r') as warehouse:\n", " self.wares = json.load(warehouse)\n", " \n", " def save_items(self): \n", " print(\"Saving inventory...\")\n", " with open(self.storage, 'w') as warehouse:\n", " json.dump(self.wares, warehouse)\n", " \n", " def remove_item(self, item, qty):\n", " if qty > self.wares[item][1]:\n", " raise OutOfStock\n", " self.wares[item][1] -= qty\n", " \n", " def add_item(self, item, qty):\n", " self.wares[item][1] += qty\n" ] }, { "cell_type": "markdown", "id": "bd1e8cab-1f0f-41b2-943c-604920ec5962", "metadata": {}, "source": [ "The test data we're using is quite small and simply, but wouldn't have to be." ] }, { "cell_type": "markdown", "id": "35299faf-e0a5-4afa-abdc-252198002567", "metadata": {}, "source": [ "You have the freedom to cut and paste this test_data and simulation code to a cell below, and then modify both. \n", "\n", "Play around! \n", "\n", "Learn the ropes!" ] }, { "cell_type": "code", "execution_count": 15, "id": "3a1b22d4-5245-4835-b680-519f3495800a", "metadata": {}, "outputs": [], "source": [ "def test_data():\n", " stuff = {\n", " \"Snicker-Snacks\": [5.99, 10],\n", " \"Polly's Peanuts\": [3.99, 10],\n", " \"Dr. Soap\": [4.99, 10]}\n", " with open(\"the_stuff.json\", 'w') as warehouse: \n", " json.dump(stuff, warehouse, indent=4)\n", " \n", "def simulation():\n", " \n", " test_data() # initialize the_stuff.json\n", " kirby = Shopper(\"Kirby\", 200)\n", " \n", " with SuperMarket(\"the_stuff.json\") as market:\n", " try:\n", " market.buy(kirby, \"Snicker-Snacks\", 2)\n", " market.buy(kirby, \"Polly's Peanuts\", 2)\n", " market.buy(kirby, \"Dr. Soap\", 11) # triggers exception\n", " except NoMoney:\n", " print(\"Uh oh, out of money!\")\n", " except OutOfStock:\n", " print(\"Uh oh, out of stock!\")\n", " else:\n", " print(kirby)\n", " print(market.inventory.wares)\n", " print(market)\n" ] }, { "cell_type": "code", "execution_count": 16, "id": "f19b5492-726f-4d38-86fe-b70a0486ecf0", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Loading inventory...\n", "Uh oh, out of stock!\n", "Saving inventory...\n" ] } ], "source": [ "simulation()" ] }, { "cell_type": "code", "execution_count": null, "id": "65d0774d-7da4-440f-bccb-45c09d5823c2", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "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.9.12" } }, "nbformat": 4, "nbformat_minor": 5 }