{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Merge and compare dictionaries\n",
    "\n",
    "**The question.** You have two dictionaries — defaults and overrides, or two snapshots of the same config — and you want to combine them (with one winning on conflicts), or diff them to see what's the same, what's different, and what's unique to each side.\n",
    "\n",
    "Merging has one canonical form: `defaults | overrides`. Comparing uses the set-operation support on `.keys()` views. Both fall out of the language once you know where to look."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Merge: right-hand side wins on conflicts\n",
    "defaults = {'theme': 'light', 'language': 'en-GB', 'font_size': 14, 'show_line_numbers': True}\n",
    "user     = {'theme': 'dark', 'font_size': 16}\n",
    "\n",
    "config = defaults | user\n",
    "print('merged:', config)\n",
    "\n",
    "# In-place merge — mutates the left-hand side\n",
    "defaults |= user\n",
    "print('in-place:', defaults)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Dictionary keys support set operations — use them to compare\n",
    "dict_a = {'name': 'Alice', 'age': 30, 'city': 'London'}\n",
    "dict_b = {'name': 'Alice', 'age': 25, 'email': 'alice@example.com'}\n",
    "\n",
    "common = dict_a.keys() & dict_b.keys()\n",
    "only_a = dict_a.keys() - dict_b.keys()\n",
    "only_b = dict_b.keys() - dict_a.keys()\n",
    "\n",
    "print('common:', common)\n",
    "print('only in A:', only_a)\n",
    "print('only in B:', only_b)\n",
    "\n",
    "# For shared keys, compare the values\n",
    "for key in common:\n",
    "    if dict_a[key] == dict_b[key]:\n",
    "        print(f'{key}: same ({dict_a[key]})')\n",
    "    else:\n",
    "        print(f'{key}: different (A={dict_a[key]}, B={dict_b[key]})')\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Why it works\n",
    "\n",
    "`|` and `|=` on dicts (Python 3.9+) read as \"union with right-side precedence\". For one-level merges, they're the cleanest option — you don't have to remember whether the older `{**a, **b}` syntax merges or whether `a.update(b)` returns the result or `None` (it returns `None` — a common trip-up).\n",
    "\n",
    "The comparison trick relies on a less-obvious property of dicts: `.keys()` returns a **view** that behaves like a set. `a.keys() & b.keys()`, `a.keys() - b.keys()`, and `a.keys() | b.keys()` all work directly — no need to wrap them in `set(...)`. The views are live and cheap; they don't copy the keys.\n",
    "\n",
    "For value comparison, once you have the common-keys set you loop once over it and check each value — O(n) in the smaller dict."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Trade-offs\n",
    "\n",
    "**`|` is shallow.** `{'user': {'name': 'A'}} | {'user': {'age': 30}}` gives you `{'user': {'age': 30}}` — the nested `name` is gone. For deep merges you need to recurse yourself, or reach for a library like [`deepmerge`](https://pypi.org/project/deepmerge/).\n",
    "\n",
    "**`update()` is the old way.** It still works in every Python 3, mutates in place, and returns `None`. If you're on 3.9 or newer, prefer `|=` — same effect, cleaner reading, doesn't look like a function that forgot to return its result.\n",
    "\n",
    "**`{**a, **b}` is equivalent to `a | b`** on Python 3.9+, but the pipe version is easier to read in a chain (`a | b | c`). The dict-unpacking form is still the right call when one of the sides is literal — `{**base, 'count': 0}` reads better than `base | {'count': 0}`.\n",
    "\n",
    "**Comparing values across different dicts is only as good as the types allow.** If values contain dicts, lists, or custom classes, `==` follows their `__eq__`. For floats, use `math.isclose` instead."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Related reading\n",
    "\n",
    "- [Convert between data structures](https://agilearn.co.uk/guides/data-structures/recipes/convert-between-structures) — the broader \"reshape your data\" toolkit.\n",
    "- [Work with nested structures](https://agilearn.co.uk/guides/data-structures/recipes/work-with-nested-structures) — when shallow merges stop being enough.\n",
    "- [Dictionary methods](https://agilearn.co.uk/guides/data-structures/reference/dictionary-methods) — every `dict` method at a glance.\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.12.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}