{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# CS 201: Memoization\n", "

\n", "\n", " \n", "Memoization is a technique to improve the efficiency of your code by avoiding repetitive calculations. The idea is to remember past calculations so as not to have to repeat them.\n", " \n", " " ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "(require racket)\n", "(require racket/base)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Factorial\n", "\n", "When calculating a factorial, it can be helpful to remember past calculations." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "(define (factorial n)\n", " (if (= n 1)\n", " 1\n", " (* n (factorial (- n 1)))))" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/html": [ "24" ], "text/plain": [ "24" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "(factorial 4)" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/html": [ "120" ], "text/plain": [ "120" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "(factorial 5)" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "(require racket/trace)\n", "(trace factorial)" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ ">(factorial 6)\n", "> (factorial 5)\n", "> >(factorial 4)\n", "> > (factorial 3)\n", "> > >(factorial 2)\n", "> > > (factorial 1)\n", "< < < 1\n", "< < <2\n", "< < 6\n", "< <24\n", "< 120\n", "<720\n" ] }, { "data": { "text/html": [ "720" ], "text/plain": [ "720" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "(factorial 6)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When we evaluate (factorial 6), we need to calculate (factorial 5).\n", "\n", "When we evaluate (factorial 5), we need to calculate (factorial 4).\n", "\n", "It would save us time if we just remembered the results of the past calculations. We can use a hash table to store those values. For those of you who know python, a hash table is a dict or dictionary. See racket hash tables\n", "\n", "Below we define a function memoize which adds a hash table memory to a given function." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "(define ht (make-hash))" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/html": [ "'#hash()" ], "text/plain": [ "'#hash()" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ht" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "(hash-set! ht 'john 23)" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/html": [ "#t" ], "text/plain": [ "#t" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "(hash-has-key? ht 'john)" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/html": [ "23" ], "text/plain": [ "23" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "(hash-ref ht 'john)" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/html": [ "#f" ], "text/plain": [ "#f" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "(hash-has-key? ht 'mary)" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "hash-ref: no value found for key\n", " key: 'mary\n", " context...:\n", " eval-one-top12\n", " /usr/share/racket/pkgs/sandbox-lib/racket/sandbox.rkt:510:0: call-with-custodian-shutdown\n", " /usr/share/racket/collects/racket/private/more-scheme.rkt:148:2: call-with-break-parameterization\n", " .../more-scheme.rkt:261:28\n", " /usr/share/racket/pkgs/sandbox-lib/racket/sandbox.rkt:878:5: loop\n" ] } ], "source": [ "(hash-ref ht 'mary)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we will use a hash table as the memory for memoize" ] }, { "cell_type": "code", "execution_count": 45, "metadata": {}, "outputs": [], "source": [ "(define (memoize func [table (make-hash)])\n", " (lambda (arg)\n", " (cond ((hash-has-key? table arg) \n", " (begin \n", " (display (list 'used-hash-for arg)) \n", " (newline)\n", " (hash-ref table arg)))\n", " (else \n", " (hash-set! table arg (func arg)) \n", " (hash-ref table arg)))))" ] }, { "cell_type": "code", "execution_count": 46, "metadata": {}, "outputs": [], "source": [ "(define factorial (memoize factorial))" ] }, { "cell_type": "code", "execution_count": 47, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ ">(factorial 4)\n", "> (factorial 3)\n", "> >(factorial 2)\n", "> > (factorial 1)\n", "< < 1\n", "< <2\n", "< 6\n", "<24\n" ] }, { "data": { "text/html": [ "24" ], "text/plain": [ "24" ] }, "execution_count": 47, "metadata": {}, "output_type": "execute_result" } ], "source": [ "(factorial 4)" ] }, { "cell_type": "code", "execution_count": 48, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ ">(factorial 5)\n", "(used-hash-for 4)\n", "<120\n" ] }, { "data": { "text/html": [ "120" ], "text/plain": [ "120" ] }, "execution_count": 48, "metadata": {}, "output_type": "execute_result" } ], "source": [ "(factorial 5)" ] }, { "cell_type": "code", "execution_count": 49, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ ">(factorial 6)\n", "(used-hash-for 5)\n", "<720\n" ] }, { "data": { "text/html": [ "720" ], "text/plain": [ "720" ] }, "execution_count": 49, "metadata": {}, "output_type": "execute_result" } ], "source": [ "(factorial 6)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We have avoided the repeated calculations!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Fibonacci\n", "\n", "The Fibonacci sequence is found in math and in nature. Here is a simple recursive function to calculate a Fibonacci number." ] }, { "cell_type": "code", "execution_count": 50, "metadata": {}, "outputs": [], "source": [ "(define (fib n)\n", " (if (< n 2)\n", " 1\n", " (+ (fib (- n 1)) (fib (- n 2)))))" ] }, { "cell_type": "code", "execution_count": 51, "metadata": {}, "outputs": [ { "data": { "text/html": [ "5" ], "text/plain": [ "5" ] }, "execution_count": 51, "metadata": {}, "output_type": "execute_result" } ], "source": [ "(fib 4)" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/html": [ "8" ], "text/plain": [ "8" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "(fib 5)" ] }, { "cell_type": "code", "execution_count": 52, "metadata": {}, "outputs": [ { "data": { "text/html": [ "13" ], "text/plain": [ "13" ] }, "execution_count": 52, "metadata": {}, "output_type": "execute_result" } ], "source": [ "(fib 6)" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [], "source": [ "(trace fib)" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ ">(fib 6)\n", "> (fib 5)\n", "> >(fib 4)\n", "> > (fib 3)\n", "> > >(fib 2)\n", "> > > (fib 1)\n", "< < < 1\n", "> > > (fib 0)\n", "< < < 1\n", "< < <2\n", "> > >(fib 1)\n", "< < <1\n", "< < 3\n", "> > (fib 2)\n", "> > >(fib 1)\n", "< < <1\n", "> > >(fib 0)\n", "< < <1\n", "< < 2\n", "< <5\n", "> >(fib 3)\n", "> > (fib 2)\n", "> > >(fib 1)\n", "< < <1\n", "> > >(fib 0)\n", "< < <1\n", "< < 2\n", "> > (fib 1)\n", "< < 1\n", "< <3\n", "< 8\n", "> (fib 4)\n", "> >(fib 3)\n", "> > (fib 2)\n", "> > >(fib 1)\n", "< < <1\n", "> > >(fib 0)\n", "< < <1\n", "< < 2\n", "> > (fib 1)\n", "< < 1\n", "< <3\n", "> >(fib 2)\n", "> > (fib 1)\n", "< < 1\n", "> > (fib 0)\n", "< < 1\n", "< <2\n", "< 5\n", "<13\n" ] }, { "data": { "text/html": [ "13" ], "text/plain": [ "13" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "(fib 6)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Oof! That's a lot of redundant calculations! Let's memoize that puppy." ] }, { "cell_type": "code", "execution_count": 53, "metadata": {}, "outputs": [], "source": [ "(define fib (memoize fib))" ] }, { "cell_type": "code", "execution_count": 54, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(used-hash-for 1)\n", "(used-hash-for 2)\n", "(used-hash-for 3)\n", "(used-hash-for 4)\n" ] }, { "data": { "text/html": [ "13" ], "text/plain": [ "13" ] }, "execution_count": 54, "metadata": {}, "output_type": "execute_result" } ], "source": [ "(fib 6)" ] }, { "cell_type": "code", "execution_count": 55, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(used-hash-for 6)\n" ] }, { "data": { "text/html": [ "13" ], "text/plain": [ "13" ] }, "execution_count": 55, "metadata": {}, "output_type": "execute_result" } ], "source": [ "(fib 6)" ] }, { "cell_type": "code", "execution_count": 56, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(used-hash-for 6)\n", "(used-hash-for 5)\n", "(used-hash-for 6)\n" ] }, { "data": { "text/html": [ "34" ], "text/plain": [ "34" ] }, "execution_count": 56, "metadata": {}, "output_type": "execute_result" } ], "source": [ "(fib 8)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "That's a big improvement." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Ackermann\n", "\n", "The Ackermann function is a famous (or infamous) recursive function." ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [], "source": [ "(define (ack m n)\n", " (cond ((= m 0) (+ n 1))\n", " ((= n 0) (ack (- m 1) 1)) ;; m > 0\n", " (else \n", " (ack (- m 1) (ack m (- n 1))))))" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/html": [ "5" ], "text/plain": [ "5" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "(ack 2 1)" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [], "source": [ "(trace ack)" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ ">(ack 2 1)\n", "> (ack 2 0)\n", "> (ack 1 1)\n", "> >(ack 1 0)\n", "> >(ack 0 1)\n", "< <2\n", "> (ack 0 2)\n", "< 3\n", ">(ack 1 3)\n", "> (ack 1 2)\n", "> >(ack 1 1)\n", "> > (ack 1 0)\n", "> > (ack 0 1)\n", "< < 2\n", "> >(ack 0 2)\n", "< <3\n", "> (ack 0 3)\n", "< 4\n", ">(ack 0 4)\n", "<5\n" ] }, { "data": { "text/html": [ "5" ], "text/plain": [ "5" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "(ack 2 1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "While ack would benefit from being memoized, we cannot use our prevous memoize function because ack, unlike factorial and fib, takes two arguments, not one. We need to modify memoize accordingly." ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [], "source": [ "(define (memoize2 func [table (make-hash)])\n", " (lambda (arg1 arg2)\n", " (cond ((hash-has-key? table (list arg1 arg2)) \n", " (begin \n", " (display (list 'used-hash-for arg1 arg2))\n", " (newline)\n", " (hash-ref table (list arg1 arg2)))) \n", " (else \n", " (hash-set! table (list arg1 arg2) (func arg1 arg2)) \n", " (hash-ref table (list arg1 arg2))))))" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [], "source": [ "(define ack (memoize2 ack))" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ ">(ack 2 1)\n", "> (ack 2 0)\n", "> >(ack 1 1)\n", "> > (ack 1 0)\n", "> > >(ack 0 1)\n", "< < <2\n", "< < 2\n", "> > (ack 0 2)\n", "< < 3\n", "< <3\n", "< 3\n", "> (ack 1 3)\n", "> >(ack 1 2)\n", "(used-hash-for 1 1)\n", "> > (ack 0 3)\n", "< < 4\n", "< <4\n", "> >(ack 0 4)\n", "< <5\n", "< 5\n", "<5\n" ] }, { "data": { "text/html": [ "5" ], "text/plain": [ "5" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "(ack 2 1)" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ ">(ack 2 2)\n", "(used-hash-for 2 1)\n", "> (ack 1 5)\n", "> >(ack 1 4)\n", "(used-hash-for 1 3)\n", "> > (ack 0 5)\n", "< < 6\n", "< <6\n", "> >(ack 0 6)\n", "< <7\n", "< 7\n", "<7\n" ] }, { "data": { "text/html": [ "7" ], "text/plain": [ "7" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "(ack 2 2)" ] }, { "cell_type": "code", "execution_count": 58, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ ">(ack 3 1)\n", "> (ack 3 0)\n", "(used-hash-for 2 1)\n", "< 5\n", "> (ack 2 5)\n", "> >(ack 2 4)\n", "> > (ack 2 3)\n", "(used-hash-for 2 2)\n", "> > >(ack 1 7)\n", "> > > (ack 1 6)\n", "(used-hash-for 1 5)\n", "> > > >(ack 0 7)\n", "< < < <8\n", "< < < 8\n", "> > > (ack 0 8)\n", "< < < 9\n", "< < <9\n", "< < 9\n", "> > (ack 1 9)\n", "> > >(ack 1 8)\n", "(used-hash-for 1 7)\n", "> > > (ack 0 9)\n", "< < < 10\n", "< < <10\n", "> > >(ack 0 10)\n", "< < <11\n", "< < 11\n", "< <11\n", "> >(ack 1 11)\n", "> > (ack 1 10)\n", "(used-hash-for 1 9)\n", "> > >(ack 0 11)\n", "< < <12\n", "< < 12\n", "> > (ack 0 12)\n", "< < 13\n", "< <13\n", "< 13\n", "<13\n" ] }, { "data": { "text/html": [ "13" ], "text/plain": [ "13" ] }, "execution_count": 58, "metadata": {}, "output_type": "execute_result" } ], "source": [ "(ack 3 1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Without memoization, ack burns through a boatload of cycles." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Racket", "language": "racket", "name": "racket" }, "language_info": { "codemirror_mode": "scheme", "file_extension": ".rkt", "mimetype": "text/x-racket", "name": "Racket", "pygments_lexer": "racket", "version": "7.4" } }, "nbformat": 4, "nbformat_minor": 4 }