[{"data":1,"prerenderedAt":258},["ShallowReactive",2],{"tag-labels":3,"site-header":4,"article-python-dictionary-en":69},{},{"featuredPost":5,"navigations":26},{"id":6,"title":7,"title_en":8,"slug":9,"excerpt":10,"excerpt_en":8,"has_translation":11,"published_at":12,"reading_time":13,"hidden_from_listing":11,"tags":14,"thumbnail_image":8,"og_image":19,"series":20,"authors":25},110,"Ketika Pilih Framework Cuma Biar Kelihatan Beda",null,"ketika-pilih-framework-cuma-biar-kelihatan-beda","Terakhir kali kamu milih framework atau library baru, itu karena masalahmu butuh alat itu, atau karena alatnya kedengaran keren dan belum banyak yang pakai? Pertanyaan ini nggak enak dijawab. Soalnya keputusan teknologi sering diambil dengan alasan yang nggak pernah ditulis di dokumen teknis mana pun.",false,"2026-10-06T15:25:21.000Z",5,[15,16,17,18],"karier","framework","opini","pengalaman","\u002Fog\u002Fketika-pilih-framework-cuma-biar-kelihatan-beda.png",{"id":21,"title":22,"title_en":23,"slug":24},9,"Artikel","Articles","all",[],[27,43,64],{"collection":28,"item":29},"series",{"__typename":28,"title":22,"title_en":23,"slug":24,"children_series":30},[31,35,39],{"title":32,"title_en":33,"slug":34},"Kecerdasan Artifisial","Artificial Intelligence","kecerdasan-artifisial",{"title":36,"title_en":37,"slug":38},"Konsep Dasar Pemrograman","Programming Fundamentals","konsep-dasar-pemrograman",{"title":40,"title_en":41,"slug":42},"Algoritma dan Struktur Data","Algorithms and Data Structures","algoritma-struktur-data",{"collection":28,"item":44},{"__typename":28,"title":45,"title_en":45,"slug":46,"children_series":47},"Tutorial","tutorial",[48,52,56,60],{"title":49,"title_en":50,"slug":51},"Belajar Python","Learning Python","belajar-python",{"title":53,"title_en":54,"slug":55},"Belajar Golang","Learning Go","belajar-golang",{"title":57,"title_en":58,"slug":59},"Belajar Java","Learning Java","belajar-java",{"title":61,"title_en":62,"slug":63},"Belajar Pascal","Learning Pascal","belajar-pascal",{"collection":28,"item":65},{"__typename":28,"title":66,"title_en":66,"slug":67,"children_series":68},"Lab","lab",[],{"article":70,"renderedBody":163,"renderedTakeaway":164,"latestArticles":165,"toc":235},{"id":71,"title":72,"title_en":8,"slug":73,"excerpt":74,"excerpt_en":8,"has_translation":11,"published_at":75,"reading_time":76,"hidden_from_listing":11,"tags":77,"thumbnail_image":8,"og_image":80,"series":81,"authors":155,"body_locale":158,"available_locales":159,"body":161,"takeway":162,"prerequisite":8},130,"Python Dictionaries: the Key That Isn't There","python-dictionary","A word counter dies the first time it meets a word twice. The fix is one line; what takes longer is knowing which of a dict's three lookups to reach for.","2026-10-28T10:00:00.000Z",6,[78,79],"python","dictionary","\u002Fog\u002Fpython-dictionary.png",{"id":82,"title":83,"title_en":84,"slug":85,"description":86,"description_en":87,"articles":88,"children_series":124,"parent_series":125},30,"Koleksi dan String","Collections and Strings","python-collections","Dictionary, set, tuple, dan cara mengolah string. Tipe data yang baru kepakai begitu program berhenti menyimpan satu nilai saja.","Dictionaries, sets, tuples, and the string methods that do the boring parts. The types you need once a program stops holding a single value.",[89,98,111],{"id":71,"title":72,"title_en":8,"slug":73,"excerpt":74,"excerpt_en":8,"has_translation":11,"published_at":75,"reading_time":76,"hidden_from_listing":11,"tags":77,"thumbnail_image":8,"og_image":80,"series":90,"authors":91},{"id":82,"title":83,"title_en":84,"slug":85},[92],{"authors_id":93},{"id":94,"name":95,"slug":96,"role":97,"profile_picture":8},1,"Daffa Izzuddin","izzudd","Writer",{"id":99,"title":100,"title_en":8,"slug":101,"excerpt":102,"excerpt_en":8,"has_translation":11,"published_at":103,"reading_time":76,"hidden_from_listing":11,"tags":104,"thumbnail_image":8,"og_image":106,"series":107,"authors":108},131,"Python Sets: the Duplicate That Keeps Coming Back","python-sets","A de-duplication loop that works on clean data leaves four tags where there should be two. The bug is case, and the fix is a `set`.","2026-10-28T11:00:00.000Z",[78,105],"set","\u002Fog\u002Fpython-sets.png",{"id":82,"title":83,"title_en":84,"slug":85},[109],{"authors_id":110},{"id":94,"name":95,"slug":96,"role":97,"profile_picture":8},{"id":112,"title":113,"title_en":8,"slug":114,"excerpt":115,"excerpt_en":8,"has_translation":11,"published_at":116,"reading_time":76,"hidden_from_listing":11,"tags":117,"thumbnail_image":8,"og_image":119,"series":120,"authors":121},132,"Python Tuples: Why All Three Points Are the Same Point","python-tuples","Three points walk into a loop and come out identical. The list was never copied three times; the name was. A tuple makes that bug impossible.","2026-10-28T12:00:00.000Z",[78,118],"tuple","\u002Fog\u002Fpython-tuples.png",{"id":82,"title":83,"title_en":84,"slug":85},[122],{"authors_id":123},{"id":94,"name":95,"slug":96,"role":97,"profile_picture":8},[],{"id":126,"title":49,"title_en":50,"slug":51,"parent_series":127,"children_series":128},10,{"id":94,"title":45,"title_en":45,"slug":46},[129,134,139,144,149,154],{"id":130,"title":131,"title_en":132,"slug":133,"cover_image":8},16,"Berkenalan dengan Bahasa Python","Getting Started with Python","python-intro",{"id":135,"title":136,"title_en":137,"slug":138,"cover_image":8},17,"Variabel, Tipe Data, dan Operasi","Variables, Types, and Operations","python-variabel-tipe-data",{"id":140,"title":141,"title_en":142,"slug":143,"cover_image":8},18,"Menggunakan Script Python","Working with Python Scripts","python-pemrograman-dengan-script",{"id":145,"title":146,"title_en":147,"slug":148,"cover_image":8},19,"Struktur Kontrol","Control Structures","python-struktur-kontrol",{"id":150,"title":151,"title_en":152,"slug":153,"cover_image":8},20,"Pemrograman Fungsional dengan Python","Functional Programming with Python","python-pemrograman-fungsional",{"id":82,"title":83,"title_en":84,"slug":85,"cover_image":8},[156],{"authors_id":157},{"id":94,"name":95,"slug":96,"role":97,"profile_picture":8},"en",[160],"id","\nI wrote a word counter in eleven lines. It ran on my test sentence, printed what I expected, and I closed the editor.\n\nThen came a paragraph where the word \"the\" appears twice, and it stopped at line 7 with `KeyError: 'the'`. Not a wrong answer — no answer at all.\n\nIf you have written the same loop, you already know roughly where the fix goes. What takes longer than the fix is knowing which of a dict's three lookups belongs in which situation, and that is the part this article is about.\n\n## Two files, one command\n\nEvery article in this series works the same way: you write the program, the article gives you a checker, and you run the checker until it goes from red to green. No repository to clone, no Docker, no account: two files and `python3`.\n\nCreate `wordcount.py` with this, in a folder with nothing else in it:\n\n```python\n# wordcount.py\nimport sys\n\ntext = sys.stdin.read().lower()\ncounts = {}\n\nfor word in text.split():\n    counts[word] += 1\n\nfor word in sorted(counts):\n    print(f\"{word}: {counts[word]}\")\n```\n\nThen create `check_wordcount.py` next to it. It runs your file the way you would, with a sentence piped in, and reads what comes out:\n\n```python\n# check_wordcount.py\nimport subprocess\nimport sys\n\nSENTENCE = \"The cat and the dog and the bird\"\nEXPECTED = {\"and\": 2, \"bird\": 1, \"cat\": 1, \"dog\": 1, \"the\": 3}\n\n\ndef run_wordcount():\n    return subprocess.run(\n        [sys.executable, \"wordcount.py\"],\n        input=SENTENCE,\n        text=True,\n        capture_output=True,\n    )\n\n\ndef parse(stdout):\n    counted = {}\n    for line in stdout.splitlines():\n        word, _, count = line.partition(\":\")\n        if word.strip() and count.strip():\n            counted[word.strip()] = count.strip()\n    return counted\n\n\ndef report(results):\n    for label, passed, detail in results:\n        mark = \"PASS\" if passed else \"FAIL\"\n        note = f\"  \u003C- {detail}\" if detail and not passed else \"\"\n        print(f\"{mark}  {label}{note}\")\n    passed = sum(1 for _, ok, _ in results if ok)\n    print(f\"\\n{passed} of {len(results)} checks passed\")\n\n\ndef main():\n    run = run_wordcount()\n    crashed = run.returncode != 0\n    last_error = run.stderr.strip().splitlines()[-1] if run.stderr.strip() else \"\"\n\n    results = [(\"the script runs without crashing\", not crashed, last_error)]\n    if crashed:\n        report(results)\n        return 1\n\n    counted = parse(run.stdout)\n    results.append(\n        (\"every word in the sentence is counted once\", sorted(counted) == sorted(EXPECTED), str(sorted(counted)))\n    )\n    for word, expected in sorted(EXPECTED.items()):\n        results.append((f\"'{word}' is counted {expected} time(s)\", counted.get(word) == str(expected), f\"got {counted.get(word)}\"))\n\n    report(results)\n    return 0 if all(ok for _, ok, _ in results) else 1\n\n\nif __name__ == \"__main__\":\n    sys.exit(main())\n```\n\nYou do not have to understand the checker yet: it reads your program's output, not your program. Run it:\n\n```\n$ python3 check_wordcount.py\nFAIL  the script runs without crashing  \u003C- KeyError: 'the'\n\n0 of 1 checks passed\n```\n\nRed on the first check, and it stops there: while the script crashes, the counts are not worth measuring.\n\n## Why the first line fails\n\n`counts[word] += 1` looks like one operation. It is three:\n\n1. read `counts[word]`,\n2. add 1,\n3. write the result back to `counts[word]`.\n\nThe read happens first, on a dict that has no shelves yet. `{}` holds no keys, and Python's `dict` does not invent a default for the subscript syntax. A missing key is an error, not a zero. That strictness is the feature: `counts[\"teh\"]` returning `0` would hide every typo in your own key names as well.\n\n## Three ways to ask a dictionary for a key\n\nThey differ only in what happens when the key is not there.\n\n- `counts[word]` raises `KeyError`. Right when the key must exist: a config field, a record you just inserted.\n- `counts.get(word)` returns `None`; `counts.get(word, 0)` returns your default. Right when absence is a normal state, which for a counter it is: a word is absent exactly until you count it.\n- `counts.setdefault(word, 0)` returns the value and inserts the default if it was missing. Right when you are about to append to or extend that value, such as a list of orders per customer or a set of tags per post.\n\nAnd `word in counts` answers \"is it there\" without touching the value, which is what you want when the question is about membership rather than a count.\n\n## The fix, in one line\n\n```python\n# wordcount.py\nimport sys\n\ntext = sys.stdin.read().lower()\ncounts = {}\n\nfor word in text.split():\n    counts[word] = counts.get(word, 0) + 1\n\nfor word in sorted(counts):\n    print(f\"{word}: {counts[word]}\")\n```\n\n```\n$ python3 check_wordcount.py\nPASS  the script runs without crashing\nPASS  every word in the sentence is counted once\nPASS  'and' is counted 2 time(s)\nPASS  'bird' is counted 1 time(s)\nPASS  'cat' is counted 1 time(s)\nPASS  'dog' is counted 1 time(s)\nPASS  'the' is counted 3 time(s)\n\n7 of 7 checks passed\n```\n\n`get(word, 0)` gives the read a value when the key is absent, so the increment always has something to add to. The dict still never invents a default for `counts[word]`. You asked for one explicitly, on the one line where absence is expected.\n\n## Why a dict is fast\n\nA dict does not walk through its contents looking for your key. It hashes the key and jumps straight to the slot, so a lookup costs about the same whether the dict holds ten entries or a million.\n\nA list is the opposite: to answer \"is this word in there\", it checks one element at a time until it finds a match. On a list of 1,000,000 words, that took 18 milliseconds. The same question to a dict took less than a thousandth of a millisecond, about 130,000 times faster.\n\nThat gap is the whole reason `counts` is a dict and not a list of pairs, and why \"I'll just search the list\" stops being a plan once the data no longer fits on one screen.\n\nTwo properties of dicts worth knowing now:\n\n- **Insertion order is preserved** (since Python 3.7). `sorted(counts)` in the script is there for readability, not correctness — iterating `counts` directly would have kept the order the words first appeared.\n- **Keys must be hashable.** Strings, numbers, and tuples can be keys; lists cannot. You will see why in the next article.\n\n## The builtin that exists for exactly this\n\nCounting occurrences is common enough that the standard library ships it:\n\n```python\nfrom collections import Counter\n\ncounts = Counter(text.split())\nprint(counts.most_common(3))\n```\n\n`Counter` is a dict subclass; `most_common(3)` is the part you would otherwise write by hand. The rule of thumb from here on: **when the job is counting, reach for `Counter`; when the key is a record you look up by name, write the dict yourself.** Use `get(key, default)` when absence is expected, and `setdefault` when you are building a nested structure. Use `[]` only when a missing key really is a bug, because that is exactly what it will raise.\n\n## Before the next article\n\nChange `SENTENCE` in the checker to a sentence of your own and update `EXPECTED` to match. Then remove `.lower()` from `wordcount.py` and run the check again — one of the seven checks catches it, and the reason it catches it is the next thing worth understanding about keys.\n\nThat is the deal this series makes: the checker is not a certificate, it is a second reader. It only knows what you told it to expect.\n","- `counts[word] += 1` reads before it writes, so on an empty dict the first occurrence of a word raises `KeyError`.\n- `counts.get(word, 0)` is the fix for counting; `counts.setdefault(word, [])` is the one for building nested structures.\n- A dict lookup jumps to the slot instead of scanning: on 1,000,000 words, checking for one took 18 milliseconds in a list and less than a thousandth of a millisecond in a dict.\n- Reach for `collections.Counter` when the job is counting, and for a plain dict when the key is a record you look up by name.\n- If you `except KeyError` instead of using `get`, the code should still say why in a comment.","\u003Cp>I wrote a word counter in eleven lines. It ran on my test sentence, printed what I expected, and I closed the editor.\u003C\u002Fp>\n\u003Cp>Then came a paragraph where the word &quot;the&quot; appears twice, and it stopped at line 7 with \u003Ccode>KeyError: &#39;the&#39;\u003C\u002Fcode>. Not a wrong answer — no answer at all.\u003C\u002Fp>\n\u003Cp>If you have written the same loop, you already know roughly where the fix goes. What takes longer than the fix is knowing which of a dict&#39;s three lookups belongs in which situation, and that is the part this article is about.\u003C\u002Fp>\n\u003Ch2 id=\"two-files-one-command\">Two files, one command\u003C\u002Fh2>\n\u003Cp>Every article in this series works the same way: you write the program, the article gives you a checker, and you run the checker until it goes from red to green. No repository to clone, no Docker, no account: two files and \u003Ccode>python3\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cp>Create \u003Ccode>wordcount.py\u003C\u002Fcode> with this, in a folder with nothing else in it:\u003C\u002Fp>\n\n\u003Cdiv class=\"code-block-wrapper my-6 not-prose rounded-xl border-2 border-black dark:border-gray-600 shadow-neo overflow-hidden bg-[#24292e] text-[#e1e4e8] transition-all\">\n  \u003Cdiv class=\"code-block-header flex items-center justify-between px-4 py-2.5 bg-[#1f2428] border-b-2 border-black dark:border-gray-600 font-mono text-xs font-bold text-gray-300 select-none\">\n    \u003Cdiv class=\"flex items-center gap-2\">\n      \u003Cspan class=\"w-3 h-3 rounded-full bg-[#ff5f56] border border-black\u002F40 inline-block\">\u003C\u002Fspan>\n      \u003Cspan class=\"w-3 h-3 rounded-full bg-[#ffbd2e] border border-black\u002F40 inline-block\">\u003C\u002Fspan>\n      \u003Cspan class=\"w-3 h-3 rounded-full bg-[#27c93f] border border-black\u002F40 inline-block\">\u003C\u002Fspan>\n      \u003Cspan class=\"ml-2 font-mono font-bold text-xs uppercase tracking-wider text-gray-300\">PYTHON\u003C\u002Fspan>\n    \u003C\u002Fdiv>\n    \u003Cbutton type=\"button\" class=\"copy-code-btn flex items-center gap-1.5 px-3 py-1 text-xs font-bold bg-[#2f363d] text-gray-200 border-2 border-black dark:border-gray-500 rounded-lg shadow-neo-sm hover:translate-x-0.5 hover:translate-y-0.5 hover:shadow-none hover:bg-gray-700 transition-all cursor-pointer\" data-code=\"%23%20wordcount.py%0Aimport%20sys%0A%0Atext%20%3D%20sys.stdin.read().lower()%0Acounts%20%3D%20%7B%7D%0A%0Afor%20word%20in%20text.split()%3A%0A%20%20%20%20counts%5Bword%5D%20%2B%3D%201%0A%0Afor%20word%20in%20sorted(counts)%3A%0A%20%20%20%20print(f%22%7Bword%7D%3A%20%7Bcounts%5Bword%5D%7D%22)\" title=\"Salin kode\">\n      \u003Csvg class=\"w-3.5 h-3.5 copy-icon-svg\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n        \u003Cpath stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z\">\u003C\u002Fpath>\n      \u003C\u002Fsvg>\n      \u003Cspan class=\"copy-text\">Copy\u003C\u002Fspan>\n    \u003C\u002Fbutton>\n  \u003C\u002Fdiv>\n  \u003Cdiv class=\"code-block-content p-4 overflow-x-auto text-sm font-mono leading-relaxed bg-[#24292e]\">\n    \u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># wordcount.py\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">import\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> sys\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">text \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> sys.stdin.read().lower()\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">counts \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> {}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">for\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> word \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">in\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> text.split():\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    counts[word] \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">+=\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\"> 1\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">for\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> word \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">in\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\"> sorted\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(counts):\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">    print\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">f\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">{\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">word\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">}\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">: \u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">{\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">counts[word]\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">}\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">)\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n  \u003C\u002Fdiv>\n\u003C\u002Fdiv>\u003Cp>Then create \u003Ccode>check_wordcount.py\u003C\u002Fcode> next to it. It runs your file the way you would, with a sentence piped in, and reads what comes out:\u003C\u002Fp>\n\n\u003Cdiv class=\"code-block-wrapper my-6 not-prose rounded-xl border-2 border-black dark:border-gray-600 shadow-neo overflow-hidden bg-[#24292e] text-[#e1e4e8] transition-all\">\n  \u003Cdiv class=\"code-block-header flex items-center justify-between px-4 py-2.5 bg-[#1f2428] border-b-2 border-black dark:border-gray-600 font-mono text-xs font-bold text-gray-300 select-none\">\n    \u003Cdiv class=\"flex items-center gap-2\">\n      \u003Cspan class=\"w-3 h-3 rounded-full bg-[#ff5f56] border border-black\u002F40 inline-block\">\u003C\u002Fspan>\n      \u003Cspan class=\"w-3 h-3 rounded-full bg-[#ffbd2e] border border-black\u002F40 inline-block\">\u003C\u002Fspan>\n      \u003Cspan class=\"w-3 h-3 rounded-full bg-[#27c93f] border border-black\u002F40 inline-block\">\u003C\u002Fspan>\n      \u003Cspan class=\"ml-2 font-mono font-bold text-xs uppercase tracking-wider text-gray-300\">PYTHON\u003C\u002Fspan>\n    \u003C\u002Fdiv>\n    \u003Cbutton type=\"button\" class=\"copy-code-btn flex items-center gap-1.5 px-3 py-1 text-xs font-bold bg-[#2f363d] text-gray-200 border-2 border-black dark:border-gray-500 rounded-lg shadow-neo-sm hover:translate-x-0.5 hover:translate-y-0.5 hover:shadow-none hover:bg-gray-700 transition-all cursor-pointer\" data-code=\"%23%20check_wordcount.py%0Aimport%20subprocess%0Aimport%20sys%0A%0ASENTENCE%20%3D%20%22The%20cat%20and%20the%20dog%20and%20the%20bird%22%0AEXPECTED%20%3D%20%7B%22and%22%3A%202%2C%20%22bird%22%3A%201%2C%20%22cat%22%3A%201%2C%20%22dog%22%3A%201%2C%20%22the%22%3A%203%7D%0A%0A%0Adef%20run_wordcount()%3A%0A%20%20%20%20return%20subprocess.run(%0A%20%20%20%20%20%20%20%20%5Bsys.executable%2C%20%22wordcount.py%22%5D%2C%0A%20%20%20%20%20%20%20%20input%3DSENTENCE%2C%0A%20%20%20%20%20%20%20%20text%3DTrue%2C%0A%20%20%20%20%20%20%20%20capture_output%3DTrue%2C%0A%20%20%20%20)%0A%0A%0Adef%20parse(stdout)%3A%0A%20%20%20%20counted%20%3D%20%7B%7D%0A%20%20%20%20for%20line%20in%20stdout.splitlines()%3A%0A%20%20%20%20%20%20%20%20word%2C%20_%2C%20count%20%3D%20line.partition(%22%3A%22)%0A%20%20%20%20%20%20%20%20if%20word.strip()%20and%20count.strip()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20counted%5Bword.strip()%5D%20%3D%20count.strip()%0A%20%20%20%20return%20counted%0A%0A%0Adef%20report(results)%3A%0A%20%20%20%20for%20label%2C%20passed%2C%20detail%20in%20results%3A%0A%20%20%20%20%20%20%20%20mark%20%3D%20%22PASS%22%20if%20passed%20else%20%22FAIL%22%0A%20%20%20%20%20%20%20%20note%20%3D%20f%22%20%20%3C-%20%7Bdetail%7D%22%20if%20detail%20and%20not%20passed%20else%20%22%22%0A%20%20%20%20%20%20%20%20print(f%22%7Bmark%7D%20%20%7Blabel%7D%7Bnote%7D%22)%0A%20%20%20%20passed%20%3D%20sum(1%20for%20_%2C%20ok%2C%20_%20in%20results%20if%20ok)%0A%20%20%20%20print(f%22%5Cn%7Bpassed%7D%20of%20%7Blen(results)%7D%20checks%20passed%22)%0A%0A%0Adef%20main()%3A%0A%20%20%20%20run%20%3D%20run_wordcount()%0A%20%20%20%20crashed%20%3D%20run.returncode%20!%3D%200%0A%20%20%20%20last_error%20%3D%20run.stderr.strip().splitlines()%5B-1%5D%20if%20run.stderr.strip()%20else%20%22%22%0A%0A%20%20%20%20results%20%3D%20%5B(%22the%20script%20runs%20without%20crashing%22%2C%20not%20crashed%2C%20last_error)%5D%0A%20%20%20%20if%20crashed%3A%0A%20%20%20%20%20%20%20%20report(results)%0A%20%20%20%20%20%20%20%20return%201%0A%0A%20%20%20%20counted%20%3D%20parse(run.stdout)%0A%20%20%20%20results.append(%0A%20%20%20%20%20%20%20%20(%22every%20word%20in%20the%20sentence%20is%20counted%20once%22%2C%20sorted(counted)%20%3D%3D%20sorted(EXPECTED)%2C%20str(sorted(counted)))%0A%20%20%20%20)%0A%20%20%20%20for%20word%2C%20expected%20in%20sorted(EXPECTED.items())%3A%0A%20%20%20%20%20%20%20%20results.append((f%22'%7Bword%7D'%20is%20counted%20%7Bexpected%7D%20time(s)%22%2C%20counted.get(word)%20%3D%3D%20str(expected)%2C%20f%22got%20%7Bcounted.get(word)%7D%22))%0A%0A%20%20%20%20report(results)%0A%20%20%20%20return%200%20if%20all(ok%20for%20_%2C%20ok%2C%20_%20in%20results)%20else%201%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20sys.exit(main())\" title=\"Salin kode\">\n      \u003Csvg class=\"w-3.5 h-3.5 copy-icon-svg\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n        \u003Cpath stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z\">\u003C\u002Fpath>\n      \u003C\u002Fsvg>\n      \u003Cspan class=\"copy-text\">Copy\u003C\u002Fspan>\n    \u003C\u002Fbutton>\n  \u003C\u002Fdiv>\n  \u003Cdiv class=\"code-block-content p-4 overflow-x-auto text-sm font-mono leading-relaxed bg-[#24292e]\">\n    \u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># check_wordcount.py\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">import\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> subprocess\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">import\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> sys\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">SENTENCE\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\"> =\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\"> \"The cat and the dog and the bird\"\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">EXPECTED\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\"> =\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> {\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"and\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">: \u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">2\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"bird\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">: \u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">1\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"cat\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">: \u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">1\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"dog\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">: \u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">1\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"the\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">: \u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">3\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">def\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> run_wordcount\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">():\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    return\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> subprocess.run(\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        [sys.executable, \u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"wordcount.py\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">],\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FFAB70\">        input\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">SENTENCE\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FFAB70\">        text\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">True\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FFAB70\">        capture_output\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">True\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    )\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">def\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> parse\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(stdout):\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    counted \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> {}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    for\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> line \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">in\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> stdout.splitlines():\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        word, _, count \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> line.partition(\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\":\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        if\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> word.strip() \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">and\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> count.strip():\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">            counted[word.strip()] \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> count.strip()\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    return\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> counted\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">def\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> report\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(results):\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    for\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> label, passed, detail \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">in\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> results:\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        mark \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\"> \"PASS\"\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\"> if\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> passed \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">else\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\"> \"FAIL\"\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        note \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\"> f\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"  &#x3C;- \u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">{\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">detail\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">}\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\"> if\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> detail \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">and\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\"> not\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> passed \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">else\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\"> \"\"\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">        print\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">f\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">{\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">mark\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">}\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">  {\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">label\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">}{\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">note\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">}\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    passed \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\"> sum\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">1\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\"> for\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> _, ok, _ \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">in\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> results \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">if\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> ok)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">    print\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">f\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">\\n{\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">passed\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">}\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\"> of \u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">{len\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(results)\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">}\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\"> checks passed\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">def\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> main\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">():\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    run \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> run_wordcount()\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    crashed \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> run.returncode \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">!=\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\"> 0\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    last_error \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> run.stderr.strip().splitlines()[\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">-\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">1\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">] \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">if\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> run.stderr.strip() \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">else\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\"> \"\"\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    results \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> [(\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"the script runs without crashing\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">not\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> crashed, last_error)]\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    if\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> crashed:\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        report(results)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\"> 1\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    counted \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> parse(run.stdout)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    results.append(\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        (\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"every word in the sentence is counted once\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">sorted\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(counted) \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">==\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\"> sorted\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">EXPECTED\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">), \u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">str\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">sorted\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(counted)))\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    )\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    for\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> word, expected \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">in\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\"> sorted\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">EXPECTED\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">.items()):\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        results.append((\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">f\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"'\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">{\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">word\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">}\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">' is counted \u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">{\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">expected\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">}\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\"> time(s)\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">, counted.get(word) \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">==\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\"> str\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(expected), \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">f\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"got \u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">{\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">counted.get(word)\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">}\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">))\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    report(results)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    return\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\"> 0\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\"> if\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\"> all\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(ok \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">for\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> _, ok, _ \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">in\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> results) \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">else\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\"> 1\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">if\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\"> __name__\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\"> ==\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\"> \"__main__\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">:\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    sys.exit(main())\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n  \u003C\u002Fdiv>\n\u003C\u002Fdiv>\u003Cp>You do not have to understand the checker yet: it reads your program&#39;s output, not your program. Run it:\u003C\u002Fp>\n\n\u003Cdiv class=\"code-block-wrapper my-6 not-prose rounded-xl border-2 border-black dark:border-gray-600 shadow-neo overflow-hidden bg-[#24292e] text-[#e1e4e8] transition-all\">\n  \u003Cdiv class=\"code-block-header flex items-center justify-between px-4 py-2.5 bg-[#1f2428] border-b-2 border-black dark:border-gray-600 font-mono text-xs font-bold text-gray-300 select-none\">\n    \u003Cdiv class=\"flex items-center gap-2\">\n      \u003Cspan class=\"w-3 h-3 rounded-full bg-[#ff5f56] border border-black\u002F40 inline-block\">\u003C\u002Fspan>\n      \u003Cspan class=\"w-3 h-3 rounded-full bg-[#ffbd2e] border border-black\u002F40 inline-block\">\u003C\u002Fspan>\n      \u003Cspan class=\"w-3 h-3 rounded-full bg-[#27c93f] border border-black\u002F40 inline-block\">\u003C\u002Fspan>\n      \u003Cspan class=\"ml-2 font-mono font-bold text-xs uppercase tracking-wider text-gray-300\">TEXT\u003C\u002Fspan>\n    \u003C\u002Fdiv>\n    \u003Cbutton type=\"button\" class=\"copy-code-btn flex items-center gap-1.5 px-3 py-1 text-xs font-bold bg-[#2f363d] text-gray-200 border-2 border-black dark:border-gray-500 rounded-lg shadow-neo-sm hover:translate-x-0.5 hover:translate-y-0.5 hover:shadow-none hover:bg-gray-700 transition-all cursor-pointer\" data-code=\"%24%20python3%20check_wordcount.py%0AFAIL%20%20the%20script%20runs%20without%20crashing%20%20%3C-%20KeyError%3A%20'the'%0A%0A0%20of%201%20checks%20passed\" title=\"Salin kode\">\n      \u003Csvg class=\"w-3.5 h-3.5 copy-icon-svg\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n        \u003Cpath stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z\">\u003C\u002Fpath>\n      \u003C\u002Fsvg>\n      \u003Cspan class=\"copy-text\">Copy\u003C\u002Fspan>\n    \u003C\u002Fbutton>\n  \u003C\u002Fdiv>\n  \u003Cdiv class=\"code-block-content p-4 overflow-x-auto text-sm font-mono leading-relaxed bg-[#24292e]\">\n    \u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\">\u003Ccode>$ python3 check_wordcount.py\nFAIL  the script runs without crashing  &lt;- KeyError: 'the'\n\n0 of 1 checks passed\u003C\u002Fcode>\u003C\u002Fpre>\n  \u003C\u002Fdiv>\n\u003C\u002Fdiv>\u003Cp>Red on the first check, and it stops there: while the script crashes, the counts are not worth measuring.\u003C\u002Fp>\n\u003Ch2 id=\"why-the-first-line-fails\">Why the first line fails\u003C\u002Fh2>\n\u003Cp>\u003Ccode>counts[word] += 1\u003C\u002Fcode> looks like one operation. It is three:\u003C\u002Fp>\n\u003Col>\n\u003Cli>read \u003Ccode>counts[word]\u003C\u002Fcode>,\u003C\u002Fli>\n\u003Cli>add 1,\u003C\u002Fli>\n\u003Cli>write the result back to \u003Ccode>counts[word]\u003C\u002Fcode>.\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>The read happens first, on a dict that has no shelves yet. \u003Ccode>{}\u003C\u002Fcode> holds no keys, and Python&#39;s \u003Ccode>dict\u003C\u002Fcode> does not invent a default for the subscript syntax. A missing key is an error, not a zero. That strictness is the feature: \u003Ccode>counts[&quot;teh&quot;]\u003C\u002Fcode> returning \u003Ccode>0\u003C\u002Fcode> would hide every typo in your own key names as well.\u003C\u002Fp>\n\u003Ch2 id=\"three-ways-to-ask-a-dictionary-for-a-key\">Three ways to ask a dictionary for a key\u003C\u002Fh2>\n\u003Cp>They differ only in what happens when the key is not there.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Ccode>counts[word]\u003C\u002Fcode> raises \u003Ccode>KeyError\u003C\u002Fcode>. Right when the key must exist: a config field, a record you just inserted.\u003C\u002Fli>\n\u003Cli>\u003Ccode>counts.get(word)\u003C\u002Fcode> returns \u003Ccode>None\u003C\u002Fcode>; \u003Ccode>counts.get(word, 0)\u003C\u002Fcode> returns your default. Right when absence is a normal state, which for a counter it is: a word is absent exactly until you count it.\u003C\u002Fli>\n\u003Cli>\u003Ccode>counts.setdefault(word, 0)\u003C\u002Fcode> returns the value and inserts the default if it was missing. Right when you are about to append to or extend that value, such as a list of orders per customer or a set of tags per post.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>And \u003Ccode>word in counts\u003C\u002Fcode> answers &quot;is it there&quot; without touching the value, which is what you want when the question is about membership rather than a count.\u003C\u002Fp>\n\u003Ch2 id=\"the-fix-in-one-line\">The fix, in one line\u003C\u002Fh2>\n\n\u003Cdiv class=\"code-block-wrapper my-6 not-prose rounded-xl border-2 border-black dark:border-gray-600 shadow-neo overflow-hidden bg-[#24292e] text-[#e1e4e8] transition-all\">\n  \u003Cdiv class=\"code-block-header flex items-center justify-between px-4 py-2.5 bg-[#1f2428] border-b-2 border-black dark:border-gray-600 font-mono text-xs font-bold text-gray-300 select-none\">\n    \u003Cdiv class=\"flex items-center gap-2\">\n      \u003Cspan class=\"w-3 h-3 rounded-full bg-[#ff5f56] border border-black\u002F40 inline-block\">\u003C\u002Fspan>\n      \u003Cspan class=\"w-3 h-3 rounded-full bg-[#ffbd2e] border border-black\u002F40 inline-block\">\u003C\u002Fspan>\n      \u003Cspan class=\"w-3 h-3 rounded-full bg-[#27c93f] border border-black\u002F40 inline-block\">\u003C\u002Fspan>\n      \u003Cspan class=\"ml-2 font-mono font-bold text-xs uppercase tracking-wider text-gray-300\">PYTHON\u003C\u002Fspan>\n    \u003C\u002Fdiv>\n    \u003Cbutton type=\"button\" class=\"copy-code-btn flex items-center gap-1.5 px-3 py-1 text-xs font-bold bg-[#2f363d] text-gray-200 border-2 border-black dark:border-gray-500 rounded-lg shadow-neo-sm hover:translate-x-0.5 hover:translate-y-0.5 hover:shadow-none hover:bg-gray-700 transition-all cursor-pointer\" data-code=\"%23%20wordcount.py%0Aimport%20sys%0A%0Atext%20%3D%20sys.stdin.read().lower()%0Acounts%20%3D%20%7B%7D%0A%0Afor%20word%20in%20text.split()%3A%0A%20%20%20%20counts%5Bword%5D%20%3D%20counts.get(word%2C%200)%20%2B%201%0A%0Afor%20word%20in%20sorted(counts)%3A%0A%20%20%20%20print(f%22%7Bword%7D%3A%20%7Bcounts%5Bword%5D%7D%22)\" title=\"Salin kode\">\n      \u003Csvg class=\"w-3.5 h-3.5 copy-icon-svg\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n        \u003Cpath stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z\">\u003C\u002Fpath>\n      \u003C\u002Fsvg>\n      \u003Cspan class=\"copy-text\">Copy\u003C\u002Fspan>\n    \u003C\u002Fbutton>\n  \u003C\u002Fdiv>\n  \u003Cdiv class=\"code-block-content p-4 overflow-x-auto text-sm font-mono leading-relaxed bg-[#24292e]\">\n    \u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># wordcount.py\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">import\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> sys\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">text \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> sys.stdin.read().lower()\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">counts \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> {}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">for\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> word \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">in\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> text.split():\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    counts[word] \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> counts.get(word, \u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">0\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">) \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\"> 1\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">for\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> word \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">in\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\"> sorted\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(counts):\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">    print\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">f\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">{\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">word\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">}\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">: \u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">{\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">counts[word]\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">}\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">)\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n  \u003C\u002Fdiv>\n\u003C\u002Fdiv>\n\u003Cdiv class=\"code-block-wrapper my-6 not-prose rounded-xl border-2 border-black dark:border-gray-600 shadow-neo overflow-hidden bg-[#24292e] text-[#e1e4e8] transition-all\">\n  \u003Cdiv class=\"code-block-header flex items-center justify-between px-4 py-2.5 bg-[#1f2428] border-b-2 border-black dark:border-gray-600 font-mono text-xs font-bold text-gray-300 select-none\">\n    \u003Cdiv class=\"flex items-center gap-2\">\n      \u003Cspan class=\"w-3 h-3 rounded-full bg-[#ff5f56] border border-black\u002F40 inline-block\">\u003C\u002Fspan>\n      \u003Cspan class=\"w-3 h-3 rounded-full bg-[#ffbd2e] border border-black\u002F40 inline-block\">\u003C\u002Fspan>\n      \u003Cspan class=\"w-3 h-3 rounded-full bg-[#27c93f] border border-black\u002F40 inline-block\">\u003C\u002Fspan>\n      \u003Cspan class=\"ml-2 font-mono font-bold text-xs uppercase tracking-wider text-gray-300\">TEXT\u003C\u002Fspan>\n    \u003C\u002Fdiv>\n    \u003Cbutton type=\"button\" class=\"copy-code-btn flex items-center gap-1.5 px-3 py-1 text-xs font-bold bg-[#2f363d] text-gray-200 border-2 border-black dark:border-gray-500 rounded-lg shadow-neo-sm hover:translate-x-0.5 hover:translate-y-0.5 hover:shadow-none hover:bg-gray-700 transition-all cursor-pointer\" data-code=\"%24%20python3%20check_wordcount.py%0APASS%20%20the%20script%20runs%20without%20crashing%0APASS%20%20every%20word%20in%20the%20sentence%20is%20counted%20once%0APASS%20%20'and'%20is%20counted%202%20time(s)%0APASS%20%20'bird'%20is%20counted%201%20time(s)%0APASS%20%20'cat'%20is%20counted%201%20time(s)%0APASS%20%20'dog'%20is%20counted%201%20time(s)%0APASS%20%20'the'%20is%20counted%203%20time(s)%0A%0A7%20of%207%20checks%20passed\" title=\"Salin kode\">\n      \u003Csvg class=\"w-3.5 h-3.5 copy-icon-svg\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n        \u003Cpath stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z\">\u003C\u002Fpath>\n      \u003C\u002Fsvg>\n      \u003Cspan class=\"copy-text\">Copy\u003C\u002Fspan>\n    \u003C\u002Fbutton>\n  \u003C\u002Fdiv>\n  \u003Cdiv class=\"code-block-content p-4 overflow-x-auto text-sm font-mono leading-relaxed bg-[#24292e]\">\n    \u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\">\u003Ccode>$ python3 check_wordcount.py\nPASS  the script runs without crashing\nPASS  every word in the sentence is counted once\nPASS  'and' is counted 2 time(s)\nPASS  'bird' is counted 1 time(s)\nPASS  'cat' is counted 1 time(s)\nPASS  'dog' is counted 1 time(s)\nPASS  'the' is counted 3 time(s)\n\n7 of 7 checks passed\u003C\u002Fcode>\u003C\u002Fpre>\n  \u003C\u002Fdiv>\n\u003C\u002Fdiv>\u003Cp>\u003Ccode>get(word, 0)\u003C\u002Fcode> gives the read a value when the key is absent, so the increment always has something to add to. The dict still never invents a default for \u003Ccode>counts[word]\u003C\u002Fcode>. You asked for one explicitly, on the one line where absence is expected.\u003C\u002Fp>\n\u003Ch2 id=\"why-a-dict-is-fast\">Why a dict is fast\u003C\u002Fh2>\n\u003Cp>A dict does not walk through its contents looking for your key. It hashes the key and jumps straight to the slot, so a lookup costs about the same whether the dict holds ten entries or a million.\u003C\u002Fp>\n\u003Cp>A list is the opposite: to answer &quot;is this word in there&quot;, it checks one element at a time until it finds a match. On a list of 1,000,000 words, that took 18 milliseconds. The same question to a dict took less than a thousandth of a millisecond, about 130,000 times faster.\u003C\u002Fp>\n\u003Cp>That gap is the whole reason \u003Ccode>counts\u003C\u002Fcode> is a dict and not a list of pairs, and why &quot;I&#39;ll just search the list&quot; stops being a plan once the data no longer fits on one screen.\u003C\u002Fp>\n\u003Cp>Two properties of dicts worth knowing now:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Insertion order is preserved\u003C\u002Fstrong> (since Python 3.7). \u003Ccode>sorted(counts)\u003C\u002Fcode> in the script is there for readability, not correctness — iterating \u003Ccode>counts\u003C\u002Fcode> directly would have kept the order the words first appeared.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Keys must be hashable.\u003C\u002Fstrong> Strings, numbers, and tuples can be keys; lists cannot. You will see why in the next article.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"the-builtin-that-exists-for-exactly-this\">The builtin that exists for exactly this\u003C\u002Fh2>\n\u003Cp>Counting occurrences is common enough that the standard library ships it:\u003C\u002Fp>\n\n\u003Cdiv class=\"code-block-wrapper my-6 not-prose rounded-xl border-2 border-black dark:border-gray-600 shadow-neo overflow-hidden bg-[#24292e] text-[#e1e4e8] transition-all\">\n  \u003Cdiv class=\"code-block-header flex items-center justify-between px-4 py-2.5 bg-[#1f2428] border-b-2 border-black dark:border-gray-600 font-mono text-xs font-bold text-gray-300 select-none\">\n    \u003Cdiv class=\"flex items-center gap-2\">\n      \u003Cspan class=\"w-3 h-3 rounded-full bg-[#ff5f56] border border-black\u002F40 inline-block\">\u003C\u002Fspan>\n      \u003Cspan class=\"w-3 h-3 rounded-full bg-[#ffbd2e] border border-black\u002F40 inline-block\">\u003C\u002Fspan>\n      \u003Cspan class=\"w-3 h-3 rounded-full bg-[#27c93f] border border-black\u002F40 inline-block\">\u003C\u002Fspan>\n      \u003Cspan class=\"ml-2 font-mono font-bold text-xs uppercase tracking-wider text-gray-300\">PYTHON\u003C\u002Fspan>\n    \u003C\u002Fdiv>\n    \u003Cbutton type=\"button\" class=\"copy-code-btn flex items-center gap-1.5 px-3 py-1 text-xs font-bold bg-[#2f363d] text-gray-200 border-2 border-black dark:border-gray-500 rounded-lg shadow-neo-sm hover:translate-x-0.5 hover:translate-y-0.5 hover:shadow-none hover:bg-gray-700 transition-all cursor-pointer\" data-code=\"from%20collections%20import%20Counter%0A%0Acounts%20%3D%20Counter(text.split())%0Aprint(counts.most_common(3))\" title=\"Salin kode\">\n      \u003Csvg class=\"w-3.5 h-3.5 copy-icon-svg\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n        \u003Cpath stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z\">\u003C\u002Fpath>\n      \u003C\u002Fsvg>\n      \u003Cspan class=\"copy-text\">Copy\u003C\u002Fspan>\n    \u003C\u002Fbutton>\n  \u003C\u002Fdiv>\n  \u003Cdiv class=\"code-block-content p-4 overflow-x-auto text-sm font-mono leading-relaxed bg-[#24292e]\">\n    \u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">from\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> collections \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">import\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> Counter\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">counts \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> Counter(text.split())\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">print\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(counts.most_common(\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">3\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">))\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n  \u003C\u002Fdiv>\n\u003C\u002Fdiv>\u003Cp>\u003Ccode>Counter\u003C\u002Fcode> is a dict subclass; \u003Ccode>most_common(3)\u003C\u002Fcode> is the part you would otherwise write by hand. The rule of thumb from here on: \u003Cstrong>when the job is counting, reach for \u003Ccode>Counter\u003C\u002Fcode>; when the key is a record you look up by name, write the dict yourself.\u003C\u002Fstrong> Use \u003Ccode>get(key, default)\u003C\u002Fcode> when absence is expected, and \u003Ccode>setdefault\u003C\u002Fcode> when you are building a nested structure. Use \u003Ccode>[]\u003C\u002Fcode> only when a missing key really is a bug, because that is exactly what it will raise.\u003C\u002Fp>\n\u003Ch2 id=\"before-the-next-article\">Before the next article\u003C\u002Fh2>\n\u003Cp>Change \u003Ccode>SENTENCE\u003C\u002Fcode> in the checker to a sentence of your own and update \u003Ccode>EXPECTED\u003C\u002Fcode> to match. Then remove \u003Ccode>.lower()\u003C\u002Fcode> from \u003Ccode>wordcount.py\u003C\u002Fcode> and run the check again — one of the seven checks catches it, and the reason it catches it is the next thing worth understanding about keys.\u003C\u002Fp>\n\u003Cp>That is the deal this series makes: the checker is not a certificate, it is a second reader. It only knows what you told it to expect.\u003C\u002Fp>\n","\u003Cul>\n\u003Cli>\u003Ccode>counts[word] += 1\u003C\u002Fcode> reads before it writes, so on an empty dict the first occurrence of a word raises \u003Ccode>KeyError\u003C\u002Fcode>.\u003C\u002Fli>\n\u003Cli>\u003Ccode>counts.get(word, 0)\u003C\u002Fcode> is the fix for counting; \u003Ccode>counts.setdefault(word, [])\u003C\u002Fcode> is the one for building nested structures.\u003C\u002Fli>\n\u003Cli>A dict lookup jumps to the slot instead of scanning: on 1,000,000 words, checking for one took 18 milliseconds in a list and less than a thousandth of a millisecond in a dict.\u003C\u002Fli>\n\u003Cli>Reach for \u003Ccode>collections.Counter\u003C\u002Fcode> when the job is counting, and for a plain dict when the key is a record you look up by name.\u003C\u002Fli>\n\u003Cli>If you \u003Ccode>except KeyError\u003C\u002Fcode> instead of using \u003Ccode>get\u003C\u002Fcode>, the code should still say why in a comment.\u003C\u002Fli>\n\u003C\u002Ful>\n",[166,171,176,181,200,218],{"id":112,"title":113,"title_en":8,"slug":114,"excerpt":115,"excerpt_en":8,"has_translation":11,"published_at":116,"reading_time":76,"hidden_from_listing":11,"tags":117,"thumbnail_image":8,"og_image":119,"series":167,"authors":168},{"id":82,"title":83,"title_en":84,"slug":85},[169],{"authors_id":170},{"id":94,"name":95,"slug":96,"role":97,"profile_picture":8},{"id":99,"title":100,"title_en":8,"slug":101,"excerpt":102,"excerpt_en":8,"has_translation":11,"published_at":103,"reading_time":76,"hidden_from_listing":11,"tags":104,"thumbnail_image":8,"og_image":106,"series":172,"authors":173},{"id":82,"title":83,"title_en":84,"slug":85},[174],{"authors_id":175},{"id":94,"name":95,"slug":96,"role":97,"profile_picture":8},{"id":71,"title":72,"title_en":8,"slug":73,"excerpt":74,"excerpt_en":8,"has_translation":11,"published_at":75,"reading_time":76,"hidden_from_listing":11,"tags":77,"thumbnail_image":8,"og_image":80,"series":177,"authors":178},{"id":82,"title":83,"title_en":84,"slug":85},[179],{"authors_id":180},{"id":94,"name":95,"slug":96,"role":97,"profile_picture":8},{"id":182,"title":183,"title_en":8,"slug":184,"excerpt":185,"excerpt_en":8,"has_translation":11,"published_at":186,"reading_time":13,"hidden_from_listing":11,"tags":187,"thumbnail_image":8,"og_image":194,"series":195,"authors":197},129,"Biar RAG nggak kena 504: pisahin jalur query dan ingestion","rag-pisah-jalur-query-dan-ingestion","Satu service RAG yang merangkap parsing dan menjawab kueri bakal tumbang pas ada yang upload PDF 200 halaman. Obatnya dua jalur dan satu queue.","2026-10-22T01:30:00.000Z",[188,189,190,191,192,193],"rag","cloud-run","cloud-tasks","gcp","serverless","arsitektur","\u002Fog\u002Frag-pisah-jalur-query-dan-ingestion.png",{"id":196,"title":32,"title_en":33,"slug":34},8,[198],{"authors_id":199},{"id":94,"name":95,"slug":96,"role":97,"profile_picture":8},{"id":201,"title":202,"title_en":8,"slug":203,"excerpt":204,"excerpt_en":8,"has_translation":11,"published_at":205,"reading_time":76,"hidden_from_listing":11,"tags":206,"thumbnail_image":8,"og_image":213,"series":214,"authors":215},128,"Linear atau graf: cara mengatur banyak agent tanpa loop abadi","multi-agen-linear-atau-grafik","Satu agent dengan dua puluh tool gampang kacau. Begitu beberapa agent saling revisi, kamu butuh graf yang mendukung cycle dan angka yang membuatnya berhenti.","2026-10-21T16:05:00.000Z",[207,208,209,210,211,212],"multi-agent","langgraph","autogen","orkestrasi","checkpoint","llm","\u002Fog\u002Fmulti-agen-linear-atau-grafik.png",{"id":196,"title":32,"title_en":33,"slug":34},[216],{"authors_id":217},{"id":94,"name":95,"slug":96,"role":97,"profile_picture":8},{"id":219,"title":220,"title_en":8,"slug":221,"excerpt":222,"excerpt_en":8,"has_translation":11,"published_at":223,"reading_time":76,"hidden_from_listing":11,"tags":224,"thumbnail_image":8,"og_image":230,"series":231,"authors":232},127,"Kenapa RAG yang cuma andalkan cosine similarity sering nyasar","kenapa-rag-cuma-cosine-similarity-sering-nyasar","RAG yang cuma andalkan embedding balikin chunk yang mirip maknanya, bukan yang kamu butuh. Ini penyebabnya, dan dua lapis penambalnya.","2026-10-19T02:04:06.718Z",[188,225,226,227,228,229],"vector-search","bm25","hybrid-search","retrieval","embedding","\u002Fog\u002Fkenapa-rag-cuma-cosine-similarity-sering-nyasar.png",{"id":196,"title":32,"title_en":33,"slug":34},[233],{"authors_id":234},{"id":94,"name":95,"slug":96,"role":97,"profile_picture":8},[236,240,243,246,249,252,255],{"text":237,"depth":238,"id":239},"Two files, one command",2,"two-files-one-command",{"text":241,"depth":238,"id":242},"Why the first line fails","why-the-first-line-fails",{"text":244,"depth":238,"id":245},"Three ways to ask a dictionary for a key","three-ways-to-ask-a-dictionary-for-a-key",{"text":247,"depth":238,"id":248},"The fix, in one line","the-fix-in-one-line",{"text":250,"depth":238,"id":251},"Why a dict is fast","why-a-dict-is-fast",{"text":253,"depth":238,"id":254},"The builtin that exists for exactly this","the-builtin-that-exists-for-exactly-this",{"text":256,"depth":238,"id":257},"Before the next article","before-the-next-article",1793246466837]