{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## Problem statement\n", "\n", "Q: *Lobb number* — Calculate the Lobb number for particular values of $m$ and $n$:\n", "\n", "$$L_{m,n} = \\frac{2m + 1}{m + n + 1}{2n \\choose m + n}$$\n", "\n", "The left term is a constant time operation, whilst the right term is a factorial. Similar to Fibbonacci, we could solve the right term using a brute-force recursive algorithm. But it's much more efficient to reuse computations from one factorial chunk for the other chunks.\n", "\n", "The factorial form of the combination calculation is:\n", "\n", "$$\\frac{2n!}{(m + n)! \\times (n - m)!}$$\n", "\n", "The key insight: if $m \\geq n$, $(m + n)!$ has the most factorial terms. Otherwise $2n!$ does. We only need to calculate one of these factorials, the rest of the factorial values will follow." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Tests" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{ [Function: ok]\n", " fail: [Function: fail],\n", " AssertionError: [Function: AssertionError],\n", " ok: [Circular],\n", " equal: [Function: equal],\n", " notEqual: [Function: notEqual],\n", " deepEqual: [Function: deepEqual],\n", " deepStrictEqual: [Function: deepStrictEqual],\n", " notDeepEqual: [Function: notDeepEqual],\n", " notDeepStrictEqual: [Function: notDeepStrictEqual],\n", " strictEqual: [Function: strictEqual],\n", " notStrictEqual: [Function: notStrictEqual],\n", " throws: [Function: throws],\n", " doesNotThrow: [Function: doesNotThrow],\n", " ifError: [Function: ifError] }" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "assert = require('assert');\n", "\n", "// TODO" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Optimized pseudocode\n", "\n", "```\n", "function lobb(m, n):\n", " mult_part = (2*m + 1) / (m + n + 1)\n", "\n", " if (m >= n):\n", " maxfact = m + n\n", " else:\n", " maxfact = 2 * n\n", "\n", " memo = fact(maxfact)\n", " comb_part = memo[2* n] / (memo[m + n] * memo[2n - m])\n", "\n", " return mult_part * comb_part\n", " \n", " \n", "function fact(n):\n", " memo = [1, 1]\n", " if n <= 1: return memo[n]\n", " \n", " for (nv in range(2, n + 1)):\n", " memo[nv] = memo[nv - 1] * nv\n", " \n", " return memo[n]\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Optimized implementation" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "function fact(n) {\n", " let memo = [1, 1];\n", " if (n <= 1) return memo[n];\n", " \n", " for (nv of [...Array(n).keys()].map(v => v + 2)) memo.push(memo[nv - 1] * nv);\n", " \n", " return memo;\n", "}\n", "\n", "function lobb(m, n) {\n", " mult_part = (2*m + 1) / (m + n + 1);\n", " \n", " let maxfact = (m >= n) ? m + n : 2*n;\n", " let memo = fact(maxfact);\n", " comb_part = memo[2* n] / (memo[m + n] * memo[n - m]);\n", " \n", " return mult_part * comb_part;\n", "}" ] } ], "metadata": { "kernelspec": { "display_name": "Javascript (Node.js)", "language": "javascript", "name": "javascript" }, "language_info": { "file_extension": ".js", "mimetype": "application/javascript", "name": "javascript", "version": "8.9.4" } }, "nbformat": 4, "nbformat_minor": 2 }