[
  {
    "path": ".babelrc",
    "content": "{\n    \"presets\": [\n        [\n            \"@babel/preset-env\",\n            {\n                \"modules\": false\n            }\n        ],\n        \"@babel/preset-react\"\n    ],\n    \"plugins\": [\n        [\n            \"@babel/plugin-proposal-decorators\",\n            {\n                \"legacy\": true\n            }\n        ],\n        \"@babel/plugin-proposal-class-properties\",\n        \"@babel/plugin-syntax-dynamic-import\",\n        \"@babel/plugin-syntax-import-meta\",\n        \"@babel/plugin-proposal-json-strings\",\n        \"@babel/plugin-proposal-function-sent\",\n        \"@babel/plugin-proposal-export-namespace-from\",\n        \"@babel/plugin-proposal-numeric-separator\",\n        \"@babel/plugin-proposal-throw-expressions\",\n        \"@babel/plugin-proposal-optional-chaining\"\n    ],\n    \"env\": {\n        \"test\": {\n            \"presets\": [\n                [\n                    \"@babel/preset-env\",\n                    {\n                        \"modules\": \"auto\"\n                    }\n                ],\n                \"@babel/preset-react\"\n            ]\n        }\n    }\n}\n"
  },
  {
    "path": ".gitignore",
    "content": "### PYTHON\n#\n# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n.hypothesis/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\n.static_storage/\n.media/\nlocal_settings.py\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# pyenv\n.python-version\n\n# celery beat schedule file\ncelerybeat-schedule\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n\n\n### NODE\n# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# Typescript v1 declaration files\ntypings/\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variables file\n.env\n\n# next.js build output\n.next\n\n# vim custom added\n*.swp\n*.swo\n\n"
  },
  {
    "path": ".prettierignore",
    "content": "package-lock.json\npackage.json\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2017-2018 Alexander Putilin\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Inside Python Dict - an explorable explanation\n\nThis repository contains the code for [\"Inside Python Dict\"](https://just-taking-a-ride.com/inside_python_dict/), a explorable explanation of python dicts. \n\n## Code\n\nThe code is quite messy, the build system works on my laptop but might not work on your machine. That's all fixable, but I am not sure if there is any interest from people in using the actual code. If so, let me know, I'll clean things up a bit and write an overview of the codebase. \n\nMeanwhile, try running `npm install && npm start` and see if if works. \n\nBugfixes will be gladly accepted.\n\n### Disclaimer\n\nI am providing code in the repository to you under an open source license. Because this is my personal repository, the license you receive to my code is from me and not my employer (Facebook)\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"inside_python_dict\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Inside python dict - an explorable explanation\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"jest\": \"jest --env=node\",\n    \"test:pystress\": \"npm run extractcode && ./stress_test_python.sh\",\n    \"test:pyunit\": \"npm run extractcode && ./unittest_python.sh\",\n    \"test\": \"npm run jest && npm run test:pyunit && npm run test:pystress\",\n    \"build:ssr\": \"./ssr-all.sh\",\n    \"update:html\": \"mkdir -p build && (for i in {chapter1,chapter2,chapter3,chapter4}; do mustache src/mustache/$i.json src/page.html.template > src/autogenerated/$i.html; done)\",\n    \"start\": \"webpack-dev-server --config webpack.dev.js --host 0.0.0.0\",\n    \"serve\": \"http-server -p 9090 dist/\",\n    \"build\": \"npm run build:ssr && webpack --config webpack.prod.js\",\n    \"babel-node\": \"npx babel-node --presets '@babel/env'\",\n    \"extractcode\": \"mkdir -p build && npm run babel-node scripts/extractPythonCode.js\",\n    \"dictserver\": \"rm pynode.sock ; npm run babel-node scripts/pyReimplWrapper.js; rm pynode.sock\",\n    \"postinstall\": \"patch-package\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/eleweek/inside_python_dict.git\"\n  },\n  \"author\": \"Alexander Putilin\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/eleweek/inside_python_dict/issues\"\n  },\n  \"homepage\": \"https://github.com/eleweek/inside_python_dict#readme\",\n  \"prettier\": {\n    \"printWidth\": 120,\n    \"tabWidth\": 4,\n    \"useTabs\": false,\n    \"singleQuote\": true,\n    \"bracketSpacing\": false,\n    \"semi\": true,\n    \"trailingComma\": \"es5\"\n  },\n  \"dependencies\": {\n    \"@fortawesome/fontawesome-svg-core\": \"^1.2.10\",\n    \"@fortawesome/free-brands-svg-icons\": \"^5.6.1\",\n    \"@fortawesome/free-solid-svg-icons\": \"^5.6.1\",\n    \"@fortawesome/react-fontawesome\": \"^0.1.3\",\n    \"bignumber.js\": \"^8.0.1\",\n    \"bootstrap\": \"^4.1.3\",\n    \"bowser\": \"^2.0.0-beta.3\",\n    \"classnames\": \"^2.2.6\",\n    \"d3\": \"^5.7.0\",\n    \"d3-selection-multi\": \"^1.0.1\",\n    \"i\": \"^0.3.6\",\n    \"immutable\": \"^4.0.0-rc.12\",\n    \"lodash\": \"^4.17.11\",\n    \"lowlight\": \"^1.11.0\",\n    \"memoize-one\": \"^4.1.0\",\n    \"mobx\": \"^5.8.0\",\n    \"mobx-react\": \"^5.4.3\",\n    \"rc-slider\": \"^8.6.4\",\n    \"react\": \"^16.6.3\",\n    \"react-css-transition-replace\": \"^3.0.3\",\n    \"react-dom\": \"^16.6.3\",\n    \"react-error-boundary\": \"^1.2.3\",\n    \"react-input-autosize\": \"^2.2.1\",\n    \"react-popper\": \"^1.3.2\",\n    \"react-smooth-scrollbar\": \"^8.0.6\",\n    \"react-stickynode\": \"^2.1.0\",\n    \"rehype\": \"^7.0.0\",\n    \"smooth-scrollbar\": \"8.3.1\"\n  },\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.2.0\",\n    \"@babel/core\": \"^7.2.2\",\n    \"@babel/node\": \"^7.2.2\",\n    \"@babel/plugin-proposal-class-properties\": \"^7.2.1\",\n    \"@babel/plugin-proposal-decorators\": \"^7.2.2\",\n    \"@babel/plugin-proposal-export-namespace-from\": \"^7.2.0\",\n    \"@babel/plugin-proposal-function-sent\": \"^7.2.0\",\n    \"@babel/plugin-proposal-json-strings\": \"^7.2.0\",\n    \"@babel/plugin-proposal-numeric-separator\": \"^7.2.0\",\n    \"@babel/plugin-proposal-object-rest-spread\": \"^7.2.0\",\n    \"@babel/plugin-proposal-optional-chaining\": \"^7.2.0\",\n    \"@babel/plugin-proposal-throw-expressions\": \"^7.2.0\",\n    \"@babel/plugin-syntax-dynamic-import\": \"^7.2.0\",\n    \"@babel/plugin-syntax-import-meta\": \"^7.2.0\",\n    \"@babel/plugin-transform-destructuring\": \"^7.2.0\",\n    \"@babel/preset-env\": \"^7.2.0\",\n    \"@babel/preset-react\": \"^7.0.0\",\n    \"babel-core\": \"^7.0.0-bridge.0\",\n    \"babel-jest\": \"^23.4.2\",\n    \"babel-loader\": \"^8.0.0\",\n    \"babel-plugin-lodash\": \"^3.3.4\",\n    \"clean-webpack-plugin\": \"^1.0.0\",\n    \"css-loader\": \"^2.0.1\",\n    \"dotenv\": \"^6.2.0\",\n    \"html-webpack-plugin\": \"^3.2.0\",\n    \"http-server\": \"^0.11.1\",\n    \"husky\": \"^1.2.1\",\n    \"ignore-styles\": \"^5.0.1\",\n    \"jest\": \"^23.6.0\",\n    \"mini-css-extract-plugin\": \"^0.5.0\",\n    \"mustache\": \"^3.0.1\",\n    \"npm\": \"^6.5.0\",\n    \"patch-package\": \"^5.1.1\",\n    \"prettier\": \"^1.15.3\",\n    \"pretty-quick\": \"^1.8.0\",\n    \"split\": \"^1.0.1\",\n    \"style-loader\": \"^0.23.1\",\n    \"unminified-webpack-plugin\": \"^2.0.0\",\n    \"webpack\": \"^4.27.1\",\n    \"webpack-bundle-analyzer\": \"^3.0.3\",\n    \"webpack-cli\": \"^3.1.2\",\n    \"webpack-dev-server\": \"^3.1.10\",\n    \"webpack-merge\": \"^4.1.5\"\n  },\n  \"husky\": {\n    \"hooks\": {\n      \"pre-commit\": \"pretty-quick --staged\"\n    }\n  }\n}\n"
  },
  {
    "path": "patches/python32_debug.diff",
    "content": "diff --git a/Objects/dictobject.c b/Objects/dictobject.c\nindex c10bfccdce..3734a08281 100644\n--- a/Objects/dictobject.c\n+++ b/Objects/dictobject.c\n@@ -321,6 +321,8 @@ lookdict(PyDictObject *mp, PyObject *key, register Py_hash_t hash)\n     PyObject *startkey;\n \n     i = (size_t)hash & mask;\n+    fprintf(stderr, \"lookdict hash = %ld\\n\", hash);\n+    fprintf(stderr, \"initial i = %zu\\n\", i);\n     ep = &ep0[i];\n     if (ep->me_key == NULL || ep->me_key == key)\n         return ep;\n@@ -355,7 +357,9 @@ lookdict(PyDictObject *mp, PyObject *key, register Py_hash_t hash)\n        least likely outcome, so test for that last. */\n     for (perturb = hash; ; perturb >>= PERTURB_SHIFT) {\n         i = (i << 2) + i + perturb + 1;\n+        fprintf(stderr, \"next i = %zu perturb = %zu\\n\", i, perturb);\n         ep = &ep0[i & mask];\n+        fprintf(stderr, \"next i & mask = %zu perturb = %zu\\n\", i & mask, perturb);\n         if (ep->me_key == NULL)\n             return freeslot == NULL ? ep : freeslot;\n         if (ep->me_key == key)\n@@ -648,6 +652,7 @@ dictresize(PyDictObject *mp, Py_ssize_t minused)\n         }\n     }\n     else {\n+        fprintf(stderr, \"PyMem_NEW branch\");\n         newtable = PyMem_NEW(PyDictEntry, newsize);\n         if (newtable == NULL) {\n             PyErr_NoMemory();\n@@ -693,6 +698,7 @@ PyObject *\n _PyDict_NewPresized(Py_ssize_t minused)\n {\n     PyObject *op = PyDict_New();\n+    fprintf(stderr, \"_PyDict_NewPresized() %p %d\\n\", op, (int)minused);\n \n     if (minused>5 && op != NULL && dictresize((PyDictObject *)op, minused) == -1) {\n         Py_DECREF(op);\ndiff --git a/Objects/longobject.c b/Objects/longobject.c\nindex e2a4ef9c5e..7d72c88417 100644\n--- a/Objects/longobject.c\n+++ b/Objects/longobject.c\n@@ -2611,6 +2611,7 @@ long_hash(PyLongObject *v)\n         sign = -1;\n         i = -(i);\n     }\n+    fprintf(stderr, \"i = %ld\\n\", i);\n     while (--i >= 0) {\n         /* Here x is a quantity in the range [0, _PyHASH_MODULUS); we\n            want to compute x * 2**PyLong_SHIFT + v->ob_digit[i] modulo\n"
  },
  {
    "path": "patches/smooth-scrollbar+8.3.1.patch",
    "content": "patch-package\n--- a/node_modules/smooth-scrollbar/events/touch.js\n+++ b/node_modules/smooth-scrollbar/events/touch.js\n@@ -1,7 +1,7 @@\n import { eventScope, TouchRecord, } from '../utils/';\n var activeScrollbar;\n export function touchHandler(scrollbar) {\n-    var MIN_EAING_MOMENTUM = 50;\n+    var MIN_EAING_MOMENTUM = 3;\n     var EASING_MULTIPLIER = /Android/.test(navigator.userAgent) ? 3 : 2;\n     var target = scrollbar.options.delegateTo || scrollbar.containerEl;\n     var touchRecord = new TouchRecord();\n--- a/node_modules/smooth-scrollbar/geometry/update.js\n+++ b/node_modules/smooth-scrollbar/geometry/update.js\n@@ -4,6 +4,9 @@ export function update(scrollbar) {\n         x: Math.max(newSize.content.width - newSize.container.width, 0),\n         y: Math.max(newSize.content.height - newSize.container.height, 0),\n     };\n+    // hack for a weird chrome on windows bug\n+    if (limit.x <= 2) limit.x = 0;\n+    if (limit.y <= 2) limit.y = 0;\n     // metrics\n     var containerBounding = scrollbar.containerEl.getBoundingClientRect();\n     var bounding = {\n--- a/node_modules/smooth-scrollbar/scrollbar.js\n+++ b/node_modules/smooth-scrollbar/scrollbar.js\n@@ -322,6 +322,10 @@ var Scrollbar = /** @class */ (function () {\n         if (limit.x === 0 && limit.y === 0) {\n             this._updateDebounced();\n         }\n+        if (Math.abs(deltaY) > Math.abs(deltaX)) {\n+            if (deltaY > 0 && offset.y === limit.y) return true;\n+            if (deltaY < 0 && offset.y === 0) return true;\n+        }\n         var destX = clamp(deltaX + offset.x, 0, limit.x);\n         var destY = clamp(deltaY + offset.y, 0, limit.y);\n         var res = true;\n--- a/node_modules/smooth-scrollbar/track/track.js\n+++ b/node_modules/smooth-scrollbar/track/track.js\n@@ -41,8 +41,9 @@ var ScrollbarTrack = /** @class */ (function () {\n         this.element.classList.remove('show');\n     };\n     ScrollbarTrack.prototype.update = function (scrollOffset, containerSize, pageSize) {\n+        // -2 is a hack for a weird chrome on windows bug\n         setStyle(this.element, {\n-            display: pageSize <= containerSize ? 'none' : 'block',\n+            display: pageSize - 2 <= containerSize ? 'none' : 'block',\n         });\n         this.thumb.update(scrollOffset, containerSize, pageSize);\n     };\ndeleted file mode 100644\n--- a/node_modules/smooth-scrollbar/track/track.js.map\n+++ /dev/null\n@@ -1 +0,0 @@\n-{\"version\":3,\"file\":\"track.js\",\"sourceRoot\":\"\",\"sources\":[\"../src/track/track.ts\"],\"names\":[],\"mappings\":\"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAEzC,OAAO,EACL,QAAQ,GACT,MAAM,WAAW,CAAC;AAEnB;IAUE,wBACE,SAAyB,EACzB,YAAwB;QAAxB,6BAAA,EAAA,gBAAwB;QAT1B;;WAEG;QACM,YAAO,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QAEzC,aAAQ,GAAG,KAAK,CAAC;QAMvB,IAAI,CAAC,OAAO,CAAC,SAAS,GAAG,qCAAmC,SAAW,CAAC;QAExE,IAAI,CAAC,KAAK,GAAG,IAAI,cAAc,CAC7B,SAAS,EACT,YAAY,CACb,CAAC;QAEF,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACpC,CAAC;IAED;;;;OAIG;IACH,iCAAQ,GAAR,UAAS,kBAA+B;QACtC,kBAAkB,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC/C,CAAC;IAED;;OAEG;IACH,6BAAI,GAAJ;QACE,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;YAClB,MAAM,CAAC;QACT,CAAC;QAED,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACrC,CAAC;IAED;;OAEG;IACH,6BAAI,GAAJ;QACE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;YACnB,MAAM,CAAC;QACT,CAAC;QAED,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;QACtB,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACxC,CAAC;IAED,+BAAM,GAAN,UACE,YAAoB,EACpB,aAAqB,EACrB,QAAgB;QAEhB,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE;YACrB,OAAO,EAAE,QAAQ,IAAI,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO;SACtD,CAAC,CAAC;QAEH,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,YAAY,EAAE,aAAa,EAAE,QAAQ,CAAC,CAAC;IAC3D,CAAC;IACH,qBAAC;AAAD,CAAC,AApED,IAoEC\"}\n\\ No newline at end of file\n"
  },
  {
    "path": "patches/subscribe-ui-event+2.0.4.patch",
    "content": "patch-package\nnew file mode 100644\nBinary files /dev/null and b/node_modules/subscribe-ui-event/.index.es.js.swp differ\n--- a/node_modules/subscribe-ui-event/index.es.js\n+++ b/node_modules/subscribe-ui-event/index.es.js\n@@ -7,7 +7,7 @@ import listenLib from './dist-es/lib/listen';\n import subscribeLib from './dist-es/subscribe';\n import unsubscribeLib from './dist-es/unsubscribe';\n \n-const IS_CLIENT = typeof window !== 'undefined';\n+var IS_CLIENT = typeof window !== 'undefined';\n \n function warn() {\n   if (process.env.NODE_ENV !== 'production') {\n@@ -15,6 +15,6 @@ function warn() {\n   }\n }\n \n-export const listen = IS_CLIENT ? listenLib : warn;\n-export const subscribe = IS_CLIENT ? subscribeLib : warn;\n-export const unsubscribe = IS_CLIENT ? unsubscribeLib : warn;\n+export var listen = IS_CLIENT ? listenLib : warn;\n+export var subscribe = IS_CLIENT ? subscribeLib : warn;\n+export var unsubscribe = IS_CLIENT ? unsubscribeLib : warn;\n"
  },
  {
    "path": "python_code/actual_dict_factory_test.py",
    "content": "import unittest\n\nfrom dict32_reimplementation_test_v2 import dict_factory\nfrom dictinfo import dump_py_dict\n\n\ndef table_size(d):\n    return len(dump_py_dict(d)[0])\n\n\nclass TestDictFactory(unittest.TestCase):\n    def test_dict_factory(self):\n        self.assertEqual(table_size(dict_factory([])), 8)\n        self.assertEqual(table_size(dict_factory([(1, 1)])), 8)\n        self.assertEqual(table_size(dict_factory([(1, 1), (1, 2), (1, 3), (1, 4)])), 8)\n        self.assertEqual(table_size(dict_factory([(1, 1), (1, 2), (1, 3), (1, 4), (1, 5)])), 8)\n        self.assertEqual(table_size(dict_factory([(1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (1, 6), (1, 7), (1, 8)])), 16)\n        self.assertEqual(table_size({1: 1, 1: 2, 1: 3, 1: 4, 1: 5, 1: 6, 1: 7, 1: 8}), 16)\n        self.assertEqual(table_size(dict([(1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (1, 6), (1, 7), (1, 8)])), 8)\n\n        self.assertEqual(table_size({\"x\": \"y\", \"abde\": 1, \"cdef\": 4, \"world\": 9, \"hmmm\": 16, \"hello\": 25, \"xxx\": 36, \"ya\": 49, \"hello,world!\": 64, \"well\": 81, \"meh\": 100}), 64)\n\n\ndef main():\n    unittest.main()\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "python_code/build_autogenerated_chapter1_hash.py",
    "content": "import os\nimport sys\nsys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'build'))\nfrom hash_chapter1_extracted import *\n\n\ndef create_new(numbers):\n    return build_insert_all(numbers)\n\n\ndef create_new_broken(numbers):\n    return build_not_quite_what_we_want(numbers)\n\n\ndef has_key(keys, key):\n    return has_number(keys, key)\n\n\ndef linear_search(numbers, number):\n    return simple_search(numbers, number)\n"
  },
  {
    "path": "python_code/build_autogenerated_chapter2.py",
    "content": "import os\nimport sys\nsys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'build'))\nfrom hash_chapter2_extracted import *\n"
  },
  {
    "path": "python_code/build_autogenerated_chapter3_chapter4.py",
    "content": "import os\nimport sys\nsys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'build'))\nfrom dict32js_extracted import Dict32Extracted\nfrom hash_class_recycling_extracted import HashClassRecyclingExtracted\nfrom hash_class_no_recycling_extracted import HashClassNoRecyclingExtracted\n"
  },
  {
    "path": "python_code/chapter1_linear_search_reimplementation_test.py",
    "content": "import random\nimport argparse\n\nimport hash_chapter1_reimpl_js\nimport hash_chapter1_impl\nimport build_autogenerated_chapter1_hash\n\nIMPLEMENTATIONS = {\n    'ref': hash_chapter1_impl.linear_search,\n    'js': hash_chapter1_reimpl_js.linear_search,\n    'py_extracted': build_autogenerated_chapter1_hash.linear_search\n}\n\n\ndef run(test_implementation, size):\n    MAX_VAL = 5000\n    ref_search = IMPLEMENTATIONS['ref']\n    test_search = IMPLEMENTATIONS[test_implementation]\n\n    numbers = [random.randint(-MAX_VAL, MAX_VAL) for _ in range(size)]\n\n    for number in numbers:\n        assert ref_search(numbers, number)\n        assert test_search(numbers, number)\n\n    for i in range(size * 3):\n        number = random.randint(-MAX_VAL, MAX_VAL)\n        assert ref_search(numbers, number) == test_search(numbers, number)\n\n\nif __name__ == \"__main__\":\n    parser = argparse.ArgumentParser(description='Stress-test chapter1 reimplementation')\n    parser.add_argument('--test-implementation', choices=['py_extracted', 'js'], required=True)\n    parser.add_argument('--size',  type=int, default=100)\n    args = parser.parse_args()\n\n    run(test_implementation=args.test_implementation,\n        size=args.size)\n"
  },
  {
    "path": "python_code/chapter4_probing_python_reimplementation_test.py",
    "content": "import json\nfrom common import AllKeyValueFactory\nfrom js_reimpl_common import _init_sock_stuff, dump_simple_py_obj\nfrom pprint import pprint\n\nsock, sockfile = _init_sock_stuff()\n\n\ndef probe_all_js(key, slots_count):\n    global sockfile\n    global sock\n\n    data = {\n        \"dict\": \"pythonProbing\",\n        \"args\": {\n            'key': dump_simple_py_obj(key),\n            'slotsCount': slots_count\n        },\n    }\n\n    sock.send(bytes(json.dumps(data) + \"\\n\", 'UTF-8'))\n    response = json.loads(sockfile.readline())\n\n    return response['result']\n\n\ndef probe_all(key, slots_count=8):\n    PERTURB_SHIFT = 5\n    links = [[] for _ in range(slots_count)]\n    hash_code = hash(key)\n    perturb = 2**64 + hash_code if hash_code < 0 else hash_code\n    idx = hash_code % slots_count\n    start_idx = idx\n    visited = set()\n    while len(visited) < slots_count:\n        visited.add(idx)\n        next_idx = (idx * 5 + perturb + 1) % slots_count\n        links[idx].append({'nextIdx': next_idx, 'perturbLink': perturb != 0})\n        idx = next_idx\n        perturb >>= PERTURB_SHIFT\n\n    return {'startIdx': start_idx, 'links': links}\n\n\ndef test():\n    factory = AllKeyValueFactory(100)\n    for slots_count in [8, 16, 32]:\n        for i in range(300):\n            key = factory.generate_key()\n            assert probe_all(key, slots_count) == probe_all_js(key, slots_count)\n\n\nif __name__ == \"__main__\":\n    test()\n"
  },
  {
    "path": "python_code/common.py",
    "content": "import random\nimport string\n\n\nclass EmptyValueClass(object):\n    def __str__(self):\n        return \"EMPTY\"\n\n    def __repr__(self):\n        return \"<EMPTY>\"\n\n\nclass DummyValueClass(object):\n    def __str__(self):\n        return \"<DUMMY>\"\n\n    def __repr__(self):\n        return \"<DUMMY>\"\n\n\nEMPTY = EmptyValueClass()\nDUMMY = DummyValueClass()\n\n\ndef get_object_field_or_null(obj, field_name):\n    try:\n        return getattr(obj, field_name)\n    except ValueError:\n        return EMPTY\n\n\ndef get_object_field_or_none(obj, field_name):\n    try:\n        return getattr(obj, field_name)\n    except ValueError:\n        return None\n\n\ndef generate_random_string(str_len=5):\n    # FROM: https://stackoverflow.com/questions/2257441/random-string-generation-with-upper-case-letters-and-digits-in-python\n    return ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(str_len))\n\n\n_unicode_chars = string.ascii_uppercase + string.digits + \"йцукенгшщзхъфывапролджэячсмитьбю\"\n\n\ndef generate_random_unicode(str_len):\n    # FROM: https://stackoverflow.com/questions/2257441/random-string-generation-with-upper-case-letters-and-digits-in-python\n    return ''.join(random.choice(_unicode_chars) for _ in range(str_len))\n\n\nclass IntKeyValueFactory(object):\n    def __init__(self, n_inserts):\n        self.n_inserts = n_inserts\n        self._insert_count = 0\n        self._key_range = list(range(n_inserts))\n\n    def generate_key(self):\n        return random.choice(self._key_range)\n\n    def generate_value(self):\n        self._insert_count += 1\n        return self._insert_count\n\n\nclass AllKeyValueFactory(object):\n    def __init__(self, n_inserts, int_chance=0.1, long_chance=0.1, len0_chance=0.01, len1_chance=0.1, len2_chance=0.3, len3_chance=0.2, len_random_chance=0.17):\n        self.int_pbf = int_chance\n        self.long_pbf = self.int_pbf + long_chance\n        self.len0_pbf = self.int_pbf + len0_chance\n        self.len1_pbf = self.len0_pbf + len1_chance\n        self.len2_pbf = self.len1_pbf + len2_chance\n        self.len3_pbf = self.len2_pbf + len3_chance\n        self.len_random_pbf = self.len3_pbf + len_random_chance\n        assert 0.0 <= self.len3_pbf <= 1.0\n\n        half_range = int(n_inserts / 2)\n        self._int_range = [i - half_range for i in range(2 * half_range)]\n\n    def _generate_obj(self):\n        r = random.random()\n        if r <= self.int_pbf:\n            return random.choice(self._int_range)\n        if r <= self.long_pbf:\n            sign = \"-\" if random.random() < 0.5 else \"\"\n            first_digit = random.choice(\"123456789\")\n            return sign + first_digit + ''.join(random.choice(\"0123456789\") for _ in range(random.randint(20, 50)))\n        if r <= self.len0_pbf:\n            return \"\"\n        if r <= self.len1_pbf:\n            return generate_random_unicode(1)\n        if r <= self.len2_pbf:\n            return generate_random_unicode(2)\n        if r <= self.len3_pbf:\n            return generate_random_unicode(3)\n        if r <= self.len_random_pbf:\n            return generate_random_unicode(random.randint(4, 25))\n        return None\n\n    def generate_key(self):\n        return self._generate_obj()\n\n    def generate_value(self):\n        return self._generate_obj()\n"
  },
  {
    "path": "python_code/dict32_reimplementation_test_v2.py",
    "content": "import random\nimport argparse\nimport json\n\nfrom common import EMPTY, AllKeyValueFactory, IntKeyValueFactory\nfrom dictinfo import dump_py_dict\nfrom dict_reimplementation import PyDictReimplementation32, dump_reimpl_dict\nfrom js_reimplementation_interface import Dict32JsImpl, AlmostPythonDictRecyclingJsImpl, AlmostPythonDictNoRecyclingJsImpl\nimport hash_chapter3_class_impl\nimport build_autogenerated_chapter3_chapter4\n\n\ndef dict_factory(pairs=None):\n    if not pairs:\n        return {}\n\n    # quick&dirty\n    def to_string(x):\n        return json.dumps(x) if x is not None else \"None\"\n    d = eval(\"{\" + \", \".join(\"{}:{}\".format(to_string(k), to_string(v)) for [k, v] in pairs) + \"}\")\n    return d\n\n\nIMPLEMENTATIONS = {\n    \"dict_actual\": (dict_factory, dump_py_dict),\n    \"dict32_reimpl_py\": (PyDictReimplementation32, dump_reimpl_dict),\n    \"dict32_reimpl_js\": (Dict32JsImpl, dump_reimpl_dict),\n\n    \"dict32_reimpl_py_extracted\": (build_autogenerated_chapter3_chapter4.Dict32Extracted, dump_reimpl_dict),\n\n    \"almost_python_dict_recycling_py\": (hash_chapter3_class_impl.AlmostPythonDictImplementationRecycling, dump_reimpl_dict),\n    \"almost_python_dict_no_recycling_py\": (hash_chapter3_class_impl.AlmostPythonDictImplementationNoRecycling, dump_reimpl_dict),\n    \"almost_python_dict_no_recycling_py_simpler\": (hash_chapter3_class_impl.AlmostPythonDictImplementationNoRecyclingSimplerVersion, dump_reimpl_dict),\n    \"almost_python_dict_recycling_js\": (AlmostPythonDictRecyclingJsImpl, dump_reimpl_dict),\n    \"almost_python_dict_no_recycling_js\": (AlmostPythonDictNoRecyclingJsImpl, dump_reimpl_dict),\n\n    \"almost_python_dict_recycling_py_extracted\": (build_autogenerated_chapter3_chapter4.HashClassRecyclingExtracted, dump_reimpl_dict),\n    \"almost_python_dict_no_recycling_py_extracted\": (build_autogenerated_chapter3_chapter4.HashClassNoRecyclingExtracted, dump_reimpl_dict),\n}\n\n\ndef verify_same(d, dump_d_func, dreimpl, dump_dreimpl_func):\n    dump_d = dump_d_func(d)\n    dump_reimpl = dump_dreimpl_func(dreimpl)\n\n    if dump_d != dump_reimpl:\n        hashes_orig, keys_orig, values_orig, fill_orig, used_orig = dump_d\n        hashes_new, keys_new, values_new, fill_new, used_new = dump_reimpl\n        print(\"ORIG SIZE\", len(hashes_orig))\n        print(\"NEW SIZE\", len(hashes_new))\n        print(\"ORIG fill/used: \", fill_orig, used_orig)\n        print(\"NEW fill/used: \", fill_new, used_new)\n        if len(hashes_orig) == len(hashes_new):\n            size = len(hashes_orig)\n            print(\"NEW | ORIG\")\n            for i in range(size):\n                if hashes_new[i] is not EMPTY or hashes_orig[i] is not EMPTY:\n                    print(i, \" \" * 3,\n                          hashes_new[i], keys_new[i], values_new[i], \" \" * 3,\n                          hashes_orig[i], keys_orig[i], values_orig[i])\n\n    assert dump_d == dump_reimpl\n\n\ndef run(ref_impl_factory, ref_impl_dump, test_impl_factory, test_impl_dump, n_inserts, extra_checks, key_value_factory, initial_state, verbose):\n    SINGLE_REMOVE_CHANCE = 0.3\n    MASS_REMOVE_CHANCE = 0.002\n    MASS_REMOVE_COEFF = 0.8\n\n    removed = set()\n\n    if initial_state:\n        d = ref_impl_factory(initial_state)\n    else:\n        d = ref_impl_factory()\n\n    if initial_state:\n        dreimpl = test_impl_factory(initial_state)\n    else:\n        dreimpl = test_impl_factory()\n\n    if verbose:\n        print(\"Starting test\")\n\n    for i in range(n_inserts):\n        should_remove = (random.random() < SINGLE_REMOVE_CHANCE)\n        if should_remove and d and d.keys():  # TODO: ugly, written while on a plane\n            to_remove = random.choice(list(d.keys()))\n            if verbose:\n                print(\"Removing {}\".format(to_remove))\n            del d[to_remove]\n            del dreimpl[to_remove]\n            if verbose:\n                print(d)\n            verify_same(d, ref_impl_dump, dreimpl, test_impl_dump)\n            removed.add(to_remove)\n\n        should_mass_remove = (random.random() < MASS_REMOVE_CHANCE)\n        if should_mass_remove and len(d) > 10:\n            to_remove_list = random.sample(list(d.keys()), int(MASS_REMOVE_COEFF * len(d)))\n            if verbose:\n                print(\"Mass-Removing {} elements\".format(len(to_remove_list)))\n            for k in to_remove_list:\n                del d[k]\n                del dreimpl[k]\n                removed.add(k)\n\n        if extra_checks:\n            for k in d.keys():\n                assert d[k] == dreimpl[k]\n\n            for r in removed:\n                try:\n                    dreimpl[r]\n                    assert False\n                except KeyError:\n                    pass\n\n        key_to_insert = key_value_factory.generate_key()\n        value_to_insert = key_value_factory.generate_value()\n        _keys_set = getattr(d, '_keys_set', None)\n        # TODO: ugly code written on a plane\n        # TODO: properly implement in/not in when I land\n        if _keys_set is not None:\n            key_present = key_to_insert in _keys_set\n        else:\n            key_present = key_to_insert in d\n\n        if not key_present:\n            if verbose:\n                print(\"Inserting ({key}, {value})\".format(key=key_to_insert, value=value_to_insert))\n            try:\n                dreimpl[key_to_insert]\n                assert False\n            except KeyError:\n                pass\n        else:\n            if verbose:\n                print(\"Replacing ({key}, {value1}) with ({key}, {value2})\".format(key=key_to_insert, value1=d[key_to_insert], value2=value_to_insert))\n        removed.discard(key_to_insert)\n        d[key_to_insert] = value_to_insert\n        dreimpl[key_to_insert] = value_to_insert\n        if verbose:\n            print(d)\n        verify_same(d, ref_impl_dump, dreimpl, test_impl_dump)\n        assert dreimpl[key_to_insert] == value_to_insert\n\n\nif __name__ == \"__main__\":\n    parser = argparse.ArgumentParser(description='Stress-test dict-like reimplementations')\n    parser.add_argument('--reference-implementation', choices=IMPLEMENTATIONS.keys(), required=True)\n    parser.add_argument('--test-implementation', choices=IMPLEMENTATIONS.keys(), required=True)\n    parser.add_argument('--no-extra-getitem-checks', dest='extra_checks', action='store_false')\n    parser.add_argument('--num-inserts',  type=int, default=500)\n    parser.add_argument('--forever', action='store_true')\n    parser.add_argument('--kv', choices=[\"numbers\", \"all\"], required=True)\n    parser.add_argument('--initial-size', type=int, default=-1)\n    parser.add_argument('--verbose', action='store_true')\n    args = parser.parse_args()\n\n    if args.kv == \"numbers\":\n        kv_factory = IntKeyValueFactory(args.num_inserts)\n    elif args.kv == \"all\":\n        kv_factory = AllKeyValueFactory(args.num_inserts)\n\n    ref_impl = IMPLEMENTATIONS[args.reference_implementation]\n    test_impl = IMPLEMENTATIONS[args.test_implementation]\n\n    def test_iteration():\n        initial_size = args.initial_size if args.initial_size >= 0 else random.randint(0, 100)\n        initial_state = [(kv_factory.generate_key(), kv_factory.generate_value()) for _ in range(initial_size)]\n        run(*(ref_impl + test_impl),\n            n_inserts=args.num_inserts,\n            extra_checks=args.extra_checks,\n            key_value_factory=kv_factory,\n            initial_state=initial_state,\n            verbose=args.verbose)\n\n    if args.forever:\n        while True:\n            test_iteration()\n    else:\n        test_iteration()\n"
  },
  {
    "path": "python_code/dict_reimpl_common.py",
    "content": "from common import EMPTY\n\n\nclass Slot(object):\n    def __init__(self, hash_code=EMPTY, key=EMPTY, value=EMPTY):\n        self.hash_code = hash_code\n        self.key = key\n        self.value = value\n\n\nclass BaseDictImpl(object):\n    def __init__(self):\n        self.slots = [Slot() for _ in range(self.START_SIZE)]\n        self.fill = 0\n        self.used = 0\n\n    def find_nearest_size(self, minused):\n        new_size = 8\n        while new_size <= minused:\n            new_size *= 2\n\n        return new_size\n"
  },
  {
    "path": "python_code/dict_reimplementation.py",
    "content": "from common import DUMMY, EMPTY\nfrom dict_reimpl_common import BaseDictImpl, Slot\nfrom operator import attrgetter\n\n\nclass PyDictReimplementationBase(BaseDictImpl):\n    START_SIZE = 8\n    PERTURB_SHIFT = 5\n\n    def __init__(self, pairs=None):\n        BaseDictImpl.__init__(self)\n        start_size = self.find_nearest_size(len(pairs)) if pairs else self.START_SIZE\n        self.slots = [Slot() for _ in range(start_size)]\n        if pairs:\n            for k, v in pairs:\n                self[k] = v\n\n    def __setitem__(self, key, value):\n        hash_code = hash(key)\n        perturb = self.signed_to_unsigned(hash_code)\n        idx = hash_code % len(self.slots)\n        target_idx = None\n        while self.slots[idx].key is not EMPTY:\n            if self.slots[idx].hash_code == hash_code and self.slots[idx].key == key:\n                target_idx = idx\n                break\n            if target_idx is None and self.slots[idx].key is DUMMY:\n                target_idx = idx\n\n            idx = (idx * 5 + perturb + 1) % len(self.slots)\n            perturb >>= self.PERTURB_SHIFT\n\n        if target_idx is None:\n            target_idx = idx\n\n        if self.slots[target_idx].key is EMPTY:\n            self.used += 1\n            self.fill += 1\n        elif self.slots[target_idx].key is DUMMY:\n            self.used += 1\n\n        self.slots[target_idx] = Slot(hash_code, key, value)\n        if self.fill * 3 >= len(self.slots) * 2:\n            self.resize()\n\n    def __delitem__(self, key):\n        idx = self.lookdict(key)\n\n        self.used -= 1\n        self.slots[idx].key = DUMMY\n        self.slots[idx].value = EMPTY\n\n    def __getitem__(self, key):\n        idx = self.lookdict(key)\n\n        return self.slots[idx].value\n\n    @staticmethod\n    def signed_to_unsigned(hash_code):\n        return 2**64 + hash_code if hash_code < 0 else hash_code\n\n    def lookdict(self, key):\n        hash_code = hash(key)\n        perturb = self.signed_to_unsigned(hash_code)\n\n        idx = hash_code % len(self.slots)\n        while self.slots[idx].key is not EMPTY:\n            if self.slots[idx].hash_code == hash_code and self.slots[idx].key == key:\n                return idx\n\n            idx = (idx * 5 + perturb + 1) % len(self.slots)\n            perturb >>= self.PERTURB_SHIFT\n\n        raise KeyError()\n\n    def resize(self):\n        old_slots = self.slots\n        new_size = self.find_nearest_size(self._next_size())\n        self.slots = [Slot() for _ in range(new_size)]\n        self.fill = self.used\n        for slot in old_slots:\n            if slot.key is not EMPTY and slot.key is not DUMMY:\n                perturb = self.signed_to_unsigned(slot.hash_code)\n                idx = slot.hash_code % len(self.slots)\n                while self.slots[idx].key is not EMPTY:\n                    idx = (idx * 5 + perturb + 1) % len(self.slots)\n                    perturb >>= self.PERTURB_SHIFT\n\n                self.slots[idx] = Slot(slot.hash_code, slot.key, slot.value)\n\n\nclass PyDictReimplementation32(PyDictReimplementationBase):\n    def _next_size(self):\n        return self.used * (4 if self.used <= 50000 else 2)\n\n\ndef dump_reimpl_dict(d):\n    def extract_fields(field_name):\n        return list(map(attrgetter(field_name), d.slots))\n    return extract_fields('hash_code'), extract_fields('key'), extract_fields('value'), d.fill, d.used\n"
  },
  {
    "path": "python_code/dictinfo.py",
    "content": "import sys\n\n\ndef dump_py_dict(d):\n    vi = sys.version_info\n\n    if vi.major != 3:\n        raise Exception(\"Unsupported major version\")\n\n    if vi.minor < 2:\n        raise Exception(\"Unsupported minor version (too old)\")\n    if vi.minor > 3:\n        raise Exception(\"Unsupported minor version (too new)\")\n\n    if vi.minor == 2:\n        import dictinfo32\n        return dictinfo32.dump_py_dict(d)\n    else:\n        import dictinfo33\n        return dictinfo33.dump_py_dict(d)\n"
  },
  {
    "path": "python_code/dictinfo32.py",
    "content": "from ctypes import Structure, c_ulong, POINTER, cast, py_object, c_long\nfrom common import get_object_field_or_null, EMPTY, DUMMY\n\n\nclass PyDictEntry(Structure):\n    _fields_ = [\n        ('me_hash', c_long),\n        ('me_key', py_object),\n        ('me_value', py_object),\n    ]\n\n\nclass PyDictObject(Structure):\n    _fields_ = [\n        ('ob_refcnt', c_ulong),\n        ('ob_type', c_ulong),\n        ('ma_fill', c_ulong),\n        ('ma_used', c_ulong),\n        ('ma_mask', c_ulong),\n        ('ma_table', POINTER(PyDictEntry)),\n    ]\n\n\ndef dictobject(d):\n    return cast(id(d), POINTER(PyDictObject)).contents\n\n\nd = {0: 0}\ndel d[0]\ndummy_internal = dictobject(d).ma_table[0].me_key\ndel d\n\n\ndef dump_py_dict(d):\n    do = dictobject(d)\n\n    keys = []\n    hashes = []\n    values = []\n\n    size = do.ma_mask + 1\n\n    for i in range(size):\n        key = get_object_field_or_null(do.ma_table[i], 'me_key')\n        keys.append(key if key is not dummy_internal else DUMMY)\n\n    for i, key in enumerate(keys):\n        if key is EMPTY:\n            hashes.append(EMPTY)\n            values.append(EMPTY)\n        else:\n            hashes.append(do.ma_table[i].me_hash)\n            values.append(get_object_field_or_null(do.ma_table[i], 'me_value'))\n\n    return hashes, keys, values, do.ma_fill, do.ma_used\n"
  },
  {
    "path": "python_code/dictinfo33.py",
    "content": "from ctypes import Structure, c_ulong, POINTER, cast, addressof, py_object, c_long, c_void_p\nfrom common import get_object_field_or_null, EMPTY, DUMMY\n\n\nclass PyDictKeyEntry(Structure):\n    _fields_ = [\n        ('me_hash', c_long),\n        ('me_key', py_object),\n        ('me_value', py_object),\n    ]\n\n\nclass PyDictKeysObject(Structure):\n    _fields_ = [\n        ('dk_refcnt', c_long),\n        ('dk_size', c_long),\n        ('dict_lookup_func', POINTER(c_void_p)),\n        ('dk_usable', c_long),\n        ('dk_entries', PyDictKeyEntry),\n    ]\n\n\nclass PyDictObject(Structure):\n    _fields_ = [\n        ('ob_refcnt', c_ulong),\n        ('ob_type', c_ulong),\n        ('ma_used', c_long),\n        ('ma_keys', POINTER(PyDictKeysObject)),\n\n        # Not actually a void*, split tables are not supported right now\n        ('ma_values', POINTER(c_void_p))\n    ]\n\n\ndef dictobject(d):\n    return cast(id(d), POINTER(PyDictObject)).contents\n\n\nd = {0: 0}\ndel d[0]\ndummy_internal = dictobject(d).ma_keys.contents.dk_entries.me_key\ndel d\n\n\ndef usable_fraction(size):\n    return (size * 2 + 1) // 3\n\n\ndef dump_py_dict(d):\n    do = dictobject(d)\n\n    keys = []\n    hashes = []\n    values = []\n\n    size = do.ma_keys.contents.dk_size\n    entries = cast(addressof(do.ma_keys.contents.dk_entries), POINTER(PyDictKeyEntry))\n    for i in range(size):\n        key = get_object_field_or_null(entries[i], 'me_key')\n        keys.append(key if key is not dummy_internal else DUMMY)\n\n    for i, key in enumerate(keys):\n        if key is EMPTY:\n            hashes.append(EMPTY)\n            values.append(EMPTY)\n        else:\n            hashes.append(entries[i].me_hash)\n            values.append(get_object_field_or_null(entries[i], 'me_value'))\n\n    return hashes, keys, values, usable_fraction(do.ma_keys.contents.dk_size) - do.ma_keys.contents.dk_usable, do.ma_used\n"
  },
  {
    "path": "python_code/hash_chapter1_impl.py",
    "content": "def create_new(numbers):\n    n = len(numbers)\n    keys = [None for i in range(2 * n)]\n\n    for num in numbers:\n        idx = num % len(keys)\n\n        while keys[idx] is not None:\n            idx = (idx + 1) % len(keys)\n\n        keys[idx] = num\n\n    return keys\n\n\ndef create_new_broken(numbers):\n    n = len(numbers)\n    keys = [None for i in range(n)]\n\n    for num in numbers:\n        idx = num % len(keys)\n        keys[idx] = num\n\n    return keys\n\n\ndef has_key(keys, key):\n    idx = key % len(keys)\n    while keys[idx] is not None:\n        if keys[idx] == key:\n            return True\n        idx = (idx + 1) % len(keys)\n\n    return False\n\n\ndef linear_search(numbers, number):\n    return number in numbers\n"
  },
  {
    "path": "python_code/hash_chapter1_reimpl_js.py",
    "content": "from js_reimpl_common import run_op_chapter1_chapter2\n\n\ndef run_op(keys, op, **kwargs):\n    return run_op_chapter1_chapter2(\"chapter1\", None, keys, op, **kwargs)\n\n\ndef create_new(numbers):\n    return run_op(None, \"create_new\", array=numbers)\n\n\ndef create_new_broken(numbers):\n    return run_op(None, \"create_new_broken\", array=numbers)\n\n\ndef has_key(keys, key):\n    return run_op(keys, \"has_key\", key=key)\n\n\ndef linear_search(numbers, key):\n    return run_op(None, \"linear_search\", key=key, array=numbers)\n"
  },
  {
    "path": "python_code/hash_chapter1_reimplementation_test.py",
    "content": "import random\nimport argparse\n\nimport hash_chapter1_reimpl_js\nimport hash_chapter1_impl\nimport build_autogenerated_chapter1_hash\n\n\ndef get_implementation(is_broken, impl):\n    if impl == \"js\":\n        module = hash_chapter1_reimpl_js\n    elif impl == \"py_ref\":\n        module = hash_chapter1_impl\n    elif impl == \"py_extracted\":\n        module = build_autogenerated_chapter1_hash\n    else:\n        assert False\n\n    return (module.create_new_broken if is_broken else module.create_new, module.has_key)\n\n\ndef run(test_implementation, is_broken, n_inserts):\n    MAX_VAL = 5000\n    ref_create_new, ref_has_key = get_implementation(is_broken, \"py_ref\")\n    test_create_new, test_has_key = get_implementation(is_broken, test_implementation)\n\n    numbers = list(set(random.randint(-MAX_VAL, MAX_VAL) for _ in range(n_inserts)))\n\n    ref_keys = ref_create_new(numbers)\n    test_keys = test_create_new(numbers)\n\n    for number in numbers:\n        if not is_broken:\n            assert ref_has_key(ref_keys, number)\n            assert test_has_key(test_keys, number)\n        else:\n            assert ref_has_key(ref_keys, number) == test_has_key(test_keys, number)\n\n    for i in range(n_inserts * 3):\n        number = random.randint(-MAX_VAL, MAX_VAL)\n        assert ref_has_key(ref_keys, number) == test_has_key(test_keys, number)\n\n\nif __name__ == \"__main__\":\n    parser = argparse.ArgumentParser(description='Stress-test chapter1 reimplementation')\n    parser.add_argument('--is-broken', action='store_true')\n    parser.add_argument('--test-implementation', choices=['py_extracted', 'js'], required=True)\n    parser.add_argument('--num-inserts',  type=int, default=500)\n    args = parser.parse_args()\n\n    run(test_implementation=args.test_implementation,\n        is_broken=args.is_broken,\n        n_inserts=args.num_inserts)\n"
  },
  {
    "path": "python_code/hash_chapter2_impl.py",
    "content": "from common import DUMMY, EMPTY\n\n\ndef create_new(from_keys):\n    n = len(from_keys)\n    hash_codes = [EMPTY for i in range(2 * n)]\n    keys = [EMPTY for i in range(2 * n)]\n\n    for key in from_keys:\n        hash_code = hash(key)\n        idx = hash_code % len(keys)\n\n        while keys[idx] is not EMPTY:\n            if hash_codes[idx] == hash_code and keys[idx] == key:\n                break\n            idx = (idx + 1) % len(keys)\n\n        hash_codes[idx] = hash_code\n        keys[idx] = key\n\n    return hash_codes, keys\n\n\ndef insert(hash_codes, keys, key):\n    hash_code = hash(key)\n    idx = hash_code % len(keys)\n\n    while hash_codes[idx] is not EMPTY:\n        if hash_codes[idx] == hash_code and keys[idx] == key:\n            return\n        idx = (idx + 1) % len(keys)\n\n    hash_codes[idx] = hash_code\n    keys[idx] = key\n\n\ndef remove(hash_codes, keys, key):\n    hash_code = hash(key)\n    idx = hash_code % len(keys)\n\n    while hash_codes[idx] is not EMPTY:\n        if hash_codes[idx] == hash_code and keys[idx] == key:\n            keys[idx] = DUMMY\n            return\n        idx = (idx + 1) % len(keys)\n\n    raise KeyError()\n\n\ndef has_key(hash_codes, keys, key):\n    hash_code = hash(key)\n    idx = hash_code % len(keys)\n    while hash_codes[idx] is not EMPTY:\n        if hash_codes[idx] == hash_code and keys[idx] == key:\n            return True\n        idx = (idx + 1) % len(keys)\n    return False\n\n\ndef resize(hash_codes, keys):\n    new_hash_codes = [EMPTY for i in range(len(hash_codes) * 2)]\n    new_keys = [EMPTY for i in range(len(keys) * 2)]\n    for hash_code, key in zip(hash_codes, keys):\n        if key is EMPTY or key is DUMMY:\n            continue\n        idx = hash_code % len(new_keys)\n        while new_hash_codes[idx] is not EMPTY:\n            idx = (idx + 1) % len(new_keys)\n        new_hash_codes[idx] = hash_code\n        new_keys[idx] = key\n\n    return new_hash_codes, new_keys\n"
  },
  {
    "path": "python_code/hash_chapter2_impl_test.py",
    "content": "import unittest\nfrom hash_chapter2_impl import create_new, has_key, insert, remove, resize, DUMMY\nfrom common import generate_random_string\n\n\nclass MyHashTest(unittest.TestCase):\n    def test_handcrafted(self):\n        expected_len = 6\n        hashes, keys = create_new([42, 43, 12])\n\n        self.assertEqual(len(hashes), expected_len)\n        self.assertEqual(len(keys), expected_len)\n        insert(hashes, keys, 42)\n\n        self.assertEqual(hashes[42 % expected_len], 42)\n        self.assertEqual(keys[42 % expected_len], 42)\n\n        self.assertEqual(hashes[43 % expected_len], 43)\n        self.assertEqual(keys[43 % expected_len], 43)\n\n        self.assertEqual(hashes[42 % expected_len], 42)\n        self.assertEqual(keys[42 % expected_len], 42)\n\n        self.assertEqual(hashes[12 % expected_len], 42)\n        self.assertEqual(keys[12 % expected_len], 42)\n        self.assertEqual(hashes[12 % expected_len + 1], 43)\n        self.assertEqual(keys[12 % expected_len + 1], 43)\n        self.assertEqual(hashes[12 % expected_len + 2], 12)\n        self.assertEqual(keys[12 % expected_len + 2], 12)\n\n        self.assertTrue(has_key(hashes, keys, 42))\n        self.assertTrue(has_key(hashes, keys, 43))\n        self.assertTrue(has_key(hashes, keys, 12))\n        self.assertFalse(has_key(hashes, keys, 45))\n\n        # table: [42, 43, 12, None, None, None]\n        insert(hashes, keys, \"\")  # hash(\"\") == 0\n        self.assertEqual(hashes[3], 0)\n        self.assertEqual(keys[3], \"\")\n\n        self.assertTrue(has_key(hashes, keys, \"\"))\n        self.assertTrue(has_key(hashes, keys, 42))\n\n        insert(hashes, keys, \"aba\")  # hash(\"aba\") % 6 == 5\n        self.assertEqual(hashes[5], hash(\"aba\"))\n        self.assertEqual(keys[5], \"aba\")\n\n        self.assertTrue(has_key(hashes, keys, 12))\n        remove(hashes, keys, 12)\n        self.assertFalse(has_key(hashes, keys, 12))\n\n        self.assertEqual(hashes[12 % expected_len], 42)\n        self.assertEqual(keys[12 % expected_len], 42)\n\n        self.assertEqual(keys[12 % expected_len + 2], DUMMY)\n\n        with self.assertRaises(KeyError):\n            remove(hashes, keys, 12)\n        with self.assertRaises(KeyError):\n            remove(hashes, keys, 45)\n\n        self.assertFalse(has_key(hashes, keys, 12))\n        self.assertFalse(has_key(hashes, keys, 45))\n        self.assertTrue(has_key(hashes, keys, 42))\n        self.assertTrue(has_key(hashes, keys, 43))\n        self.assertTrue(has_key(hashes, keys, \"\"))\n        self.assertTrue(has_key(hashes, keys, \"aba\"))\n\n        insert(hashes, keys, \"abg\")\n        self.assertTrue(has_key(hashes, keys, \"abg\"))\n        self.assertEqual(hashes[4], hash(\"abg\"))\n        self.assertEqual(keys[4], \"abg\")\n        hashes, keys = resize(hashes, keys)\n\n        self.assertTrue(has_key(hashes, keys, 42))\n        self.assertTrue(has_key(hashes, keys, 43))\n        self.assertTrue(has_key(hashes, keys, \"\"))\n        self.assertTrue(has_key(hashes, keys, \"aba\"))\n        self.assertTrue(has_key(hashes, keys, \"abg\"))\n\n        self.assertFalse(has_key(hashes, keys, 12))\n        self.assertFalse(has_key(hashes, keys, 45))\n\n        self.assertEqual(hashes[6], 42)\n        self.assertEqual(keys[6], 42)\n        self.assertEqual(hashes[7], 43)\n        self.assertEqual(keys[7], 43)\n\n        self.assertEqual(hashes[0], 0)\n        self.assertEqual(keys[0], \"\")\n        for h in hashes:\n            self.assertTrue(h != 12)\n\n        self.assertEqual(hashes[5], hash(\"aba\"))\n        self.assertEqual(keys[5], \"aba\")\n\n        self.assertEqual(hashes[11], hash(\"abg\"))\n        self.assertEqual(keys[11], \"abg\")\n\n    def test_all(self):\n        n = 10\n        initial_keys = [generate_random_string() for _ in range(n)]\n        more_keys = [generate_random_string() for _ in range(n // 3)]\n        myhashes, mykeys = create_new(initial_keys)\n\n        for key in more_keys:\n            insert(myhashes, mykeys, key)\n            insert(myhashes, mykeys, key)\n\n        existing_keys = initial_keys + more_keys\n        for key in existing_keys:\n            self.assertTrue(has_key(myhashes, mykeys, key))\n\n        myhashes, mykeys = resize(myhashes, mykeys)\n\n        for key in existing_keys:\n            self.assertTrue(has_key(myhashes, mykeys, key))\n\n        missing_keys = [generate_random_string() for _ in range(3 * n)]\n        for key in set(missing_keys) - set(existing_keys):\n            self.assertFalse(has_key(myhashes, mykeys, key))\n            with self.assertRaises(KeyError):\n                remove(myhashes, mykeys, key)\n\n        for key in existing_keys:\n            self.assertTrue(has_key(myhashes, mykeys, key))\n            remove(myhashes, mykeys, key)\n            self.assertFalse(has_key(myhashes, mykeys, key))\n\n        for key in more_keys:\n            self.assertFalse(has_key(myhashes, mykeys, key))\n            insert(myhashes, mykeys, key)\n            self.assertTrue(has_key(myhashes, mykeys, key))\n            remove(myhashes, mykeys, key)\n            self.assertFalse(has_key(myhashes, mykeys, key))\n\n\ndef main():\n    unittest.main()\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "python_code/hash_chapter2_reimpl_js.py",
    "content": "from js_reimpl_common import run_op_chapter1_chapter2\n\n\ndef run_op(hash_codes, keys, op, **kwargs):\n    return run_op_chapter1_chapter2(\"chapter2\", hash_codes, keys, op, **kwargs)\n\n\ndef create_new(from_keys):\n    return run_op(None, None, \"create_new\", array=from_keys)\n\n\ndef insert(hash_codes, keys, key):\n    new_hash_codes, new_keys = run_op(hash_codes, keys, \"insert\", key=key)\n    hash_codes[:] = new_hash_codes\n    keys[:] = new_keys\n\n\ndef remove(hash_codes, keys, key):\n    new_hash_codes, new_keys = run_op(hash_codes, keys, \"remove\", key=key)\n    hash_codes[:] = new_hash_codes\n    keys[:] = new_keys\n\n\ndef has_key(hash_codes, keys, key):\n    return run_op(hash_codes, keys, \"has_key\", key=key)\n\n\ndef resize(hash_codes, keys):\n    return run_op(hash_codes, keys, \"resize\")\n"
  },
  {
    "path": "python_code/hash_chapter2_reimplementation_test.py",
    "content": "import random\nimport argparse\n\nfrom common import DUMMY, EMPTY, AllKeyValueFactory, IntKeyValueFactory\n\nimport hash_chapter2_reimpl_js\nimport hash_chapter2_impl\nimport build_autogenerated_chapter2\n\nTEST_IMPLEMENTATIONS = {\n    'js_reimpl': hash_chapter2_reimpl_js,\n    'py_extracted': build_autogenerated_chapter2\n}\n\n\ndef verify_same(ref_hash_codes, ref_keys, hash_codes, keys):\n    if (ref_hash_codes, ref_keys) != (hash_codes, keys):\n        print(\"ORIG SIZES\", len(ref_hash_codes), len(ref_keys))\n        print(\"NEW SIZES\", len(hash_codes), len(keys))\n        if len(ref_hash_codes) == len(hash_codes) == len(ref_keys) == len(keys):\n            size = len(hash_codes)\n            print(\"NEW | ORIG\")\n            for i in range(size):\n                if ref_hash_codes[i] is not EMPTY or hash_codes[i] is not EMPTY:\n                    print(i, \" \" * 3,\n                          ref_hash_codes[i], ref_keys[i], \" \" * 3,\n                          hash_codes[i], keys[i], \" \" * 3)\n\n    assert ref_hash_codes == hash_codes and ref_keys == keys\n\n\ndef run(ref_impl, test_impl, n_inserts, key_value_factory, initial_state, extra_checks, verbose):\n    SINGLE_REMOVE_CHANCE = 0.3\n\n    ref_hash_codes, ref_keys = ref_impl.create_new(initial_state)\n    test_hash_codes, test_keys = test_impl.create_new(initial_state)\n\n    def vs():\n        verify_same(ref_hash_codes, ref_keys, test_hash_codes, test_keys)\n\n    vs()\n\n    if verbose:\n        print(\"Starting test\")\n\n    for i in range(n_inserts):\n        key_to_insert = key_value_factory.generate_key()\n\n        existing_keys = set([k for k in ref_keys if k is not DUMMY and k is not EMPTY])\n        fill = sum(1 for k in ref_keys if k is not EMPTY)\n        if existing_keys and random.random() < SINGLE_REMOVE_CHANCE:\n            key_to_remove = random.choice(list(existing_keys))\n            assert ref_impl.has_key(ref_hash_codes, ref_keys, key_to_remove)\n            assert test_impl.has_key(test_hash_codes, test_keys, key_to_remove)\n\n            ref_impl.remove(ref_hash_codes, ref_keys, key_to_remove)\n            test_impl.remove(test_hash_codes, test_keys, key_to_remove)\n            existing_keys.remove(key_to_remove)\n\n            assert not ref_impl.has_key(ref_hash_codes, ref_keys, key_to_remove)\n            assert not test_impl.has_key(test_hash_codes, test_keys, key_to_remove)\n\n        is_key_present = ref_impl.has_key(ref_hash_codes, ref_keys, key_to_insert)\n        assert (key_to_insert in existing_keys) == is_key_present\n\n        if not is_key_present:\n            if verbose:\n                print(\"Inserting {}\".format(key_to_insert))\n            assert not test_impl.has_key(test_hash_codes, test_keys, key_to_insert)\n        else:\n            if verbose:\n                print(\"Re-Inserting {}\".format(key_to_insert))\n\n        ref_impl.insert(ref_hash_codes, ref_keys, key_to_insert)\n        test_impl.insert(test_hash_codes, test_keys, key_to_insert)\n        vs()\n        assert test_impl.has_key(test_hash_codes, test_keys, key_to_insert)\n        assert ref_impl.has_key(ref_hash_codes, ref_keys, key_to_insert)\n\n        if fill / len(ref_keys) > 0.66:\n            ref_hash_codes, ref_keys = ref_impl.resize(ref_hash_codes, ref_keys)\n            test_hash_codes, test_keys = test_impl.resize(test_hash_codes, test_keys)\n        vs()\n\n        if extra_checks:\n            for k in existing_keys:\n                assert test_impl.has_key(test_hash_codes, test_keys, k)\n                assert ref_impl.has_key(ref_hash_codes, ref_keys, k)\n\n\nif __name__ == \"__main__\":\n    parser = argparse.ArgumentParser(description='Stress-test chapter2 reimplementation')\n    parser.add_argument('--test-implementation', choices=TEST_IMPLEMENTATIONS.keys(), required=True)\n    parser.add_argument('--num-inserts',  type=int, default=500)\n    parser.add_argument('--forever', action='store_true')\n    parser.add_argument('--kv', choices=[\"numbers\", \"all\"], required=True)\n    parser.add_argument('--initial-size', type=int, default=-1)\n    parser.add_argument('--extra-getitem-checks', action='store_true', default=False)\n    parser.add_argument('--verbose', action='store_true', default=False)\n    args = parser.parse_args()\n\n    if args.kv == \"numbers\":\n        kv_factory = IntKeyValueFactory(args.num_inserts)\n    elif args.kv == \"all\":\n        kv_factory = AllKeyValueFactory(args.num_inserts)\n\n    def test_iteration():\n        initial_size = args.initial_size if args.initial_size >= 0 else random.randint(0, 100)\n        initial_state = [kv_factory.generate_key() for _ in range(initial_size)]\n        run(hash_chapter2_impl,\n            TEST_IMPLEMENTATIONS[args.test_implementation],\n            n_inserts=args.num_inserts,\n            key_value_factory=kv_factory,\n            initial_state=initial_state,\n            extra_checks=args.extra_getitem_checks,\n            verbose=args.verbose)\n\n    if args.forever:\n        while True:\n            test_iteration()\n    else:\n        test_iteration()\n"
  },
  {
    "path": "python_code/hash_chapter3_class_impl.py",
    "content": "from common import DUMMY, EMPTY\nfrom dict_reimpl_common import BaseDictImpl, Slot\n\n\nclass AlmostPythonDictBase(BaseDictImpl):\n    START_SIZE = 8\n\n    def __init__(self, pairs=None):\n        BaseDictImpl.__init__(self)\n        self._keys_set = set()\n        if pairs:\n            for k, v in pairs:\n                self[k] = v\n\n    def lookdict(self, key):\n        hash_code = hash(key)\n\n        idx = hash_code % len(self.slots)\n        while self.slots[idx].key is not EMPTY:\n            if self.slots[idx].hash_code == hash_code and self.slots[idx].key == key:\n                return idx\n\n            idx = (idx + 1) % len(self.slots)\n\n        raise KeyError()\n\n    def __getitem__(self, key):\n        idx = self.lookdict(key)\n\n        return self.slots[idx].value\n\n    def __delitem__(self, key):\n        idx = self.lookdict(key)\n\n        self.used -= 1\n        self.slots[idx].key = DUMMY\n        self.slots[idx].value = EMPTY\n        self._keys_set.remove(key)\n\n    def resize(self):\n        old_slots = self.slots\n        new_size = self.find_nearest_size(2 * self.used)\n        self.slots = [Slot() for _ in range(new_size)]\n\n        for slot in old_slots:\n            if slot.key is not EMPTY and slot.key is not DUMMY:\n                idx = slot.hash_code % len(self.slots)\n                while self.slots[idx].key is not EMPTY:\n                    idx = (idx + 1) % len(self.slots)\n\n                self.slots[idx] = Slot(slot.hash_code, slot.key, slot.value)\n\n        self.fill = self.used\n\n    def keys(self):\n        return self._keys_set\n\n    def __len__(self):\n        return len(self.keys())\n\n\nclass AlmostPythonDictImplementationRecycling(AlmostPythonDictBase):\n    def __setitem__(self, key, value):\n        hash_code = hash(key)\n        idx = hash_code % len(self.slots)\n        target_idx = None\n        while self.slots[idx].key is not EMPTY:\n            if self.slots[idx].hash_code == hash_code and self.slots[idx].key == key:\n                target_idx = idx\n                break\n            if target_idx is None and self.slots[idx].key is DUMMY:\n                target_idx = idx\n\n            idx = (idx + 1) % len(self.slots)\n\n        if target_idx is None:\n            target_idx = idx\n\n        if self.slots[target_idx].key is EMPTY:\n            self.used += 1\n            self.fill += 1\n        elif self.slots[target_idx].key is DUMMY:\n            self.used += 1\n\n        self.slots[target_idx] = Slot(hash_code, key, value)\n\n        if self.fill * 3 >= len(self.slots) * 2:\n            self.resize()\n\n        self._keys_set.add(key)\n\n\nclass AlmostPythonDictImplementationNoRecycling(AlmostPythonDictBase):\n    def __setitem__(self, key, value):\n        hash_code = hash(key)\n        idx = hash_code % len(self.slots)\n        target_idx = None\n        while self.slots[idx].key is not EMPTY:\n            if self.slots[idx].hash_code == hash_code and\\\n               self.slots[idx].key == key:\n                target_idx = idx\n                break\n            idx = (idx + 1) % len(self.slots)\n\n        if target_idx is None:\n            target_idx = idx\n        if self.slots[target_idx].key is EMPTY:\n            self.used += 1\n            self.fill += 1\n\n        self.slots[target_idx] = Slot(hash_code, key, value)\n        if self.fill * 3 >= len(self.slots) * 2:\n            self.resize()\n\n        self._keys_set.add(key)\n\n\nclass AlmostPythonDictImplementationNoRecyclingSimplerVersion(AlmostPythonDictBase):\n    def __setitem__(self, key, value):\n        hash_code = hash(key)\n        idx = hash_code % len(self.slots)\n        while self.slots[idx].key is not EMPTY:\n            if self.slots[idx].hash_code == hash_code and\\\n               self.slots[idx].key == key:\n                break\n            idx = (idx + 1) % len(self.slots)\n\n        if self.slots[idx].key is EMPTY:\n            self.used += 1\n            self.fill += 1\n\n        self.slots[idx] = Slot(hash_code, key, value)\n        if self.fill * 3 >= len(self.slots) * 2:\n            self.resize()\n\n        self._keys_set.add(key)\n"
  },
  {
    "path": "python_code/hash_chapter3_class_impl_test.py",
    "content": "import unittest\nfrom common import DUMMY, EMPTY\nfrom hash_chapter3_class_impl import AlmostPythonDictImplementationRecycling, AlmostPythonDictImplementationNoRecycling\n\n\nclass HashDictImplementationTest(unittest.TestCase):\n    def test_handcrafted(self):\n        d = AlmostPythonDictImplementationRecycling()\n        self.assertEqual(len(d.slots), 8)\n\n        def assert_contains(i, h, k, v):\n            self.assertEqual(d.slots[i].hash_code, h)\n            self.assertEqual(d.slots[i].key, k)\n            self.assertEqual(d.slots[i].value, v)\n\n        d[\"\"] = 1\n        d[17] = 2\n        d[18] = 3\n        self.assertEqual(d[\"\"], 1)\n        self.assertEqual(d[17], 2)\n        self.assertEqual(d[18], 3)\n\n        assert_contains(0, 0, \"\", 1)\n        assert_contains(1, 17, 17, 2)\n        assert_contains(2, 18, 18, 3)\n\n        self.assertEqual(d.fill, 3)\n        self.assertEqual(d.used, 3)\n\n        with self.assertRaises(KeyError):\n            del d[1]\n\n        del d[17]\n        assert_contains(1, 17, DUMMY, EMPTY)\n\n        self.assertEqual(d.fill, 3)\n        self.assertEqual(d.used, 2)\n        # hash(\"abcd\") % 8 == 0\n\n        # py 3.2 hash()\n        d[\"abcd\"] = 4\n        self.assertEqual(d[\"abcd\"], 4)\n        assert_contains(1, -2835746963027601024, \"abcd\", 4)\n        self.assertEqual(d.fill, 3)\n        self.assertEqual(d.used, 3)\n\n        d[\"abcd\"] = 5\n        self.assertEqual(d[\"abcd\"], 5)\n        assert_contains(1, -2835746963027601024, \"abcd\", 5)\n        self.assertEqual(d.fill, 3)\n        self.assertEqual(d.used, 3)\n\n        del d[\"abcd\"]\n        with self.assertRaises(KeyError):\n            d[\"abcd\"]\n\n        d[15] = 6\n        d[14] = 7\n\n        assert_contains(7, 15, 15, 6)\n        assert_contains(6, 14, 14, 7)\n\n        self.assertEqual(len(d.slots), 8)\n        self.assertEqual(d.fill, 5)\n        self.assertEqual(d.used, 4)\n        d[13] = 8\n        self.assertEqual(len(d.slots), 16)\n        self.assertEqual(d.fill, 5)\n        self.assertEqual(d.used, 5)\n\n        assert_contains(0, 0, \"\", 1)\n        assert_contains(2, 18, 18, 3)\n        assert_contains(13, 13, 13, 8)\n        assert_contains(14, 14, 14, 7)\n        assert_contains(15, 15, 15, 6)\n\n    def test_handcrafted_simple_setitem(self):\n        d = AlmostPythonDictImplementationNoRecycling()\n        self.assertEqual(len(d.slots), 8)\n\n        def assert_contains(i, h, k, v):\n            self.assertEqual(d.slots[i].hash_code, h)\n            self.assertEqual(d.slots[i].key, k)\n            self.assertEqual(d.slots[i].value, v)\n\n        d[\"\"] = 1\n        d[17] = 2\n        d[18] = 3\n        self.assertEqual(d[\"\"], 1)\n        self.assertEqual(d[17], 2)\n        self.assertEqual(d[18], 3)\n\n        assert_contains(0, 0, \"\", 1)\n        assert_contains(1, 17, 17, 2)\n        assert_contains(2, 18, 18, 3)\n\n        self.assertEqual(d.fill, 3)\n        self.assertEqual(d.used, 3)\n\n        with self.assertRaises(KeyError):\n            del d[1]\n\n        del d[17]\n        assert_contains(1, 17, DUMMY, EMPTY)\n\n        self.assertEqual(d.fill, 3)\n        self.assertEqual(d.used, 2)\n        # hash(\"abcd\") % 8 == 0\n\n        # py 3.2 hash()\n        d[\"abcd\"] = 4\n        self.assertEqual(d[\"abcd\"], 4)\n        assert_contains(3, -2835746963027601024, \"abcd\", 4)\n        self.assertEqual(d.fill, 4)\n        self.assertEqual(d.used, 3)\n\n        d[\"abcd\"] = 5\n        self.assertEqual(d[\"abcd\"], 5)\n        assert_contains(3, -2835746963027601024, \"abcd\", 5)\n        self.assertEqual(d.fill, 4)\n        self.assertEqual(d.used, 3)\n\n        del d[\"abcd\"]\n        with self.assertRaises(KeyError):\n            d[\"abcd\"]\n\n        self.assertEqual(len(d.slots), 8)\n        self.assertEqual(d.fill, 4)\n        self.assertEqual(d.used, 2)\n\n        d[15] = 6\n        self.assertEqual(len(d.slots), 8)\n        self.assertEqual(d.fill, 5)\n        self.assertEqual(d.used, 3)\n        assert_contains(7, 15, 15, 6)\n\n        d[13] = 8\n        self.assertEqual(len(d.slots), 16)\n        self.assertEqual(d.fill, 4)\n        self.assertEqual(d.used, 4)\n\n        assert_contains(0, 0, \"\", 1)\n        assert_contains(2, 18, 18, 3)\n        assert_contains(13, 13, 13, 8)\n        assert_contains(15, 15, 15, 6)\n\n\ndef main():\n    unittest.main()\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "python_code/interface_test.py",
    "content": "import unittest\nfrom dict32_reimplementation import PyDictReimplementation\nfrom hash_chapter3_class_impl import AlmostPythonDictImplementationRecycling, AlmostPythonDictImplementationNoRecycling\nfrom js_reimplementation_interface import Dict32JsImpl, AlmostPythonDictRecyclingJsImpl, AlmostPythonDictNoRecyclingJsImpl\n\n\nclass Interface(unittest.TestCase):\n    def test_all(self):\n        self.do_simple_test_single_class(PyDictReimplementation)\n        self.do_simple_test_single_class(AlmostPythonDictImplementationRecycling)\n        self.do_simple_test_single_class(AlmostPythonDictImplementationNoRecycling)\n\n        self.do_simple_test_single_class(Dict32JsImpl)\n        self.do_simple_test_single_class(AlmostPythonDictRecyclingJsImpl)\n        self.do_simple_test_single_class(AlmostPythonDictNoRecyclingJsImpl)\n\n    def do_simple_test_single_class(self, klass):\n        d = klass()\n\n        for i in range(100):\n            d[i] = i\n            self.assertEqual(d[i], i)\n\n        for i in range(50):\n            del d[i]\n            with self.assertRaises(KeyError):\n                d[i]\n\n        for i in range(200):\n            d[i] = i + 1\n            self.assertEqual(d[i], i + 1)\n\n\ndef main():\n    unittest.main()\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "python_code/js_reimpl_common.py",
    "content": "import socket\nimport json\n\nfrom common import DUMMY, EMPTY\n\nnone_info = {\n    \"type\": \"None\",\n    \"hash\": str(hash(None))\n}\n\n\ndef dump_simple_py_obj(obj):\n    if obj is DUMMY:\n        return {\n            \"type\": \"DUMMY\"\n        }\n    elif obj is EMPTY:\n        return None\n    elif obj is None:\n        return none_info\n    elif isinstance(obj, int):\n        return {\n            'type': 'int',\n            'value': str(obj)\n        }\n    return obj\n\n\ndef dump_pairs(pairs):\n    res = []\n    for k, v in pairs:\n        res.append([dump_simple_py_obj(k), dump_simple_py_obj(v)])\n\n    return res\n\n\ndef dump_array(array):\n    return list(map(dump_simple_py_obj, array))\n\n\ndef parse_array(array):\n    return list(map(parse_simple_py_obj, array))\n\n\ndef parse_simple_py_obj(obj):\n    if isinstance(obj, dict):\n        assert obj[\"type\"] in [\"DUMMY\", \"None\", \"int\"]\n        if obj[\"type\"] == \"DUMMY\":\n            return DUMMY\n        if obj[\"type\"] == \"None\":\n            return None\n        return int(obj[\"value\"])\n    elif obj is None:\n        return EMPTY\n    return obj\n\n\nsock = None\nsockfile = None\n\n\ndef _init_sock_stuff():\n    global sock\n    global sockfile\n\n    # TODO: unhardcode?\n    SOCK_FILENAME = 'pynode.sock'\n\n    if sock is None:\n        sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)\n        sock.connect(SOCK_FILENAME)\n        sockfile = sock.makefile('r')\n\n    return sock, sockfile\n\n\ndef run_op_chapter1_chapter2(chapter, hash_codes, keys, op, **kwargs):\n    _init_sock_stuff()\n\n    for name in kwargs:\n        if name != 'array':\n            kwargs[name] = dump_simple_py_obj(kwargs[name])\n        else:\n            kwargs[name] = dump_array(kwargs[name])\n\n    data = {\n        \"dict\": chapter,\n        \"op\": op,\n        \"args\": kwargs,\n        \"hashCodes\": dump_array(hash_codes) if hash_codes is not None else None,\n        \"keys\": dump_array(keys) if keys is not None else None,\n    }\n\n    sock.send(bytes(json.dumps(data) + \"\\n\", 'UTF-8'))\n    response = json.loads(sockfile.readline())\n\n    if \"exception\" in response and response[\"exception\"]:\n        raise KeyError()\n\n    if 'result' in response and response['result'] is not None:\n        # TODO: this is pretty hacky\n        return response[\"result\"]\n    elif \"hashCodes\" in response:\n        return parse_array(response[\"hashCodes\"]), parse_array(response[\"keys\"])\n    else:\n        return parse_array(response[\"keys\"])\n"
  },
  {
    "path": "python_code/js_reimplementation_interface.py",
    "content": "import socket\nimport json\n\nfrom common import DUMMY, EMPTY\nfrom js_reimpl_common import dump_simple_py_obj, parse_simple_py_obj, dump_pairs\nfrom dict_reimpl_common import Slot\n\n\nclass JsImplBase(object):\n    # TODO: unhardcode?\n    SOCK_FILENAME = 'pynode.sock'\n\n    def __init__(self, pairs=None):\n        pairs = pairs or []\n\n        self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)\n        self.sock.connect(self.SOCK_FILENAME)\n        self.sockfile = self.sock.makefile('r')\n\n        self.slots = None\n        self.fill = None\n        self.used = None\n\n        self.run_op(\"__init__\", pairs=pairs)\n\n    def __del__(self):\n        self.sock.close()\n\n    def dump_slots(self):\n        def dump_slot(slot):\n            key = dump_simple_py_obj(slot.key)\n            value = dump_simple_py_obj(slot.value)\n\n            hash_code = slot.hash_code\n            if hash_code is EMPTY:\n                hash_code = None\n\n            return {\n                \"hashCode\": str(hash_code) if hash_code is not None else None,\n                \"key\": key,\n                \"value\": value,\n            }\n\n        if self.slots is None:\n            return None\n\n        return list(map(dump_slot, self.slots))\n\n    def restore_slots(self, slots):\n        def restore_slot(slot):\n            key = parse_simple_py_obj(slot[\"key\"])\n            value = parse_simple_py_obj(slot[\"value\"])\n            assert value is not DUMMY\n\n            hash_code = int(slot[\"hashCode\"]) if slot[\"hashCode\"] is not None else None\n            if hash_code is None:\n                hash_code = EMPTY\n\n            return Slot(hash_code, key, value)\n\n        self.slots = list(map(restore_slot, slots))\n\n    def run_op(self, op, **kwargs):\n        for name in kwargs:\n            if name != 'pairs':\n                kwargs[name] = dump_simple_py_obj(kwargs[name])\n            else:\n                kwargs[name] = dump_pairs(kwargs[name])\n\n        data = {\n            \"dict\": self.dict_type,\n            \"op\": op,\n            \"args\": kwargs,\n            \"self\": {\n                \"slots\": self.dump_slots(),\n                \"used\": self.used,\n                \"fill\": self.fill\n            }\n        }\n\n        # pprint((\"<< sending\", data, op, kwargs))\n        self.sock.send(bytes(json.dumps(data) + \"\\n\", 'UTF-8'))\n        response = json.loads(self.sockfile.readline())\n        # pprint((\">> receiving\", response))\n\n        self.restore_slots(response[\"self\"][\"slots\"])\n        self.fill = response[\"self\"][\"fill\"]\n        self.used = response[\"self\"][\"used\"]\n        if response[\"exception\"]:\n            raise KeyError(\"whatever\")\n\n        return parse_simple_py_obj(response[\"result\"])\n\n\nclass Dict32JsImpl(JsImplBase):\n    dict_type = \"dict32\"\n\n    def __setitem__(self, key, value):\n        return self.run_op(\"__setitem__\", key=key, value=value)\n\n    def __delitem__(self, key):\n        return self.run_op(\"__delitem__\", key=key)\n\n    def __getitem__(self, key):\n        return self.run_op(\"__getitem__\", key=key)\n\n\nclass AlmostPythonDictBaseJsImpl(JsImplBase):\n    dict_type = \"almost_python_dict\"\n\n    def __delitem__(self, key):\n        return self.run_op(\"__delitem__\", key=key)\n\n    def __getitem__(self, key):\n        return self.run_op(\"__getitem__\", key=key)\n\n\nclass AlmostPythonDictRecyclingJsImpl(AlmostPythonDictBaseJsImpl):\n    def __setitem__(self, key, value):\n        return self.run_op(\"__setitem__recycling\", key=key, value=value)\n\n\nclass AlmostPythonDictNoRecyclingJsImpl(AlmostPythonDictBaseJsImpl):\n    def __setitem__(self, key, value):\n        return self.run_op(\"__setitem__no_recycling\", key=key, value=value)\n"
  },
  {
    "path": "scripts/extractPythonCode.js",
    "content": "import 'ignore-styles';\n\nimport {\n    DICT32_INIT,\n    DICT32_SETITEM,\n    DICT32_RESIZE_CODE,\n    _DICT32_GETITEM_ONLY,\n    _DICT32_DELITEM_ONLY,\n    DICT32_LOOKDICT,\n    STATICMETHOD_SIGNED_TO_UNSIGNED,\n    PROBING_PYTHON_CODE,\n} from '../src/chapter4_real_python_dict';\n\nimport {\n    HASH_CLASS_INIT_CODE,\n    HASH_CLASS_SETITEM_RECYCLING_CODE,\n    HASH_CLASS_SETITEM_SIMPLIFIED_CODE,\n    _HASH_CLASS_GETITEM_ONLY,\n    _HASH_CLASS_DELITEM_ONLY,\n    HASH_CLASS_LOOKDICT,\n    HASH_CLASS_RESIZE_CODE,\n    FIND_NEAREST_SIZE_CODE_STRING,\n    SLOT_CLASS_CODE_STRING,\n} from '../src/chapter3_hash_class';\n\nimport {\n    HASH_CREATE_NEW_CODE,\n    HASH_SEARCH_CODE,\n    HASH_REMOVE_CODE,\n    HASH_RESIZE_CODE,\n    HASH_INSERT_CODE,\n} from '../src/chapter2_hash_table_functions';\n\nimport {\n    SIMPLIFIED_INSERT_ALL_BROKEN_CODE,\n    SIMPLIFIED_INSERT_ALL_CODE,\n    SIMPLIFIED_SEARCH_CODE,\n    SIMPLE_LIST_SEARCH,\n} from '../src/chapter1_simplified_hash';\n\nimport fs from 'fs';\nimport * as path from 'path';\n\nfunction extractCodeLines(codeWithBpAndLevels) {\n    return codeWithBpAndLevels.map(([line, bp, level]) => line);\n}\n\nfunction outputCode(filename, headers, importedCode, indent4 = true) {\n    let allLines = [];\n    for (let part of importedCode) {\n        let lines;\n        if (typeof part !== 'string') {\n            lines = extractCodeLines(part);\n        } else {\n            lines = part.split('\\n');\n        }\n\n        allLines.push(...lines);\n        if (lines[lines.length - 1] !== '') {\n            allLines.push('');\n        }\n    }\n    const joinedLines = allLines.map(line => (line.length > 0 && indent4 ? '    ' + line : line)).join('\\n');\n    fs.writeFileSync(filename, headers.join('\\n') + '\\n' + joinedLines);\n}\n\nconst commonImports = `import sys\nimport os\nsys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'python_code'))\nfrom common import DUMMY, EMPTY\n\n`;\n\nconst dict32def = `\nclass Dict32Extracted(object):`;\n\nconst DIR = 'build';\n\noutputCode(\n    path.join(DIR, 'dict32js_extracted.py'),\n    [commonImports, SLOT_CLASS_CODE_STRING, dict32def],\n    [\n        DICT32_INIT,\n        FIND_NEAREST_SIZE_CODE_STRING,\n        STATICMETHOD_SIGNED_TO_UNSIGNED,\n        DICT32_SETITEM,\n        DICT32_RESIZE_CODE,\n        _DICT32_GETITEM_ONLY,\n        _DICT32_DELITEM_ONLY,\n        DICT32_LOOKDICT,\n    ]\n);\n\nconst hashClassRecyclingDef = `\nclass HashClassRecyclingExtracted(object):`;\n\noutputCode(\n    path.join(DIR, 'hash_class_recycling_extracted.py'),\n    [commonImports, SLOT_CLASS_CODE_STRING, hashClassRecyclingDef],\n    [\n        HASH_CLASS_INIT_CODE,\n        FIND_NEAREST_SIZE_CODE_STRING,\n        HASH_CLASS_SETITEM_RECYCLING_CODE,\n        HASH_CLASS_RESIZE_CODE,\n        _HASH_CLASS_GETITEM_ONLY,\n        _HASH_CLASS_DELITEM_ONLY,\n        HASH_CLASS_LOOKDICT,\n    ]\n);\n\nconst hashClassNoRecyclingDef = `\nclass HashClassNoRecyclingExtracted(object):`;\n\noutputCode(\n    path.join(DIR, 'hash_class_no_recycling_extracted.py'),\n    [commonImports, SLOT_CLASS_CODE_STRING, hashClassNoRecyclingDef],\n    [\n        HASH_CLASS_INIT_CODE,\n        FIND_NEAREST_SIZE_CODE_STRING,\n        HASH_CLASS_SETITEM_SIMPLIFIED_CODE,\n        HASH_CLASS_RESIZE_CODE,\n        _HASH_CLASS_GETITEM_ONLY,\n        _HASH_CLASS_DELITEM_ONLY,\n        HASH_CLASS_LOOKDICT,\n    ]\n);\n\noutputCode(\n    path.join(DIR, 'hash_chapter2_extracted.py'),\n    [commonImports],\n    [HASH_CREATE_NEW_CODE, HASH_SEARCH_CODE, HASH_REMOVE_CODE, HASH_RESIZE_CODE, HASH_INSERT_CODE],\n    false\n);\n\noutputCode(\n    path.join(DIR, 'hash_chapter1_extracted.py'),\n    [commonImports],\n    [SIMPLIFIED_INSERT_ALL_CODE, SIMPLIFIED_INSERT_ALL_BROKEN_CODE, SIMPLIFIED_SEARCH_CODE, SIMPLE_LIST_SEARCH],\n    false\n);\n\noutputCode(path.join(DIR, 'chapter4_probing_python_code.py'), [commonImports], [PROBING_PYTHON_CODE], false);\n"
  },
  {
    "path": "scripts/pyReimplWrapper.js",
    "content": "const net = require('net');\nconst split = require('split');\nimport 'ignore-styles';\n\nimport {BigNumber} from 'bignumber.js';\nimport {DUMMY, EMPTY, None} from '../src/hash_impl_common';\nimport {Dict32} from '../src/chapter4_real_python_dict';\nimport {GenerateProbingLinks} from '../src/probing_visualization.js';\nimport {AlmostPythonDict} from '../src/chapter3_hash_class';\nimport {Ops as Chapter2Ops} from '../src/chapter2_hash_table_functions';\nimport {Ops as Chapter1Ops} from '../src/chapter1_simplified_hash';\nimport {Slot} from '../src/chapter3_and_4_common';\nimport {List as ImmutableList} from 'immutable';\n\nfunction parseSimplePyObj(obj) {\n    if (obj === null || typeof obj === 'string') {\n        return obj;\n    } else if (typeof obj === 'object' && obj.type === 'None') {\n        let res = None;\n        // TODO FIXME: this does not support multiple clients\n        res._hashCode = obj.hash;\n        return res;\n    } else if (typeof obj === 'object' && obj.type === 'DUMMY') {\n        return DUMMY;\n    } else if (typeof obj === 'object' && obj.type === 'EMPTY') {\n        return EMPTY;\n    } else if (typeof obj === 'object' && obj.type === 'int') {\n        return BigNumber(obj.value);\n    } else {\n        throw new Error(`Unknown obj ${JSON.stringify(obj)}`);\n    }\n}\n\nfunction parseArray(array) {\n    return array.map(parseSimplePyObj);\n}\n\nfunction dumpArray(array) {\n    return array.map(dumpSimplePyObj);\n}\n\nfunction parsePairs(pairs) {\n    return pairs.map(([k, v]) => [parseSimplePyObj(k), parseSimplePyObj(v)]);\n}\n\nfunction dumpSimplePyObj(obj) {\n    if (obj === DUMMY) {\n        return {\n            type: 'DUMMY',\n        };\n    } else if (obj === None) {\n        return {\n            type: 'None',\n        };\n    } else if (BigNumber.isBigNumber(obj)) {\n        return {\n            type: 'int',\n            value: obj.toString(),\n        };\n    } else {\n        return obj;\n    }\n}\n\nfunction restorePyDictState(state) {\n    let {pySelf} = Dict32.__init__();\n    if (state.slots != null) {\n        pySelf = pySelf.set(\n            'slots',\n            new ImmutableList(\n                state.slots.map(slot => {\n                    let key = parseSimplePyObj(slot.key);\n                    let value = parseSimplePyObj(slot.value);\n\n                    return Slot({\n                        pyHashCode: slot.hashCode ? new BigNumber(slot.hashCode) : null,\n                        key: key,\n                        value: value,\n                    });\n                })\n            )\n        );\n    } else {\n        pySelf = pySelf.set('slots', null);\n    }\n    pySelf = pySelf.set('used', state.used);\n    pySelf = pySelf.set('fill', state.fill);\n\n    return pySelf;\n}\n\nfunction dumpPyDictState(pySelf) {\n    let data = {};\n\n    data.slots = pySelf.get('slots').map(slot => {\n        return {\n            hashCode: slot.pyHashCode != null ? slot.pyHashCode.toString() : null,\n            key: dumpSimplePyObj(slot.key),\n            value: dumpSimplePyObj(slot.value),\n        };\n    });\n    data.used = pySelf.get('used');\n    data.fill = pySelf.get('fill');\n\n    return data;\n}\n\nfunction dict32RunOp(pySelf, op, key, value, pairs) {\n    switch (op) {\n        case '__init__':\n            pySelf = Dict32.__init__(pairs).pySelf;\n            return {pySelf};\n        case '__getitem__': {\n            const {result, isException} = Dict32.__getitem__(pySelf, key);\n            return {pySelf, result, isException};\n        }\n        case '__setitem__': {\n            ({pySelf} = Dict32.__setitem__(pySelf, key, value));\n            return {pySelf};\n        }\n        case '__delitem__': {\n            let isException;\n            ({pySelf, isException} = Dict32.__delitem__(pySelf, key));\n            return {pySelf, isException};\n        }\n        default:\n            throw new Error('Unknown op: ' + op);\n    }\n}\n\nfunction almostPyDictRunOp(pySelf, op, key, value, pairs) {\n    switch (op) {\n        case '__init__':\n            pySelf = AlmostPythonDict.__init__(pairs).pySelf;\n            return {pySelf};\n        case '__getitem__': {\n            const {result, isException} = AlmostPythonDict.__getitem__(pySelf, key);\n            return {pySelf, result, isException};\n        }\n        case '__setitem__recycling': {\n            ({pySelf} = AlmostPythonDict.__setitem__recycling(pySelf, key, value));\n            return {pySelf};\n        }\n        case '__setitem__no_recycling': {\n            ({pySelf} = AlmostPythonDict.__setitem__no_recycling(pySelf, key, value));\n            return {pySelf};\n        }\n        case '__delitem__': {\n            let isException;\n            ({pySelf, isException} = AlmostPythonDict.__delitem__(pySelf, key));\n            return {pySelf, isException};\n        }\n        default:\n            throw new Error('Unknown op: ' + op);\n    }\n}\n\nfunction chapter1run(keys, op, key, numbers) {\n    switch (op) {\n        case 'create_new':\n            ({keys} = Chapter1Ops.createNew(numbers));\n            return {keys};\n        case 'create_new_broken':\n            ({keys} = Chapter1Ops.createNewBroken(numbers));\n            return {keys};\n        case 'has_key': {\n            let result;\n            ({keys, result} = Chapter1Ops.hasKey(keys, key));\n            return {keys, result};\n        }\n        case 'linear_search': {\n            let {result} = Chapter1Ops.linearSearch(numbers, key);\n            return {result};\n        }\n        default:\n            throw new Error('Unknown op: ' + op);\n    }\n}\n\nfunction chapter2run(hashCodes, keys, op, key, array) {\n    switch (op) {\n        case 'create_new':\n            ({hashCodes, keys} = Chapter2Ops.createNew(array));\n            return {hashCodes, keys};\n        case 'insert':\n            ({hashCodes, keys} = Chapter2Ops.insert(hashCodes, keys, key));\n            return {hashCodes, keys};\n        case 'remove': {\n            let isException;\n            ({hashCodes, keys, isException} = Chapter2Ops.remove(hashCodes, keys, key));\n            return {hashCodes, keys, isException};\n        }\n        case 'has_key': {\n            let result;\n            ({hashCodes, keys, result} = Chapter2Ops.hasKey(hashCodes, keys, key));\n            return {hashCodes, keys, result};\n        }\n        case 'resize':\n            ({hashCodes, keys} = Chapter2Ops.resize(hashCodes, keys));\n            return {hashCodes, keys};\n        default:\n            throw new Error('Unknown op: ' + op);\n    }\n}\n\nconst server = net.createServer(c => {\n    console.log('Client connected');\n\n    c.on('end', () => {\n        console.log('Client disconnected');\n    });\n\n    c.pipe(split()).on('data', line => {\n        console.log('Received line of length ' + line.length);\n        if (!line) return;\n\n        const data = JSON.parse(line);\n        const dictType = data.dict;\n        const op = data.op;\n        let {key, value, pairs, array} = data.args;\n        if (key !== undefined) {\n            key = parseSimplePyObj(key);\n        }\n        if (value !== undefined) {\n            value = parseSimplePyObj(value);\n        }\n        if (pairs !== undefined) {\n            pairs = parsePairs(pairs);\n        }\n        if (array !== undefined) {\n            array = parseArray(array);\n        }\n\n        console.log(op, data.args);\n\n        let isException, result;\n        let response;\n\n        if (dictType === 'dict32' || dictType === 'almost_python_dict') {\n            let pySelf = restorePyDictState(data.self);\n            if (dictType === 'dict32') {\n                ({pySelf, isException, result} = dict32RunOp(pySelf, op, key, value, pairs));\n            } else if (dictType === 'almost_python_dict') {\n                ({pySelf, isException, result} = almostPyDictRunOp(pySelf, op, key, value, pairs));\n            } else {\n                throw new Error('Unknown dict type');\n            }\n\n            response = {\n                exception: isException || false,\n                result: result !== undefined ? dumpSimplePyObj(result) : null,\n                self: dumpPyDictState(pySelf),\n            };\n        } else if (dictType === 'chapter2') {\n            let hashCodes = data.hashCodes != null ? new ImmutableList(parseArray(data.hashCodes)) : undefined;\n            let keys = data.keys != null ? new ImmutableList(parseArray(data.keys)) : undefined;\n            ({hashCodes, keys, isException, result} = chapter2run(hashCodes, keys, op, key, array));\n            response = {\n                exception: isException || false,\n                result: result !== undefined ? result : null,\n                hashCodes: dumpArray(hashCodes),\n                keys: dumpArray(keys),\n            };\n        } else if (dictType === 'chapter1') {\n            let keys = data.keys != null ? new ImmutableList(parseArray(data.keys)) : undefined;\n            ({keys, result} = chapter1run(keys, op, key, array));\n            response = {\n                result: result !== undefined ? result : null,\n                keys: keys !== undefined ? dumpArray(keys) : null,\n            };\n        } else if (dictType === 'pythonProbing') {\n            let g = new GenerateProbingLinks();\n            const result = g.run(data.args.slotsCount, key, 'python');\n            response = {\n                result,\n            };\n        } else {\n            throw new Error('Unknown dict type');\n        }\n\n        c.write(JSON.stringify(response) + '\\n');\n    });\n});\n\nserver.on('error', err => {\n    throw err;\n});\n\nserver.on('listening', () => {\n    console.log(`Listening`);\n});\n\nserver.listen('pynode.sock', () => {\n    console.log('Starting listening...');\n});\n"
  },
  {
    "path": "scripts/ssr.js",
    "content": "require('dotenv').config();\n\nprocess.env.NODE_ENV = 'ssr';\n\nconsole.log = () => {}; // Do not log to stdout\nglobal.performance = {now: () => 0};\nimport 'ignore-styles';\n\nimport * as React from 'react';\nimport ReactDOMServer from 'react-dom/server';\nimport {CHAPTER_ID_TO_COMPONENT} from '../src/index';\nimport {App} from '../src/app';\nimport fs from 'fs';\n\nconst filename = process.argv[2];\nconst chapterIds = JSON.parse(process.argv[3]);\nconst chapters = chapterIds.map(id => CHAPTER_ID_TO_COMPONENT[id]);\nlet selectedChapterId;\nif (chapterIds.length === 1) {\n    selectedChapterId = chapterIds[0];\n}\n\nfs.readFile(filename, 'utf8', function(err, file) {\n    if (err) {\n        throw new Error(`Cannot read source html: ${err}`);\n    }\n    const renderedComponent = ReactDOMServer.renderToString(\n        <App chapters={chapters} selectedChapterId={selectedChapterId} />\n    );\n    let fullHtml = file.replace(/<div id=\"root\"><\\/div>/, `<div id=\"root\">${renderedComponent}</div>`);\n    const gaId = process.env.GA_ID;\n    console.warn('Google analytics ID is', gaId);\n    if (gaId) {\n        let GA_SCRIPT = `<!-- Global site tag (gtag.js) - Google Analytics -->\n        <script async src=\"https://www.googletagmanager.com/gtag/js?id=__GA_CODE_HERE__\"></script>\n        <script>\n          window.dataLayer = window.dataLayer || [];\n          function gtag(){dataLayer.push(arguments);}\n          gtag('js', new Date());\n\n          gtag('config', '__GA_CODE_HERE__');\n        </script>`;\n        GA_SCRIPT = GA_SCRIPT.replace(/__GA_CODE_HERE__/g, gaId);\n        fullHtml = fullHtml.replace('</head>', `${GA_SCRIPT}</head>`);\n    }\n    process.stdout.write(fullHtml);\n});\n"
  },
  {
    "path": "src/app.js",
    "content": "import _ from 'lodash';\nimport Bootstrap from 'bootstrap/dist/css/bootstrap.min.css';\nimport stylesCss from './styles.css';\n\nimport * as React from 'react';\nimport ReactDOM from 'react-dom';\n\nimport {MyErrorBoundary, initUxSettings, getUxSettings, BootstrapAlert, doubleRAF} from './util';\nimport {win, globalSettings} from './store';\n\nimport {faDesktop} from '@fortawesome/free-solid-svg-icons/faDesktop';\nimport {faSpinner} from '@fortawesome/free-solid-svg-icons/faSpinner';\nimport {faSyncAlt} from '@fortawesome/free-solid-svg-icons/faSyncAlt';\nimport {faEnvelope} from '@fortawesome/free-solid-svg-icons/faEnvelope';\nimport {faChevronRight} from '@fortawesome/free-solid-svg-icons/faChevronRight';\nimport {faFirefox} from '@fortawesome/free-brands-svg-icons/faFirefox';\nimport {faGithub} from '@fortawesome/free-brands-svg-icons/faGithub';\nimport {faTwitter} from '@fortawesome/free-brands-svg-icons/faTwitter';\nimport {faMailchimp} from '@fortawesome/free-brands-svg-icons/faMailchimp';\n\nimport {library, config as fontAwesomeConfig} from '@fortawesome/fontawesome-svg-core';\nfontAwesomeConfig.autoAddCss = false;\n\nlibrary.add(faDesktop);\nlibrary.add(faFirefox);\nlibrary.add(faSpinner);\nlibrary.add(faSyncAlt);\nlibrary.add(faEnvelope);\nlibrary.add(faChevronRight);\nlibrary.add(faGithub);\nlibrary.add(faMailchimp);\nlibrary.add(faTwitter);\n\nimport {FontAwesomeIcon} from '@fortawesome/react-fontawesome';\n\nimport '@fortawesome/fontawesome-svg-core/styles.css';\n\nfunction getWindowDimensions() {\n    const width = document.documentElement.clientWidth;\n    const height = document.documentElement.clientHeight;\n    return {width, height};\n}\n\nfunction logViewportStats() {\n    console.log(`DIMENSIONS: window inner: ${window.innerWidth}x${window.innerHeight}`);\n    console.log(\n        `DIMENSIONS: document.documentElement: ${document.documentElement.clientWidth}x${\n            document.documentElement.clientHeight\n        }`\n    );\n    const vv = window.visualViewport;\n    console.log(`DIMENSIONS: visualViewport: ${vv != null ? vv.width + 'x' + vv.height : vv}`);\n\n    const {width, height} = getWindowDimensions();\n    console.log(`DIMENSIONS: used: ${width}x${height}`);\n    // TODO FIXME: this is for debugging only\n    /*const url = `/viewports?wi=${window.innerWidth}x${window.innerHeight}&de=${document.documentElement.clientWidth}x${document.documentElement.clientHeight}&vv=${vv.width}x${vv.height}`;\n    const Http = new XMLHttpRequest();\n    Http.open(\"GET\", url);\n    Http.send();*/\n}\n\nconst GITHUB_REPO_URL = 'https://github.com/eleweek/inside_python_dict';\nconst MAILCHIMP_URL = 'http://eepurl.com/gbzhvn';\nconst TWITTER_LINK = 'https://twitter.com/SashaPutilin';\nconst EMAIL = 'avp-13@yandex.ru';\n\nfunction GithubRibbon() {\n    return (\n        <a href={GITHUB_REPO_URL}>\n            <img\n                style={{position: 'absolute', top: 0, right: 0, border: 0}}\n                src=\"https://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png\"\n                alt=\"Fork me on GitHub\"\n            />\n        </a>\n    );\n}\n\nfunction GithubCorner() {\n    // FROM: http://tholman.com/github-corners/\n    return (\n        <div\n            dangerouslySetInnerHTML={{\n                __html: `<a href=\"${GITHUB_REPO_URL}\" class=\"github-corner\" aria-label=\"View source on GitHub\"><svg width=\"80\" height=\"80\" viewBox=\"0 0 250 250\" style=\"fill:#151513; color:#fff; position: absolute; top: 0; border: 0; right: 0;\" aria-hidden=\"true\"><path d=\"M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z\"></path><path d=\"M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2\" fill=\"currentColor\" style=\"transform-origin: 130px 106px;\" class=\"octo-arm\"></path><path d=\"M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z\" fill=\"currentColor\" class=\"octo-body\"></path></svg></a><style>.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}</style>`,\n            }}\n        />\n    );\n}\n\nfunction GithubForkMe({windowWidth}) {\n    /*if (windowWidth != null && windowWidth > 1150) {\n        return <GithubRibbon />;\n    } else {*/\n    return <GithubCorner />;\n    /*}*/\n}\n\nconst CONTENTS_DATA = [\n    [1, 'chapter1.html', 'Searching efficiently in a list'],\n    [2, 'chapter2.html', 'Why are hash tables called hash tables?'],\n    [3, 'chapter3.html', 'Putting it all together to make an \"almost\"-python-dict'],\n    [4, 'chapter4.html', 'How python dict *really* works internally'],\n];\n\nfunction chapterIdDotHtml(chapterId) {\n    if (chapterId && !chapterId.endsWith('.html')) {\n        return chapterId + '.html';\n    } else {\n        return null;\n    }\n}\n\nfunction NextPrev({selectedChapterId}) {\n    const selectedChapter = chapterIdDotHtml(selectedChapterId);\n    if (selectedChapter == null) {\n        return null;\n    }\n\n    let prevHref, prevTitle;\n    let nextHref, nextTitle;\n\n    for (let i = 0; i < CONTENTS_DATA.length; ++i) {\n        if (CONTENTS_DATA[i][1] === selectedChapter) {\n            if (i > 0) {\n                prevHref = CONTENTS_DATA[i - 1][1];\n                prevTitle = CONTENTS_DATA[i - 1][2];\n            }\n            if (i < CONTENTS_DATA.length - 1) {\n                nextHref = CONTENTS_DATA[i + 1][1];\n                nextTitle = CONTENTS_DATA[i + 1][2];\n            }\n            break;\n        }\n    }\n\n    if (nextHref) {\n        return (\n            <div className=\"next-prev mt-4\">\n                <a href={nextHref} key={nextHref}>\n                    <h6 key={nextHref}>\n                        Next: {nextTitle} <FontAwesomeIcon key=\"chevron-right\" icon=\"chevron-right\" />{' '}\n                    </h6>\n                </a>\n            </div>\n        );\n    } else {\n        return null;\n    }\n}\n\nclass Contents extends React.PureComponent {\n    static EXTRA_ERROR_BOUNDARY = true;\n\n    render() {\n        const {selectedChapterId} = this.props;\n        const selectedChapter = chapterIdDotHtml(selectedChapterId);\n        const CIRCLE_SIZE = 30;\n        return (\n            <div className=\"mb-3\">\n                <div className=\"d-inline-flex flex-column\">\n                    {CONTENTS_DATA.map(([i, href, title]) => {\n                        const contentRow = (\n                            <React.Fragment>\n                                <div\n                                    key=\"circle-number\"\n                                    className=\"rounded-circle d-flex align-items-center justify-content-center mr-2 toc-number\"\n                                    style={{\n                                        width: CIRCLE_SIZE,\n                                        height: CIRCLE_SIZE,\n                                        minWidth: CIRCLE_SIZE,\n                                        maxWidth: CIRCLE_SIZE,\n                                        minHeight: CIRCLE_SIZE,\n                                        maxHeight: CIRCLE_SIZE,\n                                        backgroundColor: '#7FDBFF',\n                                        color: 'white',\n                                    }}\n                                >\n                                    {i}\n                                </div>\n                                <div key=\"title\" className=\"d-flex align-items-center mr-3 toc-title\">\n                                    <h6 className=\"mb-0\">{title}</h6>\n                                </div>\n                            </React.Fragment>\n                        );\n                        return (\n                            <div\n                                key={`toc-row-${i}`}\n                                className=\"d-flex p-1 align-items-center\"\n                                style={{backgroundColor: href === selectedChapter ? 'rgba(0,0,0,.05)' : undefined}}\n                            >\n                                {selectedChapter === href ? (\n                                    contentRow\n                                ) : (\n                                    <a\n                                        key=\"toc-a\"\n                                        href={href}\n                                        style={{color: '#0074D9', fontWeight: 700}}\n                                        className=\"d-flex toc-a\"\n                                    >\n                                        {contentRow}\n                                    </a>\n                                )}\n                            </div>\n                        );\n                    })}\n                </div>\n            </div>\n        );\n    }\n}\n\nclass LoadingAlert extends React.PureComponent {\n    constructor() {\n        super();\n\n        this.state = {\n            loaded: false,\n        };\n    }\n\n    render() {\n        return (\n            <BootstrapAlert\n                nondismissible={true}\n                sticky={true}\n                alertType=\"info\"\n                key=\"js-loading\"\n                hide={this.state.loaded && this.props.isRunningInBrowser}\n                extraclassName=\"mb-0\"\n            >\n                <FontAwesomeIcon key=\"js-loading-spinner\" icon=\"spinner\" spin /> JavaScript code is loading...\n            </BootstrapAlert>\n        );\n    }\n\n    componentDidMount() {\n        this.setState({loaded: true});\n    }\n}\n\nclass Alerts extends React.Component {\n    constructor() {\n        super();\n\n        this.state = {\n            mounted: false,\n        };\n    }\n\n    render() {\n        const alerts = [];\n        const isRunningInBrowser = typeof window !== 'undefined';\n        alerts.push(<LoadingAlert isRunningInBrowser={isRunningInBrowser} key=\"loading-warning\" />);\n\n        if (this.state.mounted) {\n            const {browser, windowWidth, windowHeight} = this.props;\n            if (browser) {\n                if (browser.platform.type === 'mobile') {\n                    alerts.push(\n                        <BootstrapAlert key=\"mobile-device-warning\">\n                            <FontAwesomeIcon icon=\"desktop\" /> <strong>Mobile device detected.</strong> For the best\n                            experience use a desktop browser\n                        </BootstrapAlert>\n                    );\n                    if (windowWidth < windowHeight) {\n                        alerts.push(\n                            <BootstrapAlert key=\"mobile-device-rotate-warning\">\n                                <FontAwesomeIcon icon=\"sync-alt\" /> <strong>Rotating your device is recommended</strong>{' '}\n                                - animations are better with a wider viewport\n                            </BootstrapAlert>\n                        );\n                    }\n                } else if (browser.browser.name === 'Firefox' && browser.os.name !== 'Linux') {\n                    alerts.push(\n                        <BootstrapAlert key=\"ff-warning\">\n                            <FontAwesomeIcon icon={['fab', 'firefox']} /> <strong>Firefox detected.</strong> Heavy\n                            animations may lag sometimes. If this happens, Chrome or Safari is recommended.\n                        </BootstrapAlert>\n                    );\n                }\n            }\n        }\n\n        return <React.Fragment>{alerts}</React.Fragment>;\n    }\n\n    componentDidMount() {\n        this.setState({mounted: true});\n    }\n}\n\nfunction Footer() {\n    return (\n        <footer className=\"footer\">\n            <div className=\"footer-container container-fluid\">\n                <hr />\n                <div className=\"footer-list\">\n                    <div className=\"footer-list-item\">\n                        <a className=\"text-muted\" href={GITHUB_REPO_URL} target=\"_blank\">\n                            <FontAwesomeIcon icon={['fab', 'github']} /> GitHub repo\n                        </a>\n                    </div>\n                    <div className=\"footer-list-item\">\n                        <a className=\"text-muted\" href={MAILCHIMP_URL} target=\"_blank\">\n                            <FontAwesomeIcon icon={['fab', 'mailchimp']} /> Get notified about new chapters\n                        </a>\n                    </div>\n                    <div className=\"footer-list-item\">\n                        <a className=\"text-muted\" href={TWITTER_LINK} target=\"_blank\">\n                            <FontAwesomeIcon icon={['fab', 'twitter']} /> My Twitter\n                        </a>\n                    </div>\n                    <div className=\"footer-list-item\">\n                        <a\n                            className=\"text-muted\"\n                            href={`mailto:${EMAIL}?subject=Inside Python Dict feedback`}\n                            target=\"_blank\"\n                        >\n                            <FontAwesomeIcon icon=\"envelope\" /> My Email\n                        </a>\n                    </div>\n                </div>\n            </div>\n        </footer>\n    );\n}\n\n// mainly to prevent addressbar stuff on mobile changing things excessively\nconst SIGNIFICANT_HEIGHT_CHANGE = 100;\nexport class App extends React.Component {\n    constructor() {\n        super();\n        this.state = {\n            mounted: false,\n            windowWidth: null,\n            windowHeight: null,\n        };\n    }\n\n    windowSizeChangeHandle = () => {\n        logViewportStats();\n        const dimensions = getWindowDimensions();\n        const windowWidth = dimensions.width;\n        const windowHeight = dimensions.height;\n        if (this.state.windowWidth !== windowWidth || this.state.windowHeight !== windowHeight) {\n            console.log('Processing window size change', windowWidth, windowHeight);\n            if (\n                this.state.windowWidth != windowWidth ||\n                this.state.windowHeight > windowHeight ||\n                windowHeight - this.state.windowHeight > SIGNIFICANT_HEIGHT_CHANGE\n            ) {\n                console.log('App size changed from', this.state);\n                this.setState({\n                    windowWidth,\n                    windowHeight,\n                });\n                if (win.width !== windowWidth || win.height !== windowHeight) {\n                    win.setWH(windowWidth, windowHeight);\n                }\n            }\n            fixStickyResize(windowWidth, windowHeight);\n        }\n    };\n\n    componentDidMount() {\n        const MEANINGFUL_Y_DIFF = 50; // components that depend on scroll should allow some leeway\n        let lastScrollY = null;\n        const onScroll = _.throttle(() => {\n            if (!lastScrollY || Math.abs(lastScrollY - window.scrollY) > MEANINGFUL_Y_DIFF) {\n                console.log('onScroll triggered', window.scrollY);\n                win.setScrollY(window.scrollY);\n                lastScrollY = window.scrollY;\n            }\n        }, 100);\n        window.addEventListener('scroll', onScroll);\n\n        const dimensions = getWindowDimensions();\n        const windowWidth = dimensions.width;\n        const windowHeight = dimensions.height;\n        console.log('componentDidMount() window geometry', windowWidth, windowHeight);\n\n        window.addEventListener('resize', _.throttle(this.windowSizeChangeHandle, 500));\n        globalSettings.maxCodePlaySpeed = getUxSettings().MAX_CODE_PLAY_SPEED;\n\n        this.setState({\n            windowWidth,\n            windowHeight,\n            mounted: true,\n        });\n        win.setAll(windowWidth, windowHeight, window.scrollY, true);\n    }\n\n    componentWillUnmount() {\n        window.removeEventListener('resize', this.windowSizeChangeHandle);\n    }\n\n    render() {\n        console.log('App.render()');\n        const contents = <Contents selectedChapterId={this.props.selectedChapterId} />;\n        const independentContents = this.props.selectedChapterId === 'chapter1';\n        // Make sure SSR works\n        const {windowWidth, windowHeight} = this.state.mounted ? this.state : {};\n\n        let chapters = [];\n        for (let [i, Chapter] of this.props.chapters.entries()) {\n            chapters.push(\n                <MyErrorBoundary key={`error-boundary-${i}`}>\n                    <Chapter windowWidth={windowWidth} windowHeight={windowHeight} contents={contents} />\n                </MyErrorBoundary>\n            );\n        }\n        return (\n            <React.Fragment>\n                <div className=\"app-container container-fluid\">\n                    <MyErrorBoundary>\n                        <GithubForkMe windowWidth={windowWidth} />\n                    </MyErrorBoundary>\n                    <h1> Inside python dict &mdash; an explorable explanation</h1>\n                    <MyErrorBoundary>\n                        <Alerts browser={this.props.browser} windowWidth={windowWidth} windowHeight={windowHeight} />\n                    </MyErrorBoundary>\n                    {!independentContents && <MyErrorBoundary>{contents}</MyErrorBoundary>}\n                    {chapters}\n                    <MyErrorBoundary>\n                        <NextPrev selectedChapterId={this.props.selectedChapterId} />\n                    </MyErrorBoundary>\n                </div>\n                <Footer />\n            </React.Fragment>\n        );\n    }\n}\n\nlet _fsrW, _fsrH;\nfunction fixStickyResize(windowWidth, windowHeight) {\n    // FIXME: this is a hack. This generates a fake resize event that react-stickynode seems to listen to\n    if (_fsrW !== windowWidth || _fsrH !== windowHeight) {\n        _fsrW = windowWidth;\n        _fsrH = windowHeight;\n        setTimeout(() => window.dispatchEvent(new Event('resize')), 500);\n    }\n}\n\nfunction fixSticky() {\n    // Nudges react-stickynode just a little bit\n    window.requestAnimationFrame(() => {\n        window.scrollBy(0, -1);\n        window.requestAnimationFrame(() => {\n            window.scrollBy(0, 1);\n        });\n    });\n}\n\nexport function initAndRender(chapters, chapterIds) {\n    if (typeof window !== 'undefined') {\n        initUxSettings();\n\n        window.addEventListener('load', () => {\n            logViewportStats();\n            const root = document.getElementById('root');\n            const isSSR = root.hasChildNodes();\n            let selectedChapterId;\n            if (chapterIds.length === 1) {\n                selectedChapterId = chapterIds[0];\n            }\n            const props = {\n                chapters,\n                selectedChapterId,\n                browser: window.insidePythonDictBrowser,\n            };\n\n            if (isSSR) {\n                console.log('Rehydrating');\n                ReactDOM.hydrate(<App {...props} />, root);\n            } else {\n                console.log('Rendering from scratch');\n                ReactDOM.render(<App {...props} />, root);\n            }\n            // Seems to fix stickynode not stickying on page reload\n            fixSticky();\n        });\n    }\n}\n"
  },
  {
    "path": "src/autogenerated/chapter1.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n    <head>\n        <title>Inside python dict &mdash; an explorable explanation</title>\n        <meta charset=\"utf-8\" />\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\" />\n\n        <link href=\"https://fonts.googleapis.com/css?family=Montserrat:400,700,700i\" rel=\"stylesheet\" />\n        <link href=\"https://fonts.googleapis.com/css?family=Roboto:400,400i,700,700i\" rel=\"stylesheet\" />\n        <link href=\"https://fonts.googleapis.com/css?family=Roboto+Mono:400,400i,500,500i,700,700i\" rel=\"stylesheet\" />\n    </head>\n    <body>\n        <div id=\"root\"></div>\n        <script type=\"text/javascript\">\n            window.insidePythonDictChapters = ['chapter1'];\n        </script>\n    </body>\n</html>\n"
  },
  {
    "path": "src/autogenerated/chapter2.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n    <head>\n        <title>Inside python dict &mdash; an explorable explanation</title>\n        <meta charset=\"utf-8\" />\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\" />\n\n        <link href=\"https://fonts.googleapis.com/css?family=Montserrat:400,700,700i\" rel=\"stylesheet\" />\n        <link href=\"https://fonts.googleapis.com/css?family=Roboto:400,400i,700,700i\" rel=\"stylesheet\" />\n        <link href=\"https://fonts.googleapis.com/css?family=Roboto+Mono:400,400i,500,500i,700,700i\" rel=\"stylesheet\" />\n    </head>\n    <body>\n        <div id=\"root\"></div>\n        <script type=\"text/javascript\">\n            window.insidePythonDictChapters = ['chapter2'];\n        </script>\n    </body>\n</html>\n"
  },
  {
    "path": "src/autogenerated/chapter3.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n    <head>\n        <title>Inside python dict &mdash; an explorable explanation</title>\n        <meta charset=\"utf-8\" />\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\" />\n\n        <link href=\"https://fonts.googleapis.com/css?family=Montserrat:400,700,700i\" rel=\"stylesheet\" />\n        <link href=\"https://fonts.googleapis.com/css?family=Roboto:400,400i,700,700i\" rel=\"stylesheet\" />\n        <link href=\"https://fonts.googleapis.com/css?family=Roboto+Mono:400,400i,500,500i,700,700i\" rel=\"stylesheet\" />\n    </head>\n    <body>\n        <div id=\"root\"></div>\n        <script type=\"text/javascript\">\n            window.insidePythonDictChapters = ['chapter3'];\n        </script>\n    </body>\n</html>\n"
  },
  {
    "path": "src/autogenerated/chapter4.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n    <head>\n        <title>Inside python dict &mdash; an explorable explanation</title>\n        <meta charset=\"utf-8\" />\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\" />\n\n        <link href=\"https://fonts.googleapis.com/css?family=Montserrat:400,700,700i\" rel=\"stylesheet\" />\n        <link href=\"https://fonts.googleapis.com/css?family=Roboto:400,400i,700,700i\" rel=\"stylesheet\" />\n        <link href=\"https://fonts.googleapis.com/css?family=Roboto+Mono:400,400i,500,500i,700,700i\" rel=\"stylesheet\" />\n    </head>\n    <body>\n        <div id=\"root\"></div>\n        <script type=\"text/javascript\">\n            window.insidePythonDictChapters = ['chapter4'];\n        </script>\n    </body>\n</html>\n"
  },
  {
    "path": "src/chapter1_simplified_hash.js",
    "content": "import * as React from 'react';\nimport _ from 'lodash';\nimport {List as ImmutableList} from 'immutable';\n\nimport {EQ, BreakpointFunction, displayStr} from './hash_impl_common';\nimport {\n    LineOfBoxesComponent,\n    HashBoxesComponent,\n    HashBoxesBrokenComponent,\n    TetrisFactory,\n    VisualizedCode,\n    SimpleCodeInline,\n} from './code_blocks';\nimport {PyListInput, ParsableInput, BlockInputToolbar, InputTryAnother} from './inputs';\nimport {\n    ChapterComponent,\n    DynamicP,\n    Subcontainerize,\n    singularOrPlural,\n    CrossFade,\n    COLOR_FOR_READ_OPS,\n    randint,\n    randomChoice,\n} from './util';\nimport {chapter1_2_FormatCheckCollision, commonFormatCheckNotFound} from './common_formatters';\nimport {ProbingVisualization, GenerateProbingLinks} from './probing_visualization';\nimport {parsePyNumber} from './py_obj_parsing';\n\nimport {BigNumber} from 'bignumber.js';\n\nimport memoizeOne from 'memoize-one';\nimport {observer} from 'mobx-react';\nimport {win} from './store';\n\nconst CHAPTER1_MAXNUM = 999;\n\nexport const SIMPLE_LIST_SEARCH = [\n    ['def simple_search(simple_list, key):', '', 0],\n    ['    idx = 0', 'start-from-zero', 1],\n    ['    while idx < len(simple_list):', 'check-boundary', 2],\n    ['        if simple_list[idx] == key:', 'check-found', 2],\n    ['            return True', 'found-key', 2],\n    ['        idx += 1', 'next-idx', 2],\n    ['    return False', 'found-nothing', 1],\n];\n\nfunction _parseSmallInt(value) {\n    const b = parsePyNumber(value);\n    const error = chapter1valueRangeValidator(b);\n    if (error) {\n        throw new Error(error);\n    }\n\n    return +b.toString();\n}\n\nfunction chapter1valueRangeValidator(num) {\n    if (!BigNumber.isBigNumber(num) && typeof num !== 'number') {\n        return 'Expected an integer';\n    }\n    if (num.lt(-CHAPTER1_MAXNUM) || num.gt(CHAPTER1_MAXNUM)) {\n        return `In chapter 1 only small integers are supported (between -${CHAPTER1_MAXNUM} and ${CHAPTER1_MAXNUM})`;\n    }\n}\n\nexport function PySmallIntInput({inputComponentRef, ...restProps}) {\n    return (\n        <ParsableInput {...restProps} dumpValue={JSON.stringify} parseValue={_parseSmallInt} ref={inputComponentRef} />\n    );\n}\n\nfunction simpleListSearch(l, key) {\n    let defaultBPInfo = {\n        type: 'breakpoint',\n        arg: key,\n        data: l,\n        size: l.length,\n    };\n    let breakpoints = [];\n    let newBP = (point, idx, extraInfo) => {\n        return {...defaultBPInfo, ...{point: point, idx: idx, atIdx: l[idx]}, ...extraInfo};\n    };\n\n    let idx = 0;\n    breakpoints.push(newBP('start-from-zero', idx));\n\n    while (true) {\n        breakpoints.push(newBP('check-boundary', idx));\n        if (idx >= l.length) {\n            break;\n        }\n        if (EQ(l[idx], key)) {\n            breakpoints.push(newBP('check-found', idx, {found: true}));\n            breakpoints.push(newBP('found-key', idx));\n\n            return {bp: breakpoints, result: true};\n        } else {\n            breakpoints.push(newBP('check-found', idx, {found: false}));\n        }\n\n        idx += 1;\n        breakpoints.push(newBP('next-idx', idx));\n    }\n\n    breakpoints.push(newBP('found-nothing', idx));\n\n    return {bp: breakpoints, result: false};\n}\n\nlet formatSimpleListSearchBreakpointDescription = function(bp) {\n    switch (bp.point) {\n        case 'iteration':\n            return `Check element in slot ${bp.idx} (<code>${bp.atIdx}</code>)`;\n        case 'start-from-zero':\n            return `Start from the beginning of the list`;\n        case 'check-boundary':\n            return bp.idx < bp.size\n                ? `[Try #${bp.idx + 1}] <code>${bp.idx} < ${\n                      bp.size\n                  }</code>, so some elements have not been processed yet, and the number may be there`\n                : `<code>${bp.idx} == ${bp.size}</code>, so all elements were processed`;\n        case 'check-found':\n            return bp.found\n                ? `<code>${bp.atIdx} == ${bp.arg}</code> &mdash; the wanted number is found`\n                : `<code>${bp.atIdx} != ${bp.arg}</code> &mdash; the wanted number has not been found so far`;\n        case 'found-key':\n            return `so return <code>True</code>`;\n        case 'found-nothing':\n            return `The wanted number <code>${bp.arg}</code> was not found, so return <code>False</code>`;\n        case 'next-idx':\n            return `Go to the next index: <code>${bp.idx}</code> == <code>${bp.idx - 1} + 1</code>`;\n    }\n};\n\nconst SimpleListSearchStateVisualization = TetrisFactory([\n    [LineOfBoxesComponent, [{labels: ['simple_list']}, 'data', 'idx']],\n]);\n\nconst UnnamedListVisualizationImpl = TetrisFactory([[LineOfBoxesComponent, [{labels: [null]}, 'data']]]);\n\nfunction UnnamedListVisualization(props) {\n    const serverSide = props.windowHeight == null;\n    return <UnnamedListVisualizationImpl {...props} overflow={serverSide} />;\n}\n\nexport const SIMPLIFIED_INSERT_ALL_BROKEN_CODE = [\n    ['def build_not_quite_what_we_want(original_list):', 'start-execution', 0],\n    ['    new_list = [None] * len(original_list)', 'create-new-list', 1],\n    ['', ''],\n    ['    for number in original_list:', 'for-loop', 2],\n    ['        idx = number % len(new_list)', 'compute-idx', 2],\n    ['        new_list[idx] = number', 'assign-elem', 2],\n    ['    return new_list', 'return-created-list', 1],\n];\n\nexport const SIMPLIFIED_INSERT_ALL_CODE = [\n    ['def build_insert_all(original_list):', 'start-execution', 0],\n    ['    new_list = [None] * (2 * len(original_list))', 'create-new-list', 1],\n    ['', ''],\n    ['    for number in original_list:', 'for-loop', 2],\n    ['        idx = number % len(new_list)', 'compute-idx', 2],\n    ['        while new_list[idx] is not None:', 'check-collision', 3],\n    ['            idx = (idx + 1) % len(new_list)', 'next-idx', 3],\n    ['        new_list[idx] = number', 'assign-elem', 2],\n    ['    return new_list', 'return-created-list', 1],\n];\n\nclass SimplifiedInsertAll extends BreakpointFunction {\n    constructor() {\n        super();\n\n        this._overwritten = [];\n    }\n\n    run(_originalList, isBroken = false) {\n        this.fmtIsBroken = isBroken;\n        this.originalList = new ImmutableList(_originalList);\n        this.newList = new ImmutableList();\n        if (isBroken) {\n            this.fmtMissingNumbers = new ImmutableList();\n            this.newListWithReplacements = new ImmutableList();\n        }\n        const startSize = (isBroken ? 1 : 2) * this.originalList.size;\n        for (let i = 0; i < startSize; ++i) {\n            this.newList = this.newList.push(null);\n            if (isBroken) {\n                this.newListWithReplacements = this.newListWithReplacements.push(new ImmutableList());\n            }\n        }\n        this.addBP('create-new-list', true);\n\n        for ([this.originalListIdx, this.number] of this.originalList.entries()) {\n            this.fmtCollisionCount = 0;\n\n            this.addBP('for-loop');\n            this.newListIdx = ((this.number % this.newList.size) + this.newList.size) % this.newList.size;\n            this.addBP('compute-idx');\n            if (!isBroken) {\n                while (true) {\n                    this.addBP('check-collision');\n                    if (this.newList.get(this.newListIdx) === null) {\n                        break;\n                    }\n\n                    this.fmtCollisionCount += 1;\n                    this.newListIdx = (this.newListIdx + 1) % this.newList.size;\n                    this.addBP('next-idx');\n                }\n            }\n            const prevNumber = this.newList.get(this.newListIdx);\n            if (prevNumber != null) {\n                if (!isBroken) {\n                    throw new Error(`!isBroken and overwriting a number - this should not happen`);\n                }\n                this.fmtMissingNumbers = this.fmtMissingNumbers.push(prevNumber);\n                this._overwritten.push([this.originalListIdx, prevNumber, this.number]);\n            }\n\n            this.newList = this.newList.set(this.newListIdx, this.number);\n            if (isBroken) {\n                this.newListWithReplacements = this.newListWithReplacements.updateIn([this.newListIdx], arr =>\n                    arr.insert(0, this.number)\n                );\n            }\n            this.newList = this.newList.set(this.newListIdx, this.number);\n            this.addBP('assign-elem', true);\n        }\n\n        this.addBP('return-created-list');\n\n        return this.newList;\n    }\n\n    overwrittenNumbers() {\n        return this._overwritten;\n    }\n}\n\nlet formatSimplifiedInsertAllDescription = function(bp, prevBp) {\n    switch (bp.point) {\n        case 'create-new-list':\n            return `Create a new list of <code>${bp.newList.size}</code> empty slots`;\n        case 'for-loop':\n            return `[${bp.originalListIdx + 1}/${bp.originalList.size}] The number to insert is <code>${\n                bp.number\n            }</code>`;\n        case 'compute-idx':\n            return `Compute the slot index: <code>${bp.newListIdx}</code> == <code>${bp.number} % ${\n                bp.newList.size\n            }</code>`;\n        case 'check-collision':\n            return chapter1_2_FormatCheckCollision(bp.newList, bp.newListIdx, bp.fmtCollisionCount);\n        case 'next-idx':\n            return `Keep probing, the next slot will be <code>${bp.newListIdx}</code> == <code>(${\n                prevBp.newListIdx\n            } + 1) % ${bp.newList.size}</code>`;\n        case 'assign-elem': {\n            const prevNumber = prevBp.newList.get(bp.newListIdx);\n            if (prevNumber != null) {\n                return `Collision of <code>${bp.number}</code> with <code>${prevNumber}</code> in slot <code>${\n                    bp.newListIdx\n                }</code> - the number is overwritten`;\n            } else {\n                return `Put <code>${bp.number}</code> in slot <code>${bp.newListIdx}</code>`;\n            }\n        }\n        case 'return-created-list':\n            if (bp.fmtMissingNumbers && bp.fmtMissingNumbers.size > 0) {\n                return `Return created list with some numbers missing: ${bp.fmtMissingNumbers\n                    .map(number => `<code>${number}</code>`)\n                    .join(', ')}`;\n            } else {\n                return `Return created list with all original numbers present`;\n            }\n    }\n};\n\nconst SimplifiedInsertStateVisualization = TetrisFactory([\n    [\n        LineOfBoxesComponent,\n        [\n            {labels: ['original_list']},\n            'originalList',\n            'originalListIdx',\n            undefined,\n            {selection1color: COLOR_FOR_READ_OPS},\n        ],\n    ],\n    [HashBoxesComponent, [{labels: ['new_list']}, 'newList', 'newListIdx']],\n]);\n\nconst SimplifiedInsertBrokenStateVisualization = TetrisFactory([\n    [\n        LineOfBoxesComponent,\n        [\n            {labels: ['original_list']},\n            'originalList',\n            'originalListIdx',\n            undefined,\n            {selection1color: COLOR_FOR_READ_OPS},\n        ],\n    ],\n    [HashBoxesBrokenComponent, [{labels: ['new_list']}, 'newListWithReplacements', 'newListIdx']],\n]);\n\nexport const SIMPLIFIED_SEARCH_CODE = [\n    ['def has_number(new_list, number):', 'start-execution', 0],\n    ['    idx = number % len(new_list)', 'compute-idx', 1],\n    ['    while new_list[idx] is not None:', 'check-not-found', 2],\n    ['        if new_list[idx] == number:', 'check-found', 2],\n    ['            return True', 'found-key', 2],\n    ['        idx = (idx + 1) % len(new_list)', 'next-idx', 2],\n    ['    return False', 'found-nothing', 1],\n];\n\nconst HideSpanWhenJsLoaded = observer(function HideWhenJsLoaded({children, tag}) {\n    return <span style={{opacity: win.jsLoaded ? 0 : 1, transition: 'opacity 1s ease'}}>{children}</span>;\n});\n\nclass SimplifiedSearch extends BreakpointFunction {\n    run(_newList, _number) {\n        this.newList = new ImmutableList(_newList);\n        this.number = _number;\n\n        this.fmtCollisionCount = 0;\n        this.newListIdx = ((this.number % this.newList.size) + this.newList.size) % this.newList.size;\n        this.addBP('compute-idx');\n\n        while (true) {\n            this.addBP('check-not-found');\n            if (this.newList.get(this.newListIdx) === null) {\n                break;\n            }\n            this.addBP('check-found');\n            if (EQ(this.newList.get(this.newListIdx), this.number)) {\n                this.addBP('found-key');\n                return true;\n            }\n\n            this.fmtCollisionCount += 1;\n            this.newListIdx = (this.newListIdx + 1) % this.newList.size;\n            this.addBP('next-idx');\n        }\n\n        this.addBP('found-nothing');\n\n        return false;\n    }\n}\n\nlet formatSimplifiedSearchDescription = function(bp) {\n    switch (bp.point) {\n        case 'compute-idx':\n            return `Compute the slot index: <code>${bp.newListIdx}</code> == <code>${bp.number} % ${\n                bp.newList.size\n            }</code>`;\n        case 'check-not-found':\n            return commonFormatCheckNotFound(bp.newList, bp.newListIdx, bp.fmtCollisionCount);\n        case 'check-found':\n            let found = EQ(bp.newList.get(bp.newListIdx), bp.number);\n            if (found) {\n                return `The number is found: <code>${bp.newList.get(bp.newListIdx)} == ${bp.number}</code>`;\n            } else {\n                return `The number has not been found yet: <code>${bp.newList.get(bp.newListIdx)} != ${\n                    bp.number\n                }</code>`;\n            }\n        case 'found-key':\n            return 'Now simply return <code>True</code>';\n        case 'found-nothing':\n            return 'Now simply return <code>False</code>';\n        case 'next-idx':\n            return `Keep retracing probing steps, the next slot will be <code>${bp.newListIdx}</code>`;\n        case 'return-created-list':\n            return `Return created list`;\n    }\n};\n\nconst SimplifiedSearchStateVisualization = TetrisFactory([\n    [HashBoxesComponent, [{labels: ['new_list']}, 'newList', 'newListIdx']],\n]);\n\nfunction DynamicSimplifiedInsertAllBrokenOverwrittenExample({originalNumbers, addedNumber, overwrittenNumbers}) {\n    let exampleOverwrite;\n    let includedList;\n    let [idx, n1, n2] = overwrittenNumbers[0];\n    n1 = displayStr(n1);\n    n2 = displayStr(n2);\n    if (addedNumber === null) {\n        includedList = false;\n        exampleOverwrite = (\n            <React.Fragment>\n                For example, <code>{n1}</code> will get the same slot index (<code>{idx}</code>) as <code>{n2}</code>,\n                and it will be overwritten.\n            </React.Fragment>\n        );\n    } else {\n        includedList = true;\n        const anStr = displayStr(addedNumber);\n        exampleOverwrite = (\n            <React.Fragment>\n                For the current list (\n                <code dangerouslySetInnerHTML={{__html: '[' + originalNumbers.join(', ') + ']'}} />) it would work. But\n                if we append a single number to it, for example <code>{anStr}</code>, then <code>{n1}</code> would get\n                overwritten by <code>{n2}</code>, and the simple algorithm breaks.\n            </React.Fragment>\n        );\n    }\n\n    return (\n        <DynamicP>\n            <p\n                className=\"dynamic-p\"\n                key={`oe-p-${addedNumber}-${n1}-${n2}-${idx}-${includedList ? JSON.stringify(originalNumbers) : null}`}\n            >\n                Would this approach work, however? Not entirely. {exampleOverwrite} Situations like these are called{' '}\n                <em>collisions</em>.\n            </p>\n        </DynamicP>\n    );\n}\n\nexport class Ops {\n    static createNew(numbers) {\n        let sia = new SimplifiedInsertAll();\n        const keys = sia.run(numbers);\n        const bp = sia.getBreakpoints();\n        return {keys, bp};\n    }\n\n    static createNewBroken(numbers) {\n        let sia = new SimplifiedInsertAll();\n        const keys = sia.run(numbers, true);\n        return {bp: sia.getBreakpoints(), overwrittenNumbers: sia.overwrittenNumbers(), keys};\n    }\n\n    static hasKey(keys, number) {\n        let ss = new SimplifiedSearch();\n        const result = ss.run(keys, number);\n        const bp = ss.getBreakpoints();\n        return {bp, result, keys};\n    }\n\n    static linearSearch(numbers, searchedNumber) {\n        return simpleListSearch(numbers, searchedNumber);\n    }\n}\n\nfunction anotherValue(array, RANDOM_NUMBER_CHANCE = 0.3) {\n    if (Math.random() > RANDOM_NUMBER_CHANCE) {\n        return +randomChoice(array).toString();\n    } else {\n        return randint(Math.floor(-CHAPTER1_MAXNUM / 10), Math.floor(CHAPTER1_MAXNUM / 10));\n    }\n}\n\nexport class Chapter1_SimplifiedHash extends ChapterComponent {\n    constructor() {\n        super();\n\n        this.state = {\n            numbers: [\n                BigNumber(1),\n                BigNumber(56),\n                BigNumber(50),\n                BigNumber(2),\n                BigNumber(44),\n                BigNumber(25),\n                BigNumber(17),\n                BigNumber(4),\n            ],\n            simpleSearchNumber: 25,\n            simplifiedHashSearchNumber: 2,\n        };\n    }\n\n    runSimplifiedInsertAll = memoizeOne(numbers => {\n        return Ops.createNew(numbers);\n    });\n\n    generateAlternativeDataForInsertAllBroken = memoizeOne(numbers => {\n        let {bp, overwrittenNumbers} = this.runSimplifiedInsertAllBroken(numbers);\n        if (overwrittenNumbers.length > 0) {\n            return {originalNumbers: numbers, numbers, bp, addedNumber: null, overwrittenNumbers};\n        } else {\n            const minNum = BigNumber.max(...numbers);\n\n            const addedNumber = minNum.plus(numbers.length + 1);\n            const newNumbers = [...numbers, addedNumber];\n            ({bp, overwrittenNumbers} = this.runSimplifiedInsertAllBroken(newNumbers));\n            return {originalNumbers: numbers, numbers: newNumbers, bp, addedNumber, overwrittenNumbers};\n        }\n    });\n\n    runProbingSimple = memoizeOne(slotsCount => {\n        let g = new GenerateProbingLinks();\n        const {links} = g.run(slotsCount, '', 'i+1');\n\n        return {\n            links,\n            bp: g.getBreakpoints(),\n        };\n    });\n\n    runSimplifiedInsertAllBroken = memoizeOne(numbers => {\n        return Ops.createNewBroken(numbers);\n    });\n\n    runSimplifiedSearch = memoizeOne((keys, number) => {\n        return Ops.hasKey(keys, number);\n    });\n\n    runSimpleListSearch = memoizeOne((numbers, searchedNumber) => {\n        const {bp} = Ops.linearSearch(numbers, searchedNumber);\n        return {bp};\n    });\n\n    render() {\n        const slsRes = this.runSimpleListSearch(this.state.numbers, this.state.simpleSearchNumber);\n        const siaBrokenRes = this.generateAlternativeDataForInsertAllBroken(this.state.numbers);\n        const siaRes = this.runSimplifiedInsertAll(this.state.numbers);\n        const ssRes = this.runSimplifiedSearch(siaRes.keys, this.state.simplifiedHashSearchNumber);\n\n        const probingVisSlotsCount = 8;\n        const probingSimple = this.runProbingSimple(probingVisSlotsCount);\n\n        return (\n            <div className=\"chapter chapter1\">\n                <h5>Contents</h5>\n                {this.props.contents}\n                <h2>Introduction</h2>\n                <Subcontainerize>\n                    <p>\n                        Hi! This is <em>an explorable explanation</em> of Python dictionaries. This page is dynamic and\n                        interactive &mdash; you can plug in your data and see how the algorithms work on it.{' '}\n                        <HideSpanWhenJsLoaded>(Once the javascript loads)</HideSpanWhenJsLoaded>\n                    </p>\n                    <p className=\"mb-2\">\n                        To start with, let's say we have a simple list of distinct integers (change it if you want - the\n                        page will update):\n                    </p>\n                    <BlockInputToolbar\n                        input={PyListInput}\n                        inputProps={{\n                            allowDuplicates: false,\n                            minSize: 1,\n                            extraValueValidator: chapter1valueRangeValidator,\n                        }}\n                        initialValue={this.state.numbers}\n                        onChange={this.setter('numbers', true)}\n                        bottomBoundary=\".chapter1\"\n                        {...this.props}\n                    />\n                    <p className=\"mb-0\">\n                        Python lists are actually arrays &mdash; contiguous chunks of memory. The name \"list\" may be\n                        misleading to people who know about double-linked lists but are unfamiliar with Python. You can\n                        picture a Python list as a row of slots, where each slot can hold a Python object:\n                    </p>\n                    <UnnamedListVisualization\n                        bp={{data: this.state.numbers}}\n                        compensateTopPadding={25}\n                        windowWidth={this.props.windowWidth}\n                        windowHeight={this.props.windowHeight}\n                    />\n                    <p>\n                        To check if an element is present in a list, we can use the <code>in</code> operator like this:{' '}\n                        <code className=\"text-nowrap\">number in simple_list</code>, which returns either{' '}\n                        <code>True</code> or <code>False</code>. Under the hood this short snippet does a linear scan.\n                        This can be a lot of work. To see this, let's reimplement it in Python.\n                    </p>\n                    <div className=\"div-p\">\n                        Let's say we're looking for the following number in the original list:\n                        <PySmallIntInput\n                            inline={true}\n                            value={this.state.simpleSearchNumber}\n                            onChange={this.setter('simpleSearchNumber')}\n                            anotherValue={() => anotherValue(this.state.numbers)}\n                        />\n                    </div>\n                    <VisualizedCode\n                        code={SIMPLE_LIST_SEARCH}\n                        breakpoints={slsRes.bp}\n                        formatBpDesc={formatSimpleListSearchBreakpointDescription}\n                        stateVisualization={SimpleListSearchStateVisualization}\n                        autoplayByDefault={true}\n                        {...this.props}\n                    />\n                    <p>\n                        (The visualization is interactive. The buttons allow you to step through the code. Notice here\n                        that the time slider is draggable - feel free to rewind the time or move it forward. Also, feel\n                        free to mess with the input and the original list - the visualization will update automatically)\n                    </p>\n                    <p>\n                        What's not so great about linear scans? If we have a million distinct numbers, in the worst case\n                        scenario, we may need to scan the whole list. But scanning over a few elements is no big deal.\n                        We need to have some order and predictability to make the search fast. We need to have some idea\n                        of where the searched element is located.\n                    </p>\n                    <p>\n                        A Python dict implementation is basically a scan of a list (but a pretty weird scan). We'll\n                        build the actual algorithm and data structure inside Python dictionary step by step, starting\n                        with the code above, which is intentionally verbose.{' '}\n                    </p>\n                    <h2>Chapter 1: searching efficiently in a list</h2>\n                    <p>\n                        A Python dict is a collection of key-value pairs. And, the most important part of it is handling\n                        keys. Keys need to be organized in such a way that efficient searching, inserting and deleting\n                        is possible.\n                    </p>\n                    <p>\n                        In this chapter, to keep things simple, we won't have any values, and \"keys\" will just be plain\n                        integers. So, the simplified problem is to check if a number is present in a list, but we have\n                        to do this{' '}\n                        <em>\n                            <strong>fast</strong>\n                        </em>\n                        . We'll tackle the real problem in the following chapters. But for now, bear with me.\n                    </p>\n                    <p>\n                        Accessing a single element by index is very fast. Accessing only a few elements would be fast\n                        too. We don't want to be doing a linear scan over the whole list every time we look up a number, so\n                        we need to organize our data in a clever way.\n                    </p>\n                    <p> Here's how. </p>\n                    <p>\n                        Let's begin by creating a new list of slots. Each slot will either hold a number from the\n                        original list or be empty (empty slots will hold <code>None</code>). We'll use the number itself\n                        to compute an index of a slot. The simplest way to do this is to take the remainder of{' '}\n                        <code>number</code> divided by <code>len(the_list)</code>:{' '}\n                        <code className=\"text-nowrap\">number % len(the_list)</code> and put our number in slot with this\n                        index. To check if the number is there we could compute the slot index again and see if it is\n                        empty.\n                    </p>\n                    <DynamicSimplifiedInsertAllBrokenOverwrittenExample\n                        key=\"overwritten-example-component\"\n                        {...siaBrokenRes}\n                    />\n                    <VisualizedCode\n                        code={SIMPLIFIED_INSERT_ALL_BROKEN_CODE}\n                        breakpoints={siaBrokenRes.bp}\n                        formatBpDesc={formatSimplifiedInsertAllDescription}\n                        stateVisualization={SimplifiedInsertBrokenStateVisualization}\n                        {...this.props}\n                    />\n                    <p>\n                        To make this approach viable, we need to somehow <em>resolve collisions</em>. Let's do the\n                        following: if the slot is already occupied by some other number, we'll just check the slot that\n                        comes right after it. And if that slot is empty, we'll put the number there. But, what if that\n                        slot is also occupied? Once again, we'll go ahead and check the next slot. We'll keep repeating\n                        this process until we finally hit an empty slot. This process is called <em>probing</em>. And\n                        because we do it linearly, it is called <em>linear probing</em>. In code, we would write this as{' '}\n                        <code className=\"text-nowrap\">(idx + 1) % len(simple_list)</code>, so it wraps around back to\n                        the beginning at the last index:\n                    </p>\n                    <ProbingVisualization\n                        slotsCount={probingVisSlotsCount}\n                        links={probingSimple.links}\n                        adjustTop={-70}\n                        fixedHeight={170}\n                        {...this.props}\n                    />\n                    <p>\n                        If we make the new list the same size as the original list, we'll have too many collisions. If\n                        we make it 10x larger, we'll have very few collisions, but we'll waste a lot of memory. So what\n                        size should it be? We want to hit the sweet spot where we don't use up too much memory but also\n                        don't have too many collisions. Twice the size of the original list is reasonable.\n                    </p>\n                    <p>\n                        Let's transform the original list using this method (when reading this code, keep in mind that{' '}\n                        <code>original_list</code> is a list of <em>distinct numbers</em>, so we don't need to handle\n                        duplicates just yet).\n                    </p>\n                    <VisualizedCode\n                        code={SIMPLIFIED_INSERT_ALL_CODE}\n                        breakpoints={siaRes.bp}\n                        formatBpDesc={formatSimplifiedInsertAllDescription}\n                        stateVisualization={SimplifiedInsertStateVisualization}\n                        {...this.props}\n                    />\n                    <p>\n                        To search for a number, we retrace all the steps necessary to insert it: we start from the slot{' '}\n                        <code className=\"text-nowrap\">number % len(new_list)</code> and do linear probing. We either end\n                        up finding the number or hitting an empty slot. The latter situation means that the number is\n                        not present.\n                    </p>\n                    <div className=\"div-p\">\n                        Let's say we want to search for\n                        <PySmallIntInput\n                            inline={true}\n                            value={this.state.simplifiedHashSearchNumber}\n                            onChange={this.setter('simplifiedHashSearchNumber')}\n                            anotherValue={() => anotherValue(this.state.numbers, 0.4)}\n                        />\n                    </div>\n                    <VisualizedCode\n                        code={SIMPLIFIED_SEARCH_CODE}\n                        breakpoints={ssRes.bp}\n                        formatBpDesc={formatSimplifiedSearchDescription}\n                        stateVisualization={SimplifiedSearchStateVisualization}\n                        {...this.props}\n                    />\n                    <p>\n                        Calculating an index based on the value of the number and resolving collisions by linear probing\n                        is an incredibly powerful idea. What we've just implemented is a simple <em>hash table</em>{' '}\n                        (more about the term in the next chapter). Python dict uses a hash table internally, albeit a\n                        more complex variant.\n                    </p>\n                    <p>\n                        We still haven't discussed adding more elements (what happens if a table overflows?), removing\n                        elements (removing an element without a trace would cause a hole to appear, wouldn't this cause\n                        the search algorithm to stop prematurely in many cases?), and perhaps most importantly, handling\n                        objects other than integers - strings, tuples, floats. We'll do this in the next chapters.\n                    </p>\n                    <h6>Collision resolution via separate chaining</h6>\n                    <p>\n                        There is a different method of collision resolution, called{' '}\n                        <a href=\"https://en.wikipedia.org/wiki/Hash_table#Separate_chaining\" target=\"_blank\">\n                            separate chaining\n                        </a>\n                        . It is also a good strategy which is commonly used. But that's not how Python resolves\n                        collision in dicts, so it is beyond the scope of this explanation.{' '}\n                    </p>\n                    <h6>A couple of the notes about the explanation</h6>\n                    <p>\n                        First, this explanation discusses <code>dict</code> as it is implemented in{' '}\n                        <a href=\"http://python.org/\" target=\"_blank\">\n                            CPython\n                        </a>{' '}\n                        &mdash; the \"default\" and most common implementation of the Python language (if you are not sure\n                        what implementation you use, it is almost certainly CPython). Some other implementations are{' '}\n                        <a href=\"https://pypy.org/\" target=\"_blank\">\n                            PyPy\n                        </a>\n                        ,{' '}\n                        <a href=\"http://www.jython.org/\" target=\"_blank\">\n                            Jython\n                        </a>{' '}\n                        and{' '}\n                        <a href=\"http://ironpython.net/\" target=\"_blank\">\n                            IronPython\n                        </a>\n                        . The way dicts works in each of these implementations may be similar to CPython (in the case of\n                        PyPy) or very different from CPython (in the case of Jython).\n                    </p>\n                    <p>\n                        Second, even though dict in CPython is implemented in C, this explanation uses Python for code\n                        snippets. The goal of this page is to help you understand the algorithms and the underlying data\n                        structures, not the minutiae of the C code (these details are interesting too - they are just\n                        are beyond the scope of this explanation).\n                    </p>\n                </Subcontainerize>\n            </div>\n        );\n    }\n}\n"
  },
  {
    "path": "src/chapter2_hash_table_functions.js",
    "content": "import * as React from 'react';\n\nimport {BigNumber} from 'bignumber.js';\n\nimport {List} from 'immutable';\nimport {pyHash, pyHashUnicode, pyHashLong, HashBreakpointFunction, DUMMY, EQ, displayStr} from './hash_impl_common';\nimport {\n    HashBoxesComponent,\n    LineOfBoxesComponent,\n    TetrisFactory,\n    SimpleCodeBlock,\n    VisualizedCode,\n    SMALLER_BOX_GEOMETRY,\n} from './code_blocks';\nimport {PyStringInput, PyNumberInput, PyListInput, PySNNInput, BlockInputToolbar} from './inputs';\nimport {\n    ChapterComponent,\n    Subcontainerize,\n    COLOR_FOR_READ_OPS,\n    randomMeaningfulString,\n    randomString3len,\n    randint,\n    randomChoice,\n} from './util';\nimport {chapter1_2_FormatCheckCollision, commonFormatCheckNotFound} from './common_formatters';\nimport {library} from '@fortawesome/fontawesome-svg-core';\nimport {FontAwesomeIcon} from '@fortawesome/react-fontawesome';\nimport {faRedoAlt} from '@fortawesome/free-solid-svg-icons/faRedoAlt';\nimport {faTrashAlt} from '@fortawesome/free-solid-svg-icons/faTrashAlt';\nlibrary.add(faTrashAlt);\n\nimport memoizeOne from 'memoize-one';\n\nexport const HASH_CREATE_NEW_CODE = [\n    ['def create_new(from_keys):', 'start-execution', 0],\n    ['    hash_codes = [EMPTY] * (2 * len(from_keys))', 'create-new-empty-hashes', 1],\n    ['    keys = [EMPTY] * (2 * len(from_keys))', 'create-new-empty-keys', 1],\n    ['', '', -1],\n    ['    for key in from_keys:', 'for-loop', 2],\n    ['        hash_code = hash(key)', 'compute-hash', 2],\n    ['        idx = hash_code % len(keys)', 'compute-idx', 2],\n    ['        while hash_codes[idx] is not EMPTY:', 'check-collision', 3],\n    ['            if hash_codes[idx] == hash_code and \\\\', 'check-dup-hash', 3],\n    ['               keys[idx] == key:', 'check-dup-key', 3],\n    ['                break', 'check-dup-break', 4],\n    ['            idx = (idx + 1) % len(keys)', 'next-idx', 3],\n    ['', '', -1],\n    ['        hash_codes[idx], keys[idx] = hash_code, key', 'assign-elem', 2],\n    ['', '', -1],\n    ['    return hash_codes, keys', 'return-lists', 1],\n];\n\nfunction anotherValue(array, ARRAY_CHANCE = 0.5, MEANINGFUL_CHANCE = 0.25, NUMBER_CHANCE = 0.2) {\n    const roll = Math.random();\n\n    if (roll < ARRAY_CHANCE) {\n        return randomChoice(array);\n    } else if (roll < ARRAY_CHANCE + MEANINGFUL_CHANCE) {\n        return randomMeaningfulString();\n    } else if (roll < ARRAY_CHANCE + MEANINGFUL_CHANCE + NUMBER_CHANCE) {\n        return BigNumber(randint(-100, 100));\n    } else {\n        return randomString3len();\n    }\n}\n\nclass HashCreateNew extends HashBreakpointFunction {\n    run(_fromKeys) {\n        this.fromKeys = new List(_fromKeys);\n\n        this.hashCodes = new List();\n        this.keys = new List();\n\n        for (let i = 0; i < this.fromKeys.size * 2; ++i) {\n            this.hashCodes = this.hashCodes.push(null);\n        }\n        this.addBP('create-new-empty-hashes');\n\n        for (let i = 0; i < this.fromKeys.size * 2; ++i) {\n            this.keys = this.keys.push(null);\n        }\n        this.addBP('create-new-empty-keys');\n\n        for ([this.fromKeysIdx, this.key] of this.fromKeys.entries()) {\n            this.addBP('for-loop');\n\n            this.hashCode = pyHash(this.key);\n            this.addBP('compute-hash');\n\n            this.idx = this.computeIdx(this.hashCode, this.keys.size);\n            this.addBP('compute-idx');\n\n            this.fmtCollisionCount = 0;\n            while (true) {\n                this.addBP('check-collision');\n                if (this.keys.get(this.idx) === null) {\n                    break;\n                }\n\n                this.addBP('check-dup-hash');\n                if (this.hashCodes.get(this.idx).eq(this.hashCode)) {\n                    this.addBP('check-dup-key');\n                    if (EQ(this.keys.get(this.idx), this.key)) {\n                        this.addBP('check-dup-break');\n                        break;\n                    }\n                }\n\n                this.fmtCollisionCount += 1;\n                this.idx = (this.idx + 1) % this.keys.size;\n                this.addBP('next-idx');\n            }\n\n            this.hashCodes = this.hashCodes.set(this.idx, this.hashCode);\n            this.keys = this.keys.set(this.idx, this.key);\n            this.addBP('assign-elem');\n        }\n\n        this.fromKeysIdx = null;\n        this.key = null;\n\n        this.addBP('return-lists');\n        return [this.hashCodes, this.keys];\n    }\n}\n\nconst HashCreateNewStateVisualization = TetrisFactory([\n    [\n        LineOfBoxesComponent,\n        [\n            {labels: ['from_keys'], marginBottom: 20},\n            'fromKeys',\n            'fromKeysIdx',\n            undefined,\n            {selection1color: COLOR_FOR_READ_OPS},\n        ],\n    ],\n    [HashBoxesComponent, [{labels: ['keys'], marginBottom: 7}, 'keys', 'idx']],\n    [HashBoxesComponent, [{labels: ['hash_codes']}, 'hashCodes', 'idx']],\n]);\n\nfunction formatHashCreateNewAndInsert(bp, prevBp) {\n    switch (bp.point) {\n        case 'create-new-empty-hashes':\n            return `Create a new list of size <code>${bp.hashCodes.size}</code> for hash codes`;\n        case 'create-new-empty-keys':\n            return `Create a new list of size <code>${bp.keys.size}</code> for keys`;\n        case 'for-loop':\n            return `[${bp.fromKeysIdx + 1}/${bp.fromKeys.size}] The key to insert is <code>${displayStr(\n                bp.key\n            )}</code>`;\n        case 'compute-hash':\n            return `Compute the hash code: <code>${bp.hashCode}</code>`;\n        case 'compute-idx':\n            return `Compute the starting slot index: <code>${bp.idx}</code> == <code>${bp.hashCode} % ${bp.keys.size}</code>`;\n        case 'check-collision':\n            return chapter1_2_FormatCheckCollision(bp.keys, bp.idx, bp.fmtCollisionCount);\n        case 'check-dup-hash':\n            if (EQ(bp.hashCodes.get(bp.idx), bp.hashCode)) {\n                return `<code>${bp.hashCodes.get(bp.idx)} == ${\n                    bp.hashCode\n                }</code>, we cannot rule out the slot being occupied by the same key`;\n            } else {\n                return `<code>${bp.hashCodes.get(bp.idx)} != ${\n                    bp.hashCode\n                }</code>, so there is a collision with a different key`;\n            }\n        case 'check-dup-key':\n            if (EQ(bp.keys[bp.idx], bp.key)) {\n                return `<code>${displayStr(bp.keys.get(bp.idx))} == ${displayStr(\n                    bp.key\n                )}</code>, so the key is already in the table`;\n            } else {\n                return `<code>${displayStr(bp.keys.get(bp.idx))} != ${displayStr(\n                    bp.key\n                )}</code>, so there is a collision`;\n            }\n        case 'check-dup-break':\n            return 'Because the key is found, stop';\n        case 'check-dup-return':\n            return 'Because the key is found, stop';\n        case 'next-idx':\n            return `Keep probing, the next slot will be <code>${bp.idx}</code> == <code>(${prevBp.idx} + 1) % ${bp.keys.size}</code>`;\n        case 'assign-elem':\n            if (prevBp.keys.get(bp.idx) === null) {\n                return `Put <code>${displayStr(bp.key)}</code> and its hash <code>${\n                    bp.hashCode\n                }</code> in the empty slot <code>${bp.idx}</code>`;\n            } else {\n                return `<code>${displayStr(bp.key)}</code> and its hash <code>${\n                    bp.hashCode\n                }</code> is already in the slot, overwriting it anyway`;\n            }\n        case 'return-lists':\n            return `The hash table is complete, return the lists`;\n    }\n}\n\nexport const HASH_SEARCH_CODE = [\n    ['def has_key(hash_codes, keys, key):', 'start-execution', 0],\n    ['    hash_code = hash(key)', 'compute-hash', 1],\n    ['    idx = hash_code % len(keys)', 'compute-idx', 1],\n    ['    while hash_codes[idx] is not EMPTY:', 'check-not-found', 2],\n    ['        if hash_codes[idx] == hash_code and \\\\', 'check-hash', 2],\n    ['           keys[idx] == key:', 'check-key', 2],\n    ['            return True', 'return-true', 3],\n    ['        idx = (idx + 1) % len(keys)', 'next-idx', 2],\n    ['    return False', 'return-false', 1],\n];\n\nfunction formatHashRemoveSearch(bp, prevBp) {\n    switch (bp.point) {\n        case 'compute-hash':\n            return `Compute the hash code: <code>${bp.hashCode}</code>`;\n        case 'compute-idx':\n            return `Compute the starting slot index: <code>${bp.hashCode} % ${bp.keys.size}</code> == <code>${bp.idx}</code>`;\n        case 'check-not-found':\n            return commonFormatCheckNotFound(bp.keys, bp.idx, bp.fmtCollisionCount);\n        case 'check-hash':\n            if (bp.hashCodes.get(bp.idx).eq(bp.hashCode)) {\n                return `<code>${bp.hashCodes.get(bp.idx)} == ${\n                    bp.hashCode\n                }</code>, so the slot might contain the same key`;\n            } else {\n                return `<code>${bp.hashCodes.get(bp.idx)} != ${\n                    bp.hashCode\n                }</code>, so the slot definitely contains a different key`;\n            }\n        case 'check-key':\n            if (EQ(bp.keys.get(bp.idx), bp.key)) {\n                return `<code>${displayStr(bp.keys.get(bp.idx))} == ${displayStr(bp.key)}</code>, so the key is found`;\n            } else {\n                return `<code>${displayStr(bp.keys.get(bp.idx))} != ${\n                    bp.key\n                }</code>, so there is a different key with the same hash`;\n            }\n        case 'assign-dummy':\n            return `Replace key in slot <code>${bp.idx}</code> with <code>DUMMY</code> placeholder`;\n        case 'return':\n            return `The key is removed, now return`;\n        case 'next-idx':\n            return `Keep retracing probing steps, the next slot will be <code>${bp.idx}</code> == <code>(${prevBp.idx} + 1) % ${bp.keys.size}</code>`;\n        case 'throw-key-error':\n            return `Throw an exception, because no key was found`;\n        /* search */\n        case 'return-true':\n            return `so return <code>True</code>`;\n        case 'return-false':\n            return `We hit an empty slot, so the key is not there. Return <code>False</code>`;\n    }\n}\n\nconst HashNormalStateVisualization = TetrisFactory([\n    [HashBoxesComponent, [{labels: ['keys'], marginBottom: 7}, 'keys', 'idx']],\n    [HashBoxesComponent, [{labels: ['hash_codes']}, 'hashCodes', 'idx']],\n]);\n\nconst HashNormalStateVisualizationSmallBoxes = TetrisFactory(\n    [\n        [HashBoxesComponent, [{labels: ['keys'], marginBottom: 7}, 'keys', 'idx']],\n        [HashBoxesComponent, [{labels: ['hash_codes']}, 'hashCodes', 'idx']],\n    ],\n    {fixedGeometry: SMALLER_BOX_GEOMETRY}\n);\n\nexport const HASH_REMOVE_CODE = [\n    ['def remove(hash_codes, keys, key):', 'start-execution', 0],\n    ['    hash_code = hash(key)', 'compute-hash', 1],\n    ['    idx = hash_code % len(keys)', 'compute-idx', 1],\n    ['', '', -1],\n    ['    while hash_codes[idx] is not EMPTY:', 'check-not-found', 2],\n    ['        if hash_codes[idx] == hash_code and \\\\', 'check-hash', 2],\n    ['           keys[idx] == key:', 'check-key', 2],\n    ['            keys[idx] = DUMMY', 'assign-dummy', 2],\n    ['            return', 'return', 3],\n    ['        idx = (idx + 1) % len(keys)', 'next-idx', 2],\n    ['', ''],\n    ['    raise KeyError()', 'throw-key-error', 1],\n];\n\nclass HashRemoveOrSearch extends HashBreakpointFunction {\n    run(_hashCodes, _keys, _key, isRemoveMode) {\n        this.hashCodes = new List(_hashCodes);\n        this.keys = new List(_keys);\n        this.key = _key;\n\n        this.fmtCollisionCount = 0;\n\n        this.hashCode = pyHash(this.key);\n        this.addBP('compute-hash');\n\n        this.idx = this.computeIdx(this.hashCode, this.keys.size);\n        this.addBP('compute-idx');\n\n        while (true) {\n            this.addBP('check-not-found');\n            if (this.keys.get(this.idx) === null) {\n                break;\n            }\n\n            this.addBP('check-hash');\n            if (this.hashCodes.get(this.idx).eq(this.hashCode)) {\n                this.addBP('check-key');\n                if (EQ(this.keys.get(this.idx), this.key)) {\n                    if (isRemoveMode) {\n                        this.keys = this.keys.set(this.idx, DUMMY);\n                        this.addBP('assign-dummy');\n                        this.addBP('return');\n                        return {hashCodes: this.hashCodes, keys: this.keys, isException: false, result: null};\n                    } else {\n                        this.addBP('return-true');\n                        return {hashCodes: this.hashCodes, keys: this.keys, isException: false, result: true};\n                    }\n                }\n            }\n\n            this.fmtCollisionCount += 1;\n\n            this.idx = (this.idx + 1) % this.keys.size;\n            this.addBP('next-idx');\n        }\n\n        let result, isException;\n        if (isRemoveMode) {\n            this.addBP('throw-key-error');\n            isException = true;\n        } else {\n            this.addBP('return-false');\n            isException = false;\n            result = false;\n        }\n\n        return {hashCodes: this.hashCodes, keys: this.keys, isException, result};\n    }\n}\n\nexport const HASH_RESIZE_CODE = [\n    ['def resize(hash_codes, keys):', 'start-execution', 0],\n    ['    new_hash_codes = [EMPTY] * (2 * len(hash_codes))', 'create-new-empty-hashes', 1],\n    ['    new_keys = [EMPTY] * (2 * len(keys))', 'create-new-empty-keys', 1],\n    ['    for hash_code, key in zip(hash_codes, keys):', 'for-loop', 2],\n    ['        if key is EMPTY or key is DUMMY:', 'check-skip-empty-dummy', 2],\n    ['            continue', 'continue', 3],\n    ['        idx = hash_code % len(new_keys)', 'compute-idx', 2],\n    ['        while new_hash_codes[idx] is not EMPTY:', 'check-collision', 3],\n    ['            idx = (idx + 1) % len(new_keys)', 'next-idx', 3],\n    ['        new_hash_codes[idx], new_keys[idx] = hash_code, key', 'assign-elem', 2],\n    ['', ''],\n    ['    return new_hash_codes, new_keys', 'return-lists', 1],\n];\n\nclass HashResize extends HashBreakpointFunction {\n    run(_hashCodes, _keys) {\n        this.hashCodes = _hashCodes;\n        this.keys = _keys;\n\n        this.newHashCodes = new List();\n        this.newKeys = new List();\n\n        for (let i = 0; i < this.hashCodes.size * 2; ++i) {\n            this.newHashCodes = this.newHashCodes.push(null);\n        }\n        this.addBP('create-new-empty-hashes');\n\n        for (let i = 0; i < this.hashCodes.size * 2; ++i) {\n            this.newKeys = this.newKeys.push(null);\n        }\n        this.addBP('create-new-empty-keys');\n\n        for ([this.oldIdx, [this.hashCode, this.key]] of this.hashCodes.zip(this.keys).entries()) {\n            this.addBP('for-loop');\n            this.addBP('check-skip-empty-dummy');\n            if (this.key === null || this.key === DUMMY) {\n                this.addBP('continue');\n                continue;\n            }\n\n            this.fmtCollisionCount = 0;\n            this.idx = this.computeIdx(this.hashCode, this.newKeys.size);\n            this.addBP('compute-idx');\n\n            while (true) {\n                this.addBP('check-collision');\n                if (this.newKeys.get(this.idx) === null) {\n                    break;\n                }\n\n                this.fmtCollisionCount += 1;\n                this.idx = (this.idx + 1) % this.newKeys.size;\n                this.addBP('next-idx');\n            }\n\n            this.newHashCodes = this.newHashCodes.set(this.idx, this.hashCode);\n            this.newKeys = this.newKeys.set(this.idx, this.key);\n            this.addBP('assign-elem');\n        }\n        this.addBP('return-lists');\n        return [this.newHashCodes, this.newKeys];\n    }\n}\n\nfunction formatHashResize(bp, prevBp) {\n    switch (bp.point) {\n        case 'create-new-empty-hashes':\n            return `Create a new list of size <code>${bp.newHashCodes.size}</code> for hash codes`;\n        case 'create-new-empty-keys':\n            return `Create a new list of size <code>${bp.newKeys.size}</code> for keys`;\n        case 'for-loop':\n            return `[${bp.oldIdx + 1}/${bp.keys.size}] The current key to insert is <code>${\n                bp.key === null ? 'EMPTY' : bp.key\n            }</code>, its hash is <code>${bp.hashCode === null ? 'EMPTY' : bp.hashCode}</code>`;\n        case 'compute-idx':\n            return `Compute the starting slot index: <code>${bp.idx} == ${bp.hashCode} % ${bp.newKeys.size}</code>`;\n        case 'check-skip-empty-dummy':\n            if (bp.keys.get(bp.oldIdx) === null) {\n                return `The current slot is empty`;\n            } else if (bp.keys.get(bp.oldIdx) === DUMMY) {\n                return `The current slot contains a <code>DUMMY</code> placeholder`;\n            } else {\n                return `The current slot is occupied by an actually existing key`;\n            }\n        case 'continue':\n            return 'So skip it';\n        case 'check-collision':\n            return chapter1_2_FormatCheckCollision(bp.newKeys, bp.idx, bp.fmtCollisionCount);\n        case 'next-idx':\n            return `Keep probing, the next slot will be <code>${bp.idx}</code> == <code>(${prevBp.idx} + 1) % ${bp.keys.size}</code>`;\n        case 'assign-elem':\n            return `Put <code>${displayStr(bp.key)}</code> and its hash <code>${bp.hashCode}</code> in the empty slot ${\n                bp.idx\n            }`;\n        case 'return-lists':\n            return `The hash table has been rebuilt, return the lists`;\n    }\n}\n\nconst HashResizeStateVisualization = TetrisFactory([\n    [\n        HashBoxesComponent,\n        [{labels: ['keys'], marginBottom: 7}, 'keys', 'oldIdx', undefined, {selection1color: COLOR_FOR_READ_OPS}],\n    ],\n    [\n        HashBoxesComponent,\n        [\n            {labels: ['hash_codes'], marginBottom: 20},\n            'hashCodes',\n            'oldIdx',\n            undefined,\n            {selection1color: COLOR_FOR_READ_OPS},\n        ],\n    ],\n    [HashBoxesComponent, [{labels: ['new_keys'], marginBottom: 7}, 'newKeys', 'idx']],\n    [HashBoxesComponent, [{labels: ['new_hash_codes']}, 'newHashCodes', 'idx']],\n]);\n\nexport const HASH_INSERT_CODE = [\n    ['def insert(hash_codes, keys, key):', 'start-execution'],\n    ['    hash_code = hash(key)', 'compute-hash'],\n    ['    idx = hash_code % len(keys)', 'compute-idx'],\n    ['', ''],\n    ['    while keys[idx] is not EMPTY:', 'check-collision'],\n    ['        if hash_codes[idx] == hash_code and\\\\', 'check-dup-hash'],\n    ['           keys[idx] == key:', 'check-dup-key'],\n    ['            break', 'check-dup-break'],\n    ['        idx = (idx + 1) % len(keys)', 'next-idx'],\n    ['', ''],\n    ['    hash_codes[idx], keys[idx] = hash_code, key', 'assign-elem'],\n];\n\nclass HashInsert extends HashBreakpointFunction {\n    run(_hashCodes, _keys, _key) {\n        this.hashCodes = new List(_hashCodes);\n        this.keys = new List(_keys);\n        this.key = _key;\n\n        this.hashCode = pyHash(this.key);\n        this.addBP('compute-hash');\n\n        this.idx = this.computeIdx(this.hashCode, this.keys.size);\n        this.addBP('compute-idx');\n\n        while (true) {\n            this.addBP('check-collision');\n            if (this.keys.get(this.idx) === null) {\n                break;\n            }\n\n            this.addBP('check-dup-hash');\n            if (this.hashCodes.get(this.idx).eq(this.hashCode)) {\n                this.addBP('check-dup-key');\n                if (EQ(this.keys.get(this.idx), this.key)) {\n                    this.addBP('check-dup-break');\n                    break;\n                }\n            }\n\n            this.idx = (this.idx + 1) % this.keys.size;\n            this.addBP('next-idx');\n        }\n        this.hashCodes = this.hashCodes.set(this.idx, this.hashCode);\n        this.keys = this.keys.set(this.idx, this.key);\n\n        this.addBP('assign-elem');\n        return [this.hashCodes, this.keys];\n    }\n}\n\nclass HashExamples extends React.Component {\n    constructor(props) {\n        super(props);\n        this.state = {\n            string: 'Hello',\n            integer: 42,\n        };\n    }\n\n    render() {\n        const COMMON_WIDTH = 72;\n        const style = {\n            minWidth: COMMON_WIDTH,\n            widdth: COMMON_WIDTH,\n            display: 'inline-block',\n        };\n        return (\n            <div>\n                <div className=\"div-p\">\n                    <span style={style}>Strings:</span>\n                    <code>hash(</code>\n                    <PyStringInput\n                        inline={true}\n                        value={this.state.string}\n                        onChange={value => this.setState({string: value})}\n                    />\n                    <code>)</code> = <code>{pyHashUnicode(this.state.string)}</code>\n                </div>\n                <div className=\"div-p\">\n                    <span style={style}>Integers:</span>\n                    <code>hash(</code>\n                    <PyNumberInput\n                        inline={true}\n                        value={this.state.integer}\n                        onChange={value => this.setState({integer: value})}\n                    />\n                    <code>)</code> = <code>{pyHashLong(BigNumber(this.state.integer)).toFixed()}</code>\n                </div>\n                <p>\n                    <span style={style}>Floats:</span>\n                    <code>hash(42.5)</code> = <code>1426259968</code>\n                </p>\n                <p>\n                    <span style={style}>Tuples: </span>\n                    <code>hash((\"Hi\", 11))</code> = <code>4421265786515608844</code>\n                </p>\n                <p>\n                    <span style={style}>None: </span>\n                    <code>hash(None)</code> = <code>-9223372036581563745</code>\n                </p>\n            </div>\n        );\n    }\n}\n\nexport class Ops {\n    static createNew(array) {\n        const hcn = new HashCreateNew();\n        const [hashCodes, keys] = hcn.run(array);\n        const bp = hcn.getBreakpoints();\n        return {hashCodes, keys, bp};\n    }\n\n    static hasKey(hashCodes, keys, searchedObj) {\n        const hs = new HashRemoveOrSearch();\n        const {result, hashCodes: newHashCodes, keys: newKeys} = hs.run(hashCodes, keys, searchedObj, false);\n        const bp = hs.getBreakpoints();\n\n        return {result, bp, hashCodes: newHashCodes, keys: newKeys};\n    }\n\n    static remove(hashCodes, keys, objToRemove) {\n        const hr = new HashRemoveOrSearch();\n        const {isException, hashCodes: newHashCodes, keys: newKeys} = hr.run(hashCodes, keys, objToRemove, true);\n        const bp = hr.getBreakpoints();\n\n        return {isException, bp, hashCodes: newHashCodes, keys: newKeys};\n    }\n\n    static resize(hashCodes, keys) {\n        const hres = new HashResize();\n        const [newHashCodes, newKeys] = hres.run(hashCodes, keys);\n        const bp = hres.getBreakpoints();\n\n        return {bp, hashCodes: newHashCodes, keys: newKeys};\n    }\n\n    static insert(hashCodes, keys, objToInsert) {\n        const hi = new HashInsert();\n        const [newHashCodes, newKeys] = hi.run(hashCodes, keys, objToInsert);\n        const bp = hi.getBreakpoints();\n\n        return {bp, hashCodes: newHashCodes, keys: newKeys};\n    }\n}\n\nfunction ListDescription({array}) {\n    let countStr = array.filter(e => typeof e === 'string').length;\n    let countNum = array.length - countStr;\n    if (countStr > 0 && countNum > 0) {\n        return 'a mixed list of integers and strings';\n    } else if (countStr > 0) {\n        return 'a list of strings';\n    } else {\n        return 'a list of integers';\n    }\n}\n\nclass HashResizeInPlaceAnimation extends React.PureComponent {\n    static FULL_WIDTH = true;\n    static EXTRA_ERROR_BOUNDARY = true;\n\n    constructor() {\n        super();\n        this.state = {\n            isStart: true,\n            breakpointsUpdatedCounter: 0,\n        };\n    }\n\n    static getDerivedStateFromProps(props, state) {\n        if (props.breakpoints !== state.breakpoints) {\n            return {\n                ...state,\n                breakpoints: props.breakpoints,\n                breakpointsUpdatedCounter: state.breakpointsUpdatedCounter + 1,\n            };\n        } else {\n            return null;\n        }\n    }\n\n    runAnimation = () => {\n        console.log('runAnimation');\n        this.setState(state => ({\n            isStart: !state.isStart,\n        }));\n    };\n\n    render() {\n        let hashCodes, keys;\n        let buttonIcon, buttonLabel;\n        if (this.state.isStart) {\n            ({hashCodes, keys} = this.state.breakpoints[0]);\n            buttonLabel = 'Throw away the old table';\n            buttonIcon = <FontAwesomeIcon icon=\"trash-alt\" />;\n        } else {\n            ({newHashCodes: hashCodes, newKeys: keys} = this.state.breakpoints[this.state.breakpoints.length - 1]);\n            buttonLabel = 'Reset';\n            buttonIcon = <FontAwesomeIcon icon=\"redo-alt\" />;\n        }\n        return (\n            <div className=\"hl-left\" style={{paddingLeft: 20}}>\n                <button\n                    type=\"button\"\n                    className=\"btn btn-primary btn-sm\"\n                    onClick={this.runAnimation}\n                    style={{minWidth: 180}}\n                >\n                    {buttonIcon} {buttonLabel}\n                </button>\n                <HashNormalStateVisualizationSmallBoxes\n                    bp={{hashCodes, keys}}\n                    epoch={this.state.breakpointsUpdatedCounter}\n                    fixedGeometry={SMALLER_BOX_GEOMETRY}\n                />\n            </div>\n        );\n    }\n}\n\nexport class Chapter2_HashTableFunctions extends ChapterComponent {\n    constructor() {\n        super();\n\n        this.state = {\n            array: ['uname', 'mv', 1, 'time', -6, 'ps', 'mkdir', 'less'],\n            searchedObj: 'uname',\n            objToRemove: 'mv',\n        };\n    }\n\n    runCreateNew = memoizeOne(array => {\n        return Ops.createNew(array);\n    });\n\n    runSearch = memoizeOne((hashCodes, keys, searchedObj) => {\n        const bp = Ops.hasKey(hashCodes, keys, searchedObj).bp;\n        return {bp};\n    });\n\n    runRemove = memoizeOne((hashCodes, keys, objToRemove) => {\n        const {bp, hashCodes: newHashCodes, keys: newKeys} = Ops.remove(hashCodes, keys, objToRemove);\n        return {bp, hashCodes: newHashCodes, keys: newKeys};\n    });\n\n    runResize = memoizeOne((hashCodes, keys) => {\n        const bp = Ops.resize(hashCodes, keys).bp;\n        return {bp};\n    });\n\n    /*runInsert = memoizeOne((hashCodes, keys, objToInsert) => {\n        const bp = Ops.insert(hashCodes, keys, objToInsert).bp;\n        return {bp};\n    });*/\n\n    render() {\n        const t1 = performance.now();\n        const newRes = this.runCreateNew(this.state.array);\n        let {hashCodes, keys} = newRes;\n\n        const searchRes = this.runSearch(hashCodes, keys, this.state.searchedObj);\n        const removeRes = this.runRemove(hashCodes, keys, this.state.objToRemove);\n        hashCodes = removeRes.hashCodes;\n        keys = removeRes.keys;\n\n        const resizeRes = this.runResize(hashCodes, keys);\n        /*const insertRes = this.runInsert(hashCodes, keys, this.state.objToInsert);*/\n        console.log('Chapter2 render timing', performance.now() - t1);\n\n        return (\n            <div className=\"chapter chapter2\">\n                <h2> Chapter 2. Why are hash tables called hash tables? </h2>\n                <Subcontainerize>\n                    <p>\n                        Now that we have the solution for searching in a list of numbers, can we use it for non-integer\n                        objects? We can if we find a way to turn objects into numbers for indexing.\n                    </p>\n                    <p>\n                        {' '}\n                        We don't need a perfect one-to-one correspondence between objects and integers. In fact, it is\n                        totally fine if two unrelated objects are turned into the same number &mdash; we can use linear\n                        probing to resolve this collision anyway!\n                    </p>\n                    <p>\n                        However, if we turn all objects into the same number, for example, <code>42</code>, our hash\n                        table would work, but its performance would severely degrade. So, for performance reasons it is\n                        desirable to get distinct numbers for distinct objects usually.{' '}\n                    </p>\n                    <p>\n                        The transformation also needs to be completely deterministic, i.e. we need to always get the\n                        same value for the same object. In other words, something like <code>random()</code> would not\n                        work, because we would \"forget\" where we placed our objects and we wouldn't be able to locate\n                        them during a search.\n                    </p>\n                    <p>\n                        Functions that do this kind of transformation are called <em>hash functions</em>. Since it is\n                        not required to preserve any order in the input domain, a typical hash function \"mixes up\" its\n                        input domain, hence the name \"hash\".\n                    </p>\n                    <p>\n                        In Python, there are built-in implementations of hash functions for many built-in types. They\n                        are all available through a single interface: the function <code>hash()</code>. This function\n                        can take any Python object as an input and call an appropriate implementation (if it exists).\n                    </p>\n                    <p> Here are some examples of hash function values for some of the built-in types. </p>\n                    <HashExamples />\n                    <p>\n                        In the case of strings, <code>hash()</code> returns fairly unpredictable results, as it should.\n                        However, for small integers <code className=\"text-nowrap\">hash(x) == x</code>. This fact may\n                        seem surprising to people familiar with hash functions, but it is a deliberate design decision\n                        by Python core developers.\n                    </p>\n                    <p>\n                        For \"long\" integers Python uses a different algorithm. Try typing a relatively big number, for\n                        example, <code>12345678901234567890</code> to see this.\n                    </p>\n                    <p>\n                        Another fact: <code>hash()</code> never returns <code>-1</code>, because <code>-1</code> used\n                        internally as an indicator of an error. That's why <code>hash(-1)</code> is <code>-2</code>.\n                    </p>\n                    <h5>hash() implementation notes</h5>\n                    <p>\n                        This chapter and the next two chapters will use <code>hash()</code> implementation from Python\n                        3.2 (running on an x86_64 system). So if you run Python 3.2 on your x86_64 system, you should\n                        see the same hash values for integers and strings (and the same data structure states). One\n                        exception is <code>hash(None)</code>. It changes between runs, but in this explanation{' '}\n                        <code>hash(None)</code> is always <code>-9223372036581563745</code>.\n                    </p>\n                    <p>\n                        Why Python 3.2? Because dict implementation has changed over time but Python 3.2's dict\n                        implements most major ideas, and, thus, Python 3.2's dict is a good starting point. Later\n                        versions of Python extend (rather than completely replace) Python 3.2's implementation.\n                        Eventually, we will get to these implementations as well.\n                    </p>\n                    <h5> Unhashable types </h5>\n                    <p>\n                        Not all types are hashable. For example, lists aren't and if you call{' '}\n                        <code className=\"text-nowrap\">hash([\"some\", \"values\"])</code> you will get{' '}\n                        <code>TypeError: unhashable type: 'list'</code>. Why can't we use the same hash function for\n                        lists as for tuples? The answer is because lists are mutable and tuples are not.\n                    </p>\n                    <p>\n                        We could still define a hash function for lists and other mutable objects. However changing a\n                        list would change the value of the hash function as well, and therefore we will not be able to\n                        locate the mutated list! Hashing and using lists as keys in dicts would lead to many accidental\n                        bugs, so core developers of Python chose not to allow this.\n                    </p>\n                    <h5>A note on the word \"hash\"</h5>\n                    <p>\n                        Because hash tables use hash functions and because hash tables mix up inserted elements, they\n                        are called hash tables. Sometimes people shorten \"hash table\" to just \"hash\". The output of a\n                        hash function is sometimes called \"hash value\" or \"hash code\", but very often it is also\n                        shortened to just \"hash\". Also, Python's built-in hash function is called <code>hash()</code>.\n                    </p>\n                    <p>\n                        Because people like to shorten things, several different (but related) concepts end up having\n                        the same shortened name. This can get a bit confusing sometimes.\n                    </p>\n                    <h5> Using hash functions for hash tables </h5>\n                    <p>\n                        Recall that we started with a simple problem: searching efficiently in a list of distinct\n                        numbers. Now, let's make this problem harder: our hash table needs to handle duplicates, support\n                        types other than integers, support removing and adding keys (and therefore resizing). We will\n                        see how to handle values in the next chapter, but for now let's assume we only need to search\n                        for keys.\n                    </p>\n                    <p>How does using hash functions change the insertion algorithm?</p>\n                    <p>\n                        Obviously, we have to use <code>hash()</code> function to convert objects into integers for\n                        indexing.\n                    </p>\n                    <p>\n                        Because <code>None</code> is hashable too, we will need to use some other value as a placeholder\n                        for an empty slot. The cleanest way to do this is to create a new type and use a value of this\n                        type. In Python, this is quite simple:\n                    </p>\n                    <SimpleCodeBlock>\n                        {`\nclass EmptyValueClass(object):\n    pass\n\nEMPTY = EmptyValueClass()\n              `.trim()}\n                    </SimpleCodeBlock>\n                    <p>\n                        We will now use <code>EMPTY</code> to denote an empty slot. After we do this, we will be able to\n                        insert <code>None</code> in the hash table safely.\n                    </p>\n                    <p>\n                        But here is one critical thing: checking for equality of objects can be expensive. For example,\n                        comparing strings of length 10000 may require up to 10000 comparison operations - one per each\n                        pair of corresponding characters. And, we may end up doing several equality checks when doing\n                        linear probing.\n                    </p>\n                    <p>\n                        When we only had integers, we didn't have this problem, because comparing integers is cheap. But\n                        here is a neat trick we can use to improve the performance in the case of arbitrary objects. We\n                        still get numbers from hash functions. So, we can save these numbers and compare them before\n                        comparing actual objects. When comparing hashes, there are two different outcomes. First, the\n                        hashes are different; in this case, we can safely conclude that the objects are different as\n                        well. Second, the hashes are equal; in this case, there is a good chance that the objects are\n                        equal but there is still a possibility of two distinct objects having the same hash, so we have\n                        to compare the actual objects.\n                    </p>\n                    <p>\n                        This optimization is a space-time tradeoff. We spend extra memory to make the algorithm faster.\n                    </p>\n                    <p>\n                        In this chapter, we'll allow duplicates. Remember how search works in the previous chapter? We\n                        retrace the steps necessary to insert the element and check if any slot along the way contains\n                        it. We also do all these steps when we are actually inserting an element. This means that we're\n                        effectively doing a search while inserting an element. So, handling duplicates is\n                        straightforward &mdash; we can terminate the insertion process if we find the element. And if we\n                        hit an empty slot without finding the element, then the element is not in the table and it means\n                        that we can safely insert it.\n                    </p>\n                    <p>\n                        Now, let's see this algorithm in action. We'll use a separate list called{' '}\n                        <code>hash_codes</code> for caching values of hash functions.\n                    </p>\n                    <p>\n                        Let's say we have <ListDescription array={this.state.array} />:\n                    </p>\n                    <BlockInputToolbar\n                        input={PyListInput}\n                        inputProps={{minSize: 1}}\n                        initialValue={this.state.array}\n                        onChange={this.setter('array', true)}\n                        bottomBoundary=\".chapter2\"\n                        {...this.props}\n                    />\n                    <p>Let's build a hash table from it:</p>\n                    <VisualizedCode\n                        code={HASH_CREATE_NEW_CODE}\n                        breakpoints={newRes.bp}\n                        formatBpDesc={formatHashCreateNewAndInsert}\n                        stateVisualization={HashCreateNewStateVisualization}\n                        {...this.props}\n                    />\n                    <h5> Searching </h5>\n                    <p>\n                        The search algorithm hasn't changed much. We get the <code>hash()</code> function value for the\n                        object, and do linear probing. Just like in the first chapter, we essentially have a bunch of\n                        \"islands\", separated by empty slots. Each island usually contains only a few elements, and a\n                        linear scan over a small \"island\" isn't expensive.\n                    </p>\n                    <div className=\"div-p\">\n                        For instance, let's search for\n                        <PySNNInput\n                            inline={true}\n                            value={this.state.searchedObj}\n                            onChange={this.setter('searchedObj')}\n                            anotherValue={() => anotherValue(this.state.array)}\n                        />\n                        and see what happens:\n                    </div>\n                    <VisualizedCode\n                        code={HASH_SEARCH_CODE}\n                        breakpoints={searchRes.bp}\n                        formatBpDesc={formatHashRemoveSearch}\n                        stateVisualization={HashNormalStateVisualization}\n                        {...this.props}\n                    />\n                    <h5> Removing objects </h5>\n                    <p>\n                        If we removed a key without a trace, it'd leave a hole, and this would break the search\n                        algorithm. Then, how do we remove a key?\n                    </p>\n                    <p>\n                        The answer is that if we can't remove a key without a trace, we should leave a trace. When\n                        removing a key, we replace it with a \"dummy\" object (another term for this object is\n                        \"tombstone\"). This object acts as a placeholder that indicates we shouldn't stop probing during\n                        a search.\n                    </p>\n                    <p>\n                        In code, we can create this placeholder object just like we created <code>EMPTY</code>:\n                    </p>\n                    <SimpleCodeBlock>\n                        {`\nclass DummyValueClass(object):\n    pass\n\nDUMMY = DummyValueClass()\n              `.trim()}\n                    </SimpleCodeBlock>\n                    <div className=\"div-p\">\n                        Let's see removing in action. Let's say we want to remove:\n                        <PySNNInput\n                            inline={true}\n                            value={this.state.objToRemove}\n                            onChange={this.setter('objToRemove')}\n                            anotherValue={() => anotherValue(this.state.array, 0.7, 0.2, 0.1)}\n                        />\n                    </div>\n                    <VisualizedCode\n                        code={HASH_REMOVE_CODE}\n                        breakpoints={removeRes.bp}\n                        formatBpDesc={formatHashRemoveSearch}\n                        stateVisualization={HashNormalStateVisualization}\n                        {...this.props}\n                    />\n                    <p>\n                        Removing a lot of objects may lead to a table being filled with these dummy objects. What if a\n                        table overflows with dummy objects? There is a way to clean them up. But first, let's see what\n                        happens if a table overflows with ordinary objects.\n                    </p>\n                    <h5>Resizing hash tables</h5>\n                    <p>\n                        How do we resize a hash table? The index of each element depends on the table size, so it may\n                        change if the size of a table changes. Moreover, because of collisions and linear probing, each\n                        index may depend on the indexes of other objects (which, in turn, also depend on the size of a\n                        table and the indexes of other objects). This is a tangled mess.\n                    </p>\n                    <p>\n                        There is a way to disentangle this Gordian Knot, however. We can create a new, bigger table and\n                        re-insert all the elements from the smaller table (skipping dummy placeholders). This may sound\n                        expensive. And, it <em>is</em> expensive. But, the thing is, we don't have to resize the table\n                        after every operation. If we make the new table size 1.5x, 2x or even 4x the size of the old\n                        table, it'll take a while until it fills up again. We will do resizes rarely enough that the\n                        heavy cost of it will amortize (spread out) over many insertions/deletions.\n                    </p>\n                    <HashResizeInPlaceAnimation breakpoints={resizeRes.bp} />\n                    <p className=\"mt-2\">\n                        Since we're re-building the table, the code is fairly similar to the code for building it from\n                        scratch. The differences are:\n                    </p>\n                    <ul>\n                        <li>the hash codes are already there, so we don't need to compute them the second time;</li>\n                        <li>\n                            we don't have to check for duplicates, because we know that each object is present only once\n                            in the original table;\n                        </li>\n                        <li>we skip empty and dummy slots.</li>\n                    </ul>\n                    <VisualizedCode\n                        code={HASH_RESIZE_CODE}\n                        breakpoints={resizeRes.bp}\n                        formatBpDesc={formatHashResize}\n                        stateVisualization={HashResizeStateVisualization}\n                        {...this.props}\n                    />\n                    <p>\n                        There is still one more important question. What condition should trigger the resizing\n                        operation? If we postpone resizing until a table is nearly full, the performance will severely\n                        degrade. If we resize a table when it is still sparse, we will waste memory. Typically, a hash\n                        table is resized when it is around 66% full.\n                    </p>\n                    <p>\n                        The number of non-empty slots (including dummy/tombstone slots) is called <em>fill</em>. The\n                        ratio between fill and table capacity is called the <em>load factor</em> (also, sometimes:{' '}\n                        <em>fill factor</em> or <em>fill ratio</em>\n                        ). So, using the new terms, we can say that a hash table is resized when the load factor reaches\n                        66%. When a table is resized, it's size usually goes up, but sometimes it can actually go down,\n                        if there are a lot of dummy slots.\n                    </p>\n                    <p>\n                        To implement this efficiently, we need to track the load factor. We will need two counters for\n                        tracking fill and \"real\" usage. With the current code structure, tracking these counters would\n                        be messy because we would need to pass these counters to and from every function. A much cleaner\n                        solution would be using classes. More on that in the next chapter.\n                    </p>\n                </Subcontainerize>\n            </div>\n        );\n    }\n}\n"
  },
  {
    "path": "src/chapter3_and_4_common.js",
    "content": "import _ from 'lodash';\nimport * as React from 'react';\nimport {Map, List, Record} from 'immutable';\nimport {BigNumber} from 'bignumber.js';\nimport {\n    COLOR_FOR_READ_OPS,\n    randomMeaningfulString,\n    randomString3len,\n    randint,\n    randomChoice,\n    singularOrPlural,\n    fixFirstValues,\n} from './util';\n\nimport {HashSlotsComponent, LineOfBoxesComponent, TetrisFactory, SimpleCodeBlock, VisualizedCode} from './code_blocks';\nimport {BreakpointFunction, HashBreakpointFunction, pyHash, DUMMY, EQ, displayStr} from './hash_impl_common';\nimport {commonFormatCheckCollisionLoopEndedPart, commonFormatCheckNotFound} from './common_formatters';\n\nexport const DEFAULT_STATE = {\n    pairs: [\n        ['git', 'stash'],\n        ['cp', 'a.py b.py'],\n        ['ed', BigNumber(36)],\n        ['uniq', '-c'],\n        ['ls', '/'],\n        ['su', BigNumber(0)],\n        ['du', '-h'],\n        ['cut', '-f 1'],\n        [BigNumber(-27), BigNumber(42)],\n    ],\n    keyToDel: 'rm',\n};\n\nexport function singleFormatCheckCollision(slots, idx, fmtCollisionCount) {\n    if (slots.get(idx).key == null) {\n        return commonFormatCheckCollisionLoopEndedPart(idx, fmtCollisionCount);\n    } else {\n        return `[Try #${fmtCollisionCount + 1}] Slot <code>${idx}</code> is occupied: a collision occurred`;\n    }\n}\n\nconst _lookdictIsEmpty = (slots, idx) => slots.get(idx).key == null;\nexport function formatHashClassLookdictRelated(bp) {\n    switch (bp.point) {\n        case 'check-not-found':\n            return commonFormatCheckNotFound(bp.self.get('slots'), bp.idx, bp.fmtCollisionCount, _lookdictIsEmpty);\n        case 'check-hash': {\n            const slotHash = bp.self.get('slots').get(bp.idx).pyHashCode;\n            if (slotHash.eq(bp.hashCode)) {\n                return `<code>${slotHash} == ${bp.hashCode}</code>, so the slot might be occupied by the same key`;\n            } else {\n                return `<code>${slotHash} != ${bp.hashCode}</code>, so the slot definitely contains a different key`;\n            }\n        }\n        case 'check-key': {\n            const slotKey = bp.self.get('slots').get(bp.idx).key;\n            if (EQ(slotKey, bp.key)) {\n                return `<code>${displayStr(slotKey)} == ${displayStr(bp.key)}</code>, so the key is found`;\n            } else {\n                return `<code>${displayStr(slotKey)} != ${displayStr(\n                    bp.key\n                )}</code>, so the slot contains a different key but with the same hash`;\n            }\n        }\n        case 'return-idx':\n            return `Return <code>${bp.idx}</code>, the index of the current slot`;\n        case 'raise':\n            return `Throw an exception, because no key was found`;\n        case 'call-lookdict':\n            return `Call <code>self.lookdict(${displayStr(bp.key)})</code>`;\n        /* __delitem__ */\n        case 'dec-used':\n            return `We're about to put the <code>DUMMY</code> placeholder in the slot, so set the counter of <code>used</code> slots to <code>${bp.self.get(\n                'used'\n            )}</code>`;\n        case 'replace-key-dummy':\n            return `Replace key at <code>${bp.idx}</code> with the <code>DUMMY</code> placeholder`;\n        case 'replace-value-empty':\n            return `Replace value at <code>${bp.idx}</code> with <code>EMPTY</code>`;\n        /* __getitem__ */\n        case 'return-value': {\n            const slotValue = bp.self.get('slots').get(bp.idx).value;\n            return `Return <code>${displayStr(slotValue)}</code>, value from slot <code>${bp.idx}</code>`;\n        }\n        /* misc common */\n        case 'start-execution-lookdict':\n        case 'start-execution-getitem':\n        case 'start-execution-delitem':\n            return '';\n    }\n}\n\nexport function formatHashClassSetItemAndCreate(bp) {\n    switch (bp.point) {\n        case 'target-idx-none':\n            return `Initialize <code>target_idx</code> - this is the index of the slot where we'll put the item`;\n        case 'check-collision':\n            return singleFormatCheckCollision(bp.self.get('slots'), bp.idx, bp.fmtCollisionCount);\n        case 'check-dup-hash': {\n            const slotHash = bp.self.get('slots').get(bp.idx).pyHashCode;\n            if (slotHash.eq(bp.hashCode)) {\n                return `<code>${slotHash} == ${bp.hashCode}</code>, we cannot rule out the slot being occupied by the same key`;\n            } else {\n                return `<code>${slotHash} != ${bp.hashCode}</code>, so there is a collision with a different key`;\n            }\n        }\n        case 'check-dup-key': {\n            const slotKey = bp.self.get('slots').get(bp.idx).key;\n            if (EQ(slotKey, bp.key)) {\n                return `<code>${displayStr(slotKey)} == ${displayStr(\n                    bp.key\n                )}</code>, so the key is already present in the table`;\n            } else {\n                return `<code>${displayStr(slotKey)} != ${displayStr(bp.key)}</code>, so there is a collision`;\n            }\n        }\n        case 'check-should-recycle-target-idx':\n            if (bp.targetIdx !== null) {\n                return `<code>target_idx</code> isn't <code>None</code> &mdash; we've already found a <code>DUMMY</code> slot that we can recycle`;\n            } else {\n                return `<code>target_idx</code> is currently <code>None</code> - we are still looking for a <code>DUMMY</code> slot to recycle`;\n            }\n            break;\n        case 'check-should-recycle-dummy': {\n            const slotKey = bp.self.get('slots').get(bp.idx).key;\n            if (slotKey !== DUMMY) {\n                return `but the current slot's key is <code>${displayStr(slotKey)}</code>, i.e. not dummy</code>`;\n            } else {\n                return `also the current slot's key is <code>DUMMY</code>`;\n            }\n        }\n        case 'set-target-idx-recycle':\n            return `this means we found a recyclable slot, so save its index`;\n        case 'set-target-idx-found':\n            return `We will put the value in slot <code>${bp.targetIdx}</code>`;\n        case 'check-dup-break':\n            return 'Because the key is found, stop';\n        case 'check-target-idx-is-none':\n            if (bp.targetIdx == null) {\n                return `<code>target_idx is None</code>, it means we haven't encountered a <code>DUMMY</code> slot or a slot with the key itself`;\n            } else {\n                return `<code>target_idx is not None</code>, and this means we already know where to put the item`;\n            }\n        case 'after-probing-assign-target-idx':\n            return `So we'll put the item in the current slot (<code>${bp.idx}</code>), which is empty`;\n        case 'check-used-fill-increased': {\n            const _idxOrTargetIdx = bp.targetIdx !== undefined ? bp.targetIdx : bp.idx;\n            return (\n                \"If we're putting the item in an empty slot \" +\n                (bp.self.get('slots').get(_idxOrTargetIdx).key == null ? '(and we are)' : \"(and we aren't)\")\n            );\n        }\n        case 'inc-used':\n        case 'inc-used-2': {\n            const isOnly = bp.point === 'inc-used-2';\n            return `then we ${\n                isOnly ? 'only' : ''\n            } need to increment <code>self.used</code>, which makes it <code>${bp.self.get('used')}</code>`;\n        }\n        case 'inc-fill':\n            return `and increment <code>fill</code>, which makes it <code>${bp.self.get('fill')}</code>`;\n        case 'check-recycle-used-increased':\n            return (\n                `If we're putting the item in a <code>DUMMY</code> slot ` +\n                (bp.self.get('slots').get(bp.targetIdx).key === DUMMY ? '(and we are)' : \"(and we aren't)\")\n            );\n        case 'assign-slot': {\n            const _idxOrTargetIdx = bp.targetIdx !== undefined ? bp.targetIdx : bp.idx;\n            return `Put the item in slot <code>${_idxOrTargetIdx}</code>`;\n        }\n        case 'check-resize': {\n            const _fill = bp.self.get('fill');\n            const _size = bp.self.get('slots').size;\n            const fillQ = _fill * 3;\n            const sizeQ = _size * 2;\n            let compStr, compStrShort;\n            let extraResizeStr = '';\n            if (fillQ >= sizeQ) {\n                if (fillQ > sizeQ) {\n                    compStr = 'is greater than';\n                    compStrShort = '>';\n                } else {\n                    // FIXME: I think this branch can't happen because math\n                    compStr = 'is equals to';\n                }\n            } else {\n                compStr = 'is less than';\n                compStrShort = '<';\n                extraResizeStr = ', so no need to run <code>resize()</code>';\n            }\n\n            return (\n                `fill factor (<code>${((100 * _fill) / _size).toFixed(\n                    0\n                )}%</code>) ${compStrShort} 2/3: <code> ${bp.self.get('fill')} * 3</code> (== <code>${fillQ}</code>) ` +\n                compStrShort +\n                ` <code>${bp.self.get('slots').size} * 2</code> (== <code>${sizeQ}</code>)` +\n                extraResizeStr\n            );\n        }\n        case 'resize':\n            return 'so it is time to do a resize';\n        case 'done-no-return':\n            return '';\n    }\n}\n\nexport function formatHashClassResize(bp) {\n    switch (bp.point) {\n        case 'assign-old-slots':\n            return 'Copy the reference to <code>slots</code> (no actual copying is done)';\n        case 'assign-fill':\n            return `Set fill to <code>${bp.self.get(\n                'used'\n            )}</code>, since we will skip all slots with the <code>DUMMY</code> placeholder`;\n        case 'compute-minused-32':\n            // TODO FIXME: doesn't take in account 50k limit, also this is supposed to be the common code\n            return `${bp.minused} == ${bp.self.get('used')}`;\n        case 'compute-new-size': {\n            const arg = bp.minused != null ? `${bp.minused}` : `${bp.self.get('used')} * 2`;\n            return `Find the smallest power of two greater than <code>${arg}</code>. It is <code>${bp.newSize}</code>`;\n        }\n        case 'new-empty-slots':\n            return `Create a new list of empty slots of size <code>${bp.self.get('slots').size}</code>`;\n        case 'for-loop': {\n            const {key, pyHashCode: hashCode} = bp.oldSlots.get(bp.oldIdx);\n            return `[${bp.oldIdx + 1}/${bp.oldSlots.size}] The current slot's key is <code>${\n                key === null ? 'EMPTY' : displayStr(key)\n            }</code> and its hash is <code>${hashCode === null ? 'EMPTY' : hashCode}</code>`;\n        }\n        case 'check-skip-empty-dummy': {\n            const slotKey = bp.oldSlots.get(bp.oldIdx).key;\n            if (slotKey === null) {\n                return `The current slot is empty, skipping it`;\n            } else if (slotKey === DUMMY) {\n                return `The current slot contains the <code>DUMMY</code> placeholder, skipping it`;\n            } else {\n                return `The current slot contains a normal item</code>`;\n            }\n        }\n        case 'continue' /* FIXME not currently used */:\n            return 'So skip it';\n        case 'check-collision':\n            return singleFormatCheckCollision(bp.self.get('slots'), bp.idx, bp.fmtCollisionCount);\n        case 'assign-slot':\n            return `Put the item in slot <code>${bp.idx}</code>`;\n        case 'done-no-return':\n        case 'start-execution':\n            return '';\n    }\n}\n\nexport function formatHashClassInit(bp) {\n    switch (bp.point) {\n        case 'init-start-size-pairs':\n            return `Find the power of two > 8 and > <code>${bp.pairsLength}</code> . It is <code>${bp.startSize}</code>`;\n        case 'init-start-size':\n        case 'init-start-size-8':\n            return `Start with a minimum hash table size, which is 8`;\n        case 'check-pairs-start-size':\n            return `If there are any pairs (and there are <code>${bp.pairsLength}</code> pairs)`;\n        case 'init-slots':\n            return `Start by creating a list of empty slots of size <code>${\n                bp.startSize != null ? bp.startSize : 8\n            }</code>`;\n        case 'init-fill':\n            return `Set <code>fill</code> to <code>0</code>, because there are no items (yet)`;\n        case 'init-used':\n            return `Set <code>used</code> to <code>0</code>, because there are no items (yet)`;\n        case 'check-pairs':\n            return `Check if there are <code>pairs</code> to insert: there are <code>${\n                bp.pairs.length\n            }</code> ${singularOrPlural(bp.pairs.length, 'pair', 'pairs')}`;\n        case 'for-pairs':\n            return `[${bp.oldIdx + 1}/${bp.pairs.length}] The current pair is <code>${displayStr(\n                bp.oldKey\n            )}</code> and <code>${displayStr(bp.oldValue)}</code>`;\n        case 'run-setitem':\n            return `Call self.__setitem__(<code>${displayStr(bp.oldKey)}</code>, <code>${displayStr(\n                bp.oldValue\n            )}</code>)`;\n    }\n}\n\nexport function hashClassConstructor(size = 8) {\n    let slotsTemp = [];\n    for (let i = 0; i < size; ++i) {\n        slotsTemp.push(new Slot());\n    }\n\n    let self = Map({\n        slots: new List(slotsTemp),\n        used: 0,\n        fill: 0,\n    });\n\n    return self;\n}\n\nexport class HashClassInitEmpty extends BreakpointFunction {\n    run(initStartSize = null, pairsLength = null) {\n        // This is a hack\n        if (pairsLength != null) {\n            this.pairsLength = pairsLength;\n        }\n\n        this.self = Map({slots: []});\n        let startSize;\n        if (initStartSize != null) {\n            startSize = initStartSize;\n            this.startSize = startSize;\n            this.addBP('check-pairs-start-size');\n            if (initStartSize === 8) {\n                this.addBP('init-start-size-8');\n            } else {\n                this.addBP('init-start-size-pairs');\n            }\n        } else {\n            startSize = 8;\n        }\n\n        let slotsTemp = [];\n        for (let i = 0; i < startSize; ++i) {\n            slotsTemp.push(new Slot());\n        }\n\n        this.self = this.self.set('slots', new List(slotsTemp));\n        this.addBP('init-slots');\n        this.self = this.self.set('fill', 0);\n        this.addBP('init-fill');\n        this.self = this.self.set('used', 0);\n        this.addBP('init-used');\n        this.addBP('check-pairs');\n\n        return this.self;\n    }\n}\n\nexport const Slot = Record({pyHashCode: null, key: null, value: null});\n\nexport function findClosestSize(minused) {\n    let newSize = 8;\n    while (newSize <= minused) {\n        newSize *= 2;\n    }\n\n    return newSize;\n}\n\nexport function findClosestSizeBN(minused) {\n    let newSize = BigNumber(8);\n    while (newSize.lte(minused)) {\n        newSize = newSize.times(2);\n    }\n\n    return newSize;\n}\n\nexport class HashClassSetItemBase extends HashBreakpointFunction {\n    run(_self, _key, _value, useRecycling, Resize, optimalSizeQuot) {\n        this.self = _self;\n        this.key = _key;\n        this.value = _value;\n        this.fmtCollisionCount = 0;\n\n        this.hashCode = pyHash(this.key);\n        this.addBP('compute-hash');\n\n        this.computeIdxAndSave(this.hashCode, this.self.get('slots').size);\n        if (useRecycling) {\n            this.targetIdx = null;\n            this.addBP('target-idx-none');\n        }\n\n        while (true) {\n            this.addBP('check-collision');\n            if (this.self.get('slots').get(this.idx).key === null) {\n                break;\n            }\n\n            this.addBP('check-dup-hash');\n            if (\n                this.self\n                    .get('slots')\n                    .get(this.idx)\n                    .pyHashCode.eq(this.hashCode)\n            ) {\n                this.addBP('check-dup-key');\n                if (EQ(this.self.get('slots').get(this.idx).key, this.key)) {\n                    if (useRecycling) {\n                        this.targetIdx = this.idx;\n                        this.addBP('set-target-idx-found');\n                    }\n                    this.addBP('check-dup-break');\n                    break;\n                }\n            }\n\n            if (useRecycling) {\n                this.addBP('check-should-recycle-target-idx');\n                if (this.targetIdx == null) {\n                    this.addBP('check-should-recycle-dummy');\n                    if (this.self.get('slots').get(this.idx).key === DUMMY) {\n                        this.targetIdx = this.idx;\n                        this.addBP('set-target-idx-recycle');\n                    }\n                }\n            }\n\n            this.fmtCollisionCount += 1;\n            this.nextIdxAndSave();\n        }\n\n        if (useRecycling) {\n            this.addBP('check-target-idx-is-none');\n            if (this.targetIdx === null) {\n                this.targetIdx = this.idx;\n                this.addBP('after-probing-assign-target-idx');\n            }\n        }\n\n        this.addBP('check-used-fill-increased');\n        let idx = useRecycling ? this.targetIdx : this.idx;\n        if (this.self.get('slots').get(idx).key === null) {\n            this.self = this.self.set('used', this.self.get('used') + 1);\n            this.addBP('inc-used');\n\n            this.self = this.self.set('fill', this.self.get('fill') + 1);\n            this.addBP('inc-fill');\n        } else {\n            if (useRecycling) {\n                this.addBP('check-recycle-used-increased');\n                if (this.self.get('slots').get(idx).key === DUMMY) {\n                    this.self = this.self.set('used', this.self.get('used') + 1);\n                    this.addBP('inc-used-2');\n                }\n            }\n        }\n\n        this.self = this.self.setIn(\n            ['slots', idx],\n            new Slot({pyHashCode: this.hashCode, key: this.key, value: this.value})\n        );\n\n        this.addBP('assign-slot');\n        this.addBP('check-resize');\n        if (this.self.get('fill') * 3 >= this.self.get('slots').size * 2) {\n            let hashClassResize = new Resize();\n            let _oldSelf = this.self;\n            this.self = hashClassResize.run(this.self, optimalSizeQuot);\n\n            this._resize = {\n                oldSelf: _oldSelf,\n                self: this.self,\n                breakpoints: hashClassResize.getBreakpoints(),\n            };\n\n            this.addBP('resize');\n        }\n        // this.addBP('done-no-return');\n        return this.self;\n    }\n\n    getResize() {\n        return this._resize !== undefined ? this._resize : null;\n    }\n}\n\nexport class HashClassLookdictBase extends HashBreakpointFunction {\n    run(_self, _key) {\n        this.self = _self;\n        this.key = _key;\n\n        this.fmtCollisionCount = 0;\n        this.addBP('start-execution-lookdict');\n        this.hashCode = pyHash(this.key);\n        this.addBP('compute-hash');\n        this.computeIdxAndSave(this.hashCode, this.self.get('slots').size);\n\n        while (true) {\n            this.addBP('check-not-found');\n            if (this.self.get('slots').get(this.idx).key === null) {\n                break;\n            }\n\n            this.addBP('check-hash');\n            if (\n                this.self\n                    .get('slots')\n                    .get(this.idx)\n                    .pyHashCode.eq(this.hashCode)\n            ) {\n                this.addBP('check-key');\n                if (EQ(this.self.get('slots').get(this.idx).key, this.key)) {\n                    this.addBP('return-idx');\n                    return this.idx;\n                }\n            }\n\n            this.fmtCollisionCount += 1;\n            this.nextIdxAndSave();\n        }\n\n        this.addBP('raise');\n        return null;\n    }\n}\n\nexport class HashClassGetItem extends HashBreakpointFunction {\n    run(_self, _key, Lookdict) {\n        this.self = _self;\n        this.key = _key;\n        this.addBP('start-execution-getitem');\n\n        let hcld = new Lookdict();\n        this.addBP('call-lookdict');\n        this.idx = hcld.run(this.self, this.key);\n        this._breakpoints = [...this._breakpoints, ...hcld.getBreakpoints()];\n        if (this.idx !== null) {\n            // did not throw exception\n            this.addBP('return-value');\n            return this.self.get('slots').get(this.idx).value;\n        }\n    }\n}\n\nexport class HashClassDelItem extends HashBreakpointFunction {\n    run(_self, _key, Lookdict) {\n        this.self = _self;\n        this.key = _key;\n        this.addBP('start-execution-delitem');\n\n        let hcld = new Lookdict();\n        this.addBP('call-lookdict');\n        this.idx = hcld.run(this.self, this.key);\n        this._breakpoints = [...this._breakpoints, ...hcld.getBreakpoints()];\n        if (this.idx !== null) {\n            // did not throw exception\n            this.self = this.self.set('used', this.self.get('used') - 1);\n            this.addBP('dec-used');\n            this.self = this.self.setIn(['slots', this.idx, 'key'], DUMMY);\n            this.addBP('replace-key-dummy');\n            this.self = this.self.setIn(['slots', this.idx, 'value'], null);\n            this.addBP('replace-value-empty');\n        }\n        return this.self;\n    }\n}\n\nexport class HashClassInsertAll extends HashBreakpointFunction {\n    constructor() {\n        super();\n\n        this._resizes = [];\n    }\n\n    run(_self, _pairs, useRecycling, SetItem, Resize, optimalSizeQuot) {\n        this.self = _self;\n        this.pairs = _pairs;\n        for ([this.oldIdx, [this.oldKey, this.oldValue]] of this.pairs.entries()) {\n            this.addBP('for-pairs');\n            this.addBP('run-setitem');\n            let hcsi = new SetItem();\n            hcsi.setExtraBpContext({\n                oldIdx: this.oldIdx,\n                pairs: this.pairs,\n            });\n            this.self = hcsi.run(this.self, this.oldKey, this.oldValue, useRecycling, Resize, optimalSizeQuot);\n            if (hcsi.getResize()) {\n                this._resizes.push(hcsi.getResize());\n            }\n            this._breakpoints.push(hcsi.getBreakpoints());\n        }\n        this._breakpoints = _.flatten(this._breakpoints);\n        return this.self;\n    }\n\n    getResizes() {\n        return this._resizes;\n    }\n}\n\nexport const HashClassNormalStateVisualization = TetrisFactory([\n    [\n        HashSlotsComponent,\n        [{labels: ['slots[*].key', 'slots[*].value', 'slots[*].hashCode']}, 'self.slots', 'idx', 'targetIdx'],\n    ],\n]);\n\nexport const HashClassInsertAllVisualization = TetrisFactory([\n    [\n        LineOfBoxesComponent,\n        [\n            {labels: ['pairs[*][0]', 'pairs[*][1]'], marginBottom: 20},\n            'pairs',\n            'oldIdx',\n            undefined,\n            {linesCount: 2, selection1color: COLOR_FOR_READ_OPS, kvHack: true},\n        ],\n    ],\n    [HashSlotsComponent, [{labels: ['slots[*].key', 'slots[*].value', 'slots[*].hashCode']}, 'self.slots', 'idx']],\n]);\n\nexport const HashClassResizeVisualization = TetrisFactory([\n    [\n        HashSlotsComponent,\n        [\n            {labels: ['oldSlots[*].key', 'oldSlots[*].value', 'oldSlots[*].hashCode'], marginBottom: 20},\n            'oldSlots',\n            'oldIdx',\n            undefined,\n            {selection1color: COLOR_FOR_READ_OPS},\n        ],\n    ],\n    [HashSlotsComponent, [{labels: ['slots[*].key', 'slots[*].value', 'slots[*].hashCode']}, 'self.slots', 'idx']],\n]);\n\nexport class HashClassResizeBase extends HashBreakpointFunction {\n    run(_self, optimalSizeQuot) {\n        this.self = _self;\n\n        this.oldSlots = new List();\n        this.addBP('start-execution');\n        this.oldSlots = this.self.get('slots');\n        this.addBP('assign-old-slots');\n        if (this.COMPUTE_MINUSED_HACKY_FLAG) {\n            this.minused = this.self.get('used') * optimalSizeQuot;\n        }\n        this.newSize = findClosestSize(this.self.get('used') * optimalSizeQuot);\n        this.addBP('compute-new-size');\n\n        let slotsTemp = [];\n\n        for (let i = 0; i < this.newSize; ++i) {\n            slotsTemp.push(new Slot());\n        }\n        this.self = this.self.set('slots', new List(slotsTemp));\n        this.addBP('new-empty-slots');\n\n        this.self = this.self.set('fill', this.self.get('used'));\n        this.addBP('assign-fill');\n\n        for ([this.oldIdx, this.slot] of this.oldSlots.entries()) {\n            /* For consistency with other functions, add these names */\n            this.hashCode = this.slot.pyHashCode;\n            this.key = this.slot.key;\n            this.value = this.slot.value;\n\n            this.addBP('for-loop');\n            this.addBP('check-skip-empty-dummy');\n            if (this.slot.key === null || this.slot.key === DUMMY) {\n                continue;\n            }\n            this.computeIdxAndSave(this.slot.pyHashCode, this.self.get('slots').size);\n            this.fmtCollisionCount = 0;\n\n            while (true) {\n                this.addBP('check-collision');\n                if (this.self.get('slots').get(this.idx).key === null) {\n                    break;\n                }\n\n                this.fmtCollisionCount += 1;\n                this.nextIdxAndSave();\n            }\n\n            this.self = this.self.setIn(['slots', this.idx], this.slot);\n            this.addBP('assign-slot');\n        }\n        this.addBP('done-no-return');\n\n        return this.self;\n    }\n}\n\nexport function anotherKey(pairs, ARRAY_CHANCE = 0.5, MEANINGFUL_CHANCE = 0.25, NUMBER_CHANCE = 0.2) {\n    const roll = Math.random();\n\n    if (roll < ARRAY_CHANCE) {\n        return randomChoice(pairs)[0];\n    } else if (roll < ARRAY_CHANCE + MEANINGFUL_CHANCE) {\n        return randomMeaningfulString();\n    } else if (roll < ARRAY_CHANCE + MEANINGFUL_CHANCE + NUMBER_CHANCE) {\n        return BigNumber(randint(-100, 100));\n    } else {\n        return randomString3len();\n    }\n}\n\nexport const generateNewKey = fixFirstValues(\n    function generateNewKey(MEANINGFUL_CHANCE = 0.7, NUMBER_CHANCE = 0.2) {\n        const roll = Math.random();\n\n        if (roll < MEANINGFUL_CHANCE) {\n            return randomMeaningfulString();\n        } else if (roll < MEANINGFUL_CHANCE + NUMBER_CHANCE) {\n            return BigNumber(randint(-300, 300));\n        } else {\n            return randomString3len();\n        }\n    },\n    ['head', 'tail', 'tee', 'wc', 'bc', 'dc']\n);\n\nexport function generateNonPresentKey(pySelf, getitem) {\n    while (true) {\n        const key = generateNewKey();\n        if (getitem(pySelf, key).isException) {\n            return key;\n        }\n    }\n}\n\nfunction addPairsUntilResize(pySelf, getitem, setitem) {\n    let resize = null;\n    let extraPairs = [];\n\n    while (resize == null) {\n        const key = generateNonPresentKey(pySelf, getitem);\n        const value = BigNumber(extraPairs.length + 1);\n        let newPySelf;\n        ({pySelf: newPySelf, resize} = setitem(pySelf, key, value));\n        const noRecycleOccured = resize || newPySelf.get('fill') > pySelf.get('fill');\n        if (noRecycleOccured) {\n            // Only add pairs that don't get recycled\n            pySelf = newPySelf;\n            extraPairs.push([key, value]);\n        }\n    }\n\n    return {extraPairs, resize};\n}\n\nexport function selectOrCreateResize(pySelf, resizes, getitem, setitem) {\n    let resize = null;\n    let extraPairs = null;\n    // TODO: support warning user about no resizes\n    if (resizes.length > 0) {\n        resize = resizes[0];\n    } else {\n        ({resize, extraPairs} = addPairsUntilResize(pySelf, getitem, setitem));\n    }\n\n    const bp = resize.breakpoints;\n    return {resize, bp, extraPairs, resizesCount: resizes ? resizes.length : 1};\n}\n\nconst formatExtraPair = ([k, v]) => `(${displayStr(k)}, ${displayStr(v)})`;\nexport const formatExtraPairs = extraPairs => {\n    if (extraPairs.length > 1) {\n        return '[' + extraPairs.map(formatExtraPair).join(', ') + ']';\n    } else {\n        return formatExtraPair(extraPairs[0]);\n    }\n};\n"
  },
  {
    "path": "src/chapter3_hash_class.js",
    "content": "import * as React from 'react';\n\nimport {HashBreakpointFunction, pyHash, EQ, computeIdx, displayStr, DUMMY} from './hash_impl_common';\n\nimport {BigNumber} from 'bignumber.js';\nimport {\n    hashClassConstructor,\n    HashClassInitEmpty,\n    HashClassResizeBase,\n    HashClassSetItemBase,\n    HashClassDelItem,\n    HashClassGetItem,\n    HashClassLookdictBase,\n    HashClassInsertAll,\n    HashClassNormalStateVisualization,\n    HashClassInsertAllVisualization,\n    HashClassResizeVisualization,\n    formatHashClassSetItemAndCreate,\n    formatHashClassLookdictRelated,\n    formatHashClassResize,\n    formatHashClassInit,\n    anotherKey,\n    generateNewKey,\n    selectOrCreateResize,\n    formatExtraPairs,\n    generateNonPresentKey,\n    DEFAULT_STATE,\n    findClosestSizeBN,\n} from './chapter3_and_4_common';\n\nimport {SimpleCodeBlock, VisualizedCode} from './code_blocks';\n\nimport {BlockInputToolbar, PyDictInput, PySNNInput, PyNumberInput} from './inputs';\nimport {ChapterComponent, singularOrPlural, Subcontainerize, DynamicP} from './util';\n\nimport memoizeOne from 'memoize-one';\n\nlet chapter3Extend = Base =>\n    class extends Base {\n        computeIdxAndSave(hashCode, len) {\n            this.idx = this.computeIdx(hashCode, len);\n            this.addBP('compute-idx');\n        }\n\n        nextIdxAndSave() {\n            this.idx = (this.idx + 1) % this.self.get('slots').size;\n            this.addBP('next-idx');\n        }\n    };\n\nclass HashClassSetItem extends chapter3Extend(HashClassSetItemBase) {}\nclass HashClassLookdict extends chapter3Extend(HashClassLookdictBase) {}\nclass HashClassResize extends chapter3Extend(HashClassResizeBase) {}\n\nexport class AlmostPythonDict {\n    static __init__(pairs) {\n        const ie = new HashClassInitEmpty();\n        ie.setExtraBpContext({pairs});\n        let pySelf = ie.run(null, pairs.length);\n        let bp = ie.getBreakpoints();\n\n        if (pairs && pairs.length > 0) {\n            const ia = new HashClassInsertAll();\n            pySelf = ia.run(pySelf, pairs, false, HashClassSetItem, HashClassResize, 2);\n            bp = [...bp, ...ia.getBreakpoints()];\n            const resizes = ia.getResizes();\n\n            return {pySelf, resizes: resizes, bp: bp};\n        } else {\n            return {pySelf, resizes: [], bp: bp};\n        }\n    }\n\n    static __delitem__(pySelf, key) {\n        const di = new HashClassDelItem();\n        pySelf = di.run(pySelf, key, HashClassLookdict);\n        const bp = di.getBreakpoints();\n        const isException = bp[bp.length - 1].point !== 'replace-value-empty';\n\n        return {bp, pySelf, isException};\n    }\n\n    static __getitem__(pySelf, key) {\n        const gi = new HashClassGetItem();\n        const result = gi.run(pySelf, key, HashClassLookdict);\n        const bp = gi.getBreakpoints();\n        const isException = bp[bp.length - 1].point !== 'return-value';\n\n        return {bp, isException, result, pySelf};\n    }\n\n    static __setitem__base(pySelf, key, value, isRecycling) {\n        let si = new HashClassSetItem();\n        pySelf = si.run(pySelf, key, value, isRecycling, HashClassResize, 2);\n        const bp = si.getBreakpoints();\n        const resize = si.getResize();\n        return {bp, pySelf, resize};\n    }\n\n    static __setitem__recycling(pySelf, key, value) {\n        return AlmostPythonDict.__setitem__base(pySelf, key, value, true);\n    }\n\n    static __setitem__no_recycling(pySelf, key, value) {\n        return AlmostPythonDict.__setitem__base(pySelf, key, value, false);\n    }\n}\n\nfunction formatHashClassChapter3IdxRelatedBp(bp, prevBp) {\n    switch (bp.point) {\n        case 'compute-hash':\n            return `Compute the hash code: <code>${bp.hashCode}</code>`;\n        case 'compute-idx':\n            return `Compute the starting slot index: <code>${bp.idx}</code> == <code>${bp.hashCode} % ${\n                bp.self.get('slots').size\n            }</code>`;\n        case 'next-idx':\n            return `Keep probing, the next slot will be <code>${bp.idx}</code> == <code>(${prevBp.idx} + 1) % ${\n                bp.self.get('slots').size\n            }</code>`;\n    }\n}\n\nexport const HASH_CLASS_SETITEM_SIMPLIFIED_CODE = [\n    ['def __setitem__(self, key, value):', 'setitem-def', 0],\n    ['    hash_code = hash(key)', 'compute-hash', 1],\n    ['    idx = hash_code % len(self.slots)', 'compute-idx', 1],\n    ['    while self.slots[idx].key is not EMPTY:', 'check-collision', 2],\n    ['        if self.slots[idx].hash_code == hash_code and\\\\', 'check-dup-hash', 2],\n    ['           self.slots[idx].key == key:', 'check-dup-key', 2],\n    ['            break', 'check-dup-break', 2],\n    ['        idx = (idx + 1) % len(self.slots)', 'next-idx', 2],\n    ['', ''],\n    ['    if self.slots[idx].key is EMPTY:', 'check-used-fill-increased', 1],\n    ['        self.used += 1', 'inc-used', 1],\n    ['        self.fill += 1', 'inc-fill', 1],\n    ['', ''],\n    ['    self.slots[idx] = Slot(hash_code, key, value)', 'assign-slot', 1],\n    ['    if self.fill * 3 >= len(self.slots) * 2:', 'check-resize', 1],\n    ['        self.resize()', 'resize', 1],\n    ['', 'done-no-return', 1],\n];\n\nexport const HASH_CLASS_INIT_CODE = [\n    ['def __init__(self, pairs=None):', 'start-execution', 0],\n    ['    self.slots = [Slot() for _ in range(8)]', 'init-slots', 0],\n    ['    self.fill = 0', 'init-fill', 0],\n    ['    self.used = 0', 'init-used', 0],\n    ['    if pairs:', 'check-pairs', 0],\n    ['        for k, v in pairs:', 'for-pairs', 1],\n    ['            self[k] = v', 'run-setitem', 1],\n    ['', ''],\n];\n\nconst HASH_CLASS_SETITEM_SIMPLIFIED_WITH_INIT_CODE = [...HASH_CLASS_INIT_CODE, ...HASH_CLASS_SETITEM_SIMPLIFIED_CODE];\n\nexport const HASH_CLASS_SETITEM_RECYCLING_CODE = [\n    ['def __setitem__(self, key, value):', 'start-execution-setitem', 0],\n    ['    hash_code = hash(key)', 'compute-hash', 1],\n    ['    idx = hash_code % len(self.slots)', 'compute-idx', 1],\n    ['    target_idx = None', 'target-idx-none', 1],\n    ['    while self.slots[idx].key is not EMPTY:', 'check-collision', 2],\n    ['        if self.slots[idx].hash_code == hash_code and\\\\', 'check-dup-hash', 2],\n    ['           self.slots[idx].key == key:', 'check-dup-key', 2],\n    ['            target_idx = idx', 'set-target-idx-found', 2],\n    ['            break', 'check-dup-break', 2],\n    ['        if target_idx is None and \\\\', 'check-should-recycle-target-idx', 2],\n    ['           self.slots[idx].key is DUMMY:', 'check-should-recycle-dummy', 2],\n    ['            target_idx = idx', 'set-target-idx-recycle', 2],\n    ['        idx = (idx + 1) % len(self.slots)', 'next-idx', 2],\n    ['', ''],\n    ['    if target_idx is None:', 'check-target-idx-is-none', 1],\n    ['        target_idx = idx', 'after-probing-assign-target-idx', 1],\n    ['    if self.slots[target_idx].key is EMPTY:', 'check-used-fill-increased', 1],\n    ['        self.used += 1', 'inc-used', 1],\n    ['        self.fill += 1', 'inc-fill', 1],\n    ['    elif self.slots[target_idx].key is DUMMY:', 'check-recycle-used-increased', 1],\n    ['        self.used += 1', 'inc-used-2', 1],\n    ['', ''],\n    ['    self.slots[target_idx] = Slot(hash_code, key, value)', 'assign-slot', 1],\n    ['    if self.fill * 3 >= len(self.slots) * 2:', 'check-resize', 1],\n    ['        self.resize()', 'resize', 1],\n    ['', 'done-no-return', 1],\n];\n\nexport const HASH_CLASS_RESIZE_CODE = [\n    ['def resize(self):', 'start-execution', 0],\n    ['    old_slots = self.slots', 'assign-old-slots', 1],\n    ['    new_size = self.find_closest_size(self.used * 2)', 'compute-new-size', 1],\n    ['    self.slots = [Slot() for _ in range(new_size)]', 'new-empty-slots', 1],\n    ['    self.fill = self.used', 'assign-fill', 1],\n    ['    for slot in old_slots:', 'for-loop', 2],\n    ['        if slot.key is not EMPTY and slot.key is not DUMMY:', 'check-skip-empty-dummy', 2],\n    ['            idx = slot.hash_code % len(self.slots)', 'compute-idx', 2],\n    ['            while self.slots[idx].key is not EMPTY:', 'check-collision', 3],\n    ['                idx = (idx + 1) % len(self.slots)', 'next-idx', 3],\n    ['', ''],\n    ['            self.slots[idx] = Slot(slot.hash_code, slot.key, slot.value)', 'assign-slot', 2],\n    ['', 'done-no-return', 0],\n];\n\nexport const HASH_CLASS_LOOKDICT = [\n    ['def lookdict(self, key):', 'start-execution-lookdict', 0],\n    ['    hash_code = hash(key)', 'compute-hash', 1],\n    ['    idx = hash_code % len(self.slots)', 'compute-idx', 1],\n    ['    while self.slots[idx].key is not EMPTY:', 'check-not-found', 2],\n    ['        if self.slots[idx].hash_code == hash_code and \\\\', 'check-hash', 2],\n    ['           self.slots[idx].key == key:', 'check-key', 2],\n    ['            return idx', 'return-idx', 3],\n    ['', ''],\n    ['        idx = (idx + 1) % len(self.slots)', 'next-idx', 2],\n    ['', ''],\n    ['    raise KeyError()', 'raise', 1],\n    ['', ''],\n];\n\nexport const _HASH_CLASS_GETITEM_ONLY = [\n    ['def __getitem__(self, key):', 'start-execution-getitem', 0],\n    ['    idx = self.lookdict(key)', 'call-lookdict', 1],\n    ['', ''],\n    ['    return self.slots[idx].value', 'return-value', 1],\n];\n\nconst HASH_CLASS_GETITEM = [...HASH_CLASS_LOOKDICT, ..._HASH_CLASS_GETITEM_ONLY];\n\nexport const _HASH_CLASS_DELITEM_ONLY = [\n    ['def __delitem__(self, key):', 'start-execution-delitem', 0],\n    ['    idx = self.lookdict(key)', 'call-lookdict', 1],\n    ['', ''],\n    ['    self.used -= 1', 'dec-used', 1],\n    ['    self.slots[idx].key = DUMMY', 'replace-key-dummy', 1],\n    ['    self.slots[idx].value = EMPTY', 'replace-value-empty', 1],\n];\n\nconst HASH_CLASS_DELITEM = [...HASH_CLASS_LOOKDICT, ..._HASH_CLASS_DELITEM_ONLY];\n\nexport const FIND_NEAREST_SIZE_CODE_STRING = `def find_closest_size(self, minused):\n    new_size = 8\n    while new_size <= minused:\n        new_size *= 2\n\n    return new_size`;\n\nexport const SLOT_CLASS_CODE_STRING = `class Slot(object):\n    def __init__(self, hash_code=EMPTY, key=EMPTY, value=EMPTY):\n        self.hash_code = hash_code\n        self.key = key\n        self.value = value\n`;\n\nfunction DynamicPartResize({extraPairs, resize, pairsCount, resizesCount}) {\n    let text;\n\n    let p;\n    if (extraPairs === null) {\n        p = (\n            <p className=\"dynamic-p\" key={`resize-${resizesCount}`}>\n                While the original items were being inserted, {resizesCount === 1 ? 'a' : resizesCount}{' '}\n                {singularOrPlural(resizesCount, 'resize', 'resizes')} happened. Let's look at{' '}\n                {resizesCount === 1 ? 'it' : 'the first resize'} in depth:\n            </p>\n        );\n    } else {\n        p = (\n            <p className=\"dynamic-p\" key={`no-resize-${extraPairs.length}-${JSON.stringify(extraPairs)}`}>\n                While building the hash table from the original pairs, no resize happened, because the number of pairs\n                is too low (<code>{pairsCount}</code>), and we need at least 6 to trigger a resize. So, for this\n                specific visualization only, let's add {extraPairs.length} item{extraPairs.length === 1 ? '' : 's'} to\n                the table : <code>{formatExtraPairs(extraPairs)}</code>\n            </p>\n        );\n    }\n\n    return <DynamicP>{p}</DynamicP>;\n}\n\nfunction DynamicPartSetItemRecycling({hasDummy, outcome, otherOutcomes, handleUpdateRemovedAndInsert}) {\n    const tryIt = onClick => (\n        <button type=\"button\" className=\"btn btn-primary btn-sm\" onClick={onClick}>\n            Try it\n        </button>\n    );\n    console.log('DynamicPartResize', hasDummy, outcome, otherOutcomes);\n    const singleOtherOutcome = Object.keys(otherOutcomes)[0];\n    const inserted = otherOutcomes[singleOtherOutcome].inserted;\n    const removed = otherOutcomes[singleOtherOutcome].removed;\n    let p;\n\n    if (hasDummy) {\n        if (outcome === 'recycled') {\n            // TODO: validate?\n            p = (\n                <p\n                    className=\"dynamic-p\"\n                    key={`has-dummy-recycled-${displayStr(inserted.key)}-${displayStr(inserted.value)}}`}\n                >\n                    After we inserted it, a <code>DUMMY</code> slot got recycled &mdash; as expected. However, when\n                    there is no <code>DUMMY</code> slot encountered, this version of <code>__setitem__</code> would work\n                    exactly like the previous one. For example, if we instead tried to insert an item with a key{' '}\n                    <code>{displayStr(inserted.key)}</code>, no dummy slot would get recycled, and the item would be\n                    inserted in an empty slot. {tryIt(() => handleUpdateRemovedAndInsert(inserted))}\n                </p>\n            );\n        } else {\n            p = (\n                <p\n                    className=\"dynamic-p\"\n                    key={`has-dummy-no-recycle-${displayStr(inserted.key)}-${displayStr(inserted.value)}`}\n                >\n                    While it was being inserted, no <code>DUMMY</code> slot was encountered, so, consequently, no{' '}\n                    <code>DUMMY</code> slot got recycled. So this version of <code>__setitem__</code> worked just like\n                    the previous one. But if we instead tried to insert an item with the key{' '}\n                    <code>{displayStr(inserted.key)}</code>, a <code>DUMMY</code> slot would get recycled.\n                    {tryIt(() => handleUpdateRemovedAndInsert(inserted))}\n                </p>\n            );\n        }\n    } else {\n        // TODO: expecting recycled here\n        if (inserted) {\n            p = (\n                <p\n                    className=\"dynamic-p\"\n                    key={`no-dummy-insert-remove-${displayStr(removed.key)}-${displayStr(inserted.key)}-${displayStr(\n                        inserted.value\n                    )}`}\n                >\n                    No <code>DUMMY</code> slot was originally removed, so, consequently, no <code>DUMMY</code> slot\n                    could get recycled. This version of <code>__setitem__</code> worked just like the previous one. But\n                    if we instead tried to remove the key <code>{displayStr(removed.key)}</code> and then insert an item\n                    with the key <code>{displayStr(inserted.key)}</code>, a <code>DUMMY</code> slot would appear and\n                    then get recycled.\n                    {tryIt(() => handleUpdateRemovedAndInsert(inserted, removed))}\n                </p>\n            );\n        } else {\n            p = (\n                <p className=\"dynamic-p\" key={`no-dummy-only-remove-${displayStr(removed.key)}}`}>\n                    No <code>DUMMY</code> slot got removed, so, consequently, no <code>DUMMY</code> slot got recycled.\n                    This version of <code>__setitem__</code> worked just like the previous one. But if we instead tried\n                    to remove the key <code>{displayStr(removed.key)}</code> a recyclable <code>DUMMY</code> slot would\n                    appear.\n                    {tryIt(() => handleUpdateRemovedAndInsert(null, removed))}\n                </p>\n            );\n        }\n    }\n\n    return <DynamicP>{p}</DynamicP>;\n}\n\nclass FindClosestSizeExample extends React.PureComponent {\n    MAX_MEM = BigNumber(2).pow(64);\n    constructor() {\n        super();\n        const used = BigNumber(11);\n        this.state = {\n            used: used,\n            usedDouble: used.times(2),\n            res: BigNumber(32),\n        };\n    }\n\n    handleInputChange = used => {\n        const usedDouble = used.times(2);\n        this.setState({\n            used,\n            usedDouble,\n            res: findClosestSizeBN(usedDouble),\n        });\n    };\n\n    render() {\n        return (\n            <div>\n                <code className=\"text-nowrap\">find_closest_size(2 * self.used)</code> =={' '}\n                <code className=\"text-nowrap\">find_closest_size(2 * </code>\n                <PyNumberInput inline={true} value={this.state.used} onChange={this.handleInputChange} />\n                <code>)</code> == <code>find_closest_size({displayStr(this.state.usedDouble)})</code> =={' '}\n                <code>\n                    {this.state.res.lt(this.MAX_MEM) ? displayStr(this.state.res) : 'no way you have that much memory'}\n                </code>\n            </div>\n        );\n    }\n}\n\nexport class Chapter3_HashClass extends ChapterComponent {\n    constructor() {\n        super();\n\n        this.state = {\n            pairs: DEFAULT_STATE.pairs,\n            keyToDel: 'du',\n            keyToDelIdHack: 1, // this is to connect (mirror) two inputs together\n            keyToGet: 'uniq',\n            keyToSetRecycling: 'recycling',\n            keyToSetRecyclingIdHack: 1,\n            valueToSetRecycling: 499,\n            valueToSetRecyclingIdHack: 1,\n        };\n    }\n\n    runCreateNew = memoizeOne(pairs => {\n        const {bp, resizes, pySelf} = AlmostPythonDict.__init__(pairs);\n        return {bp, pySelf, resizes};\n    });\n\n    runDelItem = memoizeOne((pySelf, key) => {\n        const {bp, pySelf: newPySelf, isException} = AlmostPythonDict.__delitem__(pySelf, key);\n        return {bp, pySelf: newPySelf, isException};\n    });\n\n    runGetItem = memoizeOne((pySelf, key) => {\n        const {bp} = AlmostPythonDict.__getitem__(pySelf, key);\n        return {bp};\n    });\n\n    selectOrCreateResize = memoizeOne((pySelf, resizes) => {\n        return selectOrCreateResize(\n            pySelf,\n            resizes,\n            AlmostPythonDict.__getitem__,\n            AlmostPythonDict.__setitem__no_recycling\n        );\n    });\n\n    // TODO: 'value' is boring\n    runSetItemRecyclingAndGetVariations = memoizeOne((originalPySelf, originalKey, originalValue) => {\n        const slots = originalPySelf.get('slots');\n\n        const hasDummy = originalPySelf.get('fill') !== originalPySelf.get('used');\n        let outcome;\n        let otherOutcomes = {};\n        const {bp, pySelf: newPySelf, resize} = AlmostPythonDict.__setitem__recycling(\n            originalPySelf,\n            originalKey,\n            originalValue\n        );\n\n        if (hasDummy) {\n            if (!resize && newPySelf.get('fill') === newPySelf.get('used')) {\n                outcome = 'recycled';\n            } else if (resize) {\n                outcome = 'missed_resized';\n            } else {\n                outcome = 'missed';\n            }\n\n            if (outcome === 'recycled') {\n                // TODO: there might be a better way of generating this\n                while (true) {\n                    const key = generateNonPresentKey(originalPySelf, AlmostPythonDict.__getitem__);\n                    const value = 'value';\n\n                    const {pySelf: varNewPySelf, resize: newResize} = AlmostPythonDict.__setitem__recycling(\n                        originalPySelf,\n                        key,\n                        value\n                    );\n                    const noRecycleOccured = newResize || varNewPySelf.get('fill') > originalPySelf.get('fill');\n                    if (noRecycleOccured) {\n                        const singleOtherOutcome = newResize ? 'missed' : 'missed_resized';\n                        otherOutcomes[singleOtherOutcome] = {inserted: {key, value}};\n                        break;\n                    }\n                }\n            } else {\n                let clusterStart = 0;\n                for (let i = 0; i < slots.size; ++i) {\n                    const curKey = slots.get(i).key;\n                    if (curKey === null) {\n                        clusterStart = i + 1;\n                    }\n                    if (curKey === DUMMY) {\n                        break;\n                    }\n                }\n\n                const value = 'value';\n                let key;\n                do {\n                    key = generateNonPresentKey(originalPySelf, AlmostPythonDict.__getitem__);\n                } while (computeIdx(pyHash(key), slots.size) != clusterStart);\n                const singleOtherOutcome = 'recycled';\n                otherOutcomes[singleOtherOutcome] = {inserted: {key, value}};\n            }\n        } else {\n            outcome = resize ? 'missed_resized' : 'missed';\n            const originalKeyIdx = computeIdx(pyHash(originalKey), slots.size);\n            const hasOriginalCollision = slots.get(originalKeyIdx).key != null;\n            let clusterStart = slots.get(0).key == null ? null : 0;\n            let bestClusterStart = null;\n            let bestClusterEnd = null;\n            let isCluster = false;\n            let idxToRemove = null;\n            for (let i = 0; i < slots.size; ++i) {\n                const curKey = slots.get(i).key;\n                if (curKey == null) {\n                    if (!hasOriginalCollision) {\n                        if (\n                            isCluster &&\n                            (bestClusterStart == null || bestClusterEnd - bestClusterStart < clusterStart - i - 1)\n                        ) {\n                            bestClusterStart = clusterStart;\n                            bestClusterEnd = i - 1;\n                            // Would look a bit better if not the last one is removed\n                            idxToRemove = clusterStart === i - 1 ? clusterStart : i - 2;\n                        }\n                    } else {\n                        if (clusterStart != null && clusterStart <= originalKeyIdx && originalKeyIdx < i) {\n                            // Try to introduce some collisions\n                            idxToRemove = originalKeyIdx === i - 1 ? originalKeyIdx : i - 2;\n                        }\n                    }\n                    clusterStart = null;\n                    isCluster = false;\n                } else {\n                    if (clusterStart === null) {\n                        clusterStart = i;\n                    }\n                    isCluster = true;\n                }\n            }\n\n            if (hasOriginalCollision) {\n                const singleOtherOutcome = 'recycled';\n                otherOutcomes[singleOtherOutcome] = {removed: {key: slots.get(idxToRemove).key}};\n            } else {\n                const singleOtherOutcome = 'recycled';\n                let key;\n                do {\n                    key = generateNonPresentKey(originalPySelf, AlmostPythonDict.__getitem__);\n                } while (computeIdx(pyHash(key), slots.size) != bestClusterStart);\n\n                otherOutcomes[singleOtherOutcome] = {\n                    removed: {key: slots.get(idxToRemove).key},\n                    inserted: {key, value: 'value'},\n                };\n            }\n        }\n\n        return {bp, outcome, otherOutcomes, hasDummy, pySelf: newPySelf};\n    });\n\n    handleUpdateRemovedAndInsert = (inserted, removed) => {\n        let newState = {};\n\n        if (inserted) {\n            newState.keyToSetRecycling = inserted.key;\n            newState.keyToSetRecyclingIdHack = this.state.keyToSetRecyclingIdHack + 1;\n            newState.valueToSetRecycling = inserted.value;\n            newState.valueToSetRecyclingIdHack = this.state.valueToSetRecyclingIdHack + 1;\n        }\n\n        if (removed) {\n            newState.keyToDel = removed.key;\n            newState.keyToDelIdHack = this.state.keyToDelIdHack + 1;\n        }\n\n        console.log('handleUpdateRemovedAndInsert', newState);\n        this.setState(newState);\n    };\n\n    render() {\n        const t1 = performance.now();\n        let newRes = this.runCreateNew(this.state.pairs);\n        let pySelf = newRes.pySelf;\n\n        let resizeRes = this.selectOrCreateResize(newRes.pySelf, newRes.resizes);\n\n        let delRes = this.runDelItem(pySelf, this.state.keyToDel);\n        pySelf = delRes.pySelf;\n\n        let getRes = this.runGetItem(pySelf, this.state.keyToGet);\n\n        let recyclingRes = this.runSetItemRecyclingAndGetVariations(\n            pySelf,\n            this.state.keyToSetRecycling,\n            this.state.valueToSetRecycling\n        );\n        pySelf = recyclingRes.pySelf;\n        console.log('Chapter3 render timing', performance.now() - t1);\n\n        return (\n            <div className=\"chapter chapter3\">\n                <h2> Chapter 3. Putting it all together to make an \"almost\"-Python-dict</h2>\n                <Subcontainerize>\n                    <p>\n                        In this chapter, we'll make a class that supports the basic interface of a Python dict. The\n                        class will keep track of the counters necessary for computing the load factor and auto-resize\n                        the hash table. On the inside, it'll work differently from Python dict, and we'll discuss these\n                        differences in the next chapter.\n                    </p>\n                    <p>\n                        The almost-Python-dict class needs to support values. To handle values we could add another list\n                        (in addition to <code>hash_codes</code> and <code>keys</code>\n                        ). This would totally work. Another alternative is to bundle <code>hash_code</code>,{' '}\n                        <code>key</code>, <code>value</code> corresponding to each slot in a single object. To do this,\n                        we'll need to create a class:\n                    </p>\n                    <SimpleCodeBlock>{SLOT_CLASS_CODE_STRING}</SimpleCodeBlock>\n                    <p>\n                        How do we initialize an empty hash table? In previous chapters, we based the initial size of\n                        hash tables on the original list. Since we now know how to resize tables, we can start with an\n                        empty table and grow it. Hash tables inside Python dictionaries are size 8 when they are empty,\n                        so let's use 8 as the initial size.\n                    </p>\n                    <p>\n                        Python hash table sizes are powers of 2, so we will also use powers of 2. even though nothing\n                        prevents us from using \"non-round\" values. The primary reason Python uses \"round\" powers of 2 is\n                        efficiency: computing <code>% 2**n</code> can be implemented using bit operations (\n                        <code>{'& (1 << n)'}</code>\n                        ).\n                    </p>\n                    <p>Here is the overview of the interface of the class:</p>\n                    <SimpleCodeBlock>\n                        {`class AlmostDict(object):\n    def __init__(self, pairs=None):\n        self.slots = [Slot() for _ in range(8)]\n        self.fill = 0\n        self.used = 0\n        # Insert all initial pairs\n        if pairs:\n            for k, v in pairs:\n                # This is syntactic sugar for self.__setitem__(k, v)\n                self[k] = v\n\n    def __setitem__(self, key, value):\n        # Allows us to set a value in a dict-like fashion\n        # d = Dict()\n        # d[1] = 2 and d.__setitem__(1, 2) gets called\n        <implementation goes here>\n\n    def __getitem__(self, key):\n        # Allows us to get a value from a dict, for example:\n        # d = Dict()\n        # d[1] = 2\n        # d[1] calls d.__getitem__(1) and returns 2\n        <implementation goes here>\n\n    def __delitem__(self, key):\n        # Allows us to use \"del\" in a dict-like fashion, for example:\n        # d = Dict()\n        # d[1] = 2\n        # del d[1] calls d.__delitem__(1)\n        # d[1] or d.__getitem__(1) raises KeyError now\n        <implementation goes here>\n`}\n                    </SimpleCodeBlock>\n                    <p>\n                        <code>__setitem__</code>, <code>__getitem__</code> and <code>__delitem__</code> are special\n                        methods, often called{' '}\n                        <a\n                            href=\"https://docs.python.org/3/reference/datamodel.html#special-method-names\"\n                            target=\"_blank\"\n                        >\n                            magic methods\n                        </a>\n                        . They are called this way because you don't have to invoke them directly: Python does it for\n                        you behind the scenes, when you use square brackets (e.g. <code>d[1]</code>). Other than that,\n                        they are normal methods.{' '}\n                    </p>\n                    <p>\n                        Some methods are going to update <code>fill</code> (the number of non-empty slots including\n                        dummy slots) and <code>used</code> (the number of normal items in the dictionary, same as the\n                        dictionary size). Fill factor is the ratio between <code>fill</code> and <code>len(slots)</code>\n                        . When it reaches 2/3, the table gets resized. The new size is based on the number of normal\n                        items in the table, which is <code>self.used</code>, and typically it is 2x of original size.\n                    </p>\n                    <p>\n                        Let's take a look at the code, starting with the <code>__init__</code> method. We're creating\n                        the dict from the following pairs:\n                    </p>\n                    <BlockInputToolbar\n                        input={PyDictInput}\n                        inputProps={{\n                            minSize: 1,\n                        }}\n                        initialValue={this.state.pairs}\n                        onChange={this.setter('pairs', true)}\n                        bottomBoundary=\".chapter3\"\n                        {...this.props}\n                    />\n                    <p>\n                        The code in <code>__init__</code> also assumes that the dict contents are passed as a list of\n                        pairs (not an actual dict &mdash; which we are reimplementing).\n                    </p>\n                    <p>Compared to the previous chapter, the differences in building hash tables are:</p>\n                    <ul>\n                        <li>\n                            inserting an individual item is now in a dedicated <code>__setitem__</code> method;\n                        </li>\n                        <li>\n                            The <code>fill</code> and <code>used</code> counters are incremented if necessary;\n                        </li>\n                        <li>\n                            most importantly, <code>resize()</code> gets called after inserting an element if the fill\n                            factor gets too high.\n                        </li>\n                    </ul>\n                    <VisualizedCode\n                        code={HASH_CLASS_SETITEM_SIMPLIFIED_WITH_INIT_CODE}\n                        breakpoints={newRes.bp}\n                        formatBpDesc={[\n                            formatHashClassInit,\n                            formatHashClassSetItemAndCreate,\n                            formatHashClassChapter3IdxRelatedBp,\n                        ]}\n                        stateVisualization={HashClassInsertAllVisualization}\n                        {...this.props}\n                    />{' '}\n                    <p>\n                        When resizing a hash table, how do we find a new optimal size? We find a valid table size\n                        greater than <code className=\"text-nowrap\">2 * self.used</code>. A valid hash table size is a\n                        power of two which is at least 8.\n                    </p>\n                    <SimpleCodeBlock>{FIND_NEAREST_SIZE_CODE_STRING}</SimpleCodeBlock>\n                    <p>\n                        The code only uses <code>self.used</code> (the number of \"useful\" non-empty slots). It does not\n                        depend on <code>self.fill</code> (the total number of non-empty slots) in any way. The idea is\n                        to double the size of the table if there are very few dummy slots and shrink it if there are too\n                        many dummy slots (so that the memory is saved).\n                    </p>\n                    <FindClosestSizeExample />\n                    <DynamicPartResize {...resizeRes} pairsCount={this.state.pairs.length} />\n                    <VisualizedCode\n                        code={HASH_CLASS_RESIZE_CODE}\n                        breakpoints={resizeRes.bp}\n                        formatBpDesc={[formatHashClassResize, formatHashClassChapter3IdxRelatedBp]}\n                        stateVisualization={HashClassResizeVisualization}\n                        {...this.props}\n                    />\n                    <p>\n                        In the previous chapter, the code for removing and the code for searching were very similar,\n                        because, to remove an element, we need to find it first. We can reorganize the code so that the\n                        removing and searching functions share much of the same code. The common function's name will be{' '}\n                        <code>lookdict()</code>.\n                    </p>\n                    <p>\n                        Other than that, removing a key will look pretty much the same as in the previous chapter.{' '}\n                        <code>__delitem__</code> magic method is now used for realism so that we can do{' '}\n                        <code className=\"text-nowrap\">del almost_dict[42]</code>. And we decrement the{' '}\n                        <code>self.used</code> counter if we end up finding the element and removing it.\n                    </p>\n                    <div className=\"div-p\">\n                        For example, let's say we want to remove\n                        <PySNNInput\n                            inline={true}\n                            value={this.state.keyToDel}\n                            valueId={this.state.keyToDelIdHack}\n                            onChange={this.setter('keyToDel', false, true)}\n                            anotherValue={() => anotherKey(this.state.pairs)}\n                        />\n                    </div>\n                    <VisualizedCode\n                        code={HASH_CLASS_DELITEM}\n                        breakpoints={delRes.bp}\n                        formatBpDesc={[formatHashClassLookdictRelated, formatHashClassChapter3IdxRelatedBp]}\n                        stateVisualization={HashClassNormalStateVisualization}\n                        {...this.props}\n                    />\n                    <p>\n                        After using the new <code>lookdict()</code> function, search function <code>__getitem__</code>{' '}\n                        also gets very short.\n                    </p>\n                    <div className=\"div-p\">\n                        Searching for\n                        <PySNNInput\n                            inline={true}\n                            value={this.state.keyToGet}\n                            onChange={this.setter('keyToGet')}\n                            anotherValue={() => anotherKey(this.state.pairs)}\n                        />\n                    </div>\n                    <VisualizedCode\n                        code={HASH_CLASS_GETITEM}\n                        breakpoints={getRes.bp}\n                        formatBpDesc={[formatHashClassLookdictRelated, formatHashClassChapter3IdxRelatedBp]}\n                        stateVisualization={HashClassNormalStateVisualization}\n                        {...this.props}\n                    />\n                    <p>\n                        So we now have a class that emulates the basic part of the dict interface. Before we move on to\n                        the next chapter, let's discuss a neat trick for inserting new items.\n                    </p>\n                    <h5> Recycling dummy keys. </h5>\n                    <p>\n                        Dummy keys are used as placeholders. The only purpose of a dummy slot is to prevent a probing\n                        algorithm from breaking. The algorithm will work as long as the \"deleted\" slot is occupied by\n                        something, be it a dummy placeholder or a normal item.{' '}\n                    </p>\n                    <p>\n                        {' '}\n                        This means that while inserting an item, if we end up hitting a dummy slot, we can put the item\n                        in that slot (if the key does isn't already inserted).{' '}\n                    </p>\n                    <p>\n                        So, we still need to do a full look up, but we will also save an index of the first dummy slot\n                        to <code>target_idx</code> (if we encounter it). If we find that a key already exists, we save\n                        the index to <code>target_idx</code> and break. If we find neither a dummy slot nor the key,\n                        then we insert it in the first empty slot - as we did before.\n                    </p>\n                    <p>\n                        In the absence of dummy slots, the code works the same. So, even though we built the table with\n                        a simpler version of <code>__setitem__</code>, it would look exactly the same as if we built it\n                        applying this optimization.\n                    </p>\n                    <div className=\"div-p\">\n                        Remember that we removed the key\n                        <PySNNInput\n                            inline={true}\n                            value={this.state.keyToDel}\n                            valueId={this.state.keyToDelIdHack}\n                            onChange={this.setter('keyToDel', false, true)}\n                            anotherValue={() => anotherKey(this.state.pairs)}\n                        />\n                        ?\n                    </div>\n                    <div className=\"div-p\">\n                        Now, what happens after if we insert another item to the modified hash table:\n                        <br />\n                        <div style={{minWidth: 65}} className=\"inline-block mb-3 mt-3\">\n                            key =\n                        </div>\n                        <PySNNInput\n                            inline={true}\n                            value={this.state.keyToSetRecycling}\n                            valueId={this.state.keyToSetRecyclingIdHack}\n                            onChange={this.setter('keyToSetRecycling', false, true)}\n                            anotherValue={() => anotherKey(this.state.pairs, 0.2, 0.5, 0.2)}\n                        />\n                        <br />\n                        <div style={{minWidth: 65}} className=\"inline-block\">\n                            value ={' '}\n                        </div>\n                        <PySNNInput\n                            inline={true}\n                            value={this.state.valueToSetRecycling}\n                            valueId={this.state.valueToSetRecyclingIdHack}\n                            onChange={this.setter('valueToSetRecycling', false, true)}\n                        />\n                    </div>\n                    <VisualizedCode\n                        code={HASH_CLASS_SETITEM_RECYCLING_CODE}\n                        breakpoints={recyclingRes.bp}\n                        formatBpDesc={[formatHashClassSetItemAndCreate, formatHashClassChapter3IdxRelatedBp]}\n                        stateVisualization={HashClassNormalStateVisualization}\n                        {...this.props}\n                    />\n                    <DynamicPartSetItemRecycling\n                        {...recyclingRes}\n                        handleUpdateRemovedAndInsert={this.handleUpdateRemovedAndInsert}\n                    />\n                </Subcontainerize>\n            </div>\n        );\n    }\n}\n"
  },
  {
    "path": "src/chapter4_real_python_dict.js",
    "content": "import * as React from 'react';\n\nimport {BigNumber} from 'bignumber.js';\nimport {\n    hashClassConstructor,\n    HashClassInitEmpty,\n    HashClassResizeBase,\n    HashClassSetItemBase,\n    HashClassDelItem,\n    HashClassGetItem,\n    HashClassLookdictBase,\n    HashClassInsertAll,\n    HashClassNormalStateVisualization,\n    HashClassInsertAllVisualization,\n    HashClassResizeVisualization,\n    formatHashClassSetItemAndCreate,\n    formatHashClassLookdictRelated,\n    formatHashClassResize,\n    formatHashClassInit,\n    postBpTransform,\n    findClosestSize,\n    anotherKey,\n    selectOrCreateResize,\n    formatExtraPairs,\n    DEFAULT_STATE,\n} from './chapter3_and_4_common';\nimport {computePerturb, perturbShift, nextIdxPerturb, displayStr} from './hash_impl_common';\nimport {AlmostPythonDict} from './chapter3_hash_class';\nimport {ProbingVisualization, ProbingStateVisualization, GenerateProbingLinks} from './probing_visualization';\n\nimport {VisualizedCode, TetrisFactory, HashSlotsComponent} from './code_blocks';\nimport {PyDictInput, PySNNInput, BlockInputToolbar} from './inputs';\nimport {ChapterComponent, Subcontainerize, singularOrPlural, DynamicP, DebounceWhenOutOfView} from './util';\n\nimport memoizeOne from 'memoize-one';\n\nlet chapter4Extend = Base =>\n    class extends Base {\n        computeIdxAndSave(hashCode, len) {\n            this.idx = this.computeIdx(hashCode, len);\n            this.addBP('compute-idx');\n            this.perturb = computePerturb(hashCode);\n            this.addBP('compute-perturb');\n        }\n\n        nextIdxAndSave() {\n            this.idx = nextIdxPerturb(this.idx, this.perturb, +this.self.get('slots').size);\n            this.addBP('next-idx');\n            this.perturb = perturbShift(this.perturb);\n            this.addBP('perturb-shift');\n        }\n    };\n\nconst SideBySideDictsImpl = TetrisFactory([\n    [HashSlotsComponent, [{labels: ['almost-python-dict'], marginBottom: 20}, 'almostPythonDictSlots']],\n    [HashSlotsComponent, [{labels: ['python 3.2 dict']}, 'pythonDictSlots']],\n]);\n\nclass SideBySideDicts extends React.Component {\n    static FULL_WIDTH = true;\n    static EXTRA_ERROR_BOUNDARY = true;\n\n    render() {\n        const {windowHeight, windowWidth, ...restProps} = this.props;\n        const serverSide = windowHeight == null;\n\n        return (\n            <DebounceWhenOutOfView\n                windowHeight={windowHeight}\n                childProps={restProps}\n                childFunc={(props, innerRef) => (\n                    <SideBySideDictsImpl\n                        {...props}\n                        innerRef={innerRef}\n                        windowWidth={windowWidth}\n                        windowHeight={windowHeight}\n                        overflow={serverSide}\n                    />\n                )}\n            />\n        );\n    }\n}\n\nexport {hashClassConstructor, HashClassGetItem, HashClassDelItem};\nexport class Dict32SetItem extends chapter4Extend(HashClassSetItemBase) {}\nexport class Dict32Lookdict extends chapter4Extend(HashClassLookdictBase) {}\nexport class Dict32Resize extends chapter4Extend(HashClassResizeBase) {\n    // TODO FIXME: this hack is here because I don't want to figure out how to do things properly with HashResizeBase\n    COMPUTE_MINUSED_HACKY_FLAG = true;\n}\n\nfunction formatDict32IdxRelatedBp(bp, prevBp) {\n    switch (bp.point) {\n        case 'compute-hash':\n            return `Compute the hash code: <code>${bp.hashCode}</code>`;\n        case 'compute-idx':\n            return `Compute the starting slot index: <code>${bp.hashCode} % ${\n                bp.self.get('slots').size\n            }</code> == <code>${bp.idx}</code>`;\n        case 'compute-perturb':\n            return `Compute <code>perturb</code> by converting the hash code to unsigned: <code>${bp.perturb}</code>`;\n        case 'next-idx':\n            return `Keep probing, the next slot will be <code>(${prevBp.idx} * 5 + ${bp.perturb} + 1) % ${\n                bp.self.get('slots').size\n            }</code> == <code>${bp.idx}</code>`;\n        case 'perturb-shift':\n            return `Shifting <code>perturb</code>: <code>${bp.perturb}</code> == <code>${prevBp.perturb} >> 5</code> `;\n    }\n}\n\nfunction formatPythonProbing(bp, prevBp) {\n    switch (bp.point) {\n        case 'const-perturb':\n            return `<code>PERTURB_SHIFT</code> needs to be greater than 0, set it to <code>${\n                this.PERTURB_SHIFT\n            }</code> `;\n        case 'def-probe-all':\n            return `Called with the key <code>${displayStr(bp.key)}</code>`;\n        case 'compute-hash':\n            return `Compute the hash code: <code>${bp.hashCode}</code>`;\n        case 'compute-idx':\n            return `Compute the starting slot index: <code>${bp.hashCode} % ${bp.slotsCount}</code> == <code>${\n                bp.idx\n            }</code>`;\n        case 'compute-perturb': {\n            if (bp.perturb.eq(bp.hashCode)) {\n                return `<code>perturb</code> is <code>${bp.perturb}</code>, the same as hash (because it is positive)`;\n            } else {\n                return `Compute <code>perturb</code> is <code>${\n                    bp.perturb\n                }</code>, converted from the negative hash <code>${bp.hashCode}</code>`;\n            }\n        }\n        case 'next-idx':\n            return `The next slot will be <code>${bp.idx}</code> == <code>(${prevBp.idx} * 5 + ${bp.perturb} + 1) % ${\n                bp.slotsCount\n            }</code>`;\n        case 'perturb-shift':\n            return `Shifting perturb <code>perturb</code>: <code>${prevBp.perturb} >> 5</code> == <code>${\n                bp.perturb\n            }</code>`;\n        case 'create-empty-set':\n            return 'Initialize the set of visited slots';\n        case 'visited-add':\n            return `Add slot <code>${bp.idx}</code> to the set of visited slots`;\n        case 'while-loop':\n            if (bp.visitedIdx.size === bp.slotsCount) {\n                return `all <code>${bp.visitedIdx.size}</code> / <code>${\n                    bp.slotsCount\n                }</code> slots are visited &mdash; stop`;\n            } else {\n                return `Visited <code>${bp.visitedIdx.size}</code> / <code>${\n                    bp.slotsCount\n                }</code> slots, keep looping until all are visited`;\n            }\n    }\n}\n\nexport const STATICMETHOD_SIGNED_TO_UNSIGNED = [\n    ['@staticmethod', ''],\n    ['def signed_to_unsigned(hash_code):', ''],\n    ['    if hash_code < 0:', ''],\n    ['        return 2**64 + hash_code', ''],\n    ['    else:', ''],\n    ['        return hash_code', ''],\n    ['', ''],\n];\n\nexport const DICT32_INIT = [\n    ['def __init__(self, pairs=None):', 'start-execution', 0],\n    ['    if pairs:', 'check-pairs-start-size', 0],\n    ['        start_size = self.find_closest_size(len(pairs))', 'init-start-size-pairs', 0],\n    ['    else:', '', 0],\n    ['        start_size = 8', 'init-start-size-8', 0],\n    ['    self.slots = [Slot() for _ in range(start_size)]', 'init-slots', 0],\n    ['    self.fill = 0', 'init-fill', 0],\n    ['    self.used = 0', 'init-used', 0],\n    ['    if pairs:', 'check-pairs', 0],\n    ['        for k, v in pairs:', 'for-pairs', 1],\n    ['            self[k] = v', 'run-setitem', 1],\n    ['', ''],\n];\n\nexport const DICT32_SETITEM = [\n    ['PERTURB_SHIFT = 5', '', 0],\n    ['', '', 0],\n    ['def __setitem__(self, key, value):', 'setitem-def', 0],\n    ['    hash_code = hash(key)', 'compute-hash', 1],\n    ['    idx = hash_code % len(self.slots)', 'compute-idx', 1],\n    ['    perturb = self.signed_to_unsigned(hash_code)', 'compute-perturb', 1],\n    ['    target_idx = None', 'target-idx-none', 1],\n    ['    while self.slots[idx].key is not EMPTY:', 'check-collision', 2],\n    ['        if self.slots[idx].hash_code == hash_code and\\\\', 'check-dup-hash', 2],\n    ['           self.slots[idx].key == key:', 'check-dup-key', 2],\n    ['            target_idx = idx', 'set-target-idx-found', 2],\n    ['            break', 'check-dup-break', 2],\n    ['        if target_idx is None and \\\\', 'check-should-recycle-target-idx', 2],\n    ['           self.slots[idx].key is DUMMY:', 'check-should-recycle-dummy', 2],\n    ['            target_idx = idx', 'set-target-idx-recycle', 2],\n    ['        idx = (idx * 5 + perturb + 1) % len(self.slots)', 'next-idx', 2],\n    ['        perturb >>= self.PERTURB_SHIFT', 'perturb-shift', 2],\n    ['', '', 1],\n    ['    if target_idx is None:', 'check-target-idx-is-none', 1],\n    ['        target_idx = idx', 'after-probing-assign-target-idx', 1],\n    ['    if self.slots[target_idx].key is EMPTY:', 'check-used-fill-increased', 1],\n    ['        self.used += 1', 'inc-used', 1],\n    ['        self.fill += 1', 'inc-fill', 1],\n    ['    elif self.slots[target_idx].key is DUMMY:', 'check-recycle-used-increased', 1],\n    ['        self.used += 1', 'inc-used-2', 1],\n    ['', ''],\n    ['    self.slots[target_idx] = Slot(hash_code, key, value)', 'assign-slot', 1],\n    ['    if self.fill * 3 >= len(self.slots) * 2:', 'check-resize', 1],\n    ['        self.resize()', 'resize', 1],\n    ['', 'done-no-return', 0],\n];\n\nconst DICT32_SETITEM_WITH_INIT = [...STATICMETHOD_SIGNED_TO_UNSIGNED, ...DICT32_INIT, ...DICT32_SETITEM];\n\nexport const DICT32_RESIZE_CODE = [\n    ['def resize(self):', 'start-execution', 0],\n    ['    old_slots = self.slots', 'assign-old-slots', 1],\n    ['    minused = self.used * (4 if self.used <= 50000 else 2)', 'compute-minused-32', 1],\n    ['    new_size = self.find_closest_size(minused)', 'compute-new-size', 1],\n    ['    self.slots = [Slot() for _ in range(new_size)]', 'new-empty-slots', 1],\n    ['    self.fill = self.used', 'assign-fill', 1],\n    ['    for slot in old_slots:', 'for-loop', 2],\n    ['        if slot.key is not EMPTY and slot.key is not DUMMY:', 'check-skip-empty-dummy', 2],\n    ['            idx = slot.hash_code % len(self.slots)', 'compute-idx', 2],\n    ['            perturb = self.signed_to_unsigned(slot.hash_code)', 'compute-perturb', 2],\n    ['            while self.slots[idx].key is not EMPTY:', 'check-collision', 3],\n    ['                idx = (idx * 5 + perturb + 1) % len(self.slots)', 'next-idx', 3],\n    ['                perturb >>= self.PERTURB_SHIFT', 'perturb-shift', 3],\n    ['', ''],\n    ['            self.slots[idx] = Slot(slot.hash_code, slot.key, slot.value)', 'assign-slot', 2],\n    ['', 'done-no-return'],\n];\n\nexport const DICT32_LOOKDICT = [\n    ['def lookdict(self, key):', 'start-execution-lookdict', 0],\n    ['    hash_code = hash(key)', 'compute-hash', 1],\n    ['    idx = hash_code % len(self.slots)', 'compute-idx', 1],\n    ['    perturb = self.signed_to_unsigned(hash_code)', 'compute-perturb', 1],\n    ['    while self.slots[idx].key is not EMPTY:', 'check-not-found', 2],\n    ['        if self.slots[idx].hash_code == hash_code and \\\\', 'check-hash', 2],\n    ['           self.slots[idx].key == key:', 'check-key', 2],\n    ['            return idx', 'return-idx', 3],\n    ['', ''],\n    ['        idx = (idx * 5 + perturb + 1) % len(self.slots)', 'next-idx', 2],\n    ['        perturb >>= self.PERTURB_SHIFT', 'perturb-shift', 2],\n    ['', ''],\n    ['    raise KeyError()', 'raise', 1],\n    ['', ''],\n];\n\nexport const _DICT32_GETITEM_ONLY = [\n    ['def __getitem__(self, key):', 'start-execution-getitem', 0],\n    ['    idx = self.lookdict(key)', 'call-lookdict', 1],\n    ['', ''],\n    ['    return self.slots[idx].value', 'return-value', 1],\n];\n\nconst DICT32_GETITEM = [...DICT32_LOOKDICT, ..._DICT32_GETITEM_ONLY];\n\nexport const _DICT32_DELITEM_ONLY = [\n    ['def __delitem__(self, key):', 'start-execution-delitem', 0],\n    ['    idx = self.lookdict(key)', 'call-lookdict', 1],\n    ['', ''],\n    ['    self.used -= 1', 'dec-used', 1],\n    ['    self.slots[idx].key = DUMMY', 'replace-key-dummy', 1],\n    ['    self.slots[idx].value = EMPTY', 'replace-value-empty', 1],\n];\n\nconst DICT32_DELITEM = [...DICT32_LOOKDICT, ..._DICT32_DELITEM_ONLY];\n\nexport class Dict32 {\n    static __init__(pairs) {\n        if (pairs && pairs.length >= 50000) {\n            throw new Error(\"Too many pairs, it's hard to visualize them anyway\");\n        }\n        let startSize;\n        let pairsLength;\n        if (pairs && pairs.length > 0) {\n            startSize = findClosestSize(pairs.length);\n            pairsLength = pairs.length;\n        } else {\n            startSize = 8;\n            pairsLength = 0;\n        }\n\n        const ie = new HashClassInitEmpty();\n        ie.setExtraBpContext({pairs});\n        let pySelf = ie.run(startSize, pairsLength);\n        let bp = ie.getBreakpoints();\n\n        if (pairs && pairs.length > 0) {\n            const ia = new HashClassInsertAll();\n            pySelf = ia.run(\n                pySelf,\n                pairs,\n                true,\n                Dict32SetItem,\n                Dict32Resize,\n                4 /* Depends on the dict size, but an exception is thrown anyway if the dict is too largy */\n            );\n            bp = [...bp, ...ia.getBreakpoints()];\n            const resizes = ia.getResizes();\n\n            return {resizes: resizes, bp: bp, pySelf};\n        } else {\n            return {resizes: [], bp: bp, pySelf};\n        }\n    }\n\n    static __delitem__(pySelf, key) {\n        const di = new HashClassDelItem();\n        pySelf = di.run(pySelf, key, Dict32Lookdict);\n        const bp = di.getBreakpoints();\n        const isException = bp[bp.length - 1].point !== 'replace-value-empty';\n\n        return {bp, pySelf, isException};\n    }\n\n    static __getitem__(pySelf, key) {\n        const gi = new HashClassGetItem();\n        const result = gi.run(pySelf, key, Dict32Lookdict);\n        const bp = gi.getBreakpoints();\n        const isException = bp[bp.length - 1].point !== 'return-value';\n\n        return {bp, isException, result, pySelf};\n    }\n\n    static __setitem__(pySelf, key, value) {\n        let si = new Dict32SetItem();\n        if (pySelf.get('used') >= 50000) {\n            throw new Error(\"Too much inserts, can't visualize this anyway\");\n        }\n        pySelf = si.run(\n            pySelf,\n            key,\n            value,\n            true,\n            Dict32Resize,\n            4 /* should depend on the size but an exception is throw before condition is reached */\n        );\n        const bp = si.getBreakpoints();\n        const resize = si.getResize();\n        return {bp, pySelf, resize};\n    }\n}\n\nexport const PROBING_PYTHON_CODE = [\n    ['PERTURB_SHIFT = 5', 'const-perturb', 0],\n    ['def probe_all(key, slots_count=8):', 'def-probe-all', 0],\n    ['    hash_code = hash(key)', 'compute-hash', 1],\n    ['    perturb = 2**64 + hash_code if hash_code < 0 else hash_code', 'compute-perturb', 1],\n    ['    idx = hash_code % slots_count', 'compute-idx', 1],\n    ['    visited = set()', 'create-empty-set', 1],\n    ['    while len(visited) < slots_count:', 'while-loop', 2],\n    ['        visited.add(idx)', 'visited-add', 2],\n    ['        idx = (idx * 5 + perturb + 1) % slots_count', 'next-idx', 2],\n    ['        perturb >>= PERTURB_SHIFT', 'perturb-shift', 2],\n];\n\nfunction DynamicPartResize({extraPairs, resize}) {\n    let p;\n\n    if (extraPairs === null) {\n        const fill = resize.oldSelf.get('fill');\n        const oldSize = resize.oldSelf.get('slots').size;\n        const size = resize.self.get('slots').size;\n        p = (\n            <p className=\"dynamic-p\" key={`no-extra-pairs-${fill}-${oldSize}-${size}`}>\n                During building the dict from original pairs, after inserting the first <code>{fill}</code> pairs, it\n                got resized from <code>{oldSize}</code> slots to <code>{size}</code> slots. Python tries to guess the\n                correct size of the resulting hash table inside dict, but sometimes it misses, so a resize like this can\n                happen.\n            </p>\n        );\n    } else {\n        // TODO: better formatting of pairs\n        p = (\n            <p className=\"dynamic-p\" key={`extra-pairs-${JSON.stringify(extraPairs)}`}>\n                While building the dict from the original pairs, no resize operation was run, because Python correctly\n                guessed the number of slots needed. To see a resize in action, let's insert{' '}\n                {extraPairs.length === 1 ? 'an' : 'some'} additional{' '}\n                {singularOrPlural(extraPairs.length, 'pair', 'pairs')}: <code>{formatExtraPairs(extraPairs)}</code>\n            </p>\n        );\n    }\n\n    return <DynamicP>{p}</DynamicP>;\n}\n\nexport class Chapter4_RealPythonDict extends ChapterComponent {\n    constructor() {\n        super();\n\n        this.state = {\n            pairs: DEFAULT_STATE.pairs,\n            keyToDel: 'du',\n            keyToGet: 'uniq',\n            keyForProbingVis: 'hello',\n        };\n    }\n\n    chapter3dict = memoizeOne(pairs => {\n        const {pySelf} = AlmostPythonDict.__init__(pairs);\n        return pySelf;\n    });\n\n    runCreateNew = memoizeOne(pairs => {\n        const {bp, resizes, pySelf} = Dict32.__init__(pairs);\n        return {bp, pySelf, resizes};\n    });\n\n    selectOrCreateResize = memoizeOne((pySelf, resizes) => {\n        return selectOrCreateResize(pySelf, resizes, Dict32.__getitem__, Dict32.__setitem__);\n    });\n\n    runDelItem = memoizeOne((pySelf, key) => {\n        const {bp, pySelf: newPySelf} = Dict32.__delitem__(pySelf, key);\n        return {bp, pySelf: newPySelf};\n    });\n\n    runGetItem = memoizeOne((pySelf, key) => {\n        const {bp} = Dict32.__getitem__(pySelf, key);\n        return {bp};\n    });\n\n    runProbingSimple = memoizeOne(slotsCount => {\n        let g = new GenerateProbingLinks();\n        const {links} = g.run(slotsCount, '', 'i+1');\n\n        return {\n            links,\n            bp: g.getBreakpoints(),\n        };\n    });\n\n    runProbing5iPlus1 = memoizeOne(slotsCount => {\n        let g = new GenerateProbingLinks();\n        const {links} = g.run(slotsCount, '', '5i+1');\n\n        return {\n            links,\n            bp: g.getBreakpoints(),\n        };\n    });\n\n    runProbingPython = memoizeOne((slotsCount, obj) => {\n        let g = new GenerateProbingLinks();\n        const {links} = g.run(slotsCount, obj, 'python');\n\n        return {\n            links: links.toJS(),\n            bp: g.getBreakpoints(),\n        };\n    });\n\n    render() {\n        const t1 = performance.now();\n        let newRes = this.runCreateNew(this.state.pairs);\n        let pySelf = newRes.pySelf;\n\n        let almostPythonDictSelf = this.chapter3dict(this.state.pairs);\n\n        let delRes = this.runDelItem(pySelf, this.state.keyToDel);\n        pySelf = delRes.pySelf;\n\n        let getRes = this.runGetItem(pySelf, this.state.keyToGet);\n\n        let resizeRes = this.selectOrCreateResize(pySelf, newRes.resizes);\n\n        const slotsCount = 8;\n        const probingSimple = this.runProbingSimple(slotsCount);\n        const probing5iPlus1 = this.runProbing5iPlus1(slotsCount);\n        const probingPython = this.runProbingPython(slotsCount, this.state.keyForProbingVis);\n        const isWeirdPattern =\n            BigNumber.isBigNumber(this.state.keyForProbingVis) &&\n            this.state.keyForProbingVis.lt(0) &&\n            this.state.keyForProbingVis.gt(-100000000);\n\n        console.log('Chapter4 render timing', performance.now() - t1);\n\n        return (\n            <div className=\"chapter chapter4\">\n                <h2>Chapter 4. How Python dict *really* works internally</h2>\n                <Subcontainerize>\n                    <p>Now it is (finally!) time to explore how the dict works in Python!</p>\n                    <p>\n                        This explanation is about the dict in CPython (the most popular, \"default\", implementation of\n                        Python). Most of CPython is implemented in C, and the dict implementation is written in C too.\n                        This explanations reimplements everything in Python, because the main focus of this explanation\n                        is algorithms. Nevertheless, the state of dicts on this page should match the state of dicts\n                        inside CPython 3.2. I.e. the hash() codes of keys should match, the probing result should match,\n                        and as a result the slots match as well.\n                    </p>\n                    <p>\n                        In other words, this is an actual reimplementation of Python dict in Python that mirrors all of\n                        the aspects of the underlying data structure. And the visualization might as well be the\n                        visualization of the state produced from the C code.\n                    </p>\n                    <p>\n                        Why CPython 3.2? It's a good starting point, later versions expand the implementation (rather\n                        than replace it), so this chapter focuses on CPython 3.2's dict, and future chapters will\n                        discuss changes in the later versions (3.3 - 3.7).\n                    </p>\n                    <p>\n                        The central difference between almost-Python-dict from the third chapter and real Python dicts\n                        is the probing algorithm. This probing algorithm stayed the same in all versions (at least up\n                        until 3.7, which is the latest version at the time of writing)\n                    </p>\n                    <h5>The probing algorithm</h5>\n                    <p>\n                        The problem with the simple linear probing is that it doesn't mix up the keys well in many\n                        real-world data patterns. Real world data patterns tend to be regular, and a pattern like{' '}\n                        <code>16</code>, <code>0</code>, <code>1</code>, <code>2</code>, <code>3</code>, <code>4</code>\n                        <code>...</code> would lead to many collisions.\n                    </p>\n                    <p>\n                        Linear probing is prone to clustering: once you get a \"clump\" of keys, the clump tends to grow,\n                        which causes more collisions, which cause the clump to grow further, which causes even more\n                        collisions. This is detrimental to performance.\n                    </p>\n                    <p>\n                        One way to address this problem by using a better hash function, in particular when it comes to\n                        integers (<code className=\"text-nowrap\">hash(x)</code> == <code>x</code> for small integers in\n                        Python). Another way to address this problem is by using a different probing algorithm - and\n                        this is what CPython developers decided.\n                    </p>\n                    <p>There are two requirements for a probing algorithm:</p>\n                    <ol>\n                        <li>It should be deterministic.</li>\n                        <li>\n                            It should always hit an empty slot eventually (even if it takes many steps). We need it to\n                            work even in the worst possible scenario: when there is a collision in every non-empty slot.\n                        </li>\n                    </ol>\n                    <p>\n                        Let's take a look at linear probing first. If we repeatedly run its recurrence (\n                        <code className=\"text-nowrap\">idx = (idx + 1) % size</code>) until we end up hitting a slot\n                        twice, we get the following picture:\n                    </p>\n                    <ProbingVisualization\n                        slotsCount={slotsCount}\n                        links={probingSimple.links}\n                        adjustTop={-70}\n                        fixedHeight={170}\n                        {...this.props}\n                    />\n                    <p>\n                        It does not matter what slot we start from, the picture will look exactly the same. Linear\n                        probing is very regular and predictable. Now, let's change the recurrence to{' '}\n                        <code className=\"text-nowrap\">idx = (5 * idx + 1) % size</code> (note the <code>5</code>\n                        ):\n                    </p>\n                    <ProbingVisualization\n                        slotsCount={slotsCount}\n                        links={probing5iPlus1.links}\n                        adjustTop={-40}\n                        fixedHeight={170}\n                        {...this.props}\n                    />\n                    <p>\n                        <code className=\"text-nowrap\">idx = (5 * idx + 1) % size</code> still guarantees to eventually\n                        hit every possible slot if <code>size</code> is a power of two (the proof of this fact is\n                        outside the scope of this page). Also, the algorithm is obviously deterministic. So, both\n                        requirements for a probing algorithm are satisfied. This algorithm scrambles the order of\n                        indexes a bit. It certainly less regular but it is still prone to clustering.\n                    </p>\n                    <p>\n                        The probing algorithm in CPython takes this recurrence and adds a ton of scrambling to it:{' '}\n                        <code className=\"text-nowrap\">idx = ((5 * idx) + 1 + perturb) % size</code>. What is this{' '}\n                        <code>perturb</code> weirdness though? In C code, it is initialized as basically this:{' '}\n                        <code className=\"text-nowrap\">size_t perturb = hash_code</code>. Then, in every iteration, it is\n                        right-shifted by <code>5</code> bits (<code>{'perturb >>= 5'}</code>\n                        ).\n                    </p>\n                    <p>\n                        This probing algorithm uses some \"randomness\" in the form of bits from the hash code - but the\n                        probing is still fully deterministic because hash functions by their nature are deterministic.{' '}\n                        <code>perturb</code> eventually reaches zero, and the recurrence becomes{' '}\n                        <code className=\"text-nowrap\">idx = (5 * idx) + 1</code>, which is guaranteed to hit every slot\n                        (eventually).\n                    </p>\n                    <p>\n                        We can reimplement this algorithm in pure Python. However, in Python there are no unsigned\n                        (logical) bit shifts, and there is also no built-in way to convert a 64-bit signed integer to a\n                        64-bit unsigned integer. The solution is to do the conversion with the following one-liner:{' '}\n                        <code>{'2**64 + hash_code if hash_code < 0 else hash_code'}</code> and then use regular bit\n                        shifts (i.e. <code>{`>>`}</code> or <code>{`>>=`}</code>)\n                    </p>\n                    <div className=\"div-p\">\n                        Let's see how the algorithm works for the following key:\n                        <PySNNInput\n                            inline={true}\n                            value={this.state.keyForProbingVis}\n                            onChange={this.setter('keyForProbingVis')}\n                            anotherValue={() => anotherKey(this.state.pairs)}\n                        />\n                    </div>\n                    <VisualizedCode\n                        code={PROBING_PYTHON_CODE}\n                        breakpoints={probingPython.bp}\n                        formatBpDesc={formatPythonProbing}\n                        stateVisualization={ProbingStateVisualization}\n                        keepTimeOnNewBreakpoints={true}\n                        comment={\n                            <p className=\"text-muted\">\n                                Arrows are color-coded: green means <code>perturb != 0</code> and blue means{' '}\n                                <code>perturb == 0</code>{' '}\n                                {isWeirdPattern\n                                    ? [\n                                          <br key=\"weird-pattern-br\" />,\n                                          '(Also, it may seem surprising, but this weird repeated pattern is totally real)',\n                                      ]\n                                    : null}\n                            </p>\n                        }\n                        {...this.props}\n                    />\n                    <p>\n                        Adding noise (from <code>perturb</code>) makes things slower when a hash table is full, the\n                        worst case scenario becomes even worse (compared to{' '}\n                        <code className=\"text-nowrap\">(5 * idx) + 1</code>\n                        ). However, in practice, we keep dicts sparse, by ensuring the load factor never goes above{' '}\n                        <code>2/3</code>\n                        ), so naturally there are many chances to hit an empty slot.\n                    </p>\n                    <p>\n                        If you are interested in more subtleties and technical details, you can check{' '}\n                        <a href=\"https://github.com/python/cpython/blob/3.2/Objects/dictnotes.txt\" target=\"_blank\">\n                            Objects/dictnotes.txt\n                        </a>{' '}\n                        and{' '}\n                        <a href=\"https://github.com/python/cpython/blob/3.2/Objects/dictobject.c\" target=\"_blank\">\n                            comments near the top of Objects/dictobject.c\n                        </a>\n                    </p>\n                    <h5>Python 3.2's dict</h5>\n                    <p>There are a couple more changes to almost-python-dict, but they are small. </p>\n                    <p>When you type a dict literal in your code, for example: </p>\n                    <BlockInputToolbar\n                        input={PyDictInput}\n                        initialValue={this.state.pairs}\n                        onChange={this.setter('pairs', true)}\n                        {...this.props}\n                    />\n                    <p>\n                        Python actually knows the number of key-value pairs and tries to guess the optimal hash table\n                        size to possibly avoid some or all resizes. In most cases, the resulting hash table ends up\n                        being the same size or smaller. However, in some cases the resulting hash table may actually be\n                        larger if there are a lot of repeated keys in the literal (e.g.{' '}\n                        <code>{'{1: 1, 1: 2, 1: 3, 1: 4, 1: 5, 1: 6, 1: 7, 1: 8, 1: 9}'}</code>)\n                    </p>\n                    <p>\n                        So in <code>__init__</code> we use the familiar <code>find_closest_size()</code> function to\n                        find the power of two greater than the number of items. This guarantees that there will be no\n                        more than one resize.\n                    </p>\n                    <p>\n                        The code for the resize is a bit different, because a resize operation can increase the size of\n                        the hashtable by as much as 4x. The <code>__setitem__</code> is the same, except for the probing\n                        algorithm, and it also tries to recycle slots containing tombstones (<code>DUMMY</code>{' '}\n                        placeholders), just like the insert operation in the previous chapter did. Although, in case of\n                        <code>__init__</code> recycling is not going to happen, as we start with an empty table\n                        containing no dummy slots.\n                    </p>\n                    <VisualizedCode\n                        code={DICT32_SETITEM_WITH_INIT}\n                        breakpoints={newRes.bp}\n                        formatBpDesc={[formatHashClassInit, formatHashClassSetItemAndCreate, formatDict32IdxRelatedBp]}\n                        stateVisualization={HashClassInsertAllVisualization}\n                        {...this.props}\n                    />\n                    <p>\n                        How much difference the probing algorithm and the other changes make? How different is the\n                        resulting dict compared to almost-python-dict from chapter 3? Here are the two versions side by\n                        side:\n                    </p>\n                    <SideBySideDicts\n                        bp={{\n                            almostPythonDictSlots: almostPythonDictSelf.get('slots'),\n                            pythonDictSlots: newRes.pySelf.get('slots'),\n                        }}\n                        compensateTopPadding={30}\n                        {...this.props}\n                    />\n                    <p>\n                        The code for removing an item stays mostly the same. Again, a different probing algoithm is\n                        used, but conceptually it is the same exact algoirthm: try to find a key, and if it is there,\n                        overwrite the item with a <code>DUMMY</code> placeholder.\n                    </p>\n                    <div className=\"div-p\">\n                        Deleting\n                        <PySNNInput\n                            inline={true}\n                            value={this.state.keyToDel}\n                            onChange={this.setter('keyToDel')}\n                            anotherValue={() => anotherKey(this.state.pairs)}\n                        />\n                    </div>\n                    <VisualizedCode\n                        code={DICT32_DELITEM}\n                        breakpoints={delRes.bp}\n                        formatBpDesc={[formatHashClassLookdictRelated, formatDict32IdxRelatedBp]}\n                        stateVisualization={HashClassNormalStateVisualization}\n                        {...this.props}\n                    />\n                    <div className=\"div-p\">\n                        The search is also conceptually the same, with the only difference being &mdash; you probably\n                        guessed it at this point &mdash; the probing algorithm. For example, let's say we want to get\n                        the following key\n                        <PySNNInput\n                            inline={true}\n                            value={this.state.keyToGet}\n                            onChange={this.setter('keyToGet')}\n                            anotherValue={() => anotherKey(this.state.pairs)}\n                        />\n                    </div>\n                    <VisualizedCode\n                        code={DICT32_GETITEM}\n                        breakpoints={getRes.bp}\n                        formatBpDesc={[formatHashClassLookdictRelated, formatDict32IdxRelatedBp]}\n                        stateVisualization={HashClassNormalStateVisualization}\n                        {...this.props}\n                    />\n                    <h5> Resize </h5>\n                    <p>\n                        Resizes are expensive, so it is better to have less of them. So Python sometimes quadruples the\n                        size of a table. This is more reasonable for smaller hash tables, when the number of items is\n                        smaller than 50000. When the number of items is greater than 50 thousand, Python 3.2 aims to\n                        double the size. Although, just like in previous chapter, a resize operation can shrink the\n                        table or keep the size the same (while dropping the unnecessary dummy slots), if too many slots\n                        are wasted by <code>DUMMY</code> placeholders.\n                    </p>\n                    <DynamicPartResize {...resizeRes} />\n                    <p>\n                        After running <code>__setitem__</code> multiple times for these pairs, we can take a look at the\n                        resize in-depth:{' '}\n                    </p>\n                    <VisualizedCode\n                        code={DICT32_RESIZE_CODE}\n                        breakpoints={resizeRes.bp}\n                        formatBpDesc={[formatHashClassResize, formatDict32IdxRelatedBp]}\n                        stateVisualization={HashClassResizeVisualization}\n                        {...this.props}\n                    />\n                    <h5>More chapters to come</h5>\n                    <p>\n                        This is the last chapter currently available. Developing and debugging the engine for this\n                        explorable explanation took way, way longer than I expected, so I am releasing the first few\n                        chapters before the following ones.\n                    </p>\n                    <p>\n                        Did interactive visualizations help you understand something? Did you discover something\n                        interested because of interactivity and animations? Did it help building an intuition for hash\n                        tables? I am really curious about this, I'd love to hear from you, so drop me a message. And if\n                        you find a bug,{' '}\n                        <a href=\"https://github.com/eleweek/inside_python_dict\" target=\"_blank\">\n                            please open a new issue on Github\n                        </a>\n                        .\n                    </p>\n                    <p>\n                        Is dict in Python 3.7 similar to dict Python 3.2? I'd say yes. It's still an open addressing\n                        hash table with weird perturb-based probing. The hash function for strings changed a couple of\n                        times, some memory sharing for keys was introduced, and dicts became ordered.\n                    </p>\n                    <h5>A very brief history of changes in versions 3.3 - 3.7</h5>\n                    <p>\n                        3.3 introduced changes to internal structure of dicts (\n                        <a href=\"https://www.python.org/dev/peps/pep-0412/\" target=\"_blank\">\n                            \"Key-Sharing Dictionary\"\n                        </a>\n                        ) that improved memory consumption in certain cases. 3.3 also randomizes seed for hash\n                        functions, so that <code>hash()</code> return values are less predictable from the outside. This\n                        is a security-related change, and object hashes are still stable within the same \"run\" of Python\n                        interpreter.\n                    </p>\n                    <p>\n                        In 3.4, the hash function for strings was changed{' '}\n                        <a href=\"https://www.python.org/dev/peps/pep-0456/\" target=\"_blank\">\n                            to a more secure algorithm\n                        </a>{' '}\n                        which is more resistant to hash collision attacks.\n                    </p>\n                    <p>\n                        In 3.6{' '}\n                        <a href=\"https://bugs.python.org/issue27350\" target=\"_blank\">\n                            the dict internal structure became more compact and the dict became \"ordered\"\n                        </a>\n                        .\n                    </p>\n                    <h5>Contents</h5>\n                    {this.props.contents}\n                </Subcontainerize>\n            </div>\n        );\n    }\n}\n"
  },
  {
    "path": "src/code_blocks.js",
    "content": "import _ from 'lodash';\nimport classNames from 'classnames';\nimport memoizeOne from 'memoize-one';\nimport * as React from 'react';\n\nimport {BigNumber} from 'bignumber.js';\n\nimport low from 'lowlight/lib/core';\n\nimport unified from 'unified';\nimport rehypestringify from 'rehype-stringify';\n\nimport pythonHl from 'highlight.js/lib/languages/python';\nlow.registerLanguage('python', pythonHl);\n\nimport HighLightJStyle from 'highlight.js/styles/default.css';\n\nimport Slider from 'rc-slider/lib/Slider';\nimport 'rc-slider/assets/index.css';\n\nimport SmoothScrollbar from 'react-smooth-scrollbar';\n\nimport {MyErrorBoundary, getUxSettings, DebounceWhenOutOfView, RED, BLUE, isDefinedSmallBoxScreen} from './util';\nimport {isNone, isDummy, repr, displayStr} from './hash_impl_common';\nimport {globalSettings} from './store';\nimport {observer} from 'mobx-react';\n\nimport {library} from '@fortawesome/fontawesome-svg-core';\nimport {FontAwesomeIcon} from '@fortawesome/react-fontawesome';\nimport {faPlay} from '@fortawesome/free-solid-svg-icons/faPlay';\nimport {faStepForward} from '@fortawesome/free-solid-svg-icons/faStepForward';\nimport {faStepBackward} from '@fortawesome/free-solid-svg-icons/faStepBackward';\nimport {faFastForward} from '@fortawesome/free-solid-svg-icons/faFastForward';\nimport {faFastBackward} from '@fortawesome/free-solid-svg-icons/faFastBackward';\nimport {faPause} from '@fortawesome/free-solid-svg-icons/faPause';\nimport {faRedoAlt} from '@fortawesome/free-solid-svg-icons/faRedoAlt';\nimport {\n    List as ImmutableList,\n    Map as ImmutableMap,\n    fromJS as immutableFromJS,\n    Record as ImmutableRecord,\n} from 'immutable';\n\nlibrary.add(faPlay);\nlibrary.add(faStepForward);\nlibrary.add(faStepBackward);\nlibrary.add(faFastForward);\nlibrary.add(faFastBackward);\nlibrary.add(faPause);\nlibrary.add(faRedoAlt);\n\nfunction reflow(node) {\n    if (!node) {\n        console.error('Not reflowing non-existant node!');\n        return;\n    }\n    node && node.scrollTop;\n}\n\nfunction renderPythonCode(codeString) {\n    let lowAst = low.highlight('python', codeString).value;\n\n    const processor = unified().use(rehypestringify);\n    return processor.stringify({\n        type: 'root',\n        children: lowAst,\n    });\n}\n\nexport function dummyFormat(bp) {\n    /* return JSON.stringify(bp); */\n    return '';\n}\n\nexport function SimpleCodeBlock(props) {\n    return (\n        <pre className=\"simple-code-block hl-left pl-2\">\n            <code dangerouslySetInnerHTML={{__html: renderPythonCode(props.children)}} />\n        </pre>\n    );\n}\n\nexport function SimpleCodeInline(props) {\n    return <code dangerouslySetInnerHTML={{__html: renderPythonCode(props.children)}} />;\n}\n\nexport const DEFAULT_BOX_GEOMETRY = {\n    boxGeometry: {boxSize: 40, boxPadding: 2, spacingX: 2, spacingY: 7, fontSize: 12, borderRadius: 4},\n    labelFontSize: 16,\n};\nexport const SMALLER_BOX_GEOMETRY = {\n    boxGeometry: {boxSize: 30, boxPadding: 1, spacingX: 2, spacingY: 4, fontSize: 9, borderRadius: 3},\n    labelFontSize: 12,\n};\n\nfunction computeBoxTransformProperty(idx, y, boxSize, spacingX) {\n    let x = (spacingX + boxSize) * idx;\n    return `translate(${x}px, ${y}px)`;\n}\n\nfunction pyObjToReactKey(obj) {\n    return repr(obj, false);\n}\n\nclass ActiveBoxSelectionUnthrottled extends React.PureComponent {\n    render() {\n        let {extraClassName, idx, status, transitionDuration, color, boxSize, spacingX, borderRadius} = this.props;\n        let yOffset = this.props.yOffset || 0;\n\n        const animatedClass = 'active-box-selection-animated';\n        let classes = ['active-box-selection', extraClassName, animatedClass];\n\n        let opacity;\n        switch (status) {\n            case 'removing':\n                opacity = 0;\n                break;\n            case 'created':\n                opacity = 0.15;\n                break;\n            case 'adding':\n                opacity = 1;\n                break;\n        }\n        const style = {\n            opacity: opacity,\n            transitionDuration: `${transitionDuration}ms`,\n            borderColor: color,\n            width: boxSize,\n            height: boxSize,\n            borderRadius,\n            // TODO: the part after : is weird/wrong\n            transform: idx != null ? computeBoxTransformProperty(idx, yOffset, boxSize, spacingX) : undefined,\n        };\n        return <div ref={this.props.setInnerRef} className={classNames(classes)} style={style} />;\n    }\n}\n\nclass ActiveBoxSelectionThrottledHelper extends React.Component {\n    handleRef = node => {\n        this.node = node;\n    };\n\n    render() {\n        let {idx, status, extraClassName, yOffset, color, boxSize, spacingX, borderRadius} = this.props;\n        return (\n            <ActiveBoxSelectionUnthrottled\n                setInnerRef={this.handleRef}\n                extraClassName={extraClassName}\n                idx={idx}\n                status={status}\n                yOffset={yOffset}\n                color={color}\n                boxSize={boxSize}\n                spacingX={spacingX}\n                borderRadius={borderRadius}\n                transitionDuration={this.props.transitionDuration}\n            />\n        );\n    }\n\n    handleTransitionEnd = transitionId => {\n        this.props.onTransitionEnd(this.props.propsId);\n    };\n\n    componentDidUpdate() {\n        if (this.props.onTransitionEnd) {\n            this.node.addEventListener('transitionend', () => this.handleTransitionEnd(this.props.propsId), {\n                once: true,\n                capture: false,\n            });\n        }\n    }\n}\n\nfunction SingleBoxSelection({idx, status, boxSize, spacingX, spacingY, borderRadius, ...restProps}) {\n    return (\n        <SelectionGroup\n            idx={idx}\n            status={status}\n            individualSelectionsProps={[{key: 0, ...restProps}]}\n            boxSize={boxSize}\n            borderRadius={borderRadius}\n            spacingX={spacingX}\n            spacingY={spacingY}\n        />\n    );\n}\n\nclass SelectionGroup extends React.Component {\n    TRANSITION_DURATION = 300;\n\n    constructor() {\n        super();\n        this.state = {\n            transitionRunning: false,\n            transitionStarting: false,\n            epoch: 0,\n        };\n    }\n\n    render() {\n        const isSelectionThrottled = getUxSettings().THROTTLE_SELECTION_TRANSITIONS;\n\n        const {idx, status} = this.state;\n\n        const {individualSelectionsProps, ...restProps} = this.props;\n        return individualSelectionsProps.map(extraProps => (\n            <ActiveBoxSelectionThrottledHelper\n                {...extraProps}\n                {...restProps}\n                idx={idx}\n                status={status}\n                transitionDuration={this.TRANSITION_DURATION}\n                propsId={isSelectionThrottled && this.state.epoch}\n                onTransitionEnd={isSelectionThrottled && this.handleTransitionEnd}\n            />\n        ));\n    }\n\n    handleTransitionEnd = epoch => {\n        if (epoch === this.state.epoch) {\n            this.setState(state => {\n                if (state.transitionRunning) {\n                    return {transitionRunning: false};\n                } else {\n                    return null;\n                }\n            });\n        }\n    };\n\n    componentDidUpdate() {\n        if (this.state.transitionRunning && this.state.transitionStarting) {\n            this.setState(state => {\n                return {\n                    transitionStarting: false,\n                };\n            });\n            this.handleStartingTransition();\n        }\n    }\n\n    handleStartingTransition() {\n        let selectionTimeout = getUxSettings().THROTTLE_SELECTION_TIMEOUT;\n        if (typeof selectionTimeout !== 'number') {\n            selectionTimeout = this.TRANSITION_DURATION + 20;\n        } else {\n            selectionTimeout = Math.min(this.TRANSITION_DURATION + 20, selectionTimeout);\n        }\n        const currentEpoch = this.state.epoch;\n        setTimeout(() => {\n            if (currentEpoch === this.state.epoch) {\n                this.handleTransitionEnd(currentEpoch);\n            }\n        }, selectionTimeout);\n    }\n\n    static getDerivedStateFromProps(props, state) {\n        const isSelectionThrottled = getUxSettings().THROTTLE_SELECTION_TRANSITIONS;\n        if (isSelectionThrottled) {\n            if (!state.transitionRunning && (state.idx !== props.idx || state.status !== props.status)) {\n                const statusAllowsTransition = state.status === 'adding' && props.status === 'adding';\n                let newState = {\n                    idx: props.idx,\n                    status: props.status,\n                    transitionRunning: statusAllowsTransition,\n                    transitionStarting: statusAllowsTransition,\n                    epoch: state.epoch + 1,\n                };\n                return newState;\n            } else {\n                return null;\n            }\n        } else {\n            return {idx: props.idx, status: props.status};\n        }\n    }\n}\n\nclass Box extends React.PureComponent {\n    shortDisplayedString(value) {\n        const extraType =\n            value === 'DUMMY' ||\n            value === 'EMPTY' ||\n            value === '' ||\n            (typeof value === 'string' && /^[-+]?[0-9]+$/.test(value));\n        let isEmpty = false;\n        const maxLen = extraType ? 8 : 12;\n        // TODO: add hover?\n        let s = displayStr(value, false);\n        let shortenedValue;\n        if (s.length <= maxLen) {\n            shortenedValue = [s];\n        } else {\n            const cutCharsCount = extraType ? 3 : 4;\n\n            if (cutCharsCount === 4) {\n                shortenedValue = [\n                    s.substring(0, cutCharsCount),\n                    <br key=\"br1\" />,\n                    '\\u22EF',\n                    <br key=\"br2\" />,\n                    s.substring(s.length - cutCharsCount, s.length),\n                ];\n            } else {\n                shortenedValue = [\n                    s.substring(0, cutCharsCount),\n                    '\\u2026',\n                    s.substring(s.length - cutCharsCount, s.length),\n                ];\n            }\n        }\n        return {\n            shortenedValue,\n            extraType,\n            isEmpty,\n        };\n    }\n\n    render() {\n        const {\n            value,\n            idx,\n            status,\n            extraStyleWhenAdding,\n            removedOffset,\n            createdOffset,\n            boxSize,\n            spacingX,\n            fontSize,\n            spacingY,\n            borderRadius,\n            boxPadding,\n        } = this.props;\n        const yOffset = (this.props.yRel || 0) * (boxSize + spacingY);\n\n        let classes = ['box', {'box-animated': status !== 'removed' && status !== 'created'}];\n        let content;\n        if (value != null) {\n            const {shortenedValue, extraType} = this.shortDisplayedString(value);\n            let extraTypeSpan;\n            let style = {fontSize: this.props.fontSize * 0.75};\n            if (\n                shortenedValue.length === 1 &&\n                shortenedValue[0] === '' /* TODO FIXME: this check is kidna ugly & is a leaky abstraction */\n            ) {\n                extraTypeSpan = (\n                    <span className=\"box-content-extra-type\" style={style}>\n                        (empty str)\n                    </span>\n                );\n            } else {\n                extraTypeSpan = extraType ? (\n                    <span className=\"box-content-extra-type\" style={style}>\n                        (str)\n                    </span>\n                ) : null;\n            }\n            classes.push('box-full');\n            content = (\n                <span className=\"box-content\">\n                    {shortenedValue} {extraTypeSpan}\n                </span>\n            );\n        } else {\n            classes.push('box-empty');\n        }\n\n        let y;\n        let extraStyle;\n\n        switch (status) {\n            case 'removed':\n                classes.push('box-removed');\n                break;\n            case 'removing':\n                classes.push('box-removing');\n                y = value != null ? yOffset - (removedOffset != null ? removedOffset : boxSize) : yOffset;\n                break;\n            case 'created':\n                classes.push('box-created');\n                y = value != null ? yOffset - (createdOffset != null ? createdOffset : boxSize) : yOffset;\n                break;\n            case 'adding':\n                y = yOffset;\n                extraStyle = this.props.extraStyleWhenAdding;\n                break;\n        }\n\n        return (\n            <div\n                style={{\n                    transform:\n                        status !== 'removed'\n                            ? computeBoxTransformProperty(idx, y, boxSize, spacingX)\n                            : 'translate(0px, 0px)',\n                    width: boxSize,\n                    height: boxSize,\n                    fontSize: fontSize,\n                    lineHeight: `${fontSize}px`,\n                    borderRadius,\n                    padding: boxPadding,\n                    ...extraStyle,\n                }}\n                className={classNames(classes)}\n            >\n                {content}\n            </div>\n        );\n    }\n}\n\nclass SlotSelection extends React.PureComponent {\n    render() {\n        const {extraClassName, idx, status, color, boxSize, spacingX, spacingY, borderRadius} = this.props;\n\n        const individualSelectionsProps = [\n            {\n                key: `${extraClassName}-hashCode`,\n                extraClassName,\n                color,\n                yOffset: 0,\n            },\n            {key: `${extraClassName}-key`, extraClassName, color, yOffset: boxSize + spacingY},\n            {\n                key: `${extraClassName}-value`,\n                extraClassName,\n                color,\n                yOffset: 2 * (boxSize + spacingY),\n            },\n        ];\n\n        return (\n            <SelectionGroup\n                individualSelectionsProps={individualSelectionsProps}\n                idx={idx}\n                status={status}\n                boxSize={boxSize}\n                spacingX={spacingX}\n                spacingY={spacingY}\n                borderRadius={borderRadius}\n            />\n        );\n    }\n}\n\nclass LineOfBoxesSelection extends React.PureComponent {\n    render() {\n        const {extraClassName, idx, status, count, color, boxSize, spacingX, spacingY, borderRadius} = this.props;\n\n        let individualSelectionsProps = [];\n        for (let i = 0; i < this.props.count; ++i) {\n            individualSelectionsProps.push({\n                key: `${extraClassName}-${i}`,\n                extraClassName,\n                color,\n                yOffset: i * (boxSize + spacingY),\n            });\n        }\n\n        return (\n            <SelectionGroup\n                individualSelectionsProps={individualSelectionsProps}\n                idx={idx}\n                status={status}\n                boxSize={boxSize}\n                spacingX={spacingX}\n                spacingY={spacingY}\n                spacingY={spacingY}\n                borderRadius={borderRadius}\n            />\n        );\n    }\n}\n\nconst BBRecord = ImmutableRecord({\n    status: null,\n    modId: null,\n    box: null,\n    value: null,\n    group: null,\n    id: null,\n    idx: null,\n    someProps: null,\n});\n\nclass BaseBoxesComponent extends React.PureComponent {\n    // Use slightly lower number than the actual 1300\n    // Because it seems to produce less \"stupid\" looking results\n    static ANIMATION_DURATION_TIMEOUT = 1100;\n\n    constructor() {\n        super();\n\n        this.state = {\n            keyData: null,\n            activeBoxSelection1: null,\n            activeBoxSelection1status: null,\n            activeBoxSelection2: null,\n            activeBoxSelection2status: null,\n            lastIdx: null,\n            lastIdx2: null,\n            needProcessCreatedAfterRender: false,\n            needReflow: false,\n            firstRender: true,\n            modificationId: 0,\n            lastBoxId: 0,\n            removingValueToGroupToKeyToId: null,\n        };\n        this.ref = React.createRef();\n        this.gcTimeout = null;\n    }\n\n    static markRemoved(state, targetModId) {\n        let updatedCount = 0;\n        let toMerge = {};\n        let gcModId = state.gcModId;\n        if (targetModId) {\n            gcModId = Math.max(gcModId, targetModId);\n        }\n        console.log('BaseBoxesComponent markRemoved', gcModId);\n        for (const [key, data] of state.keyData.entries()) {\n            if (data.status === 'removing' && data.modId <= gcModId) {\n                updatedCount++;\n                toMerge[key] = {\n                    box: React.cloneElement(data.box, {status: 'removed'}),\n                    status: 'removed',\n                };\n            }\n        }\n        if (updatedCount > 0) {\n            console.log('BaseBoxesComponent.markRemoved() removed', updatedCount);\n            return {\n                keyData: state.keyData.mergeDeep(toMerge),\n                needReflow: true,\n                gcModId,\n            };\n        } else {\n            return null;\n        }\n    }\n\n    // FIXME: this function\n    // FIXME: you may not like it, but this is what peak engineering looks like\n    static getDerivedStateFromProps(nextProps, state) {\n        const t1 = performance.now();\n        // BaseBoxesComponent.staticDebugLogState(state);\n\n        // Some boxes are already timed out, so mark them as such\n        if (!state.firstRender) {\n            const mrState = BaseBoxesComponent.markRemoved(state);\n            if (mrState) {\n                state = {...state, ...mrState};\n            }\n        }\n\n        const modificationId = state.modificationId + 1;\n        const gcModId = state.gcModId;\n        const Selection = nextProps.selectionClass;\n        const boxFactory = nextProps.boxFactory;\n        const boxGeometry = nextProps.boxGeometry;\n\n        const geometryChanged = !state.firstRender && boxGeometry !== state.boxGeometry;\n        if (geometryChanged) {\n            const toMerge = {};\n            for (const [key, data] of state.keyData.entries()) {\n                toMerge[key] = {box: React.cloneElement(data.box, {...boxGeometry})};\n            }\n            state = {...state, keyData: state.keyData.mergeDeep(toMerge), boxGeometry};\n        }\n\n        let nextArray = nextProps.array;\n        let nextArrayPreConversion = nextArray;\n        const convertNextArray = () => {\n            if (isImmutableListOrMap(nextArray)) {\n                // TODO: use Immutable.js api?\n                // console.log('immutable.js next provided');\n                const _tna = performance.now();\n                nextArray = nextArray.toJS();\n                // console.log('toJS() timing', performance.now() - _tna);\n            } else {\n                // console.warn('nextArray non-immutable');\n            }\n        };\n        let lastBoxId = state.lastBoxId;\n\n        let newState;\n        const t2 = performance.now();\n        console.log('BaseBoxesComponent::gdsp before update state timing', t2 - t1);\n        if (!state.firstRender) {\n            // This should help when setState is called after needProcessCreatedAfterRender = true\n            // (And also can possible help sometimes when a slider is dragged)\n            if (nextArrayPreConversion !== state.lastNextArrayPreConversion) {\n                convertNextArray();\n                nextArray = nextArray || [];\n                const nextArrayKeys = nextProps.getKeys(nextArray);\n\n                let newRemovingValueToGroupToKeyToId = state.removingValueToGroupToKeyToId.asMutable();\n                let toMerge = {};\n\n                let nextKeysSet = new Set(_.flatten(nextArrayKeys));\n\n                let needGarbageCollection = false;\n                // First check boxes that will be removed\n                // FIXME: some are already removed, it might be a good idea to maintain a list of \"present\" boxes\n                for (let [key, oldData] of state.keyData.entries()) {\n                    if (!nextKeysSet.has(key)) {\n                        const status = oldData.status;\n                        if (status !== 'removing' && status !== 'removed') {\n                            const group = oldData.group;\n                            const value = oldData.value;\n                            const box = React.cloneElement(oldData.box, {status: 'removing'});\n                            toMerge[key] = {status: 'removing', modId: modificationId, box};\n                            newRemovingValueToGroupToKeyToId.setIn([repr(value, true), group, key], {\n                                id: oldData.id,\n                                idx: oldData.idx,\n                            });\n                            needGarbageCollection = true;\n                        }\n                    }\n                }\n\n                const newBox = (key, idx, someProps, group, value) => {\n                    needProcessCreatedAfterRender = true;\n                    needReflow = true;\n                    const id = (++lastBoxId).toString();\n                    const box = <Box idx={idx} status=\"created\" key={id} {...boxGeometry} {...someProps} />;\n                    toMerge[key] = new BBRecord({\n                        status: 'created',\n                        id,\n                        box,\n                        modId: modificationId,\n                        group,\n                        value,\n                        idx,\n                        someProps,\n                    });\n                };\n\n                let needProcessCreatedAfterRender = false;\n                let needReflow = false;\n\n                // const t3 = performance.now();\n                // console.log('BaseBoxesComponent::gdsp before processing adding stage1', t3 - t2);\n                let notExistingKeyToData = {};\n                for (let idx = 0; idx < nextArray.length; ++idx) {\n                    const keys = nextArrayKeys[idx];\n                    const idxBoxesProps = boxFactory(keys, nextArray[idx]);\n                    for (const [group, [key, someProps]] of idxBoxesProps.entries()) {\n                        const value = someProps.value;\n\n                        const oldData = state.keyData.get(key);\n\n                        if (!oldData) {\n                            // if box does not exist at all\n                            // Then there are two options\n                            if (value != null) {\n                                // There may be an opportunity to recycle non-empty boxes\n                                notExistingKeyToData[key] = {value, group, idx, someProps};\n                            } else {\n                                // But empty boxes should be recreated. It may make sense to recycle\n                                // non-empty boxes as well, but in the current explanation there are no patterns\n                                // where it'd help (empty box keys are very regular and have index in it)\n                                newBox(key, idx, someProps, group, value);\n                            }\n                        } else {\n                            // if box already exists\n                            const status = oldData.status;\n                            // potential FIXME: does not properly compare someProps, may not update boxes if someProps become more important\n                            if (\n                                status !== 'adding' ||\n                                oldData.idx !== idx ||\n                                oldData.someProps.yOffset !== someProps.yOffset ||\n                                oldData.someProps.yRel !== someProps.yRel\n                            ) {\n                                // Box is changed, time to update it\n                                const box = oldData.box;\n                                const newStatus = status === 'removed' ? 'created' : 'adding';\n                                if (newStatus === 'created') {\n                                    needProcessCreatedAfterRender = true;\n                                    needReflow = true;\n                                }\n                                const newBox = React.cloneElement(box, {\n                                    idx,\n                                    status: newStatus,\n                                    ...someProps,\n                                });\n                                toMerge[key] = {\n                                    box: newBox,\n                                    status: newStatus,\n                                    modId: modificationId,\n                                    idx,\n                                    someProps,\n                                };\n                                BaseBoxesComponent.notSoDeepDel(newRemovingValueToGroupToKeyToId, [\n                                    repr(value, true),\n                                    group,\n                                    key,\n                                ]);\n                            }\n                        }\n                    }\n                }\n\n                let keyToRecycledBox = {};\n                let instaRemovedKeys = [];\n\n                const recycleId = (key, idx, value, groupOfKeyToId, keyToId) => {\n                    let keyWithRecycledId;\n                    if (keyToId.size > 1) {\n                        for (let [key, {idx: otherIdx}] of keyToId.entries()) {\n                            // TODO: the best value could probably be selected based as argmin(abs(otherIdx - idx))\n                            // TODO: but for now this feels good enough, since it does not swap two boxes with the same value\n                            if (otherIdx === idx) {\n                                keyWithRecycledId = key;\n                                break;\n                            }\n                        }\n                    }\n                    if (keyWithRecycledId == null) {\n                        keyWithRecycledId = keyToId.keySeq().first();\n                    }\n                    keyToRecycledBox[key] = state.keyData.get(keyWithRecycledId).box;\n                    BaseBoxesComponent.notSoDeepDel(newRemovingValueToGroupToKeyToId, [\n                        repr(value, true),\n                        groupOfKeyToId,\n                        keyWithRecycledId,\n                    ]);\n                    instaRemovedKeys.push(keyWithRecycledId);\n                };\n\n                // const t4 = performance.now();\n                // console.log('BaseBoxesComponent::gdsp before processing recycling 1', t4 - t3);\n                // Do a first pass and attempt to recycle boxes in the same row\n                for (let key in notExistingKeyToData) {\n                    const data = notExistingKeyToData[key];\n                    const potentialKeyToId = newRemovingValueToGroupToKeyToId.getIn([\n                        repr(data.value, true),\n                        data.group,\n                    ]);\n                    if (potentialKeyToId) {\n                        recycleId(key, data.idx, data.value, data.group, potentialKeyToId);\n                    }\n                }\n\n                // const t5 = performance.now();\n                // console.log('BaseBoxesComponent::gdsp before processing recycling 2', t5 - t4);\n                // Do a second pass and attempt to recycle boxes in other rows\n                for (let key in notExistingKeyToData) {\n                    if (key in keyToRecycledBox) {\n                        continue;\n                    }\n                    const data = notExistingKeyToData[key];\n                    const potentialGroupToKeyToId = newRemovingValueToGroupToKeyToId.get(repr(data.value, true));\n                    if (potentialGroupToKeyToId) {\n                        // TODO: might be better to prefer keys and values for each others rather than select randoml\n                        //       (this might lead to box from hash codes being transferred to keys/values)\n                        const firstGroup = potentialGroupToKeyToId.keySeq().first();\n                        const keyToId = potentialGroupToKeyToId.get(firstGroup);\n                        recycleId(key, data.idx, data.value, firstGroup, keyToId);\n                    }\n                }\n                // const t6 = performance.now();\n                // console.log('BaseBoxesComponent::gdsp before processing recycling 3', t6 - t5);\n\n                for (let key in notExistingKeyToData) {\n                    const data = notExistingKeyToData[key];\n                    if (key in keyToRecycledBox) {\n                        // if found a recycled box\n                        const value = data.someProps.value;\n                        const box = keyToRecycledBox[key];\n                        const status = box.props.status;\n                        const id = box.key;\n                        const idx = data.idx;\n\n                        const newStatus = status === 'removed' ? 'created' : 'adding';\n                        if (newStatus === 'created') {\n                            needProcessCreatedAfterRender = true;\n                            needReflow = true;\n                        }\n\n                        const newBox = React.cloneElement(box, {\n                            idx: idx,\n                            status: newStatus,\n                            ...data.someProps,\n                        });\n                        toMerge[key] = new BBRecord({\n                            box: newBox,\n                            idx,\n                            id,\n                            status: newStatus,\n                            modId: modificationId,\n                            group: data.group,\n                            value: data.value,\n                            someProps: data.someProps,\n                        });\n                    } else {\n                        // if no recycled box found, then just create a new box\n                        newBox(key, data.idx, data.someProps, data.group, data.value);\n                    }\n                }\n                //const t7 = performance.now();\n                //console.log('BaseBoxesComponent::gdsp before merging', t7 - t6);\n\n                let newKeyData = state.keyData.mergeDeep(toMerge);\n                newKeyData = newKeyData.deleteAll(instaRemovedKeys);\n                // const t8 = performance.now();\n                // console.log('BaseBoxesComponent::gdsp after merging', t8 - t7);\n\n                newState = {\n                    keyData: newKeyData,\n                    removingValueToGroupToKeyToId: newRemovingValueToGroupToKeyToId.asImmutable(),\n                    firstRender: false,\n                    needProcessCreatedAfterRender: needProcessCreatedAfterRender,\n                    needReflow: needReflow,\n                    needGarbageCollection: needGarbageCollection,\n                    modificationId: modificationId,\n                    gcModId: gcModId,\n                    lastBoxId: lastBoxId,\n                    lastNextArrayPreConversion: nextArrayPreConversion,\n                    epoch: nextProps.epoch,\n                    boxGeometry: boxGeometry,\n                };\n            }\n        } else {\n            convertNextArray();\n            let keyData = {};\n            let arrayBoxKeys = nextProps.getKeys(nextArray);\n            for (let idx = 0; idx < nextArray.length; ++idx) {\n                const keys = arrayBoxKeys[idx];\n                const idxBoxesProps = boxFactory(keys, nextArray[idx]);\n                for (const [group, [key, someProps]] of idxBoxesProps.entries()) {\n                    const value = someProps.value;\n                    const id = (++lastBoxId).toString();\n                    const box = <Box idx={idx} key={id} status=\"adding\" {...boxGeometry} {...someProps} />;\n                    keyData[key] = new BBRecord({\n                        status: 'adding',\n                        modId: modificationId,\n                        box,\n                        id,\n                        group,\n                        value,\n                        someProps,\n                    });\n                }\n            }\n\n            newState = {\n                keyData: new ImmutableMap(keyData),\n                removingValueToGroupToKeyToId: new ImmutableMap(),\n                firstRender: false,\n                needProcessCreatedAfterRender: false,\n                needGarbageCollection: false,\n                gcModId: -1,\n                modificationId: modificationId,\n                lastBoxId: lastBoxId,\n                lastNextArray: nextArray,\n                epoch: nextProps.epoch,\n                boxGeometry: boxGeometry,\n            };\n        }\n\n        // This can be located in the beginning of the function, but I think it is better to move it here, b/c this might lead\n        //  to better recycling of boxes\n        //\n        // Epoch changes when the main \"toolbar input changes\". It is a hack to make dragging sliders work better\n        // Re-using old boxes rather than creating new ones seems to work better in most browsers (and much better in firefox)\n        if (!state.firstRender && state.epoch != nextProps.epoch) {\n            const currentState = newState != null ? newState : state;\n            const gcState = BaseBoxesComponent.garbageCollect(currentState, gcModId);\n            if (gcState) {\n                newState = {...currentState, ...gcState};\n            }\n        }\n\n        // Can happen when there is no change between arrays\n        if (newState == null) {\n            newState = {...state};\n        }\n\n        let activeBoxSelection1 = state.activeBoxSelection1;\n        let activeBoxSelection2 = state.activeBoxSelection2;\n\n        let activeBoxSelection1status = state.activeBoxSelection1status;\n        let activeBoxSelection2status = state.activeBoxSelection2status;\n\n        // FIXME: handling active selection is extremely ugly, should be rewritten in a much cleaner fashion\n        // FIXME: probably better to get rid of created/removing/adding statuses here\n        //\n        // TODO: it may be a good idea to combine it with Selection component\n        const getOrModSelection = (selection, extraClassName, oldIdx, _idx, status, color) => {\n            if (_idx == null) {\n                status = 'removing';\n            } else if (status === 'created' || _idx != null) {\n                status = 'adding';\n            }\n\n            const idx = _idx != null ? _idx : oldIdx;\n            if (!selection) {\n                return [\n                    <Selection\n                        {...nextProps.selectionProps}\n                        key={extraClassName}\n                        keyTemplate={extraClassName}\n                        extraClassName={extraClassName}\n                        idx={idx}\n                        status={status}\n                        color={color}\n                        {...boxGeometry}\n                    />,\n                    status,\n                ];\n            } else {\n                return [React.cloneElement(selection, {idx, status, ...boxGeometry}), status];\n            }\n        };\n\n        if (activeBoxSelection1 || nextProps.idx != null) {\n            [activeBoxSelection1, activeBoxSelection1status] = getOrModSelection(\n                activeBoxSelection1,\n                'active-box-selection-1',\n                state.lastIdx,\n                nextProps.idx,\n                activeBoxSelection1status,\n                nextProps.selection1color || RED\n            );\n        }\n\n        if (activeBoxSelection2 || nextProps.idx2 != null) {\n            [activeBoxSelection2, activeBoxSelection2status] = getOrModSelection(\n                activeBoxSelection2,\n                'active-box-selection-2',\n                state.lastIdx2,\n                nextProps.idx2,\n                activeBoxSelection1status,\n                nextProps.selection1color || BLUE\n            );\n        }\n\n        if (nextProps.idx != null) {\n            newState.lastIdx = nextProps.idx;\n        } else {\n            newState.lastIdx = state.lastIdx;\n        }\n\n        if (nextProps.idx2 != null) {\n            newState.lastIdx2 = nextProps.idx2;\n        } else {\n            newState.lastIdx2 = state.lastIdx2;\n        }\n\n        newState.activeBoxSelection1status = activeBoxSelection1status;\n        newState.activeBoxSelection2status = activeBoxSelection2status;\n        newState.activeBoxSelection1 = activeBoxSelection1;\n        newState.activeBoxSelection2 = activeBoxSelection2;\n\n        const totalTiming = performance.now() - t1;\n        if (typeof window !== 'undefined') {\n            if (!('bbTiming' in window)) {\n                window.bbTiming = 0;\n            }\n            window.bbTiming += totalTiming;\n        }\n        console.log('BaseBoxesComponent.getDerivedStateFromProps timing', totalTiming);\n        return newState;\n    }\n\n    static notSoDeepDel(m, [e1, e2, e3]) {\n        m = m.deleteIn([e1, e2, e3]);\n        let subm2 = m.getIn([e1, e2]);\n        if (subm2 !== undefined && subm2.size === 0) {\n            m = m.deleteIn([e1, e2]);\n            let subm1 = m.get(e1);\n            if (subm1.size === 0) {\n                m = m.delete(e1);\n            }\n        }\n        return m;\n    }\n\n    static garbageCollect(state, targetModId) {\n        console.log('Boxes garbageCollect() older than', targetModId);\n        const t1 = performance.now();\n\n        const removed = [];\n\n        let removingValueToGroupToKeyToId = state.removingValueToGroupToKeyToId.asMutable();\n        for (const [key, data] of state.keyData.entries()) {\n            if ((data.status === 'removing' || data.status === 'removed') && data.modId <= targetModId) {\n                removed.push(key);\n                const value = data.value;\n                const group = data.group;\n                BaseBoxesComponent.notSoDeepDel(removingValueToGroupToKeyToId, [repr(value, true), group, key]);\n            }\n        }\n\n        let newState = null;\n        if (removed.length > 0) {\n            newState = {\n                keyData: state.keyData.deleteAll(removed),\n                removingValueToGroupToKeyToId: removingValueToGroupToKeyToId.asImmutable(),\n                needGarbageCollection: false,\n            };\n        }\n        console.log('Boxes garbageCollect() collected', removed.length);\n\n        if (newState) {\n            console.log('Non-trivial garbageCollect timing', performance.now() - t1);\n        }\n\n        return newState;\n    }\n\n    // It is probably better to don't let unnecessary dom objects persist for too long\n    // But for now disabling, as this seems to make dragging the time slider faster\n    /*garbageCollectAfterAnimationDone = targetModId => {\n        this.gcTimeout = null;\n        this.setState(state => {\n            return BaseBoxesComponent.garbageCollect(state, targetModId);\n        });\n    };*/\n\n    debugLogState() {\n        BaseBoxesComponent.staticDebugLogState(this.state);\n    }\n\n    static staticDebugLogState(state) {\n        console.log('~~~~~~~~~~~~~~~');\n        console.log('debugLogState()');\n        for (let [k, v] of Object.entries(state)) {\n            console.log(k, v != null && typeof v.toJS === 'function' ? v.toJS() : v);\n            if (isImmutableListOrMap(v)) {\n                if (v.size > 200) {\n                    console.warn(`debugLogState(): ${k} size is too big: ${v.size}`);\n                }\n            }\n        }\n        console.log('~~~~~~~~~~~~~~~');\n    }\n\n    updateModIdForGC = modId => {\n        this.setState(state => {\n            const gcModId = Math.max(state.gcModId, modId);\n            if (gcModId > state.gcModId) {\n                return {gcModId};\n            } else {\n                return null;\n            }\n        });\n    };\n\n    render() {\n        // console.log('BaseBoxesComponent.render()');\n        // this.debugLogState();\n        if (this.state.needGarbageCollection) {\n            /*console.log(\"Scheduling garbage collection\");\n            console.log(this.state);*/\n            const currentModificationId = this.state.modificationId;\n\n            /*if (this.gcTimeout) {\n                clearTimeout(this.gcTimeout);\n            }\n\n            this.gcTimeout = setTimeout(() => {\n                this.setState(state => {\n                    return BaseBoxesComponent.markRemoved(state, currentModificationId);\n                });\n            }, BaseBoxesComponent.ANIMATION_DURATION_TIMEOUT);*/\n\n            // The next line is very important, and this timer does not need to be heavily debounced\n            setTimeout(\n                () => this.updateModIdForGC(currentModificationId),\n                BaseBoxesComponent.ANIMATION_DURATION_TIMEOUT\n            );\n        }\n\n        const boxes = [];\n        for (let key of this.state.keyData.keys()) {\n            boxes.push(this.state.keyData.get(key).box);\n        }\n        boxes.sort((b1, b2) => (b1.key > b2.key ? 1 : b1.key < b2.key ? -1 : 0));\n        // console.log('BaseBoxesComponent rendered', boxes);\n\n        if (this.state.activeBoxSelection1) {\n            boxes.push(this.state.activeBoxSelection1);\n        }\n        if (this.state.activeBoxSelection2) {\n            boxes.push(this.state.activeBoxSelection2);\n        }\n\n        // TODO: set proper width\n        return (\n            <div className=\"hash-vis\" style={{height: this.props.height}} ref={this.ref}>\n                {boxes}\n            </div>\n        );\n    }\n\n    componentDidUpdate() {\n        const node = this.ref.current;\n        if (this.state.needReflow) {\n            reflow(node);\n            if (this.props.onExpectedWidthChange) {\n                this.props.onExpectedWidthChange();\n            }\n            // check to prevent extra setState\n            if (!this.state.needProcessCreatedAfterRender) {\n                this.setState({\n                    needReflow: false,\n                });\n            }\n        }\n\n        if (this.state.needProcessCreatedAfterRender) {\n            const t1 = performance.now();\n\n            this.setState(state => {\n                const modificationId = state.modificationId + 1;\n\n                let toMerge = {};\n                for (let [key, oldData] of state.keyData.entries()) {\n                    if (oldData.status === 'created') {\n                        toMerge[key] = {\n                            status: 'adding',\n                            box: React.cloneElement(oldData.box, {status: 'adding'}),\n                            modId: modificationId,\n                        };\n                    }\n                }\n                return {\n                    keyData: state.keyData.mergeDeep(toMerge),\n                    needProcessCreatedAfterRender: false,\n                    needReflow: false,\n                    modificationId,\n                };\n            });\n            console.log(\n                'BaseBoxesComponent.componentDidUpdate needProcessCreatedAfterRender==true timing',\n                performance.now() - t1\n            );\n        }\n    }\n}\n\nfunction isImmutableListOrMap(obj) {\n    return ImmutableList.isList(obj) || ImmutableMap.isMap(obj);\n}\n\nfunction deepGet(obj, path) {\n    const parts = path.split('.');\n    let node = obj;\n    for (const part of parts) {\n        if (isImmutableListOrMap(node)) {\n            node = node.get(part);\n        } else {\n            node = node[part];\n        }\n    }\n\n    return node;\n}\n\nexport function TetrisFactory(lines, opts) {\n    const fixedGeometry = opts?.fixedGeometry;\n\n    return class extends React.PureComponent {\n        static FULL_WIDTH = true;\n        static EXTRA_ERROR_BOUNDARY = true;\n\n        static getExpectedHeight(windowWidth, windowHeight) {\n            return Tetris._getExpectedHeight(windowWidth, windowHeight, lines, fixedGeometry);\n        }\n\n        render() {\n            return (\n                <Tetris lines={lines} {...this.props} innerRef={this.props.innerRef} fixedGeometry={fixedGeometry} />\n            );\n        }\n    };\n}\n\nfunction selectGeometry(windowWidth, windowHeight) {\n    if (isDefinedSmallBoxScreen(windowWidth, windowHeight)) {\n        return SMALLER_BOX_GEOMETRY;\n    } else {\n        return DEFAULT_BOX_GEOMETRY;\n    }\n}\n\nexport class Tetris extends React.PureComponent {\n    static VIS_MARGIN = 10; // should match .hash-vis-wrapper margin\n\n    static _getExpectedHeight(windowWidth, windowHeight, lines, fixedGeometry) {\n        // TODO: use linesData.marginBottom in computation\n        const {boxGeometry} = fixedGeometry || selectGeometry(windowWidth, windowHeight);\n        return (\n            this.VIS_MARGIN * (lines.length - 1) +\n            _.sum(\n                lines.map(\n                    ([Component, [ld, d, i, i2, subProps]]) =>\n                        Component.getExpectedGeometry({boxGeometry, ...subProps}).height\n                )\n            )\n        );\n    }\n\n    constructor() {\n        super();\n        this.scrollbarRef = React.createRef();\n    }\n\n    updateScrollbar = () => {\n        this.scrollbarRef.current.scrollbar.update();\n        /*// TODO FIXME XXX this is a temporary workaround for some testing\n        setTimeout(() => {\n            this.scrollbarRef.current.scrollbar.update();\n        }, 2000);*/\n    };\n\n    render() {\n        const props = this.props;\n        let elems = [];\n        let labels = [];\n        const transformedBp = props.bp;\n        let labelsEnabled = false;\n        const {boxGeometry, labelFontSize} =\n            this.props.fixedGeometry || selectGeometry(props.windowWidth, props.windowHeight);\n        // console.log('selectBoxGeometry', boxGeometry, props.windowWidth, props.windowHeight);\n        for (let [i, [Component, [linesData, dataName, idxName, idx2Name, subProps]]] of props.lines.entries()) {\n            const component = (\n                <Component\n                    onExpectedWidthChange={this.updateScrollbar}\n                    array={deepGet(props.bp, dataName)}\n                    idx={props.bp[idxName]}\n                    idx2={props.bp[idx2Name]}\n                    epoch={props.epoch}\n                    boxGeometry={boxGeometry}\n                    {...subProps}\n                />\n            );\n\n            const tetrisRowMarginBottom = linesData.marginBottom || Tetris.VIS_MARGIN;\n            const {rowMarginBottom, rowHeight, height, rowsNumber} = Component.getExpectedGeometry({\n                boxGeometry,\n                ...subProps,\n            });\n            labelsEnabled = labelsEnabled || !linesData.labels.every(label => label == null);\n\n            labels = labels.concat(\n                linesData.labels.map((label, index) => {\n                    const marginBottom =\n                        index === linesData.labels.length - 1 ? tetrisRowMarginBottom : rowMarginBottom;\n                    const expectedNumLabels = rowsNumber;\n                    const actualNumLabels = linesData.labels.length;\n                    if (actualNumLabels != expectedNumLabels && actualNumLabels != 1)\n                        throw new Error(`Mismatched number of labels`);\n                    return (\n                        <div\n                            className=\"tetris-label-div\"\n                            key={label}\n                            style={{\n                                fontSize: labelFontSize,\n                                height: actualNumLabels === expectedNumLabels ? rowHeight : height,\n                                marginBottom: marginBottom,\n                            }}\n                        >\n                            <span className=\"tetris-label\">{label || ''}</span>\n                        </div>\n                    );\n                })\n            );\n\n            elems.push(\n                <div className=\"tetris-row\" key={`row-${i}`} style={{marginBottom: tetrisRowMarginBottom}}>\n                    <div className=\"hash-vis-wrapper\" style={{height: height}}>\n                        {component}\n                    </div>\n                </div>\n            );\n        }\n\n        let style;\n        if (this.props.compensateTopPadding && this.props.compensateTopPadding > 0) {\n            style = {position: 'relative', top: -this.props.compensateTopPadding};\n        }\n        return (\n            <SmoothScrollbar alwaysShowTracks={true} style={style} ref={this.scrollbarRef}>\n                <div\n                    className=\"fix-animation\"\n                    ref={this.props.innerRef}\n                    style={{overflowX: this.props.overflow && 'scroll'}}\n                >\n                    <div className=\"some-hacky-padding\" style={{height: boxGeometry.boxSize * 0.75}} />\n                    <div className=\"tetris\">\n                        {labelsEnabled && <div className=\"tetris-labels\">{labels}</div>}\n                        <div className=\"tetris-rows\">{elems}</div>\n                    </div>\n                </div>\n            </SmoothScrollbar>\n        );\n    }\n\n    componentDidUpdate() {\n        this.updateScrollbar();\n    }\n\n    componentDidMount() {\n        this.updateScrollbar();\n    }\n}\n\nclass CodeBlockWithActiveLineAndAnnotations extends React.Component {\n    constructor() {\n        super();\n        this.ssRef = React.createRef();\n    }\n\n    _highlightLines = memoizeOne(code => {\n        let lines = [];\n        let maxLen = Math.max(...code.map(([line, bpPoint]) => line.length));\n        for (let i = 0; i < code.length; ++i) {\n            const line = code[i][0];\n            const paddedLine = _.padEnd(line, maxLen);\n            const hlCode = renderPythonCode(paddedLine);\n            lines.push(hlCode);\n        }\n\n        return lines;\n    });\n\n    getCodeWithExplanationHtmlLines(visibleBreakpoints, activeBp) {\n        const t1 = performance.now();\n        const code = this.props.code;\n        const hlLines = this._highlightLines(code);\n        let lines = [];\n\n        let isAnyLineHighlighted = false;\n        for (let i = 0; i < code.length; ++i) {\n            const bpPoint = code[i][1];\n\n            let explanation = '';\n            let isCurrentLineHighlighted = bpPoint === activeBp.point;\n            isAnyLineHighlighted |= isCurrentLineHighlighted;\n\n            if (bpPoint in visibleBreakpoints) {\n                const {bp, prevBp} = visibleBreakpoints[bpPoint];\n                let desc = null;\n                if (typeof this.props.formatBpDesc === 'function') {\n                    desc = this.props.formatBpDesc(bp, prevBp);\n                } else {\n                    for (const formatBpDesc of this.props.formatBpDesc) {\n                        desc = formatBpDesc(bp, prevBp);\n                        if (desc != null) break;\n                    }\n                }\n\n                if (desc == null) {\n                    throw new Error('Unknown bp type: ' + bpPoint);\n                }\n\n                if (desc) {\n                    explanation = (\n                        <span\n                            style={{fontSize: this.props.fontSize}}\n                            key=\"explanation\"\n                            className=\"code-explanation\"\n                            dangerouslySetInnerHTML={{__html: `\\u00A0\\u00A0\\u00A0~ ${desc}`}}\n                        />\n                    );\n                }\n            }\n\n            let hlCodeHtml = hlLines[i];\n\n            let formattedLine = (\n                <pre className=\"code-line-container\" key=\"pre\">\n                    <code>\n                        <span\n                            style={{fontSize: this.props.fontSize}}\n                            className={isCurrentLineHighlighted ? 'code-highlight' : undefined}\n                            dangerouslySetInnerHTML={{__html: hlCodeHtml}}\n                        />\n                    </code>\n                </pre>\n            );\n            lines.push(\n                <span className=\"line-with-annotation inline-block\" key={`line-${i}`}>\n                    {formattedLine}\n                    {explanation}\n                </span>\n            );\n            lines.push(<br key={`br-${i}`} />);\n        }\n        if (!isAnyLineHighlighted) {\n            throw new Error(`No line found corresponding to \"${activeBp.point}`);\n        }\n\n        return lines;\n    }\n\n    getVisibleBreakpoints(activeBp) {\n        const t1 = performance.now();\n        let visibleBreakpoints = {};\n        let pointToLevel = {};\n        for (let [line, bpPoint, level] of this.props.code) {\n            if (line === '' || bpPoint === '') {\n                continue;\n            }\n            if (level === undefined) {\n                pointToLevel = null;\n                break;\n            }\n            pointToLevel[bpPoint] = level;\n        }\n\n        let prevBp = null;\n        for (let [time, bp] of this.props.breakpoints.entries()) {\n            if (time > this.props.time) {\n                break;\n            }\n\n            if (bp.point in visibleBreakpoints) {\n                let level = pointToLevel[bp.point];\n                for (let visibleBpPoint in visibleBreakpoints) {\n                    if (pointToLevel[visibleBpPoint] >= level) {\n                        delete visibleBreakpoints[visibleBpPoint];\n                    }\n                }\n            }\n\n            visibleBreakpoints[bp.point] = {bp, prevBp};\n            prevBp = bp;\n        }\n\n        return visibleBreakpoints;\n    }\n\n    render() {\n        let activeBp = this.props.breakpoints[this.props.time];\n\n        const visibleBreakpoints = this.getVisibleBreakpoints(activeBp);\n        const lines = this.getCodeWithExplanationHtmlLines(visibleBreakpoints, activeBp);\n\n        return (\n            <SmoothScrollbar\n                ref={this.ssRef}\n                alwaysShowTracks={true}\n                className=\"code-block-with-annotations-scrollbar-container\"\n            >\n                <div\n                    style={{\n                        maxHeight: this.props.height,\n                        lineHeight: this.props.lineHeight,\n                        overflowX: this.props.overflow && 'scroll',\n                    }}\n                    className=\"code-block-with-annotations fix-animation\"\n                >\n                    {lines}\n                </div>\n            </SmoothScrollbar>\n        );\n    }\n\n    getScrollTopTarget() {\n        const scrollbar = this.ssRef.current.scrollbar;\n        const totalLines = this.props.code.length;\n        let activeLine = 0;\n\n        // TODO: remove copy&paste\n        let activeBp = this.props.breakpoints[this.props.time];\n        for (let [i, [_, bpPoint]] of this.props.code.entries()) {\n            if (bpPoint === activeBp.point) {\n                activeLine = i;\n            }\n        }\n\n        if (!scrollbar.size) return {scrollTopTarget: null, needsUpdating: false};\n\n        const scrollHeight = scrollbar.size.content.height;\n        const scrollTop = scrollbar.scrollTop;\n        const containerHeight = scrollbar.getSize().container.height;\n        const estimatedLineHeight = scrollHeight / totalLines;\n\n        const scrollBottom = scrollTop + containerHeight;\n\n        const activeLinePos = (activeLine / totalLines) * scrollHeight;\n        const needsUpdating =\n            activeLinePos < scrollTop + 1.2 * estimatedLineHeight ||\n            activeLinePos > scrollBottom - 2.0 * estimatedLineHeight;\n\n        let scrollTopTarget = activeLinePos - containerHeight / 2;\n        if (scrollTopTarget < 0) {\n            scrollTopTarget = 0;\n        } else if (scrollTopTarget > scrollHeight) {\n            scrollTopTarget = scrollHeight;\n        }\n\n        return {scrollTopTarget, needsUpdating};\n    }\n\n    updateScroll = () => {\n        const {scrollTopTarget, needsUpdating} = this.getScrollTopTarget();\n        const scrollbar = this.ssRef.current.scrollbar;\n        if (needsUpdating) {\n            scrollbar.scrollTo(scrollbar.offset.x, scrollTopTarget, 500);\n        }\n    };\n\n    updateScrollDebounced = _.debounce(this.updateScroll, getUxSettings().CODE_SCROLL_DEBOUNCE_TIME);\n\n    componentDidUpdate() {\n        this.updateScrollDebounced();\n    }\n}\n\n// TODO: remove time from state?\n@observer\nclass TimeSliderWithControls extends React.Component {\n    AUTOPLAY_BASE_TIMEOUT = 1000;\n\n    constructor() {\n        super();\n        this.state = {time: null, autoPlaying: false, speed: 1, userInteracted: false};\n        this.timeoutId = null;\n        this.timeoutStarted = null;\n    }\n\n    unixtimestamp() {\n        return new Date().getTime();\n    }\n\n    static getDerivedStateFromProps(nextProps, state) {\n        return {time: nextProps.time};\n    }\n\n    handleSliderValueChange = value => {\n        this.stop();\n        this.handleTimeChange(value);\n    };\n\n    handleTimeChange = (value, userInteracted = true, autoPlaying = false) => {\n        this.setState(state => ({time: value, autoPlaying, userInteracted: state.userInteracted || userInteracted}));\n        this.props.handleTimeChange(value);\n    };\n\n    prevStep = () => {\n        this.stop();\n        if (this.props.time > 0) {\n            const newTime = this.props.time - 1;\n            this.handleTimeChange(newTime);\n        }\n    };\n\n    nextStep = () => {\n        this.stop();\n        if (this.props.time < this.props.maxTime) {\n            const newTime = this.props.time + 1;\n            this.handleTimeChange(newTime);\n        }\n    };\n\n    firstStep = () => {\n        this.stop();\n        this.handleTimeChange(0);\n    };\n\n    lastStep = () => {\n        this.stop();\n        this.handleTimeChange(this.props.maxTime);\n    };\n\n    getAutoplayTimeout = speed => {\n        return this.AUTOPLAY_BASE_TIMEOUT / (speed || globalSettings.codePlaySpeed);\n    };\n\n    autoPlayNextStep = (userInteracted = false) => {\n        if (this.state.time < this.props.maxTime) {\n            let newTime = this.state.time + 1;\n            if (newTime < this.props.maxTime) {\n                this.timeoutId = setTimeout(this.autoPlayNextStep, this.getAutoplayTimeout());\n                this.timeoutStarted = this.unixtimestamp();\n            } else {\n                this.timeoutId = null;\n            }\n            this.handleTimeChange(newTime, userInteracted, newTime < this.props.maxTime);\n        }\n    };\n\n    repeatPlay = (userInteracted = true) => {\n        this.handleTimeChange(0, userInteracted, true);\n        this.timeoutId = setTimeout(this.autoPlayNextStep, this.getAutoplayTimeout());\n        this.timeoutStarted = this.unixtimestamp();\n    };\n\n    autoPlay = (userInteracted = true) => {\n        if (this.props.time < this.props.maxTime) {\n            this.autoPlayNextStep(userInteracted);\n        } else {\n            this.repeatPlay(userInteracted);\n        }\n    };\n\n    stop = () => {\n        if (this.timeoutId != null) {\n            clearTimeout(this.timeoutId);\n            this.timeoutId = null;\n            this.timeoutStarted = null;\n        }\n        if (this.state.autoPlaying || !this.state.userInteracted) {\n            this.setState({autoPlaying: false, userInteracted: true});\n        }\n    };\n\n    setSpeed = speed => {\n        if (speed !== globalSettings.speed) {\n            if (\n                this.state.autoPlaying &&\n                this.getAutoplayTimeout() - (this.unixtimestamp() - this.timeoutStarted) >\n                    this.getAutoplayTimeout(speed)\n            ) {\n                clearTimeout(this.timeoutId);\n                this.timeoutId = setTimeout(this.autoPlayNextStep, this.getAutoplayTimeout(speed));\n            }\n            globalSettings.setCodePlaySpeed(speed);\n        }\n    };\n\n    static getDerivedStateFromProps(props, state) {\n        if (state.autoPlaying && props.time === props.maxTime) {\n            return {...state, autoPlaying: false};\n        } else {\n            return null;\n        }\n    }\n\n    componentDidUpdate() {\n        if (!this.state.autoPlaying && this.timeoutId) {\n            this.stop();\n        }\n        const stillDefaultAutoplay = this.props.autoplayByDefault && !this.state.userInteracted;\n        if (!this.state.autoPlaying && stillDefaultAutoplay) {\n            this.autoPlay(false);\n        }\n    }\n\n    componentDidMount() {\n        if (this.props.autoplayByDefault) {\n            this.autoPlay(false);\n        }\n    }\n\n    render() {\n        let marks = {};\n        if (this.props.maxTime < 25) {\n            for (let i = 0; i <= this.props.maxTime; ++i) {\n                marks[i] = '';\n            }\n        }\n        const time = this.props.time;\n\n        const button = (onClick, iconId) => {\n            return (\n                <button\n                    key={iconId}\n                    type=\"button\"\n                    className={classNames('btn', 'btn-outline-dark', 'slider-controls-button')}\n                    onClick={onClick}\n                >\n                    <FontAwesomeIcon key={`font-awesome-${iconId}`} icon={iconId} />\n                </button>\n            );\n        };\n\n        let timeControls = [];\n        timeControls.push(button(this.firstStep, 'fast-backward'));\n        timeControls.push(button(this.prevStep, 'step-backward'));\n        if (!this.state.autoPlaying) {\n            const playIcon = this.props.time === this.props.maxTime ? 'redo-alt' : 'play';\n            timeControls.push(button(this.autoPlay, playIcon));\n        } else {\n            timeControls.push(button(this.stop, 'pause'));\n        }\n        timeControls.push(button(this.nextStep, 'step-forward'));\n        timeControls.push(button(this.lastStep, 'fast-forward'));\n\n        let speedControls = [];\n\n        const speeds = [0.5, 1, 2, 4, 8, 16];\n        for (let i = 0; i < speeds.length; ++i) {\n            const speed = speeds[i];\n            if (speed > globalSettings.maxCodePlaySpeed) {\n                break;\n            }\n            const isActive = speed === globalSettings.codePlaySpeed;\n            let label = i === 0 ? `Autoplay speed ${speed}x` : `${speed}x`;\n            speedControls.push(\n                <label key={label} className={classNames('btn', 'btn-outline-dark', {active: isActive})}>\n                    <input type=\"radio\" checked={isActive} onChange={() => this.setSpeed(speed)} /> {label}\n                </label>\n            );\n        }\n\n        return (\n            <React.Fragment>\n                <div className=\"row\">\n                    <div className=\"col-sm-12 col-md-12\">\n                        <div className=\"btn-toolbar slider-controls\">\n                            <div className=\"btn-group btn-group-sm mr-2 mb-1\">{timeControls}</div>\n                            <div className=\"btn-group btn-group-sm btn-group-toggle mr-2 mb-1\">{speedControls}</div>\n                        </div>\n                    </div>\n                </div>\n                <div className=\"row slider-row fix-animation\">\n                    <div className=\"col-md-6 col-sm-12\">\n                        <Slider\n                            marks={marks}\n                            onChange={this.handleSliderValueChange}\n                            min={0}\n                            max={this.props.maxTime}\n                            value={time}\n                            dotStyle={{\n                                top: -1,\n                                height: 12,\n                                width: 12,\n                            }}\n                            handleStyle={{\n                                height: 20,\n                                width: 20,\n                                marginTop: -6,\n                            }}\n                            railStyle={{height: 10}}\n                            trackStyle={{\n                                height: 10,\n                            }}\n                        />\n                    </div>\n                </div>\n            </React.Fragment>\n        );\n    }\n}\n\nexport class VisualizedCode extends React.Component {\n    static FULL_WIDTH = true;\n    static EXTRA_ERROR_BOUNDARY = true;\n\n    constructor(props) {\n        super(props);\n        this.state = {\n            time: props.breakpoints.length - 1,\n            userAdjustedToMax: true,\n            breakpointsUpdatedCounter: 0,\n        };\n        const throttleTime = getUxSettings().TIME_SLIDER_THROTTLE_TIME;\n        if (throttleTime != null) {\n            this.handleTimeChangeThrottled = _.throttle(\n                this.handleTimeChange,\n                getUxSettings().TIME_SLIDER_THROTTLE_TIME\n            );\n        } else {\n            this.handleTimeChangeThrottled = this.handleTimeChange;\n        }\n    }\n\n    handleTimeChange = time => {\n        const userAdjustedToMax = time === this.props.breakpoints.length - 1;\n        this.setState({\n            time: time,\n            userAdjustedToMax,\n        });\n    };\n\n    static getDerivedStateFromProps(props, state) {\n        if (props.breakpoints !== state.breakpoints) {\n            if (!props.keepTimeOnNewBreakpoints) {\n                return {\n                    ...state,\n                    breakpoints: props.breakpoints,\n                    time: props.breakpoints.length - 1,\n                    breakpointsUpdatedCounter: state.breakpointsUpdatedCounter + 1,\n                };\n            } else {\n                return {\n                    ...state,\n                    breakpoints: props.breakpoints,\n                    breakpointsUpdatedCounter: state.breakpointsUpdatedCounter + 1,\n                };\n            }\n        } else {\n            return null;\n        }\n    }\n\n    render() {\n        const windowWidth = this.props.windowWidth;\n        const windowHeight = this.props.windowHeight;\n\n        const tallScreen = windowHeight && windowHeight > 450;\n        const serverSide = windowHeight == null;\n        const smallerFont = tallScreen || serverSide;\n\n        let bp = this.props.breakpoints[this.state.time];\n        const StateVisualization = this.props.stateVisualization;\n\n        let codeHeight;\n        if (windowHeight) {\n            const approximateSliderAndControlsHeight = 100;\n            // Hacky extraspace. Usually 135, but add some more\n            // when play+speed controls and input toolbar get bigger height on narrower screen\n            const extraSpace = 135 + (this.props.windowHeight < 850 ? 50 : 0);\n            codeHeight =\n                this.props.windowHeight -\n                StateVisualization.getExpectedHeight(windowWidth, windowHeight) -\n                approximateSliderAndControlsHeight -\n                extraSpace;\n            if (codeHeight < 225) {\n                codeHeight += 50;\n            }\n            codeHeight = Math.max(codeHeight, smallerFont ? 125 : 90);\n        }\n\n        let time = this.props.keepTimeOnNewBreakpoints\n            ? this.state.userAdjustedToMax\n                ? this.props.breakpoints.length - 1\n                : Math.min(this.state.time, this.props.breakpoints.length - 1)\n            : this.state.time;\n\n        return (\n            <MyErrorBoundary>\n                <div className=\"visualized-code hl-left\">\n                    <TimeSliderWithControls\n                        handleTimeChange={this.handleTimeChangeThrottled}\n                        time={time}\n                        maxTime={this.props.breakpoints.length - 1}\n                        autoplayByDefault={this.props.autoplayByDefault}\n                    />\n                    <DebounceWhenOutOfView\n                        childProps={{\n                            /* TODO: probably better not figure out how to not construct this object every time */\n                            bp: bp,\n                            /* the breakpoints and bpIdx is for chapter4 probing visualization */\n                            breakpoints: this.props.breakpoints,\n                            bpIdx: time,\n                            epoch: this.state.breakpointsUpdatedCounter,\n                        }}\n                        windowHeight={windowHeight}\n                        childFunc={(props, innerRef) => (\n                            <StateVisualization\n                                {...props}\n                                innerRef={innerRef}\n                                windowWidth={windowWidth}\n                                windowHeight={windowHeight}\n                                overflow={serverSide}\n                            />\n                        )}\n                    />\n                    {this.props.comment}\n                    <div className=\"row code-block-row fix-animation\">\n                        <div className=\"col\">\n                            <CodeBlockWithActiveLineAndAnnotations\n                                height={codeHeight}\n                                time={time}\n                                code={this.props.code}\n                                overflow={serverSide}\n                                fontSize={smallerFont ? 12 : 9}\n                                lineHeight={smallerFont ? 1.15 : 0.8}\n                                breakpoints={this.props.breakpoints}\n                                formatBpDesc={this.props.formatBpDesc}\n                            />\n                        </div>\n                    </div>\n                </div>\n            </MyErrorBoundary>\n        );\n    }\n}\n\nexport class HashBoxesComponent extends React.PureComponent {\n    static getExpectedGeometry({boxGeometry: {boxSize, spacingY}}) {\n        return {height: boxSize, rowHeight: boxSize, rowMarginBottom: spacingY, rowsNumber: 1};\n    }\n\n    static getKeys(array) {\n        let res = [];\n        let counterList = new CounterList();\n        for (let i = 0; i < array.length; ++i) {\n            const value = array[i];\n            if (value != null) {\n                const keyPart = pyObjToReactKey(value);\n                let cnt = counterList.inc(0, keyPart);\n                const key = `${keyPart}-${cnt}`;\n                res.push([key]);\n            } else {\n                res.push([`empty-${i}`]);\n            }\n        }\n        return res;\n    }\n\n    static boxFactory(keys, value) {\n        return [[keys[0], {value}]];\n    }\n\n    render() {\n        const height = HashBoxesComponent.getExpectedGeometry(this.props).height;\n        return (\n            <BaseBoxesComponent\n                {...this.props}\n                getKeys={HashBoxesComponent.getKeys}\n                boxFactory={HashBoxesComponent.boxFactory}\n                selectionClass={SingleBoxSelection}\n                height={height}\n            />\n        );\n    }\n}\n\nexport class HashBoxesBrokenComponent extends React.PureComponent {\n    static MAX_BOXES = 4;\n\n    static height(boxSize) {\n        return (HashBoxesBrokenComponent.MAX_BOXES - 0.5) * boxSize;\n    }\n\n    static getExpectedGeometry({boxGeometry: {boxSize, spacingY}}) {\n        return {\n            height: HashBoxesBrokenComponent.height(boxSize),\n            rowHeight: boxSize,\n            rowMarginBottom: spacingY,\n            rowsNumber: 1,\n        };\n    }\n\n    static getKeys(array) {\n        return array.map((value, idx) => {\n            if (value.length != 0) {\n                return value.slice(0, HashBoxesBrokenComponent.MAX_BOXES).map(subValue => pyObjToReactKey(subValue));\n            } else {\n                return [`empty-${idx}`];\n            }\n        });\n    }\n\n    static boxFactory(keys, value) {\n        if (value.length != 0) {\n            return value.slice(0, HashBoxesBrokenComponent.MAX_BOXES).map((subValue, i) => {\n                return [\n                    keys[i],\n                    {\n                        value: subValue,\n                        yRel: 0.7 * (i > 0 ? 1 : 0) + (1 / 3) * 2 * i,\n                        extraStyleWhenAdding: {opacity: 1.0 / Math.pow(2.5, i)},\n                        removedOffset: i === 0 ? undefined : 0,\n                        createdOffset: i === 0 ? undefined : 0,\n                    },\n                ];\n            });\n        } else {\n            return [[keys[0], {value: null}]];\n        }\n    }\n\n    render() {\n        const {boxSize} = this.props.boxGeometry;\n        const height = HashBoxesBrokenComponent.height(boxSize);\n        return (\n            <BaseBoxesComponent\n                {...this.props}\n                getKeys={HashBoxesBrokenComponent.getKeys}\n                boxFactory={HashBoxesBrokenComponent.boxFactory}\n                selectionClass={SingleBoxSelection}\n                height={height}\n            />\n        );\n    }\n}\n\nexport class HashSlotsComponent extends React.PureComponent {\n    static getExpectedGeometry({boxGeometry: {boxSize, spacingY}}) {\n        return {height: 3 * boxSize + 2 * spacingY, rowHeight: boxSize, rowMarginBottom: spacingY, rowsNumber: 3};\n    }\n\n    static boxFactory(keys, value) {\n        const slot = value;\n        return [\n            [keys[1], {value: slot.key, yOffset: 0}],\n            [keys[2], {value: slot.value, yRel: 1}],\n            [keys[0], {value: slot.pyHashCode, yRel: 2}],\n        ];\n    }\n\n    // Relies on the fact that keys are unique\n    static getKeys(array) {\n        const emptyOrKey = (v, idx, name, keyKey) => {\n            if (v != null) {\n                const key = pyObjToReactKey(v);\n                return `${key}-${name}-${keyKey}`;\n            } else {\n                return `empty-${idx}-${name}`;\n            }\n        };\n        return array.map((slot, idx) => {\n            const keyKey = slot.key != null ? pyObjToReactKey(slot.key) : null;\n            return [\n                emptyOrKey(slot.pyHashCode, idx, 'hashCode', keyKey),\n                emptyOrKey(slot.key, idx, 'key', keyKey),\n                emptyOrKey(slot.value, idx, 'value', keyKey),\n            ];\n        });\n    }\n\n    render() {\n        const height = HashSlotsComponent.getExpectedGeometry(this.props).height;\n        return (\n            <BaseBoxesComponent\n                {...this.props}\n                getKeys={HashSlotsComponent.getKeys}\n                boxFactory={HashSlotsComponent.boxFactory}\n                selectionClass={SlotSelection}\n                height={height}\n            />\n        );\n    }\n}\n\nclass CounterList {\n    constructor() {\n        this.counters = [];\n    }\n\n    inc(group, value) {\n        // TODO: this is all ugly\n        // TODO: refactor into a proper defaultdict/defaultlist kind of data structure\n        if (group === this.counters.length) {\n            this.counters.push(new Map());\n        }\n\n        let counter = this.counters[group];\n        let cnt;\n        if (!counter.has(value)) {\n            cnt = 0;\n            counter.set(value, 0);\n        } else {\n            cnt = counter.get(value);\n            cnt++;\n            counter.set(value, cnt + 1);\n        }\n\n        return cnt;\n    }\n}\n\nexport class LineOfBoxesComponent extends React.PureComponent {\n    static getKeys(array) {\n        let counters = [];\n        let keys = [];\n        // Does not support nulls/\"empty\"\n        for (let [idx, values] of array.entries()) {\n            if (!Array.isArray(values)) {\n                values = [values];\n            }\n\n            let currentKeys = [];\n            let counterList = new CounterList();\n\n            for (let [j, value] of values.entries()) {\n                const keyPart = pyObjToReactKey(value);\n\n                let cnt = counterList.inc(j, keyPart);\n                const key = `${keyPart}-${j}-${cnt}`;\n                currentKeys.push(key);\n            }\n            keys.push(currentKeys);\n        }\n\n        return keys;\n    }\n\n    // TODO: This is a bit of a specific hack for key-value pairs\n    // TODO: maybe it should be refactored out elsewhere\n    //\n    // FIXME: also I forgot why I added it. I think it might be not useful\n    // FIXME: Or it might make better rearranging key-value pairs?\n    // FIXME: Yep, I think rearranging key-value pairs would be easier with this thing\n    // FIXME: ...I think...\n    static getKeysForKVPairs(array) {\n        let keys = [];\n        let counterList = new CounterList();\n        // Does not support nulls/\"empty\"\n        for (let i = 0; i < array.length; ++i) {\n            const [k, v] = array[i];\n\n            const keyPart = pyObjToReactKey(k);\n            const cnt = counterList.inc(0, keyPart);\n            const valuePart = pyObjToReactKey(v);\n            keys.push([`${keyPart}-${cnt}-key`, `${keyPart}-${cnt}-${valuePart}-value`]);\n        }\n\n        return keys;\n    }\n\n    static boxFactory(keys, values) {\n        if (keys.length === 1) {\n            return [[keys[0], {value: values}]];\n        }\n\n        return _.zip(keys, values).map(([key, value], j) => {\n            return [key, {value, yRel: j}];\n        });\n    }\n\n    static getExpectedGeometry(props) {\n        const linesCount = props.linesCount || 1;\n        const {boxSize, spacingY} = props.boxGeometry;\n        return {\n            height: linesCount * boxSize + (linesCount - 1) * spacingY,\n            rowHeight: boxSize,\n            rowMarginBottom: spacingY,\n            rowsNumber: linesCount,\n        };\n    }\n\n    render() {\n        const height = LineOfBoxesComponent.getExpectedGeometry(this.props).height;\n        const {kvHack, ...restProps} = this.props;\n        return (\n            <BaseBoxesComponent\n                {...restProps}\n                getKeys={!kvHack ? LineOfBoxesComponent.getKeys : LineOfBoxesComponent.getKeysForKVPairs}\n                boxFactory={LineOfBoxesComponent.boxFactory}\n                selectionClass={LineOfBoxesSelection}\n                selectionProps={{count: this.props.linesCount || 1}}\n                height={height}\n            />\n        );\n    }\n}\n"
  },
  {
    "path": "src/common_formatters.js",
    "content": "import {singularOrPlural} from './util';\n\nexport function commonFormatCheckCollisionLoopEndedPart(idx, fmtCollisionCount) {\n    if (fmtCollisionCount > 0) {\n        return `After ${fmtCollisionCount} ${singularOrPlural(\n            fmtCollisionCount,\n            'collision',\n            'collisions'\n        )}, an empty slot (at <code>${idx}</code>) is found: ${singularOrPlural(\n            fmtCollisionCount,\n            'the collision is',\n            'the collisions are'\n        )} successfully resolved`;\n    } else {\n        return `Slot <code>${idx}</code> is empty: no need to do collision resolution`;\n    }\n}\n\nexport function chapter1_2_FormatCheckCollision(l, idx, fmtCollisionCount) {\n    if (l.get(idx) == null) {\n        return commonFormatCheckCollisionLoopEndedPart(idx, fmtCollisionCount);\n    } else {\n        return `[Try #${fmtCollisionCount + 1}] Slot <code>${idx}</code> is occupied: a collision occurred`;\n    }\n}\n\nconst _defaultIsEmpty = (l, i) => l.get(i) == null;\nexport function commonFormatCheckNotFound(l, idx, fmtCollisionCount, isEmpty = _defaultIsEmpty) {\n    const tryN = fmtCollisionCount + 1;\n    if (isEmpty(l, idx)) {\n        if (fmtCollisionCount == 0) {\n            return `[Try #${tryN}] Slot <code>${idx}</code> is empty, so don't loop`;\n        } else {\n            return `[Try #${tryN}] Slot <code>${idx}</code> is empty, stop looping`;\n        }\n    } else {\n        return `[Try #${tryN}] Slot <code>${idx}</code> is occupied, so check it`;\n    }\n}\n"
  },
  {
    "path": "src/hash_impl_common.js",
    "content": "import {BigNumber} from 'bignumber.js';\n\nexport function repr(obj, allowNull = false) {\n    let res;\n    if (typeof obj === 'number') {\n        res = ['int', obj];\n    } else if (typeof obj === 'string') {\n        res = ['string', obj];\n    } else if (isNone(obj)) {\n        res = ['none'];\n    } else if (isDummy(obj)) {\n        res = ['dummy'];\n    } else if (BigNumber.isBigNumber(obj)) {\n        res = ['bignumber.js', obj.toFixed()];\n    } else if (obj === null && allowNull) {\n        res = ['jsnull', obj];\n    } else {\n        throw new Error(`Unknown key: ${JSON.stringify(obj)}`);\n    }\n\n    return JSON.stringify(res);\n}\n\nexport function displayStr(obj, quoteString = true) {\n    if (typeof obj === 'number' || isNone(obj) || isDummy(obj)) {\n        return obj.toString();\n    } else if (BigNumber.isBigNumber(obj)) {\n        return obj.toFixed();\n    } else if (typeof obj === 'string') {\n        if (quoteString) {\n            return JSON.stringify(obj);\n        } else {\n            return obj;\n        }\n    } else {\n        throw new Error(`Unknown key: ${JSON.stringify(obj)}`);\n    }\n}\n\nclass Int64 {\n    SIZE = 64;\n    JS_NUM_MAX_SIZE = 32;\n\n    constructor(jsNumInt32 = 0) {\n        this.JS_NUM_MAX_SIZE = 32;\n\n        this.data = [];\n        let signBit = jsNumInt32 >= 0 ? 0 : 1;\n\n        for (let i = 0; i < this.JS_NUM_MAX_SIZE; ++i) {\n            let bit = jsNumInt32 & (1 << i) ? 1 : 0;\n            this.data.push(bit);\n        }\n\n        for (let i = this.JS_NUM_MAX_SIZE; i < this.SIZE; ++i) {\n            this.data.push(signBit);\n        }\n    }\n\n    xor(other) {\n        for (let i = 0; i < this.SIZE; ++i) {\n            this.data[i] ^= other.data[i];\n        }\n\n        return this;\n    }\n\n    sign() {\n        return this.data[this.data.length - 1] == 1 ? -1 : 1;\n    }\n\n    inc() {\n        this.data[0] += 1;\n        this._carryOverAll();\n    }\n\n    eq(other) {\n        for (let i = 0; i < this.SIZE; ++i) {\n            if (this.data[i] != other.data[i]) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    add(other) {\n        let carry = 0;\n        for (let i = 0; i < this.SIZE; ++i) {\n            this.data[i] += other.data[i] + carry;\n            carry = (this.data[i] / 2) | 0;\n            this.data[i] %= 2;\n        }\n\n        return this;\n    }\n\n    complement() {\n        for (let i = 0; i < this.SIZE; ++i) {\n            this.data[i] = this.data[i] == 0 ? 1 : 0;\n        }\n\n        return this;\n    }\n\n    mul(other) {\n        let newData = [];\n\n        for (let i = 0; i < this.SIZE; ++i) {\n            newData[i] = 0;\n        }\n\n        for (let i = 0; i < this.SIZE; ++i) {\n            if (this.data[i] === 0) {\n                continue;\n            }\n\n            for (let j = 0; j < this.SIZE - i; ++j) {\n                newData[i + j] += this.data[i] * other.data[j];\n            }\n        }\n\n        this.data = newData;\n        this._carryOverAll();\n\n        return this;\n    }\n\n    toNumber() {\n        let res = 0;\n        for (let i = 0; i < 32; ++i) {\n            if (this.data[i]) {\n                res |= 1 << i;\n            }\n        }\n\n        return res;\n    }\n\n    toStringDestroying() {\n        let sign = this.sign();\n        if (sign < 0) {\n            this.complement().inc();\n        }\n\n        let decPower = [1];\n        let decRes = [0];\n        for (let i = 0; i < this.SIZE; ++i) {\n            let carry = 0;\n            if (this.data[i]) {\n                for (let j = 0; j < decPower.length; ++j) {\n                    if (j >= decRes.length) decRes.push(0);\n\n                    decRes[j] += decPower[j] + carry;\n                    carry = (decRes[j] / 10) | 0;\n                    decRes[j] %= 10;\n                }\n            }\n            if (carry) {\n                decRes.push(carry);\n            }\n\n            carry = 0;\n            for (let j = 0; j < decPower.length; ++j) {\n                decPower[j] = decPower[j] * 2 + carry;\n                carry = (decPower[j] / 10) | 0;\n                decPower[j] %= 10;\n            }\n            if (carry) {\n                decPower.push(carry);\n            }\n        }\n\n        this.data = null;\n\n        let res = [];\n        if (sign < 0) res.push('-');\n        for (let j = decRes.length - 1; j >= 0; j--) {\n            res.push(String.fromCharCode('0'.charCodeAt(0) + decRes[j]));\n        }\n\n        return res.join('');\n    }\n\n    _carryOverAll() {\n        let carry = 0;\n        for (let i = 0; i < this.SIZE; ++i) {\n            this.data[i] += carry;\n            carry = (this.data[i] / 2) | 0;\n            this.data[i] %= 2;\n        }\n    }\n}\n\nfunction pyHashStringAndUnicode(s) {\n    let res = new Int64(s.charCodeAt(0) << 7);\n    let magic = new Int64(1000003);\n\n    for (let i = 0; i < s.length; ++i) {\n        res.mul(magic).xor(new Int64(s.charCodeAt(i)));\n    }\n\n    res.xor(new Int64(s.length));\n\n    if (res.eq(new Int64(-1))) {\n        return '-2';\n    } else {\n        return res.toStringDestroying();\n    }\n}\n\nexport function pyHashString(s) {\n    let sUtf8 = unescape(encodeURIComponent(s));\n    return pyHashStringAndUnicode(sUtf8);\n}\n\nexport function pyHashUnicode(s) {\n    return pyHashStringAndUnicode(s);\n}\n\nexport function pyHashLong(num) {\n    const twoToPyLong_SHIFT = BigNumber(2).pow(30);\n    const BASE = twoToPyLong_SHIFT;\n    const _PyHASH_MODULUS = BigNumber(2)\n        .pow(61)\n        .minus(1);\n\n    let x = BigNumber(0);\n    let sign = 1;\n    if (num.lt(0)) {\n        sign = -1;\n        num = num.negated();\n    }\n\n    let digits = [];\n    while (num.gt(0)) {\n        const d = num.mod(BASE);\n        num = num.idiv(BASE);\n        digits.push(d);\n    }\n\n    for (const d of digits.reverse()) {\n        x = x\n            .times(twoToPyLong_SHIFT)\n            .plus(d)\n            .modulo(_PyHASH_MODULUS);\n    }\n\n    if (sign < 0) {\n        x = x.negated();\n    }\n    if (x.gte(BigNumber(2).pow(63))) {\n        x = BigNumber(2)\n            .pow(64)\n            .minus(x);\n    }\n\n    if (x.eq(-1)) {\n        x = BigNumber(-2);\n    }\n\n    return x;\n}\n\nexport function pyHash(o) {\n    if (typeof o === 'string') {\n        return BigNumber(pyHashUnicode(o));\n    } else if (typeof o === 'number') {\n        // TODO:\n        // throw new Error(`Plain JS numbers are not supported, use BigNumber`)\n        return pyHashLong(BigNumber(o));\n    } else if (BigNumber.isBigNumber(o)) {\n        return pyHashLong(o);\n    } else if (isNone(o)) {\n        // TODO: for None hash seems to be always different\n        return BigNumber(o._hashCode);\n    } else {\n        throw new Error('pyHash called with an object of unknown type: ' + JSON.stringify(o));\n    }\n}\n\nexport class BreakpointFunction {\n    constructor() {\n        this._breakpoints = [];\n        this._extraBpContext = null;\n    }\n\n    addBP(point) {\n        let bp = {\n            point: point,\n        };\n\n        for (let key in this) {\n            if (key[0] !== '_') {\n                const value = this[key];\n                if (value !== undefined && typeof value !== 'function') {\n                    bp[key] = value;\n                }\n            }\n        }\n\n        if (this._extraBpContext) {\n            for (let key in this._extraBpContext) {\n                bp[key] = this._extraBpContext[key];\n            }\n        }\n\n        this._breakpoints.push(bp);\n    }\n\n    setExtraBpContext(extraBpContext) {\n        this._extraBpContext = extraBpContext;\n    }\n\n    getBreakpoints() {\n        return this._breakpoints;\n    }\n}\n\nexport function computeIdx(hashCodeBig, len) {\n    return +hashCodeBig\n        .mod(len)\n        .plus(len)\n        .mod(len)\n        .toString();\n}\n\nexport class HashBreakpointFunction extends BreakpointFunction {\n    computeIdx(hashCodeBig, len) {\n        return computeIdx(hashCodeBig, len);\n    }\n}\n\nclass DummyClass {\n    toString() {\n        return 'DUMMY';\n    }\n}\n\nclass NoneClass {\n    _hashCode = '-9223372036581563745';\n\n    toString() {\n        return 'None';\n    }\n}\n\nexport const DUMMY = new DummyClass();\nexport const None = new NoneClass();\n\nexport function isNone(o) {\n    return o instanceof NoneClass;\n}\n\nexport function isDummy(o) {\n    return o instanceof DummyClass;\n}\n\nexport function EQ(o1, o2) {\n    if (BigNumber.isBigNumber(o1) && (BigNumber.isBigNumber(o2) || typeof o2 === 'number')) {\n        return o1.eq(o2);\n    } else if (BigNumber.isBigNumber(o2) && typeof o1 === 'number') {\n        return o2.eq(o1);\n    }\n\n    return o1 === o2;\n}\n\nfunction signedToUnsigned(num) {\n    if (num.lt(0)) {\n        return num.plus(BigNumber(2).pow(64));\n    } else {\n        return num;\n    }\n}\n\nexport function computePerturb(hashCode) {\n    return signedToUnsigned(hashCode);\n}\n\nexport function nextIdxPerturb(idx, perturb, size) {\n    return +BigNumber(5 * idx + 1)\n        .plus(perturb)\n        .mod(size)\n        .toString();\n}\n\nexport function perturbShift(perturb) {\n    return perturb.idiv(BigNumber(2).pow(5)); // >>= 5\n}\n"
  },
  {
    "path": "src/hash_impl_common.test.js",
    "content": "import {BigNumber} from 'bignumber.js';\nimport {pyHashLong} from './hash_impl_common';\n\ntest('pyHashLong() from short int', () => {\n    expect(pyHashLong(BigNumber(42)).eq(42)).toBe(true);\n\n    expect(pyHashLong(BigNumber(-1)).eq(-2)).toBe(true);\n    expect(pyHashLong(BigNumber(-2)).eq(-2)).toBe(true);\n    expect(pyHashLong(BigNumber(-3)).eq(-3)).toBe(true);\n    expect(pyHashLong(BigNumber(-18)).eq(-18)).toBe(true);\n});\n\ntest('pyHashLong() from longs', () => {\n    expect(pyHashLong(BigNumber('1232432432543654645365437543')).eq(BigNumber('742873684407681575'))).toBe(true);\n    expect(\n        pyHashLong(BigNumber('-12324324325436546453654375433424324324234')).eq(BigNumber('-1100952482444585566'))\n    ).toBe(true);\n    expect(\n        pyHashLong(\n            BigNumber('1232432432543654645365437543342432432423434243242342353463246546342582472359237465243623')\n        ).eq(BigNumber('1877707948436126692'))\n    ).toBe(true);\n    expect(pyHashLong(BigNumber(2).pow(61)).eq(BigNumber(1))).toBe(true);\n    expect(\n        pyHashLong(\n            BigNumber(2)\n                .pow(61)\n                .negated()\n        ).eq(BigNumber(-2))\n    ).toBe(true);\n});\n"
  },
  {
    "path": "src/index.js",
    "content": "import {initAndRender} from './app';\nimport {Chapter1_SimplifiedHash} from './chapter1_simplified_hash.js';\nimport {Chapter2_HashTableFunctions} from './chapter2_hash_table_functions.js';\nimport {Chapter3_HashClass} from './chapter3_hash_class.js';\nimport {Chapter4_RealPythonDict} from './chapter4_real_python_dict.js';\n\nexport const CHAPTER_ID_TO_COMPONENT = {\n    chapter1: Chapter1_SimplifiedHash,\n    chapter2: Chapter2_HashTableFunctions,\n    chapter3: Chapter3_HashClass,\n    chapter4: Chapter4_RealPythonDict,\n};\n\nif (typeof window !== 'undefined') {\n    const chapterIds = window.insidePythonDictChapters;\n    const chapters = chapterIds.map(chapterId => CHAPTER_ID_TO_COMPONENT[chapterId]);\n    initAndRender(chapters, chapterIds);\n}\n"
  },
  {
    "path": "src/inputs.js",
    "content": "import * as React from 'react';\nimport _ from 'lodash';\nimport {BigNumber} from 'bignumber.js';\nimport Sticky from 'react-stickynode';\nimport {\n    parsePyList,\n    dumpPyList,\n    dumpPyDict,\n    parsePyDict,\n    parsePyNumber,\n    parsePyString,\n    parsePyStringOrNumber,\n    parsePyStringOrNumberOrNone,\n} from './py_obj_parsing';\nimport {isNone} from './hash_impl_common';\nimport {isClient} from './util';\n\nimport classNames from 'classnames';\nimport AutosizeInput from 'react-input-autosize';\nimport {Manager, Reference, Popper} from 'react-popper';\nimport {List as ImmutableList, Map as ImmutableMap} from 'immutable';\n\nimport {faUndoAlt} from '@fortawesome/free-solid-svg-icons/faUndoAlt';\nimport {faRedoAlt} from '@fortawesome/free-solid-svg-icons/faRedoAlt';\nimport {FontAwesomeIcon} from '@fortawesome/react-fontawesome';\nimport {library} from '@fortawesome/fontawesome-svg-core';\n\nlibrary.add(faUndoAlt);\nlibrary.add(faRedoAlt);\n\n// TODO: this is still kinda ugly and needs some refactoring\nclass ParsableInputBase extends React.Component {\n    constructor(props) {\n        super(props);\n        // TODO: this is a hack\n        // there should probably be a single source of truth\n        this.state = {\n            valueRaw: this.props.dumpValue(this.props.value),\n            value: this.props.value,\n            error: null,\n            lastError: null,\n        };\n        this.inputComponentRef = React.createRef();\n        this.lastScrollLeft = null;\n    }\n\n    forceSetValue(value) {\n        this.setState({\n            valueRaw: this.props.dumpValue(value),\n            value: value,\n            valueId: this.props.valueId != null ? this.props.valueId + 1 : null,\n        });\n    }\n\n    handleChange = event => {\n        try {\n            let newState = {\n                valueRaw: event.target.value,\n                lastError: this.state.error || this.state.lastError,\n                error: null,\n                value: this.props.parseValue(event.target.value),\n                valueId: this.props.valueId != null ? this.props.valueId + 1 : null,\n            };\n\n            this.setState(newState);\n            this.props.onChange(newState.value);\n        } catch (e) {\n            this.setState({\n                valueRaw: event.target.value,\n                lastError: e,\n                error: e,\n            });\n        }\n    };\n}\n\nclass ParsableInputBlock extends ParsableInputBase {\n    handleSelect = () => {\n        const scrollLeft = this.inputComponentRef.current.scrollLeft;\n        if (scrollLeft !== this.lastScrollLeft) {\n            this.forceUpdate();\n        }\n    };\n\n    measureCharWidth = () => {\n        const exampleStr = 'qwerty1234567890asdfghzxcb';\n        // FROM: https://stackoverflow.com/questions/44302717/get-input-text-width-when-typing\n        const c = document.createElement('canvas');\n        const ctx = c.getContext('2d');\n\n        const prop = ['font-style', 'font-variant', 'font-weight', 'font-size', 'font-family'];\n        let font = '';\n        for (let p of prop) {\n            font += window.getComputedStyle(this.inputComponentRef.current, null).getPropertyValue(p) + ' ';\n        }\n        ctx.font = font;\n\n        const txtWidth = ctx.measureText(exampleStr).width;\n\n        return txtWidth / exampleStr.length;\n    };\n\n    formatErrorMessage(e) {\n        const padding = 8; // TODO: unhardcode*/\n        const {scrollLeft, clientWidth} = this.inputComponentRef.current;\n        const charWidth = this.charWidth;\n\n        const visibleLeft = Math.ceil(scrollLeft / charWidth);\n        const visibleRight = Math.floor((scrollLeft + clientWidth) / charWidth);\n        const totalVisible = visibleRight - visibleLeft;\n\n        const text = e.message;\n        const pos = e.pos;\n        // TODO: check math for off-by-one type problems\n\n        // TODO: what if the error message does not fit on scren?\n        this.lastScrollLeft = scrollLeft;\n        const relativePos = pos - visibleLeft;\n        if (visibleLeft <= pos && pos <= visibleRight) {\n            if (text.length < relativePos - 1) {\n                return _.padEnd(text + ' ', relativePos, '-') + '^';\n            } else if (text.length + relativePos + 5 < totalVisible) {\n                return _.padStart('', relativePos, ' ') + '^--- ' + text;\n            } else {\n                return [_.padStart('', relativePos, ' ') + '^', <br key=\"br-sep\" />, text];\n            }\n        } else if (pos < visibleLeft) {\n            return '<--- ' + text;\n        } else {\n            return _.padEnd(text + ' ', totalVisible - 4, '-') + '>';\n        }\n    }\n\n    render() {\n        let error;\n        if (this.state.error) {\n            // TODO: check if -1 is necessary\n            const width = this.inputComponentRef.current.offsetWidth - 1;\n            const errorText = this.formatErrorMessage(this.state.error);\n            // TODO: does not resize back properly if stretched with error\n            error = (\n                <div\n                    className=\"invalid-feedback invalid-feedback-block-parsable-input\"\n                    style={{width}}\n                    key=\"error-text\"\n                >\n                    {errorText}\n                </div>\n            );\n        }\n\n        const className = classNames('parsable-input', 'form-control', 'form-control-sm', {\n            'is-invalid': !!error,\n        });\n        const divClassNames = classNames('parsable-input-with-error-div', 'parsable-input-block');\n\n        return (\n            <div className={divClassNames}>\n                <input\n                    type=\"text\"\n                    className={className}\n                    value={this.state.valueRaw}\n                    onChange={this.handleChange}\n                    ref={this.inputComponentRef}\n                    onSelect={this.handleSelect}\n                    key=\"input\"\n                />\n                {error}\n            </div>\n        );\n    }\n\n    componentDidMount() {\n        if (isClient) {\n            this.charWidth = this.measureCharWidth();\n        }\n    }\n}\n\nclass ParsableInputInline extends ParsableInputBase {\n    static getDerivedStateFromProps(props, state) {\n        // TODO: this is a hack\n        // TODO: also if the user changes both inputs fast enough, there may be some issues\n        if (props.valueId != null && (state.valueId == null || state.valueId < props.valueId)) {\n            return {\n                ...state,\n                valueId: props.valueId,\n                value: props.value,\n                valueRaw: props.dumpValue(props.value),\n                lastError: state.error || state.lastError,\n                error: null,\n            };\n        } else {\n            return null;\n        }\n    }\n\n    tryAnotherClick = () => {\n        const last = this.state.anotherValue?.last;\n        let res;\n        do {\n            res = this.props.anotherValue(this.state, this.setState.bind(this));\n        } while (res === last || res === this.state.valueRaw);\n\n        this.setState({\n            anotherValue: {\n                last: res,\n            },\n            valueRaw: this.props.dumpValue(res),\n            valueId: this.props.valueId + 1,\n            value: res,\n            error: null,\n            lastError: this.state.error || this.state.lastError,\n        });\n\n        this.props.onChange(res);\n    };\n\n    render() {\n        let errorText;\n        if (this.state.error) {\n            errorText = this.state.error.message;\n        }\n        let lastErrorText;\n        if (this.state.lastError) {\n            lastErrorText = this.state.lastError.message;\n        }\n\n        return (\n            <ParsableInputInlineImpl\n                anotherValue={this.props.anotherValue}\n                tryAnotherClick={this.tryAnotherClick}\n                onChange={this.handleChange}\n                errorText={errorText}\n                lastErrorText={lastErrorText}\n                value={this.state.valueRaw}\n            />\n        );\n    }\n}\n\nclass ParsableInputInlineImpl extends React.Component {\n    render() {\n        const errorText = this.props.errorText;\n        const lastErrorText = this.props.lastErrorText;\n        let tryAnotherButtonDiv;\n        if (this.props.anotherValue) {\n            tryAnotherButtonDiv = (\n                <div className=\"input-group-append\" key=\"try-another-button-div\">\n                    <button className=\"btn btn-secondary\" type=\"button\" onClick={this.props.tryAnotherClick}>\n                        Try another\n                    </button>\n                </div>\n            );\n        }\n        const inputClassName = classNames('parsable-input', 'form-control', 'form-control-sm', 'fc-inline', {\n            'mr-0': !!this.props.tryAnotherClick,\n            'is-invalid': !!errorText,\n        });\n        const divClassNames = classNames(\n            'parsable-input-with-error-div',\n            'inline-block',\n            'parsable-input-inline',\n            'mr-3'\n        );\n        return (\n            <div className={divClassNames}>\n                <Manager>\n                    <Reference>\n                        {({ref}) => (\n                            <div className=\"input-group input-group-sm\">\n                                <input\n                                    ref={ref}\n                                    type=\"text\"\n                                    className={inputClassName}\n                                    value={this.props.value}\n                                    onChange={this.props.onChange}\n                                    key=\"input\"\n                                />\n                                {tryAnotherButtonDiv}\n                            </div>\n                        )}\n                    </Reference>\n                    <Popper placement=\"bottom\">\n                        {({ref, style, placement, arrowProps}) => (\n                            <div\n                                ref={ref}\n                                style={style}\n                                data-placement={placement}\n                                className={classNames(\n                                    'popover',\n                                    'bs-popover-bottom',\n                                    errorText ? 'show' : 'hide',\n                                    'fade'\n                                )}\n                            >\n                                <div className=\"arrow\" ref={arrowProps.ref} style={arrowProps.style} />\n                                <div className=\"popover-body\">{errorText || lastErrorText}</div>\n                            </div>\n                        )}\n                    </Popper>\n                </Manager>\n            </div>\n        );\n    }\n}\n\nclass ParsableInputAutogrowing extends ParsableInputBase {\n    render() {\n        return (\n            <AutosizeInput\n                minWidth={140}\n                type=\"text\"\n                className=\"parsable-input-autosize\"\n                value={this.state.valueRaw}\n                onChange={this.handleChange}\n            />\n        );\n    }\n}\n\nexport function ParsableInput(props) {\n    const {inputComponentRef, ...restProps} = props;\n\n    if (props.autogrowing) {\n        return <ParsableInputAutogrowing ref={inputComponentRef} {...restProps} />;\n    }\n\n    if (props.inline) {\n        return <ParsableInputInline ref={inputComponentRef} {...restProps} />;\n    } else {\n        return <ParsableInputBlock ref={inputComponentRef} {...restProps} />;\n    }\n}\n\nexport function PyListInput({inputComponentRef, extraValueValidator, allowDuplicates, minSize, ...restProps}) {\n    return (\n        <ParsableInput\n            {...restProps}\n            dumpValue={dumpPyList}\n            parseValue={s => parsePyList(s, allowDuplicates, minSize, extraValueValidator)}\n            inputComponentRef={inputComponentRef}\n        />\n    );\n}\n\nexport function PyDictInput({inputComponentRef, minSize, ...restProps}) {\n    return (\n        <ParsableInput\n            {...restProps}\n            dumpValue={dumpPyDict}\n            parseValue={s => parsePyDict(s, minSize)}\n            inputComponentRef={inputComponentRef}\n        />\n    );\n}\n\nfunction _dumpStringOrNumOrNone(obj) {\n    if (BigNumber.isBigNumber(obj)) {\n        return obj.toString();\n    } else if (typeof obj === 'number' || isNone(obj)) {\n        return obj.toString();\n    } else {\n        return JSON.stringify(obj);\n    }\n}\n\nexport function PyNumberInput({inputComponentRef, ...restProps}) {\n    return (\n        <ParsableInput\n            {...restProps}\n            dumpValue={_dumpStringOrNumOrNone}\n            parseValue={parsePyNumber}\n            inputComponentRef={inputComponentRef}\n        />\n    );\n}\n\nexport function PyStringInput({inputComponentRef, ...restProps}) {\n    return (\n        <ParsableInput\n            {...restProps}\n            dumpValue={_dumpStringOrNumOrNone}\n            parseValue={parsePyString}\n            inputComponentRef={inputComponentRef}\n        />\n    );\n}\n\nexport function PyStringOrNumberInput({inputComponentRef, ...restProps}) {\n    return (\n        <ParsableInput\n            {...restProps}\n            dumpValue={_dumpStringOrNumOrNone}\n            parseValue={parsePyStringOrNumber}\n            inputComponentRef={inputComponentRef}\n        />\n    );\n}\n\nexport function PySNNInput({inputComponentRef, ...restProps}) {\n    return (\n        <ParsableInput\n            {...restProps}\n            dumpValue={_dumpStringOrNumOrNone}\n            parseValue={parsePyStringOrNumberOrNone}\n            inputComponentRef={inputComponentRef}\n        />\n    );\n}\n\nexport class BlockInputToolbar extends React.Component {\n    static FULL_WIDTH = true;\n    static EXTRA_ERROR_BOUNDARY = true;\n\n    constructor() {\n        super();\n\n        this.wrapperRef = React.createRef();\n        this.state = {\n            height: null,\n        };\n    }\n\n    render() {\n        const {bottomBoundary, ...restProps} = this.props;\n        const wideScreen = this.props.windowWidth && this.props.windowWidth > 600;\n        const tallScreen = this.props.windowHeight && this.props.windowHeight > 450;\n        // XXX: kind of a hack for mobiles when there is a keyboard. Maybe it is better here to check for browser platfrom\n        const squareishScreen =\n            this.props.windowWidth && this.props.windowHeight && 1.7 * this.props.windowHeight > this.props.windowWidth;\n        // TODO: 50, 10 are hardcoded and I am not sure why\n        return (\n            <div className=\"my-sticky-outer-outer-wrapper-this-time-really\">\n                <div\n                    style={{\n                        height: this.state.height ? this.state.height + 10 : undefined,\n                        paddingBottom: this.state.height ? undefined : 10,\n                    }}\n                >\n                    <Sticky innerZ={10} bottomBoundary={bottomBoundary} enabled={tallScreen || squareishScreen}>\n                        <div className=\"my-sticky-wrapper\" ref={this.wrapperRef}>\n                            <BlockInputToolbarImpl {...restProps} />\n                        </div>\n                    </Sticky>\n                </div>\n            </div>\n        );\n    }\n\n    updateHeight() {\n        const height = this.wrapperRef.current.offsetHeight;\n        if (height !== this.state.height) {\n            this.setState({\n                height,\n            });\n        }\n    }\n\n    componentDidMount() {\n        this.updateHeight();\n    }\n\n    componentDidUpdate() {\n        this.updateHeight();\n    }\n}\n\nclass BlockInputToolbarImpl extends React.Component {\n    constructor() {\n        super();\n\n        this.state = {\n            valuesStack: new ImmutableList(),\n            valuesStackIndex: 0,\n            value: null,\n            instantUpdates: true,\n        };\n\n        this.inputComponentRef = null;\n    }\n\n    setInputComponentRef = ref => {\n        this.inputComponentRef = ref;\n    };\n\n    static getDerivedStateFromProps(props, state) {\n        if (state.valuesStack.isEmpty()) {\n            return {\n                valuesStack: state.valuesStack.push(props.initialValue),\n            };\n        } else {\n            return null;\n        }\n    }\n\n    hackyPossibleWorkingDeepEqual(o1, o2) {\n        if (o1 === o2) return true;\n\n        if (BigNumber.isBigNumber(o1)) return o1.eq(o2);\n\n        if (Array.isArray(o1) && Array.isArray(o2)) {\n            if (o1.length != o2.length) {\n                return false;\n            }\n\n            for (let [v1, v2] of _.zip(o1, o2)) {\n                if (!this.hackyPossibleWorkingDeepEqual(v1, v2)) {\n                    return false;\n                }\n            }\n\n            return true;\n        }\n\n        return false;\n    }\n\n    _updateStack(value) {\n        let stack = this.state.valuesStack;\n        let idx = this.state.valuesStackIndex;\n        if (!stack.isEmpty() && stack.get(idx) === value) {\n            return false;\n        }\n        if (this.hackyPossibleWorkingDeepEqual(stack.get(idx), value)) return false;\n\n        stack = stack.slice(0, idx + 1).push(value);\n        idx = stack.size - 1;\n\n        this.setState({valuesStack: stack, valuesStackIndex: idx, value});\n        return true;\n    }\n\n    handleChange = value => {\n        if (this.state.instantUpdates) {\n            const stackUpdated = this._updateStack(value);\n            if (stackUpdated) {\n                this.props.onChange(value);\n            }\n        } else {\n            this.setState({value});\n        }\n    };\n\n    commitValueIfNecessary = () => {\n        if (this.state.value && this.state.valuesStack.get(this.state.valuesStackIndex) !== this.state.value) {\n            this._updateStack(this.state.value);\n            this.props.onChange(this.state.value);\n        }\n    };\n\n    handleUpdateClick = () => {\n        this.commitValueIfNecessary();\n    };\n\n    handleIUChange = () => {\n        this.setState({\n            instantUpdates: !this.state.instantUpdates,\n        });\n        this.commitValueIfNecessary();\n    };\n\n    handleUndoClick = () => {\n        let idx = this.state.valuesStackIndex;\n        if (idx > 0) {\n            idx--;\n            const value = this.state.valuesStack.get(idx);\n            this.props.onChange(value);\n            this.inputComponentRef.forceSetValue(value);\n            this.setState({\n                valuesStackIndex: idx,\n                value,\n            });\n        }\n    };\n\n    handleRedoClick = () => {\n        let idx = this.state.valuesStackIndex;\n        if (idx < this.state.valuesStack.size - 1) {\n            idx++;\n            const value = this.state.valuesStack.get(idx);\n            this.props.onChange(value);\n            this.inputComponentRef.forceSetValue(value);\n            this.setState({\n                valuesStackIndex: idx,\n                value,\n            });\n        }\n    };\n\n    render() {\n        const Input = this.props.input;\n        const stack = this.state.valuesStack;\n        const idx = this.state.valuesStackIndex;\n        const undoCount = idx;\n        const redoCount = stack.size - idx - 1;\n        const updateDisabled =\n            this.state.value == null ||\n            this.state.value === stack.get(idx) ||\n            this.hackyPossibleWorkingDeepEqual(this.state.value, stack.get(idx));\n        return (\n            <div className=\"row row-block-input-toolbar\">\n                <div className=\"col col-input\">\n                    <Input\n                        {...this.props.inputProps}\n                        inputComponentRef={this.setInputComponentRef}\n                        value={this.props.initialValue}\n                        onChange={this.handleChange}\n                    />\n                </div>\n                <div className=\"col-auto col-buttons\">\n                    <div className=\"btn-toolbar\">\n                        <div className=\"form-check-inline form-check mr-2\">\n                            <label className=\"form-check-label\">\n                                <input\n                                    type=\"checkbox\"\n                                    className=\"form-check-input\"\n                                    checked={this.state.instantUpdates}\n                                    onChange={this.handleIUChange}\n                                />\n                                Instant updates\n                            </label>\n                        </div>\n                        <div className=\"btn-group btn-group-sm\">\n                            <button\n                                type=\"button\"\n                                className={classNames('btn', 'btn-primary', {\n                                    invisible: this.state.instantUpdates,\n                                })}\n                                onClick={this.handleUpdateClick}\n                                disabled={updateDisabled}\n                            >\n                                Update\n                            </button>\n                        </div>\n                        <div className=\"btn-group btn-group-sm ml-3\">\n                            <button\n                                type=\"button\"\n                                className=\"btn btn-primary\"\n                                onClick={this.handleUndoClick}\n                                disabled={undoCount === 0}\n                            >\n                                <FontAwesomeIcon icon={'undo-alt'} />\n                                <span className=\"input-toolbar-button-label\"> Undo</span>{' '}\n                                <span className=\"badge badge-light badge-undo-redo-count\">{undoCount}</span>\n                            </button>\n                            <button\n                                type=\"button\"\n                                className=\"btn btn-primary\"\n                                onClick={this.handleRedoClick}\n                                disabled={redoCount === 0}\n                            >\n                                <FontAwesomeIcon icon={'redo-alt'} />\n                                <span className=\"input-toolbar-button-label\"> Redo</span>{' '}\n                                <span className=\"badge badge-light badge-undo-redo-count\">{redoCount}</span>\n                            </button>\n                        </div>\n                    </div>\n                </div>\n            </div>\n        );\n    }\n}\n"
  },
  {
    "path": "src/mustache/chapter1.json",
    "content": "{\n    \"chapters\": \"['chapter1']\"\n}\n"
  },
  {
    "path": "src/mustache/chapter2.json",
    "content": "{\n    \"chapters\": \"['chapter2']\"\n}\n"
  },
  {
    "path": "src/mustache/chapter3.json",
    "content": "{\n    \"chapters\": \"['chapter3']\"\n}\n"
  },
  {
    "path": "src/mustache/chapter4.json",
    "content": "{\n    \"chapters\": \"['chapter4']\"\n}\n"
  },
  {
    "path": "src/page.html.template",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <title>Inside python dict &mdash; an explorable explanation</title>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n\n    <link href=\"https://fonts.googleapis.com/css?family=Montserrat:400,700,700i\" rel=\"stylesheet\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:400,400i,700,700i\" rel=\"stylesheet\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto+Mono:400,400i,500,500i,700,700i\" rel=\"stylesheet\">\n  </head>\n  <body>\n      <div id=\"root\"></div>\n      <script type=\"text/javascript\">window.insidePythonDictChapters={{{chapters}}}</script>\n  </body>\n</html>\n"
  },
  {
    "path": "src/probing_visualization.js",
    "content": "import * as React from 'react';\nimport {Map as ImmutableMap, List as ImmutableList, Set as ImmutableSet} from 'immutable';\nimport {\n    BreakpointFunction,\n    pyHash,\n    computeIdx,\n    computePerturb,\n    perturbShift,\n    nextIdxPerturb,\n    displayStr,\n} from './hash_impl_common';\n\nimport {isDefinedSmallBoxScreen} from './util';\n\nconst d3 = Object.assign(\n    {},\n    require('d3-selection'),\n    require('d3-interpolate'),\n    require('d3-shape'),\n    require('d3-transition'),\n    require('d3-array')\n);\n\nconst DEFAULT_PROBING_BOX_GEOMETRY = {\n    boxSize: 40,\n    boxSpacing: 8,\n};\n\nconst SMALLER_PROBING_BOX_GEOMETRY = {\n    boxSize: 30,\n    boxSpacing: 6,\n};\n\nclass ProbingVisualizationImpl extends React.Component {\n    TRANSITION_TIME = 500;\n    TOP_SPACE = 66;\n    BOTTOM_SPACE = 66;\n\n    transitionId = null;\n    lastTransitionId = 0;\n\n    constructor(props) {\n        super(props);\n\n        this.state = {\n            firstRender: true,\n            bpIdx: props.bpIdx,\n            breakpoints: props.breakpoints,\n            transitionRunning: false,\n            transitionToBpIdx: null,\n            boxSize: null,\n            boxSpacing: null,\n        };\n    }\n\n    setRef = node => {\n        this.gChild = node;\n    };\n\n    shouldComponentUpdate(nextProps, nextState) {\n        let waitForTransition = false;\n        let shouldUpdate = false;\n        if (this.state.boxSize !== nextState.boxSize || this.state.boxSize == null || nextState.boxSize == null)\n            return true;\n\n        /*if (nextProps.boxSize !== this.props.boxSize) {\n            shouldUpdate = true;\n            waitForTransition = true;\n        } else */\n        if (nextProps.breakpoints !== nextState.breakpoints) {\n            waitForTransition = true;\n            shouldUpdate = true;\n        } else if (\n            nextProps.bpIdx !== nextState.bpIdx &&\n            (nextState.transitionToBpIdx == null || nextProps.bpIdx !== nextState.transitionToBpIdx)\n        ) {\n            shouldUpdate = true;\n            waitForTransition =\n                nextState.transitionToBpIdx != null &&\n                ((nextState.bpIdx > nextState.transitionToBpIdx && nextProps.bpIdx > nextState.transitionToBpIdx) ||\n                    (nextState.bpIdx < nextState.transitionToBpIdx && nextProps.bpIdx < nextState.transitionToBpIdx));\n        }\n\n        return shouldUpdate && (!nextState.transitionRunning || !waitForTransition);\n    }\n\n    render() {\n        const computedHeight =\n            this.state.boxSize +\n            this.TOP_SPACE +\n            this.BOTTOM_SPACE +\n            10 +\n            30 +\n            25; /* TODO FIXME: this is all a bunch of hacks because repeatedAdj can make patterns overlap TOP_SPACE / BOTTOM_SPACE */\n\n        let {fixedHeight, adjustTop} = this.props;\n        const height = fixedHeight ? fixedHeight : computedHeight;\n        adjustTop = adjustTop || 0;\n\n        return (\n            <div ref={this.props.innerRef}>\n                <svg width={10 + this.props.slotsCount * (this.state.boxSize + this.state.boxSpacing)} height={height}>\n                    <defs>\n                        {['blue', 'green'].map(color => (\n                            <marker\n                                id={`arrow-${color}`}\n                                key={`arrow-${color}`}\n                                markerUnits=\"strokeWidth\"\n                                markerWidth=\"10\"\n                                markerHeight=\"10\"\n                                viewBox=\"0 0 12 12\"\n                                refX=\"6\"\n                                refY=\"6\"\n                                orient=\"auto\"\n                            >\n                                <path d=\"M2,2 L10,6 L2,10 L6,6 L2,2\" style={{fill: color}} />\n                            </marker>\n                        ))}\n                    </defs>\n                    <g ref={this.setRef} transform={`translate(0, ${adjustTop + 30})`} />\n                </svg>\n            </div>\n        );\n    }\n\n    transitionEnd() {\n        const newBpIdx = this.transitionToBpIdx;\n        this.transitionId = null;\n        if (this.state.transitionRunning) {\n            this.setState({\n                transitionRunning: false,\n                bpIdx: this.state.transitionToBpIdx,\n                transitionToBpIdx: null,\n            });\n        }\n    }\n\n    // TODO: hacky handling of boxSize changing (also if only boxSpacing changes, this may not work properly (noticeable on the initial render)\n    d3render() {\n        const slotsCount = this.props.slotsCount;\n\n        const bp = this.props.breakpoints[this.props.bpIdx];\n        let links = bp.links.toJS();\n        let startBoxIdx = bp.startIdx != null ? bp.startIdx : null;\n\n        let linksStartIdx = [];\n        let nextIdxRepeatedAdjustment = [];\n        for (let i = 0; i < links.length; ++i) {\n            let counter = {};\n            nextIdxRepeatedAdjustment.push([]);\n            for (let j = 0; j < links[i].length; ++j) {\n                const nextIdx = links[i][j].nextIdx;\n                if (!(nextIdx in counter)) {\n                    counter[nextIdx] = 0;\n                } else {\n                    counter[nextIdx]++;\n                }\n                linksStartIdx.push([i, j]);\n                nextIdxRepeatedAdjustment[i].push(counter[nextIdx]);\n            }\n        }\n\n        const oldLinks = this.oldLinks;\n        const oldBoxSize = this.oldBoxSize;\n        const oldNextIdxRepeatedAdjustment = this.oldNextIdxRepeatedAdjustment;\n\n        let transitionTime;\n        let newState = {\n            transitionToBpIdx: this.props.bpIdx,\n            breakpoints: this.props.breakpoints,\n        };\n        if (this.state.firstRender) {\n            newState.firstRender = false;\n            transitionTime = 0;\n        } else {\n            transitionTime = this.TRANSITION_TIME;\n            newState.transitionRunning = true;\n        }\n\n        let t = d3.transition().duration(transitionTime);\n\n        this.lastTransitionId++;\n        let transitionId = this.lastTransitionId;\n        this.transitionId = transitionId;\n\n        t.on('end', () => {\n            if (this.transitionId === transitionId) {\n                // XXX: this is a hack for d3, because .end() callbacks seem to be executed in the weird order\n                // XXX: it is necessary, because .end() removes some necessary classes as well\n                setTimeout(() => this.transitionEnd(), 0);\n            }\n        });\n\n        let g = d3.select(this.gChild);\n        let lineFunction = d3\n            .line()\n            .x(function(d) {\n                return d.x;\n            })\n            .y(function(d) {\n                return d.y;\n            })\n            .curve(d3.curveMonotoneX);\n\n        const {boxSize, boxSpacing} = this.state;\n        // FIXME: this is more of hack to force re-rendering of links\n        const boxSizeChanged = boxSize !== oldBoxSize;\n        let rects = g.selectAll('rect').data(d3.range(slotsCount));\n        rects\n            /*.attr('x', (d, i) => (boxSize + boxSpacing) * i)\n            .attr('y', this.TOP_SPACE)\n            .attr('width', boxSize)\n            .attr('height', boxSize)*/\n            .enter()\n            .append('rect')\n            .style('fill', '#dadada')\n            .attr('x', (d, i) => (boxSize + boxSpacing) * i)\n            .attr('y', this.TOP_SPACE)\n            .attr('width', boxSize)\n            .attr('height', boxSize)\n            .merge(rects)\n            .style('stroke', (d, i) => (i === startBoxIdx ? 'green' : 'none'))\n            .style('stroke-width', 1);\n\n        const arrowLinePointsAsArray = (i1, i2, repeatedAdj) => {\n            let ystart, yend, ymid;\n\n            let xstartAdjust, xendAdjust;\n            if (i1 < i2) {\n                ystart = this.TOP_SPACE;\n                yend = this.TOP_SPACE;\n                ymid = this.TOP_SPACE * (1 - (Math.max(i2 - i1, 1) + repeatedAdj) / slotsCount);\n                xstartAdjust = boxSize * 0.66;\n                xendAdjust = boxSize * 0.33;\n            } else if (i1 == i2) {\n                ystart = this.TOP_SPACE;\n                yend = this.TOP_SPACE;\n                ymid = this.TOP_SPACE * (1 - (1 + repeatedAdj) / slotsCount);\n                xstartAdjust = boxSize * 0.33;\n                xendAdjust = boxSize * 0.66;\n            } else {\n                const yOffset = this.TOP_SPACE + boxSize;\n                ystart = yOffset;\n                yend = yOffset;\n                ymid = yOffset + this.BOTTOM_SPACE * ((Math.max(i1 - i2, 1) + repeatedAdj) / slotsCount);\n                xstartAdjust = boxSize * 0.33;\n                xendAdjust = boxSize * 0.66;\n            }\n            const xstart = (boxSize + boxSpacing) * i1 + xstartAdjust;\n            const xend = (boxSize + boxSpacing) * i2 + xendAdjust;\n            const xmid = (xstart + xend) / 2;\n\n            return [[xstart, ystart], [xmid, ymid], [xend, yend]];\n        };\n\n        const toPoints = array => array.map(([x, y]) => ({x, y}));\n        const arrowLinePoints = (i1, i2, repeatedAdj) => toPoints(arrowLinePointsAsArray(i1, i2, repeatedAdj));\n        const getLinkColor = ([start, idx]) => {\n            const perturbLink = links[start][idx].perturbLink;\n            return perturbLink ? 'green' : 'blue';\n        };\n        const getLinkArrow = ([start, idx]) => {\n            return `url(#arrow-${getLinkColor([start, idx])})`;\n        };\n\n        let updatePaths = g.selectAll('path').data(linksStartIdx, d => d);\n        const enterPaths = updatePaths.enter();\n        const exitPaths = updatePaths.exit();\n\n        enterPaths\n            .append('path')\n            .style('stroke', getLinkColor)\n            .style('stroke-width', 1)\n            .style('fill', 'none')\n            .attr('d', ([start, idx]) => {\n                let end = links[start][idx].nextIdx;\n                const repeatedAdj = nextIdxRepeatedAdjustment[start][idx];\n                const lp = arrowLinePoints(start, end, repeatedAdj);\n                return lineFunction(lp);\n            })\n            .each(function(d, i) {\n                const node = this;\n                const totalLength = node.getTotalLength();\n                const selected = d3.select(node);\n                selected\n                    .classed('entering', true)\n                    .attr('stroke-dasharray', totalLength + ' ' + totalLength)\n                    .attr('stroke-dashoffset', totalLength)\n                    .transition(t)\n                    .attr('stroke-dashoffset', 0)\n                    .on('end', () => {\n                        selected.attr('marker-end', getLinkArrow(d));\n                        selected.classed('entering', false);\n                    });\n            });\n\n        updatePaths\n            .filter(function(d, i) {\n                const [start, idx] = d;\n                return (\n                    !d3.select(this).classed('entering') ||\n                    boxSizeChanged ||\n                    oldLinks[start][idx].nextIdx != links[start][idx].nextIdx\n                );\n            })\n            .style('stroke', getLinkColor)\n            .attr('stroke-dasharray', null)\n            .attr('stroke-dashoffset', null)\n            .transition(t)\n            .attrTween('d', ([start, idx]) => {\n                let end = links[start][idx].nextIdx;\n                let oldEnd = oldLinks[start][idx].nextIdx;\n                const oldRepeatedAdj = oldNextIdxRepeatedAdjustment[start][idx];\n                const repeatedAdj = nextIdxRepeatedAdjustment[start][idx];\n                const oldLp = arrowLinePoints(start, oldEnd, oldRepeatedAdj);\n                const lp = arrowLinePoints(start, end, repeatedAdj);\n                const ip = d3.interpolateArray(oldLp, lp);\n                return t => lineFunction(ip(t));\n            })\n            .attr('marker-end', getLinkArrow);\n\n        exitPaths\n            .filter(function(d, i) {\n                return !d3.select(this).classed('exiting');\n            })\n            .classed('exiting', true)\n            .each(function() {\n                const node = this;\n                const totalLength = node.getTotalLength();\n                const selected = d3.select(node);\n                selected\n                    .attr('stroke-dasharray', totalLength + ' ' + totalLength)\n                    .attr('stroke-dashoffset', 0)\n                    .attr('marker-end', null)\n                    .transition(t)\n                    .attr('stroke-dashoffset', totalLength)\n                    .remove();\n            });\n\n        this.oldLinks = links;\n        this.oldBoxSize = boxSize;\n        this.oldNextIdxRepeatedAdjustment = nextIdxRepeatedAdjustment;\n        this.setState(newState);\n    }\n\n    _initOrUpd() {\n        if (this.state.boxSize == null && this.props.boxSize != null) {\n            this.setState({\n                boxSize: this.props.boxSize,\n                boxSpacing: this.props.boxSpacing,\n            });\n        } else if (this.state.boxSize != null) {\n            this.d3render();\n        }\n    }\n\n    componentDidUpdate() {\n        this._initOrUpd();\n    }\n\n    componentDidMount() {\n        this._initOrUpd();\n    }\n}\n\nfunction selectProbingGeometry(windowWidth, windowHeight) {\n    if (windowWidth == null) return null;\n    const smallBoxScreen = windowWidth == null || windowHeight == null || Math.min(windowWidth, windowHeight) < 550;\n    console.log('selectProbingGeometry()', windowWidth, windowHeight, smallBoxScreen);\n\n    return smallBoxScreen ? SMALLER_PROBING_BOX_GEOMETRY : DEFAULT_PROBING_BOX_GEOMETRY;\n}\n\nexport class ProbingStateVisualization extends React.Component {\n    static getExpectedHeight() {\n        return 270; // TODO: compute?\n    }\n\n    render() {\n        const {breakpoints, bpIdx, innerRef, windowWidth, windowHeight} = this.props;\n        return (\n            <ProbingVisualizationImpl\n                slotsCount={this.props.slotsCount || 8}\n                breakpoints={breakpoints}\n                bpIdx={bpIdx}\n                innerRef={innerRef}\n                {...selectProbingGeometry(windowWidth, windowHeight)}\n            />\n        );\n    }\n}\n\nexport class ProbingVisualization extends React.Component {\n    static FULL_WIDTH = true;\n    static EXTRA_ERROR_BOUNDARY = true;\n\n    render() {\n        // Pretty hacky passing links like this\n        return (\n            <ProbingVisualizationImpl\n                slotsCount={this.props.slotsCount || 8}\n                breakpoints={[{links: this.props.links}]}\n                bpIdx={0}\n                {...this.props}\n                {...selectProbingGeometry(this.props.windowWidth, this.props.windowHeight)}\n            />\n        );\n    }\n}\n\nexport class GenerateProbingLinks extends BreakpointFunction {\n    run(_slotsCount, _key, algo) {\n        if (algo === 'python') {\n            this.PERTURB_SHIFT = 5;\n        }\n        this.slotsCount = _slotsCount;\n        this.key = _key;\n        this.links = new ImmutableList();\n        for (let i = 0; i < this.slotsCount; ++i) {\n            this.links = this.links.set(i, new ImmutableList());\n        }\n        this.addBP('def-probe-all');\n\n        this.hashCode = pyHash(this.key);\n        this.addBP('compute-hash');\n\n        if (algo === 'python') {\n            this.perturb = computePerturb(this.hashCode);\n            this.addBP('compute-perturb');\n        }\n\n        this.idx = computeIdx(this.hashCode, this.slotsCount);\n        this.startIdx = this.idx;\n        this.addBP('compute-idx');\n        this.visitedIdx = new ImmutableMap();\n        this.addBP('create-empty-set');\n        let prevPerturbLink = !!this.perturb && !this.perturb.eq(0);\n        while (true) {\n            this.addBP('while-loop');\n            if (this.visitedIdx.size === this.slotsCount) {\n                break;\n            }\n            if (!this.visitedIdx.has(this.idx)) {\n                this.visitedIdx = this.visitedIdx.set(this.idx, {perturbLink: prevPerturbLink});\n            }\n            this.addBP('visited-add');\n            let nIdx;\n            if (algo === 'python') {\n                nIdx = nextIdxPerturb(this.idx, this.perturb, this.slotsCount);\n            } else if (algo === '5i+1') {\n                nIdx = (5 * this.idx + 1) % this.slotsCount;\n            } else if (algo === 'i+1') {\n                nIdx = (this.idx + 1) % this.slotsCount;\n            } else {\n                throw new Error(`Unknown probing algorithm: ${algo}`);\n            }\n\n            const perturbLink = this.perturb != null && !this.perturb.eq(0);\n            prevPerturbLink = perturbLink;\n            this.links = this.links.set(this.idx, this.links.get(this.idx).push({nextIdx: nIdx, perturbLink}));\n            this.idx = nIdx;\n            this.addBP('next-idx');\n            if (algo === 'python') {\n                this.perturb = perturbShift(this.perturb);\n                this.addBP('perturb-shift');\n            }\n        }\n\n        return {links: this.links, startIdx: this.startIdx};\n    }\n}\n"
  },
  {
    "path": "src/py_obj_parsing.js",
    "content": "import {None, isNone, EQ} from './hash_impl_common';\n\nimport {BigNumber} from 'bignumber.js';\n\nclass PyParsingError extends Error {\n    constructor(text, pos) {\n        // super(`${text} (at position ${pos})`);\n        super(text);\n        this.pos = pos;\n    }\n}\n\nconst digitsMinusPlus = '-+0123456789';\nconst minusPlus = '-+';\n\n// TODO: add mode for validating stuff: e.g. parseString() should throw on `\"string contents\" stuff after`\nexport class PyObjParser {\n    constructor(literal) {\n        this.s = literal;\n        this.pos = 0;\n    }\n\n    skipWhitespace() {\n        while (this.pos < this.s.length && /\\s/.test(this.s[this.pos])) {\n            this.pos++;\n        }\n    }\n\n    current() {\n        return this.s[this.pos];\n    }\n\n    next() {\n        return this.s[this.pos + 1];\n    }\n\n    isWhiteSpaceOrEol(c) {\n        return c == null || /\\s/.test(c);\n    }\n\n    isCurrentWhitespaceOrEol() {\n        return this.isWhiteSpaceOrEol(this.current());\n    }\n\n    consume(expectedChar) {\n        const c = this.current();\n        if (c == null) {\n            this.throwErr(`Encountered unexpected EOL, expected ${expectedChar}`);\n        }\n\n        if (c !== expectedChar) {\n            this.throwErr(`Expected \\`${expectedChar}\\`, got \\`${c}\\``);\n        }\n        this.pos++;\n    }\n\n    consumeWS(expectedChar) {\n        this.skipWhitespace();\n        this.consume(expectedChar);\n    }\n\n    throwErr(text, pos) {\n        // TODO FIXME: pos computation looks way too complicated\n        let posToInclude = pos != null ? pos : this.pos;\n        posToInclude = Math.min(posToInclude, this.s.length - 1);\n        if (posToInclude < 0) posToInclude = 0;\n        throw new PyParsingError(text, posToInclude);\n    }\n\n    _parseStringOrNumberOrNone(allowedSeparators, fromDict, allowNonesInError) {\n        // TODO: The whole None parsing and error reporting for unwrapped strings\n        // TODO: is a bit of a mess\n        if (this.isNextNone(allowedSeparators)) {\n            return this._parseNoneOrThrowUnknownIdentifier(allowedSeparators);\n        }\n        return this._parseStringOrNumber(allowedSeparators, fromDict, allowNonesInError);\n    }\n\n    _parseStringOrNumber(allowedSeparators, fromDict = true, allowNonesInError = false) {\n        this.skipWhitespace();\n        let startPos = this.pos;\n        const c = this.current();\n        if (fromDict) {\n            if (c === '{' || c === '[') {\n                this.throwErr('Nested lists and dictionaries are not supported. Only strings and ints are.');\n            }\n            if (c == null) {\n                this.throwErr('Dict literal added abruptly - expected value');\n            }\n        }\n\n        if (digitsMinusPlus.includes(c)) {\n            return {res: this.parseNumber(allowedSeparators), startPos};\n        } else if (`\"'`.includes(c)) {\n            return {res: this.parseString(), startPos};\n        } else {\n            this.throwErr(\n                `Expected value - string, integer ${\n                    allowNonesInError ? 'or None' : ''\n                }. If you wanted a string, wrap it in quotes`\n            );\n        }\n    }\n\n    parseDict(minSize = null) {\n        const allowedSeparators = ',:}';\n        const c = this.current();\n\n        this.consumeWS('{');\n        let res = [];\n        this.skipWhitespace();\n        while (this.current() !== '}') {\n            if (this.current() == null) {\n                this.throwErr('Dict literal ended abruptly - no closing }');\n            }\n            let key = this._parseStringOrNumberOrNone(allowedSeparators).res;\n            this.consumeWS(':');\n            let value = this._parseStringOrNumberOrNone(allowedSeparators).res;\n            res.push([key, value]);\n\n            this.skipWhitespace();\n            if (this.current() !== '}' && this.current() != null) this.consume(',');\n        }\n        this.consumeWS('}');\n        if (minSize != null) {\n            if (res.length < minSize) {\n                if (minSize > 1) {\n                    this.throwErr(`In this chapter, there should be at least ${minSize} pairs`);\n                } else {\n                    this.throwErr(`In this chapter, the data cannot be empty`);\n                }\n            }\n        }\n        return res;\n    }\n\n    parseList(allowDuplicates = true, minSize = null, extraValueValidator) {\n        const allowedSeparators = ',]';\n        const c = this.current();\n\n        this.consumeWS('[');\n        let res = [];\n        this.skipWhitespace();\n        while (this.current() !== ']') {\n            if (this.current() == null) {\n                this.throwErr('List literal ended abruptly - no closing ]');\n            }\n            let {res: val, startPos: valStartPos} = this._parseStringOrNumberOrNone(allowedSeparators);\n            if (!allowDuplicates) {\n                for (let existingVal of res) {\n                    if (EQ(val, existingVal)) {\n                        this.throwErr('Duplicates are not allowed in this list');\n                    }\n                }\n            }\n            if (extraValueValidator) {\n                const error = extraValueValidator(val);\n                if (error) {\n                    this.throwErr(error, valStartPos);\n                }\n            }\n            res.push(val);\n            this.skipWhitespace();\n            if (this.current() !== ']' && this.current() != null) this.consume(',');\n            this.skipWhitespace();\n        }\n        this.consumeWS(']');\n        if (minSize != null) {\n            if (res.length < minSize) {\n                if (minSize > 1) {\n                    this.throwErr(`In this chapter, the list need to have length at least ${minSize}`);\n                } else {\n                    this.throwErr(`In this chapter, the list cannot be empty`);\n                }\n            }\n        }\n        return res;\n    }\n\n    parseNumber(allowedSeparators = '') {\n        this.skipWhitespace();\n        if (this.current() == null) {\n            this.throwErr(\"Number can't be empty\");\n        }\n\n        const originalPos = this.pos;\n        while (digitsMinusPlus.includes(this.current())) {\n            this.pos++;\n        }\n\n        if (this.current() === '.') {\n            this.throwErr('Floats are not supported (yet)');\n        }\n        const nonDecimalErrorString = 'Non-decimal bases are not supported (yet)';\n        if (this.current() === 'e') {\n            this.throwErr('Floats in scientific notation are not supported (yet)');\n        }\n        if (this.current() === 'x') {\n            this.throwErr(nonDecimalErrorString);\n        }\n        if (!this.isCurrentWhitespaceOrEol() && (!allowedSeparators || !allowedSeparators.includes(this.current()))) {\n            // TODO: a bit more descriptive? and a bit less hacky?\n            this.throwErr('Invalid syntax: number with non-digit characters');\n        }\n\n        const num = this.s.slice(originalPos, this.pos);\n        if (num[0] === '0' && num.length > 1) {\n            this.throwErr(nonDecimalErrorString);\n        }\n        // TODO: python parses numbers like ++1, -+--1, etc properly\n        if (isNaN(+num)) {\n            this.throwErr('Invalid number', originalPos);\n        }\n        return BigNumber(num);\n    }\n\n    parseString() {\n        // TODO: handle escape characters\n        // TODO: handle/throw an error on triple-quoted strings\n        this.skipWhitespace();\n        const c = this.current();\n        if (c !== \"'\" && c !== '\"') {\n            this.throwErr('String must be wrapped in quotation characters (either `\\'` or `\"`)');\n        }\n        const quote = c;\n        this.consume(quote);\n\n        const originalPos = this.pos;\n        let res = [];\n        while (this.current() != null && this.current() !== quote) {\n            if (this.current() === '\\\\') {\n                if (this.next() !== '\\\\' && this.next() !== '\"') {\n                    this.throwErr('The only supported escape sequences are for \\\\\\\\ and \\\\\"', this.pos + 1);\n                }\n                res.push(this.next());\n                this.pos += 2;\n            } else {\n                res.push(this.current());\n                this.pos++;\n            }\n        }\n        this.consume(quote);\n        return res.join('');\n    }\n\n    isNextNone(allowedSeparators = '') {\n        this.skipWhitespace();\n        return (\n            this.s.slice(this.pos, this.pos + 4) === 'None' &&\n            (this.isWhiteSpaceOrEol(this.s[this.pos + 4]) || allowedSeparators.includes(this.s[this.pos + 4]))\n        );\n    }\n\n    // Quite hacky\n    _parseNoneOrThrowUnknownIdentifier(allowedSeparators) {\n        this.skipWhitespace();\n        if (this.isNextNone(allowedSeparators)) {\n            const startPos = this.pos;\n            this.pos += 4;\n            return {res: None, startPos};\n        }\n        this.throwErr('Unknown identifier (if you wanted a string, wrap it in quotation marks - `\"` or `\\'`)');\n    }\n\n    checkTrailingChars() {\n        this.skipWhitespace();\n        if (this.pos < this.s.length) {\n            this.throwErr('Trailing characters');\n        }\n    }\n}\n\nfunction _checkTrailingChars(parser, parseFunc) {\n    const res = parseFunc();\n    parser.checkTrailingChars();\n    return res;\n}\n\nexport function parsePyString(s) {\n    let parser = new PyObjParser(s);\n    return _checkTrailingChars(parser, () => parser.parseString());\n}\n\nexport function parsePyNumber(s) {\n    let parser = new PyObjParser(s);\n    return _checkTrailingChars(parser, () => parser.parseNumber());\n}\n\nexport function parsePyDict(s, minSize = null) {\n    let parser = new PyObjParser(s);\n    return _checkTrailingChars(parser, () => parser.parseDict(minSize));\n}\n\nexport function parsePyList(s, allowDuplicates = true, minSize = null, extraValueValidator) {\n    let parser = new PyObjParser(s);\n    return _checkTrailingChars(parser, () => parser.parseList(allowDuplicates, minSize, extraValueValidator));\n}\n\nexport function parsePyStringOrNumber(s) {\n    let parser = new PyObjParser(s);\n    return _checkTrailingChars(parser, () => parser._parseStringOrNumber(null, false).res);\n}\n\nexport function parsePyStringOrNumberOrNone(s) {\n    let parser = new PyObjParser(s);\n    return _checkTrailingChars(parser, () => parser._parseStringOrNumberOrNone(null, false, true).res);\n}\n\n// TODO: Dump functions are very hacky right now\n\nfunction dumpSimplePyObj(o) {\n    if (isNone(o)) {\n        return 'None';\n    }\n    if (BigNumber.isBigNumber(o)) {\n        return o.toString();\n    }\n    return JSON.stringify(o);\n}\n\nexport function dumpPyList(l) {\n    let strItems = [];\n    for (let item of l) {\n        strItems.push(dumpSimplePyObj(item));\n    }\n    return '[' + strItems.join(', ') + ']';\n}\n\nexport function dumpPyDict(d) {\n    let strItems = [];\n    for (let [k, v] of d) {\n        strItems.push(`${dumpSimplePyObj(k)}: ${dumpSimplePyObj(v)}`);\n    }\n    return '{' + strItems.join(', ') + '}';\n}\n"
  },
  {
    "path": "src/py_obj_parsing.test.js",
    "content": "import {BigNumber} from 'bignumber.js';\nimport {\n    parsePyString,\n    parsePyNumber,\n    parsePyStringOrNumber,\n    parsePyDict,\n    parsePyList,\n    dumpPyList,\n    dumpPyDict,\n    PyObjParser,\n} from './py_obj_parsing';\nimport {None, isNone} from './hash_impl_common';\n\ntest('Parsing empty strings', () => {\n    expect(parsePyString('\"\"')).toEqual('');\n    expect(parsePyString(\"''\")).toEqual('');\n});\n\ntest('Parsing non-empty strings', () => {\n    expect(parsePyString('\"aba\"')).toEqual('aba');\n    expect(parsePyString('    \"aba\"')).toEqual('aba');\n    expect(parsePyString('    \"aba\"     ')).toEqual('aba');\n    expect(parsePyString('\"aba\"     ')).toEqual('aba');\n\n    expect(parsePyString(\"'aba'\")).toEqual('aba');\n    expect(parsePyString(\"    'aba'\")).toEqual('aba');\n    expect(parsePyString(\"    'aba'     \")).toEqual('aba');\n    expect(parsePyString(\"'aba'     \")).toEqual('aba');\n\n    expect(parsePyString('\"aba caba\"')).toEqual('aba caba');\n    expect(parsePyString(\"'aba caba'\")).toEqual('aba caba');\n\n    expect(parsePyString('\"aba caba  \"')).toEqual('aba caba  ');\n    expect(parsePyString(\"'  aba caba'\")).toEqual('  aba caba');\n    expect(parsePyString(\"'  aba caba  '\")).toEqual('  aba caba  ');\n    expect(parsePyString(\"'aba caba  '\")).toEqual('aba caba  ');\n\n    expect(parsePyString(\"\\\"'''\\\"\")).toEqual(\"'''\");\n    expect(() => parsePyString('aba caba')).toThrowError(/String must be wrapped.*quot/);\n    expect(() => parsePyString(\"'aba caba\")).toThrowError(/EOL/);\n});\n\ntest('Parsing escaped strings', () => {\n    expect(parsePyString('\"\\\\\\\\\"')).toEqual('\\\\');\n    expect(parsePyString('\"\\\\\\\\ \\\\\"\"')).toEqual('\\\\ \"');\n    expect(() => parsePyString('\"\\\\n\"')).toThrow(/escape sequences/);\n    expect(() => parsePyString('\"ababab\\\\\"')).toThrow(/EOL/);\n});\n\ntest('Parsing regular numbers', () => {\n    expect(parsePyNumber('0')).toEqual(BigNumber(0));\n    expect(parsePyNumber('1')).toEqual(BigNumber(1));\n    expect(parsePyNumber('-1')).toEqual(BigNumber(-1));\n    expect(parsePyNumber('+1')).toEqual(BigNumber(1));\n\n    expect(parsePyNumber('   0    ')).toEqual(BigNumber(0));\n    expect(parsePyNumber('  1  ')).toEqual(BigNumber(1));\n    expect(parsePyNumber('  -1    ')).toEqual(BigNumber(-1));\n    expect(parsePyNumber('     +1   ')).toEqual(BigNumber(1));\n\n    expect(parsePyNumber('     +1   ')).toEqual(BigNumber(1));\n\n    expect(parsePyNumber('+123132')).toEqual(BigNumber(123132));\n    expect(parsePyNumber('123132')).toEqual(BigNumber(123132));\n    expect(parsePyNumber('+131')).toEqual(BigNumber(131));\n    expect(parsePyNumber('-131')).toEqual(BigNumber(-131));\n    expect(parsePyNumber('-123132')).toEqual(BigNumber(-123132));\n});\n\ntest('Parsing numbers: reject floats and non-decimals', () => {\n    expect(() => parsePyNumber('+1.')).toThrowError(/Floats.*not supported/);\n    expect(() => parsePyNumber('1.')).toThrowError(/Floats.*not supported/);\n    expect(() => parsePyNumber('1.2')).toThrowError(/Floats.*not supported/);\n    expect(() => parsePyNumber('1.22323')).toThrowError(/Floats.*not supported/);\n    expect(() => parsePyNumber('1e5')).toThrowError(/Floats.*not supported/);\n    // The next one is a bit questionable, because it is not really a number\n    expect(() => parsePyNumber('1e')).toThrowError(/Floats.*not supported/);\n\n    expect(() => parsePyNumber('0777')).toThrowError(/Non-decimal/);\n    expect(() => parsePyNumber('07')).toThrowError(/Non-decimal/);\n    expect(() => parsePyNumber('0x777')).toThrowError(/Non-decimal/);\n    expect(() => parsePyNumber('0x777')).toThrowError(/Non-decimal/);\n\n    // again, it is not expected to properly validate non-decimals\n    expect(() => parsePyNumber('0x777dsfdsf')).toThrowError(/Non-decimal/);\n});\n\ntest('Parsing numbers: reject non-numbers', () => {\n    expect(() => parsePyNumber('')).toThrowError(/Number can't be empty/);\n    expect(() => parsePyNumber('    ')).toThrowError(/Number can't be empty/);\n    expect(() => parsePyNumber('a')).toThrowError(/Invalid syntax/);\n    expect(() => parsePyNumber('  a ')).toThrowError(/Invalid syntax/);\n    expect(() => parsePyNumber('ababab')).toThrowError(/Invalid syntax/);\n    expect(() => parsePyNumber('  a bababba')).toThrowError(/Invalid syntax/);\n    expect(() => parsePyNumber('123abc')).toThrowError(/Invalid syntax/);\n    expect(() => parsePyNumber('   123a ')).toThrowError(/Invalid syntax/);\n\n    // Techically, a number in python, but isn't considered one by the parser right now\n    expect(() => parsePyNumber('--1')).toThrowError(/Invalid number/);\n});\n\ntest('Parsing py strings or numbers', () => {\n    expect(parsePyStringOrNumber('  \"aba\"  ')).toEqual('aba');\n    expect(parsePyStringOrNumber('  17  ')).toEqual(BigNumber(17));\n    expect(parsePyStringOrNumber('  \"17\"  ')).toEqual('17');\n});\n\ntest('Parsing dicts: empty dict', () => {\n    const empty = [];\n    expect(parsePyDict('{}')).toEqual(empty);\n    expect(parsePyDict('{        }')).toEqual(empty);\n    expect(parsePyDict('          {        }')).toEqual(empty);\n    expect(parsePyDict('          {        }              ')).toEqual(empty);\n    expect(parsePyDict('{        }              ')).toEqual(empty);\n    expect(parsePyDict('{}       ')).toEqual(empty);\n    expect(parsePyDict('         {}       ')).toEqual(empty);\n});\n\ntest('Parsing dicts: just ints', () => {\n    expect(parsePyDict(' {1:2,  2:  3,4:     5,6:7   }')).toEqual([\n        [BigNumber(1), BigNumber(2)],\n        [BigNumber(2), BigNumber(3)],\n        [BigNumber(4), BigNumber(5)],\n        [BigNumber(6), BigNumber(7)],\n    ]);\n    expect(parsePyDict('{   1:2,2:  3,4:   5,6:7}')).toEqual([\n        [BigNumber(1), BigNumber(2)],\n        [BigNumber(2), BigNumber(3)],\n        [BigNumber(4), BigNumber(5)],\n        [BigNumber(6), BigNumber(7)],\n    ]);\n\n    const m12 = [[BigNumber(1), BigNumber(2)]];\n    expect(parsePyDict('{1:2}')).toEqual(m12);\n    expect(parsePyDict('  {1:2}')).toEqual(m12);\n    expect(parsePyDict('  {1:2}   ')).toEqual(m12);\n    expect(parsePyDict('{1:2}   ')).toEqual(m12);\n});\n\ntest('Parsing dicts: just strings', () => {\n    const e = [['a', 'b'], ['b', 'c'], ['d', 'e'], ['f', 'g']];\n    expect(parsePyDict(\" {'a':'b',  'b':  'c','d':     'e','f':'g'   }\")).toEqual(e);\n    expect(parsePyDict(\"{   'a':\\\"b\\\",\\\"b\\\":  'c','d':   'e','f':'g'}\")).toEqual(e);\n});\n\ntest('Parsing dicts: mixed strings and ints', () => {\n    expect(parsePyDict(\" {'a':2,  3:  'c','d':     4,5:'g'   }\")).toEqual([\n        ['a', BigNumber(2)],\n        [BigNumber(3), 'c'],\n        ['d', BigNumber(4)],\n        [BigNumber(5), 'g'],\n    ]);\n});\n\ntest('Parsing dicts: mixed strings, ints and Nones with repeated keys', () => {\n    expect(parsePyDict(\" {'a':2,  3:  'c','d':     4,5:'g'   , 'a': 'b', 5: 'f'      }               \")).toEqual([\n        ['a', BigNumber(2)],\n        [BigNumber(3), 'c'],\n        ['d', BigNumber(4)],\n        [BigNumber(5), 'g'],\n        ['a', 'b'],\n        [BigNumber(5), 'f'],\n    ]);\n    expect(\n        parsePyDict(\n            \" {'a':2,  3:  'c', None: 'abc', 'd':     4,5:'g'   , 'a': 'b', 5: 'f'      , 'a': None, None  : 42 }               \"\n        )\n    ).toEqual([\n        ['a', BigNumber(2)],\n        [BigNumber(3), 'c'],\n        [None, 'abc'],\n        ['d', BigNumber(4)],\n        [BigNumber(5), 'g'],\n        ['a', 'b'],\n        [BigNumber(5), 'f'],\n        ['a', None],\n        [None, BigNumber(42)],\n    ]);\n});\n\ntest('Parsing dicts: malformed dicts', () => {\n    // TODO: more of this?\n    expect(() => parsePyDict(' {')).toThrowError(/abrupt/);\n    expect(() => parsePyDict(' {     ')).toThrowError(/abrupt/);\n    expect(() => parsePyDict(' }     ')).toThrowError(/Expected.*{/);\n    expect(() => parsePyDict('a')).toThrowError(/Expected.*{/);\n    expect(() => parsePyDict(\"{'a':5\")).toThrowError(/abrupt/);\n    expect(() => parsePyDict(\"{'a':5\")).toThrowError(/abrupt/);\n    expect(() => parsePyDict(\"{'a',5\")).toThrowError(/Expected.*:/);\n    expect(() => parsePyDict(\"{'a':5e}\")).toThrowError(/Floats/);\n    expect(() => parsePyDict(\"{'a': 'b' 5: 6\")).toThrowError(/Expected.*,/);\n    expect(() => parsePyDict(\"{'a':5} fd  fds\")).toThrowError(/Trailing/);\n});\n\ntest('Parsing lists: empty list', () => {\n    expect(parsePyList('[]')).toEqual([]);\n    expect(parsePyList('[        ]')).toEqual([]);\n    expect(parsePyList('          [        ]')).toEqual([]);\n    expect(parsePyList('          [        ]              ')).toEqual([]);\n    expect(parsePyList('[        ]              ')).toEqual([]);\n    expect(parsePyList('[]       ')).toEqual([]);\n    expect(parsePyList('         []       ')).toEqual([]);\n});\n\ntest('Parsing lists: just ints', () => {\n    expect(parsePyList(' [1,2,  2,  3,4,     5,6,7   ]')).toEqual([\n        BigNumber(1),\n        BigNumber(2),\n        BigNumber(2),\n        BigNumber(3),\n        BigNumber(4),\n        BigNumber(5),\n        BigNumber(6),\n        BigNumber(7),\n    ]);\n    expect(parsePyList('[   1,2,2,  3,4,   5,6,7]')).toEqual([\n        BigNumber(1),\n        BigNumber(2),\n        BigNumber(2),\n        BigNumber(3),\n        BigNumber(4),\n        BigNumber(5),\n        BigNumber(6),\n        BigNumber(7),\n    ]);\n\n    expect(parsePyList('[1,2]')).toEqual([BigNumber(1), BigNumber(2)]);\n    expect(parsePyList('  [1,2]')).toEqual([BigNumber(1), BigNumber(2)]);\n    expect(parsePyList('  [1,2]   ')).toEqual([BigNumber(1), BigNumber(2)]);\n    expect(parsePyList('[1,2]   ')).toEqual([BigNumber(1), BigNumber(2)]);\n});\n\ntest('Parsing lists: just strings', () => {\n    expect(parsePyList(\" ['a','b',  'b',  'c','d',     'e','f','g'   ]\")).toEqual([\n        'a',\n        'b',\n        'b',\n        'c',\n        'd',\n        'e',\n        'f',\n        'g',\n    ]);\n    expect(parsePyList(\"[   'a',\\\"b\\\",\\\"b\\\",  'c','d',   'e','f','g']\")).toEqual([\n        'a',\n        'b',\n        'b',\n        'c',\n        'd',\n        'e',\n        'f',\n        'g',\n    ]);\n});\n\ntest('Parsing lists: mixed strings and ints', () => {\n    expect(parsePyList(\" ['a',2,  3,  'c','d',     4,5,'g'   ]\")).toEqual([\n        'a',\n        BigNumber(2),\n        BigNumber(3),\n        'c',\n        'd',\n        BigNumber(4),\n        BigNumber(5),\n        'g',\n    ]);\n});\n\ntest('Parsing lists: mixed strings, ints and Nones with repeated values', () => {\n    expect(parsePyList(\" ['a',2,  3,  'c'   ,'d',     4,5,'g'   , 'a', 'b', 5, 'f'      ]               \")).toEqual([\n        'a',\n        BigNumber(2),\n        BigNumber(3),\n        'c',\n        'd',\n        BigNumber(4),\n        BigNumber(5),\n        'g',\n        'a',\n        'b',\n        BigNumber(5),\n        'f',\n    ]);\n    expect(\n        parsePyList(\n            \" ['a',2,  None ,  3,  'c','d',  None  ,    4,5,'g' ,  None,None,None , 'a', 'b', 5, 'f'      ]               \"\n        )\n    ).toEqual([\n        'a',\n        BigNumber(2),\n        None,\n        BigNumber(3),\n        'c',\n        'd',\n        None,\n        BigNumber(4),\n        BigNumber(5),\n        'g',\n        None,\n        None,\n        None,\n        'a',\n        'b',\n        BigNumber(5),\n        'f',\n    ]);\n});\n\ntest('Parsing lists: malformed lists', () => {\n    // TODO: more of this?\n    expect(() => parsePyList(' [')).toThrowError(/abrupt/);\n    expect(() => parsePyList(' [     ')).toThrowError(/abrupt/);\n    expect(() => parsePyList(' ]     ')).toThrowError(/Expected.*\\[/);\n    expect(() => parsePyList(' [5 5]     ')).toThrowError(/Expected.*,/);\n    expect(() => parsePyList('a')).toThrowError(/Expected.*\\[/);\n    expect(() => parsePyList(\"['a',5\")).toThrowError(/abrupt/);\n    expect(() => parsePyList(\"['a',5e]\")).toThrowError(/Floats/);\n    expect(() => parsePyList(\"['a',5] fdsfds\")).toThrowError(/Trailing/);\n});\n\ntest('Parsing None', () => {\n    const parseNone = s => {\n        let p = new PyObjParser(s);\n        return p._parseNoneOrThrowUnknownIdentifier().res;\n    };\n\n    expect(isNone(parseNone('None'))).toBe(true);\n    expect(isNone(parseNone('   None'))).toBe(true);\n    expect(isNone(parseNone('None    '))).toBe(true);\n    expect(isNone(parseNone('   None    '))).toBe(true);\n    expect(() => parseNone('   Nonenone    ')).toThrowError(/Unknown identifier/);\n    expect(() => parseNone('Nonee')).toThrowError(/Unknown identifier/);\n});\n\ntest('Dumping lists', () => {\n    expect(dumpPyList([])).toEqual('[]');\n    expect(dumpPyList([1, 1, 2, 3, 5])).toEqual('[1, 1, 2, 3, 5]');\n    expect(dumpPyList(['abc', 'def', 2, 3, 5])).toEqual('[\"abc\", \"def\", 2, 3, 5]');\n    expect(dumpPyList([None, 'abc', None, 'def', 2, 3, 5])).toEqual('[None, \"abc\", None, \"def\", 2, 3, 5]');\n});\n\ntest('Dumping dicts', () => {\n    expect(dumpPyDict(new Map())).toEqual('{}');\n    expect(\n        dumpPyDict([\n            [BigNumber(1), BigNumber(2)],\n            [BigNumber(2), BigNumber(3)],\n            [BigNumber(3), BigNumber(4)],\n            [BigNumber(5), BigNumber(9)],\n        ])\n    ).toEqual('{1: 2, 2: 3, 3: 4, 5: 9}');\n    expect(\n        dumpPyDict([\n            ['abc', BigNumber(4)],\n            ['def', 'fgh'],\n            [BigNumber(2), BigNumber(9)],\n            [BigNumber(3), 'ar'],\n            [BigNumber(5), ''],\n        ])\n    ).toEqual('{\"abc\": 4, \"def\": \"fgh\", 2: 9, 3: \"ar\", 5: \"\"}');\n    expect(\n        dumpPyDict([\n            [None, BigNumber(3)],\n            ['abc', BigNumber(4)],\n            ['def', 'fgh'],\n            [BigNumber(2), BigNumber(9)],\n            [BigNumber(3), 'ar'],\n            [BigNumber(5), ''],\n            [None, 'abc'],\n            ['abc', BigNumber(5)],\n        ])\n    ).toEqual('{None: 3, \"abc\": 4, \"def\": \"fgh\", 2: 9, 3: \"ar\", 5: \"\", None: \"abc\", \"abc\": 5}');\n});\n"
  },
  {
    "path": "src/store.js",
    "content": "import {observable, action} from 'mobx';\n\nexport let globalSettings = observable({\n    codePlaySpeed: 1,\n    maxCodePlaySpeed: 8,\n});\n\nglobalSettings.setCodePlaySpeed = action(function setCodePlaySpeed(speed) {\n    console.log('action setCodePlaySpeed', speed);\n    globalSettings.codePlaySpeed = speed;\n});\n\nglobalSettings.setMaxCodePlaySpeed = action(function setCodePlaySpeed(maxSpeed) {\n    console.log('action setMaxCodePlaySpeed', maxSpeed);\n    globalSettings.maxCodePlaySpeed = maxSpeed;\n});\n\nexport let win = observable({\n    width: null,\n    height: null,\n    scrollY: 0,\n    jsLoaded: false,\n});\n\nwin.setAll = action(function(width, height, scrollY, jsLoaded) {\n    win.width = width;\n    win.height = height;\n    win.scrollY = scrollY;\n    win.jsLoaded = jsLoaded;\n});\n\nwin.setScrollY = action(function setScrollY(scrollY) {\n    win.scrollY = scrollY;\n});\n\nwin.setWH = action(function setWH(w, h) {\n    console.log('setWH', w, h);\n    win.width = w;\n    win.height = h;\n});\n"
  },
  {
    "path": "src/styles.css",
    "content": "/* TODO: remove unnessary fonts/font-weights */\n\n/* FROM: https://stackoverflow.com/questions/12502234/how-to-prevent-webkit-text-rendering-change-during-css-transition */\n@media screen and (-webkit-min-device-pixel-ratio: 2) {\n    body {\n        -webkit-font-smoothing: subpixel-antialiased;\n    }\n}\n\n.app-container,\n.footer-container {\n    padding-right: 30px;\n    padding-left: 30px;\n}\n\n.app-container {\n    padding-top: 20px;\n}\n\n.footer-container {\n    margin-top: 40px;\n    padding-bottom: 30px;\n}\n\n.footer-list {\n    display: flex;\n    flex-direction: row;\n    flex-wrap: wrap;\n}\n\n.footer-list-item {\n    padding-right: 30px;\n    padding-bottom: 8px;\n}\n\n.chapter > .subcontainer {\n    max-width: 768px;\n}\n\n.my-full-width {\n    max-width: none !important;\n}\n\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n    font-family: 'Montserrat', sans-serif;\n    font-weight: 700;\n}\n\ncode,\n.parsable-input,\n.invalid-feedback.invalid-feedback-block-parsable-input {\n    font-family: 'Roboto Mono', monospace;\n}\n\np,\na,\nli,\nblockquote {\n    font-family: 'Roboto', 'Helvetica', sans-serif;\n    font-weight: 400;\n}\n\n.parsable-input,\n.parsable-input-autosize {\n    /*font-size: 14px;*/\n}\n\ndiv.hash-example-instance {\n    padding-bottom: 10px;\n}\n\n.invalid-feedback.invalid-feedback-block-parsable-input {\n    padding-left: 8px;\n    font-size: 14px;\n    white-space: pre;\n    margin-top: 0px;\n    background-color: rgba(220, 53, 69, 0.25); /* from box-shadow color of input in bootstrap  */\n}\n\n.parsable-input-with-error-div {\n    background-color: #fff;\n}\n\n.parsable-input-inline {\n    display: inline-block;\n}\n\n.parsable-input-block {\n    min-width: 300px;\n}\n\n.array-vis,\n.hash-vis-wrapper {\n    width: 100%;\n    position: relative;\n}\n\n.hash-vis-wrapper {\n    display: flex;\n}\n\n.tetris-labels {\n    display: flex;\n    flex-direction: column;\n}\n\n.tetris-label-div {\n    display: flex;\n    align-items: center;\n}\n\n.tetris-label {\n    /* make it align to the right */\n    margin-left: auto;\n    margin-right: 20px;\n}\n\n.box {\n    display: flex;\n    font-family: 'Roboto Mono', monospace;\n    position: absolute;\n    opacity: 1;\n\n    justify-content: center;\n    align-items: center;\n    text-align: center;\n}\n\n.box-content {\n    word-wrap: break-word;\n    min-width: 0;\n}\n\n.box-content-extra-type {\n    color: grey;\n}\n\n.box-empty {\n    background: #f4f4f4;\n    z-index: -1;\n}\n\n.box-full {\n    background: #e0e0e0;\n}\n\n.box-animated {\n    transition-property: opacity, transform;\n    transition-duration: 1.3s;\n    transition-timing-function: ease;\n}\n\n.active-box-selection {\n    background: rgba(0, 0, 0, 0);\n    width: 40px;\n    height: 40px;\n    z-index: 1;\n    position: absolute;\n}\n\n.active-box-selection-1 {\n    border-style: solid;\n    border-width: 1px;\n}\n\n.active-box-selection-2 {\n    /* border-style: dashed; */\n    border-style: solid;\n    border-width: 1px;\n}\n\n.active-box-selection-animated {\n    transition-property: opacity, transform;\n    transition-timing-function: ease;\n}\n\n.box.box-created,\n.box.box-removing,\n.box.box-removed {\n    opacity: 0;\n}\n\n.box.box-removed,\n.box.box-created {\n    transition: none;\n}\n\n.highlight {\n    background-color: #fcf8e3;\n}\n\n/*pre > code,\n.code-explanation,\n.code-explanation > code {\n    font-size: 12px !important;\n}*/\n\n.code-block {\n    /*line-height: 1.15;*/\n    margin-bottom: 15px;\n}\n\n/* TODO: apply only when there is a scrollbar ? */\n.code-block-with-annotations-scrollbar-container {\n    box-shadow: 0px 0px 2px 2px rgba(171, 226, 251, 0.5);\n}\n\n.code-block-with-annotations-scrollbar-container .scrollbar-track-y {\n    left: 0px !important;\n    right: none !important;\n}\n\n.visualized-code > .code-block-row {\n    margin-top: 20px;\n}\n\n.code-block-with-annotations {\n    line-height: 1.15;\n    padding-top: 10px;\n    padding-right: 10px;\n    padding-bottom: 10px;\n    padding-left: 15px;\n    white-space: nowrap;\n}\n\npre.code-line-container {\n    margin: 0px;\n    display: inline;\n}\n\n.code-highlight {\n    background-color: #c2dfff;\n}\n\n.cross-fade-leave {\n    opacity: 1;\n}\n\n.cross-fade-leave.cross-fade-leave-active {\n    opacity: 0;\n    transition: opacity 0.2s ease-in;\n}\n\n.cross-fade-enter {\n    opacity: 0;\n}\n.cross-fade-enter.cross-fade-enter-active {\n    opacity: 1;\n    transition: opacity 0.2s ease-in;\n}\n\n.cross-fade-height {\n    transition: height 0.5s ease-in-out;\n}\n\n.fc-inline {\n    display: inline-block !important;\n    width: 120px !important;\n    line-height: 1 !important;\n    margin-left: 5px;\n    margin-right: 5px;\n}\n\ndiv.visualized-code {\n    margin-top: 25px;\n    margin-bottom: 35px;\n    padding-left: 18px;\n}\n\n.hl-left {\n    border-left: solid 4px #999999;\n}\n\n.tetris {\n    display: flex;\n    padding-bottom: 5px;\n}\n\n/* supposedly creates another layer */\n.fix-animation {\n    transform: translateZ(0);\n    will-change: transform;\n}\n\n.tetris-row {\n    display: flex;\n    margin-bottom: 10px;\n}\n\nblockquote.blockquote {\n    font-size: 1rem !important;\n    background: #f9f9f9;\n    border-left: 5px solid #ccc;\n    padding: 0.5em 10px;\n    margin-top: 5px;\n}\n\n.inline-block {\n    display: inline-block;\n}\n\n.slider-controls {\n    margin-bottom: 10px;\n}\n\n.slider-controls-button-short {\n    width: 40px;\n}\n\n.button-without-scbl-hideable {\n    width: 100px;\n}\n\n@media (max-width: 600px) {\n    .button-with-scbl-hideable {\n        width: 40px;\n    }\n\n    .scbl-hideable {\n        display: none;\n    }\n\n    .input-toolbar-button-label {\n        display: none;\n    }\n}\n\n@media (min-width: 601px) {\n    .button-with-scbl-hideable {\n        width: 100px;\n    }\n\n    .scbl-hideable {\n    }\n\n    .input-toolbar-button-label {\n    }\n}\n\n.my-sticky-outer-outer-wrapper {\n    margin-bottom: 12px;\n}\n\n.my-sticky-wrapper {\n    padding-top: 5px;\n}\n\n.sticky-outer-wrapper .my-sticky-wrapper {\n    margin-left: -30px;\n    padding-left: 30px;\n    margin-right: -30px;\n    padding-right: 30px;\n}\n\n.sticky-outer-wrapper.active .my-sticky-wrapper {\n    animation: 0.5s stickied-animation 0s ease;\n    box-shadow: 0 5px 7px rgba(0, 0, 0, 0.19), 0 3px 3px rgba(0, 0, 0, 0.23);\n    background: white;\n}\n\n.sticky-outer-wrapper:not(.active) .my-sticky-wrapper {\n    border-top: 1px solid #dee2e6;\n    border-bottom: 1px solid #dee2e6;\n}\n\n@keyframes stickied-animation {\n    0% {\n        transform: translateX(0px);\n    }\n    14% {\n        transform: translateX(10px);\n    }\n    28% {\n        transform: translateX(-10px);\n    }\n    42% {\n        transform: translateX(-5px);\n    }\n    56% {\n        transform: translateX(5px);\n    }\n    70% {\n        transform: translateX(-2px);\n    }\n    85% {\n        transform: translateX(2px);\n    }\n    100% {\n        transform: translateX(0px);\n    }\n}\n\n.row-block-input-toolbar {\n    background: white;\n}\n\n.row-block-input-toolbar .col-input {\n    margin-bottom: 5px;\n}\n\n.row-block-input-toolbar .col-buttons {\n    margin-bottom: 5px;\n}\n\n.force-stick-to-top {\n    position: sticky !important;\n    top: 5px;\n    z-index: 1000;\n}\n\n.badge-undo-redo-count {\n    font-family: 'Roboto Mono', monospace;\n    min-width: 22px;\n}\n\n.div-p {\n    margin-bottom: 16px;\n}\n\n.toc-a:hover {\n    text-decoration: none !important;\n}\n\n.toc-a:hover .toc-title {\n    text-decoration: underline !important;\n}\n\n.line-with-annotation:hover {\n    background: #e9ecef;\n    width: 100%;\n}\n\n.line-with-annotation {\n    width: 100%;\n}\n\n.dynamic-p {\n    margin-bottom: 0;\n}\n\n.dynamic-p-inner-wrapper {\n    transition-property: background-color;\n    transition-duration: 750ms;\n    transition-function: ease;\n}\n"
  },
  {
    "path": "src/util.js",
    "content": "import _ from 'lodash';\nimport * as React from 'react';\nimport {observer} from 'mobx-react';\nimport {reaction} from 'mobx';\nimport {win} from './store';\n\nimport classNames from 'classnames';\n\nimport {ErrorBoundary} from 'react-error-boundary';\nimport bowser from 'bowser';\nimport ReactCSSTransitionReplace from 'react-css-transition-replace';\n\nexport const OLIVE = '#3D9970';\nexport const RED = '#FF4136';\nexport const BLUE = '#0074D9';\n\nexport const COLOR_FOR_READ_OPS = '#13920b';\n\nexport function doubleRAF(callback) {\n    window.requestAnimationFrame(() => {\n        window.requestAnimationFrame(callback);\n    });\n}\n\nexport class CrossFade extends React.Component {\n    render() {\n        return (\n            <ReactCSSTransitionReplace\n                transitionName=\"cross-fade\"\n                transitionEnterTimeout={200}\n                transitionLeaveTimeout={200}\n            >\n                {this.props.children}\n            </ReactCSSTransitionReplace>\n        );\n    }\n}\n\nexport class DynamicP extends React.PureComponent {\n    HIGHLIGHT_TIMEOUT = 1500;\n\n    // TODO: hacky margin hack\n    constructor() {\n        super();\n        this.ref = React.createRef();\n        this.timeoutId = null;\n        this.state = {highlight: false, key: null};\n\n        this.resizeReaction = reaction(() => win.width, () => this.setState({height: undefined}));\n    }\n\n    static getDerivedStateFromProps(props, state) {\n        const oldKey = state.key;\n        const newKey = React.Children.only(props.children).key;\n        // CrossFade / ReactCSSTransitionReplace relies on key changing\n        if (oldKey !== newKey) {\n            const firstRender = oldKey == null;\n            return {...state, key: newKey, highlight: !firstRender};\n        } else {\n            return null;\n        }\n    }\n\n    render() {\n        const className = classNames('dynamic-p-inner-wrapper', {highlight: this.state.highlight});\n        return (\n            <MyErrorBoundary>\n                <div className={className} ref={this.ref} style={{minHeight: this.state.height}}>\n                    <CrossFade>{this.props.children}</CrossFade>\n                </div>\n                <div style={{marginBottom: 16}} />\n            </MyErrorBoundary>\n        );\n    }\n\n    removeHighlight = () => {\n        this.setState({\n            highlight: false,\n        });\n    };\n\n    updateHeight = () => {\n        const {height} = this.ref.current.getBoundingClientRect();\n        this.setState({height});\n    };\n\n    componentDidUpdate() {\n        if (this.state.highlight) {\n            if (this.timeoutId != null) {\n                clearTimeout(this.timeoutId);\n            }\n            this.timeoutId = setTimeout(this.removeHighlight, this.HIGHLIGHT_TIMEOUT);\n        }\n        this.updateHeight();\n    }\n\n    componentDidMount() {\n        this.updateHeight();\n    }\n}\n\n@observer\nexport class DebounceWhenOutOfView extends React.Component {\n    render() {\n        return <DebounceWhenOutOfViewImpl {...this.props} scrollY={win.scrollY} />;\n    }\n}\n\nconst LEEWAY_Y = 100;\nclass DebounceWhenOutOfViewImpl extends React.Component {\n    DEBOUNCE_TIMEOUT = 500;\n\n    constructor() {\n        super();\n\n        this.ref = React.createRef();\n        this.timeoutId = null;\n\n        this.state = {};\n    }\n\n    static getDerivedStateFromProps(props, state) {\n        let isVisible;\n        if (state.height != null && state.top != null) {\n            const top = state.top;\n            const bottom = state.top + state.height;\n            const {windowHeight, scrollY} = props;\n            isVisible =\n                (scrollY - LEEWAY_Y <= top && top <= scrollY + windowHeight + LEEWAY_Y) ||\n                (scrollY - LEEWAY_Y <= bottom && bottom <= scrollY + windowHeight + LEEWAY_Y);\n        }\n\n        if (isVisible == null || state.isVisible == null || isVisible || state.isVisible) {\n            return {\n                isVisible,\n                childProps: props.childProps,\n            };\n        } else {\n            return null;\n        }\n    }\n\n    render() {\n        const childProps = this.state.childProps;\n        return this.props.childFunc(childProps, this.ref);\n    }\n\n    componentDidUpdate() {\n        this.updateGeometry();\n        this.checkDebounceProps();\n    }\n\n    componentDidMount() {\n        this.updateGeometry();\n        this.checkDebounceProps();\n    }\n\n    updateGeometry() {\n        const node = this.ref.current;\n        const rect = node.getBoundingClientRect();\n        const {height} = rect;\n        const top = window.scrollY + rect.top;\n        console.log('DebounceWhenOutOfView updateGeometry top', top);\n        this.setState(state => {\n            if (state.height !== height || state.top !== top) {\n                return {height, top};\n            } else {\n                return null;\n            }\n        });\n    }\n\n    updateChildProps = () => {\n        const childProps = this.props.childProps;\n        this.setState(state => {\n            if (childProps !== state.childProps) {\n                return {\n                    childProps,\n                };\n            } else {\n                return null;\n            }\n        });\n    };\n\n    checkDebounceProps() {\n        if (this.timeoutId) {\n            clearTimeout(this.timeoutId);\n        }\n        if (this.props.childProps !== this.state.childProps) {\n            this.timeoutId = setTimeout(this.updateChildProps, this.DEBOUNCE_TIMEOUT);\n        }\n    }\n}\n\nfunction linebreaks(s) {\n    return s\n        .split('\\n')\n        .map((l, i) => [l, <br key={`br-${i}`} />])\n        .flat();\n}\n\nfunction MyFallbackComponent({componentStack, error}) {\n    return (\n        <div style={{backgroundColor: 'pink'}}>\n            <h3 className=\"text-danger\">\n                An error occured. This should not happen. Please file a bug report{' '}\n                <a href=\"https://github.com/eleweek/inside_python_dict\">on github</a>{' '}\n            </h3>\n            <p>{linebreaks(error.message)}</p>\n            <h6 className=\"text-danger\">Component stack</h6>\n            <p>{linebreaks(componentStack)}</p>\n        </div>\n    );\n}\n\nexport function MyErrorBoundary(props) {\n    const onError = (error, componentStack) => {\n        console.error('ErrorBoundary caught error\\n\\n', error, '\\n\\n\\nComponent stack', componentStack);\n    };\n\n    return (\n        <ErrorBoundary onError={onError} FallbackComponent={MyFallbackComponent}>\n            {props.children}\n        </ErrorBoundary>\n    );\n}\n\n// TODO: This does not seem to be better than _.debounce(..., 0)\n// TODO: should probably get rid of this function and use debounce()\nfunction squashUpdates(func) {\n    let queue = [];\n    let epoch = 0;\n    return value => {\n        let currentEpoch = epoch;\n        queue.push(value);\n        setTimeout(() => {\n            if (queue.length > 0 && currentEpoch === epoch) {\n                func(queue[queue.length - 1]);\n                queue = [];\n                epoch++;\n            }\n        }, 0);\n    };\n}\n\nexport class ChapterComponent extends React.Component {\n    setterFuncs = {};\n\n    setter(name, throttled = false, incId = false) {\n        if (!(name in this.setterFuncs)) {\n            let updateState;\n            if (!incId) {\n                updateState = value => this.setState({[name]: value});\n            } else {\n                updateState = value =>\n                    this.setState(state => {\n                        const idKey = `${name}IdHack`;\n                        let id = state[idKey];\n                        id++;\n                        return {[name]: value, [idKey]: id};\n                    });\n            }\n            const updateStateDebounced = squashUpdates(updateState);\n            if (throttled) {\n                this.setterFuncs[name] = _.throttle(updateStateDebounced, 50);\n            } else {\n                this.setterFuncs[name] = updateStateDebounced;\n            }\n        }\n        return this.setterFuncs[name];\n    }\n}\n\nexport function Subcontainerize({children}) {\n    let accumulatedChildren = [];\n    let res = [];\n    const dropAccumulated = () => {\n        if (accumulatedChildren.length > 0) {\n            res.push(\n                <div className=\"subcontainer\" key={`subcontainer-${res.length}`}>\n                    {accumulatedChildren}\n                </div>\n            );\n            accumulatedChildren = [];\n        }\n    };\n    let ebCount = 0;\n    const wrapEbIfNeeded = child => {\n        if (child.type && child.type.EXTRA_ERROR_BOUNDARY) {\n            return <MyErrorBoundary key={child.key || `subcontainerize-eb-${++ebCount}`}>{child}</MyErrorBoundary>;\n        } else {\n            return child;\n        }\n    };\n\n    for (let child of children) {\n        if (typeof child.type === 'string' || typeof child.type === 'undefined' || !child.type.FULL_WIDTH) {\n            accumulatedChildren.push(wrapEbIfNeeded(child));\n        } else {\n            dropAccumulated();\n            res.push(wrapEbIfNeeded(child));\n        }\n    }\n\n    dropAccumulated();\n\n    return res;\n}\n\nexport class BootstrapAlert extends React.Component {\n    ALERT_REMOVAL_TIMEOUT = 150;\n\n    constructor() {\n        super();\n\n        this.state = {\n            dismissed: false,\n            dismissedDone: false,\n        };\n    }\n\n    dismiss = () => {\n        this.setState({dismissed: true});\n        setTimeout(() => this.setState({dismissedDone: true}), this.ALERT_REMOVAL_TIMEOUT);\n    };\n\n    render() {\n        let {hide, sticky, alertType, boldText, regularText} = this.props;\n        alertType = alertType || 'warning';\n\n        if (!this.state.dismissedDone) {\n            return (\n                <div\n                    className={classNames(\n                        'alert',\n                        `alert-${alertType}`,\n                        {'alert-dismissible': !this.props.nondismissible},\n                        'fade',\n                        {'force-stick-to-top': sticky && !hide},\n                        {show: !this.state.dismissed && !hide},\n                        this.props.extraclassName\n                    )}\n                >\n                    {this.props.children}\n                    {!this.props.nondismissible ? (\n                        <button type=\"button\" className=\"close\" onClick={this.dismiss}>\n                            <span>&times;</span>\n                        </button>\n                    ) : null}\n                </div>\n            );\n        } else {\n            return null;\n        }\n    }\n}\n\nconst defaultUxSettings = {\n    TIME_SLIDER_THROTTLE_TIME: 50,\n    CODE_SCROLL_DEBOUNCE_TIME: 200,\n    THROTTLE_SELECTION_TRANSITIONS: true,\n    THROTTLE_SELECTION_TIMEOUT: 150,\n    MAX_CODE_PLAY_SPEED: 8,\n};\n\nlet insidePythonDictUxSettings;\n\nexport function initUxSettings() {\n    const browser = bowser.getParser(window.navigator.userAgent).parse().parsedResult;\n    console.log('Detected browser', browser);\n\n    const engine = browser.engine.name;\n    const osName = browser.os.name;\n    const platformType = browser.platform.type;\n    console.log('Detected engine', engine, 'on', osName);\n    let settings = {...defaultUxSettings};\n\n    // 'Throttling' transitions for selection is important because they can be buggy as heck\n    // The problem is jumpiness (if transform: translate(...) is changed while transition is running, it resets)\n    // This works fine in Chrome/Blink-based browsers\n    // (I think there is a similar problem for boxes, but it is less acute, because boxes transitions are longer)\n    switch (engine) {\n        case 'Blink':\n            settings.THROTTLE_SELECTION_TRANSITIONS = false;\n            settings.THROTTLE_SELECTION_TIMEOUT = 0;\n            break;\n        case 'Gecko': {\n            settings.THROTTLE_SELECTION_TRANSITIONS = true;\n            switch (osName) {\n                // Somehow Firefox doesn't do this stuff nearly as bad on Linux\n                case 'Linux':\n                    settings.THROTTLE_SELECTION_TIMEOUT = 125;\n                    break;\n                // It seems to be as bad on macOS as on Windows (maybe somewhat worse on macOS)\n                // It's pretty bad on mobile too\n                case 'macOS':\n                    settings.THROTTLE_SELECTION_TIMEOUT = 'transitionend';\n                    break;\n                default:\n                    settings.THROTTLE_SELECTION_TIMEOUT = 275; // almost 'transitionend'\n                    break;\n            }\n            break;\n        }\n        case 'EdgeHTML':\n            settings.THROTTLE_SELECTION_TRANSITIONS = true;\n            // 150 is kind of ok, but 225-275 seems better\n            settings.THROTTLE_SELECTION_TIMEOUT = 250;\n            break;\n        case 'WebKit': {\n            settings.THROTTLE_SELECTION_TRANSITIONS = true;\n            switch (osName) {\n                case 'Linux':\n                    settings.THROTTLE_SELECTION_TIMEOUT = 150;\n                    break;\n                case 'macOS':\n                    settings.THROTTLE_SELECTION_TIMEOUT = 'transitionend';\n                    break;\n                default:\n                    settings.THROTTLE_SELECTION_TIMEOUT = 275;\n                    break;\n            }\n            break;\n        }\n    }\n\n    switch (engine) {\n        case 'Blink':\n        case 'WebKit':\n            // kind of ended up optimizing for chrome\n            settings.TIME_SLIDER_THROTTLE_TIME = null;\n            settings.CODE_SCROLL_DEBOUNCE_TIME = 150;\n            settings.MAX_CODE_PLAY_SPEED = 16;\n            break;\n        case 'Gecko':\n            settings.TIME_SLIDER_THROTTLE_TIME = null;\n            // Firefox doesn't seems to tolerate auto-scrolling\n            settings.CODE_SCROLL_DEBOUNCE_TIME = 200;\n            break;\n    }\n\n    if (platformType === 'mobile') {\n        settings.MAX_CODE_PLAY_SPEED = Math.min(settings.MAX_CODE_PLAY_SPEED, 8);\n    }\n\n    insidePythonDictUxSettings = settings;\n    window.insidePythonDictBrowser = browser;\n    console.log('UX settings', getUxSettings());\n}\n\nexport function getUxSettings() {\n    if (typeof window === 'undefined' || !insidePythonDictUxSettings) {\n        return defaultUxSettings;\n    }\n    return insidePythonDictUxSettings;\n}\n\nexport function singularOrPlural(num, singular, plural) {\n    return num === 1 ? singular : plural;\n}\n\nexport function randint(a, b) {\n    if (b <= a) {\n        throw new Error(`randInt called with b (${b}) <= a (${a})`);\n    }\n    // TODO: check round/floor/ceil stuff\n    // since it may not generate uniformly distributed numbers\n    return a + Math.round(Math.random() * (b - a + 1));\n}\n\nexport function randomChoice(array) {\n    // TODO: check rounding\n    return array[Math.floor(Math.random() * array.length)];\n}\n\nexport function randomMeaningfulString() {\n    return randomChoice(RANDOM_STRINGS);\n}\n\nconst LETTERS = 'abcdefghijklmnopqrstuvwxyz';\n\nexport function randomString3len() {\n    return randomChoice(LETTERS) + randomChoice(LETTERS) + randomChoice(LETTERS);\n}\n\nexport const isClient = process.env.NODE_ENV !== 'ssr';\n\n// This is useful for set first few values of a random()-like function\n// Useful for SSR\nexport function fixFirstValues(func, values) {\n    let calledCounter = 0;\n    return function() {\n        let res;\n        if (calledCounter < values.length) {\n            res = values[calledCounter];\n        } else {\n            res = func.apply(null, arguments);\n        }\n\n        calledCounter++;\n\n        return res;\n    };\n}\n\nexport function isDefinedSmallBoxScreen(windowWidth, windowHeight) {\n    return windowWidth && windowHeight && (windowWidth < 950 || windowHeight < 520);\n}\n\nconst RANDOM_STRINGS = [\n    'bash',\n    'cat',\n    'chmod',\n    'cp',\n    'date',\n    'df',\n    'echo',\n    'ed',\n    'expr',\n    'link',\n    'ls',\n    'mkdir',\n    'mv',\n    'ps',\n    'pwd',\n    'rm',\n    'rmdir',\n    'sh',\n    'sleep',\n    'test',\n    'zsh',\n    'alias',\n    'apply',\n    'at',\n    'atrm',\n    'awk',\n    'batch',\n    'bg',\n    'bzip2',\n    'cd',\n    'clear',\n    'cmp',\n    'curl',\n    'cut',\n    'diff',\n    'dig',\n    'du',\n    'egrep',\n    'env',\n    'ex',\n    'fg',\n    'file',\n    'find',\n    'grep',\n    'grn',\n    'gzip',\n    'head',\n    'iconv',\n    'less',\n    'make',\n    'more',\n    'nc',\n    'open',\n    'sed',\n    'sort',\n    'ssh',\n    'stat',\n    'sudo',\n    'tail',\n    'tar',\n    'tee',\n    'time',\n    'top',\n    'tr',\n    'uname',\n    'uniq',\n    'unzip',\n    'wc',\n    'whois',\n    'xxd',\n    'yes',\n];\n"
  },
  {
    "path": "ssr-all.sh",
    "content": "#!/bin/bash\nmkdir -p build\nfor i in {chapter1,chapter2,chapter3,chapter4}; do npm run --silent babel-node scripts/ssr.js src/autogenerated/${i}.html \"[\\\"${i}\\\"]\" > build/${i}.html; done;\n"
  },
  {
    "path": "stress_test_python.sh",
    "content": "#!/bin/bash\nset -e -o pipefail\n\nNUM_INSERTS=200\nNUM_INSERTS_SMALLER=100\n\neval \"`pyenv init -`\"\n\npyenv shell 3.2.6\n\nfor kv in {numbers,all}; do\n    echo \"DICT 3.2: kv = ${kv}, num_inserts = $NUM_INSERTS\"\n    for is in {0,9,-1}; do\n        echo \"    initial size = ${is}\"\n        for reimpl in {dict32_reimpl_py_extracted,dict_actual,dict32_reimpl_py,dict32_reimpl_js}; do \n            echo \"        Implementation: $reimpl\"\n            python3 python_code/dict32_reimplementation_test_v2.py --reference-implementation dict_actual --test-implementation $reimpl --no-extra-getitem-checks --num-inserts $NUM_INSERTS --kv all --initial-size $is\n        done\n    done\ndone\n\n# TODO: merge with previous loop to remove copy&paste\nfor kv in {numbers,all}; do\n    echo \"HASH from chapter 3 (w/o recycling): kv = ${kv}, num_inserts = $NUM_INSERTS\"\n    for is in {0,9,-1}; do\n        echo \"    initial size = ${is}\"\n        for reimpl in {almost_python_dict_no_recycling_py_simpler,almost_python_dict_no_recycling_py_extracted,almost_python_dict_no_recycling_js}; do \n            echo \"        Implementation: $reimpl\"\n            python3 python_code/dict32_reimplementation_test_v2.py --reference-implementation almost_python_dict_no_recycling_py --test-implementation $reimpl --no-extra-getitem-checks --num-inserts $NUM_INSERTS --kv all --initial-size $is\n        done\n    done\ndone\n\n\n# TODO: merge with previous loop to remove copy&paste\nfor kv in {numbers,all}; do\n    echo \"HASH from chapter 3 (w/ recycling): kv = ${kv}, num_inserts = $NUM_INSERTS\"\n    for is in {0,9,-1}; do\n        echo \"    initial size = ${is}\"\n        for reimpl in {almost_python_dict_recycling_py_extracted,almost_python_dict_recycling_js}; do \n            echo \"        Implementation: $reimpl\"\n            python3 python_code/dict32_reimplementation_test_v2.py --reference-implementation almost_python_dict_recycling_py --test-implementation $reimpl --no-extra-getitem-checks --num-inserts $NUM_INSERTS --kv all --initial-size $is\n        done\n    done\ndone\n\nfor kv in {numbers,all}; do\n    echo \"HASH from chapter 2: kv = ${kv}, num_inserts = $NUM_INSERTS\"\n    for is in {5,10,20,-1}; do\n        echo \"    initial size = ${is}\"\n        for reimpl in {js_reimpl,py_extracted}; do \n            echo \"        Implementation: $reimpl\"\n            python3 python_code/hash_chapter2_reimplementation_test.py --test-implementation py_extracted --num-inserts $NUM_INSERTS --initial-size $is --kv $kv\n        done;\n    done;\ndone;\n\necho \"HASH from chapter 1: num_inserts = $NUM_INSERTS_SMALLER\"\nfor reimpl in {js,py_extracted}; do\n    echo \"        Implementation: $reimpl\"\n    python3 python_code/hash_chapter1_reimplementation_test.py --test-implementation $reimpl --num-inserts $NUM_INSERTS_SMALLER\ndone;\n\necho \"Linear search from chapter 1: size = $NUM_INSERTS_SMALLER\"\nfor reimpl in {js,py_extracted}; do\n    echo \"        Implementation: $reimpl\"\n    python3 python_code/chapter1_linear_search_reimplementation_test.py   --test-implementation $reimpl --size $NUM_INSERTS_SMALLER\ndone;\n\necho \"Testing probing visualization from chapter4\"\npython3 python_code/chapter4_probing_python_reimplementation_test.py\n"
  },
  {
    "path": "unittest_python.sh",
    "content": "#!/bin/bash\nset -e -o pipefail\n\neval \"`pyenv init -`\"\npyenv shell 3.2.6\n\npython3 python_code/hash_chapter2_impl_test.py\npython3 python_code/hash_chapter3_class_impl_test.py\npython3 python_code/interface_test.py\npython3 python_code/actual_dict_factory_test.py\n"
  },
  {
    "path": "webpack.common.js",
    "content": "const webpack = require('webpack');\nconst path = require('path');\nconst MiniCssExtractPlugin = require('mini-css-extract-plugin');\nconst CleanWebpackPlugin = require('clean-webpack-plugin');\nconst {RawSource} = require('webpack-sources');\nconst {exec} = require('child_process');\nconst fs = require('fs');\n\nmodule.exports = {\n    entry: './src/index.js',\n    output: {\n        filename: '[name].[contenthash].js',\n        path: path.resolve(__dirname, 'dist'),\n    },\n    module: {\n        rules: [\n            {\n                test: /\\.js$/,\n                use: {\n                    loader: 'babel-loader',\n                    /* presets come from .babelrc */\n                    options: {\n                        plugins: ['lodash'],\n                    },\n                },\n            },\n            {\n                test: /\\.css$/,\n                use: [MiniCssExtractPlugin.loader, 'css-loader'],\n            },\n        ],\n    },\n    plugins: [\n        new CleanWebpackPlugin(['dist']),\n        new MiniCssExtractPlugin({\n            filename: '[name].[contenthash].css',\n        }),\n    ],\n    watchOptions: {\n        // does not work properly, ssr/mustache/etc is a mess now\n        ignored: /\\.html$/,\n    },\n    optimization: {\n        runtimeChunk: 'single',\n        splitChunks: {\n            cacheGroups: {\n                vendor: {\n                    test: /[\\\\/]node_modules[\\\\/]/,\n                    name: 'vendors',\n                    chunks: 'all',\n                },\n            },\n        },\n    },\n};\n"
  },
  {
    "path": "webpack.dev.js",
    "content": "const merge = require('webpack-merge');\nconst common = require('./webpack.common.js');\nconst HtmlWebpackPlugin = require('html-webpack-plugin');\n\nmodule.exports = merge(common, {\n    mode: 'development',\n    devtool: 'cheap-module-eval-source-map',\n    plugins: [\n        new HtmlWebpackPlugin({\n            template: 'src/autogenerated/chapter1.html',\n            filename: 'chapter1.html',\n        }),\n        new HtmlWebpackPlugin({\n            template: 'src/autogenerated/chapter2.html',\n            filename: 'chapter2.html',\n        }),\n        new HtmlWebpackPlugin({\n            template: 'src/autogenerated/chapter3.html',\n            filename: 'chapter3.html',\n        }),\n        new HtmlWebpackPlugin({\n            template: 'src/autogenerated/chapter4.html',\n            filename: 'chapter4.html',\n        }),\n    ],\n    devServer: {\n        contentBase: './dist',\n    },\n});\n"
  },
  {
    "path": "webpack.prod.js",
    "content": "const webpack = require('webpack');\nconst merge = require('webpack-merge');\nconst common = require('./webpack.common.js');\nconst BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;\nconst HtmlWebpackPlugin = require('html-webpack-plugin');\n\nmodule.exports = merge(common, {\n    mode: 'production',\n    devtool: 'source-map',\n    plugins: [\n        new webpack.optimize.ModuleConcatenationPlugin(),\n        new webpack.DefinePlugin({\n            'process.env.NODE_ENV': JSON.stringify('production'),\n        }),\n        new HtmlWebpackPlugin({\n            template: 'build/chapter1.html',\n            filename: 'chapter1.html',\n        }),\n        new HtmlWebpackPlugin({\n            template: 'build/chapter2.html',\n            filename: 'chapter2.html',\n        }),\n        new HtmlWebpackPlugin({\n            template: 'build/chapter3.html',\n            filename: 'chapter3.html',\n        }),\n        new HtmlWebpackPlugin({\n            template: 'build/chapter4.html',\n            filename: 'chapter4.html',\n        }),\n        new BundleAnalyzerPlugin(),\n    ],\n});\n"
  }
]