{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Convert between data structures\n",
    "\n",
    "**The question.** You've got data in one shape — a list of tuples, a dictionary, a CSV line — and you need it in another. You want a single reference for the common conversions that doesn't make you guess at constructor names.\n",
    "\n",
    "The short version: the built-in constructors (`list`, `tuple`, `set`, `dict`) accept any iterable, so most conversions are a one-liner. The only ones that need more are the \"pair two sequences\" and \"transpose records\" cases — both shown below."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# The common conversions, one after another\n",
    "\n",
    "# list <-> tuple: same sequence, different mutability\n",
    "nums = [1, 2, 3]\n",
    "frozen = tuple(nums)\n",
    "back = list(frozen)\n",
    "print('list -> tuple -> list:', frozen, back)\n",
    "\n",
    "# list <-> set: strips duplicates; set is unordered\n",
    "names = ['Alice', 'Bob', 'Alice', 'Charlie', 'Bob']\n",
    "unique = set(names)\n",
    "ordered = sorted(unique)            # sort when you need a stable order\n",
    "print('list -> set -> sorted list:', unique, ordered)\n",
    "\n",
    "# dict <-> list: three useful views via keys(), values(), items()\n",
    "prices = {'apples': 1.50, 'bread': 1.20, 'milk': 0.95}\n",
    "print('keys:  ', list(prices.keys()))\n",
    "print('values:', list(prices.values()))\n",
    "print('items: ', list(prices.items()))\n",
    "\n",
    "# list-of-pairs -> dict: dict() accepts any iterable of key/value pairs\n",
    "pairs = [('apples', 1.50), ('bread', 1.20), ('milk', 0.95)]\n",
    "print('pairs -> dict:', dict(pairs))\n",
    "\n",
    "# two parallel lists -> dict: zip() pairs them, dict() consumes the pairs\n",
    "keys = ['name', 'age', 'city']\n",
    "vals = ['Alice', 30, 'London']\n",
    "print('zipped dict:', dict(zip(keys, vals)))\n",
    "\n",
    "# string <-> list: split on a delimiter, join with one\n",
    "csv_line = 'Alice,30,London'\n",
    "fields = csv_line.split(',')\n",
    "print('split:', fields, '| rejoined:', ' | '.join(fields))\n",
    "\n",
    "# list-of-records -> column dict (transpose): one entry per field\n",
    "records = [\n",
    "    {'name': 'Alice', 'age': 30},\n",
    "    {'name': 'Bob',   'age': 25},\n",
    "    {'name': 'Charlie', 'age': 35},\n",
    "]\n",
    "columns: dict[str, list] = {}\n",
    "for r in records:\n",
    "    for k, v in r.items():\n",
    "        columns.setdefault(k, []).append(v)\n",
    "print('transposed:', columns)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Why it works\n",
    "\n",
    "Every one of these conversions leans on the same principle: the built-in container constructors (`list`, `tuple`, `set`, `dict`) accept any **iterable**. You don't need a dedicated API to go from a list of pairs to a dict — `dict(pairs)` is enough, because a list of two-element tuples iterates as exactly what `dict` wants.\n",
    "\n",
    "`zip(a, b)` produces an iterable of pairs by walking two sequences in lockstep; feeding that to `dict()` is the canonical \"pair two lists\" move. `dict.keys()`, `dict.values()`, and `dict.items()` return **views** — live references to the dict's contents — so wrapping them in `list()` gives you a snapshot you can mutate independently.\n",
    "\n",
    "The transposition case is the only one that isn't a single constructor call, because \"list of records → column dict\" needs you to loop once per field. `setdefault` keeps the loop body to one line: if the column list doesn't exist yet, create it; either way, return the list so we can `.append` to it."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Trade-offs\n",
    "\n",
    "**Sets don't preserve order.** `set(names)` gives you uniqueness but no promise about iteration order. If order matters, reach for `list(dict.fromkeys(names))` — dicts remember insertion order, and `fromkeys` builds a dict from any iterable, dropping duplicates on the way.\n",
    "\n",
    "**`zip` stops at the shorter sequence.** Pairing a 3-item list with a 5-item list gives you three pairs, not five. Use `itertools.zip_longest` when missing values should be padded rather than truncated.\n",
    "\n",
    "**`dict(pairs)` silently overwrites.** If two pairs share a key, the later one wins. For counting or grouping instead of mapping, reach for `collections.Counter` or a small loop with `setdefault`.\n",
    "\n",
    "**Converting is cheap; copying isn't free.** `list(big_tuple)` walks the whole tuple. For genuinely large data, convert once and work with the target shape; don't round-trip in a hot loop."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Related reading\n",
    "\n",
    "- [Choose the right data structure](https://agilearn.co.uk/guides/data-structures/recipes/choose-the-right-structure) — which shape fits the problem in the first place.\n",
    "- [Merge and compare dictionaries](https://agilearn.co.uk/guides/data-structures/recipes/merge-and-compare-dictionaries) — conversions that keep you inside the dict world.\n",
    "- [Work with nested structures](https://agilearn.co.uk/guides/data-structures/recipes/work-with-nested-structures) — transposition at more depth.\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.12.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}