{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 17. 내부 상태에서 원소가 없는 경우를 처리할 때는 setdefault보다 defaultdict을 사용하라"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "키가 없는 경우의 딕셔너리를 처리하는 법인 get, setdefault 를 사용하는 법을 앞서 보았다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "visits = {\n",
    "    '미국': {'뉴욕', '로스엔젤레스'},\n",
    "    '일본': {'하코네'},\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "visits.setdefault('프랑스', set()).add('칸') "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'미국': {'뉴욕', '로스엔젤레스'}, '일본': {'하코네'}, '프랑스': {'칸'}}"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "visits"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "if (japan := visits.get('일본')) is None:  \n",
    "    visits['일본'] = japan = set()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'미국': {'뉴욕', '로스엔젤레스'}, '일본': {'하코네'}, '프랑스': {'칸'}}"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "visits"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "if (japan := visits.get('한국')) is None:  \n",
    "    visits['한국'] = japan = set()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'미국': {'뉴욕', '로스엔젤레스'}, '일본': {'하코네'}, '프랑스': {'칸'}, '한국': set()}"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "visits"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "직접 딕셔너리 생성을 제어할 수 있다면 어떨까?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Visits:\n",
    "    def __init__(self):\n",
    "        self.data = {}\n",
    "\n",
    "    def add(self, country, city):\n",
    "        city_set = self.data.setdefault(country, set())\n",
    "        city_set.add(city)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "setdefault 호출의 복잡도를 감춰주며 더 나은 인터페이스를 제공한다"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'러시아': {'예카테린부르크'}, '탄자니아': {'잔지바르'}}\n"
     ]
    }
   ],
   "source": [
    "visits = Visits()\n",
    "visits.add('러시아', '예카테린부르크')\n",
    "visits.add('탄자니아', '잔지바르')\n",
    "print(visits.data)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "하지만 Visits.add는 이상적이지 않으므로 collections 내장 모듈에 존재하는 defaultdict을 사용해보자\n",
    "\n",
    "이는 클래스에 키가 없을 때 자동으로 디폴트 값을 저장한다"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "from collections import defaultdict"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Visits:\n",
    "    def __init__(self):\n",
    "        self.data = defaultdict(set)\n",
    "\n",
    "    def add(self, country, city):\n",
    "        self.data[country].add(city)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "defaultdict(<class 'set'>, {'영국': {'런던', '바스'}})\n"
     ]
    }
   ],
   "source": [
    "visits = Visits()\n",
    "visits.add('영국', '바스')\n",
    "visits.add('영국', '런던')\n",
    "print(visits.data)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 기억해야 할 내용\n",
    "- 키로 어떤 값이 들어올지 모르는 딕셔너리를 관리해야 하는데 collections 내장 모듈에 있는 defaultdict 인스턴스가 여러분의 필요에 맞아 떨어진다면 defaultdict을 사용하라.\n",
    "- 임의의 키가 들어 있는 딕셔너리가 여러분에게 전달됐고 그 딕셔너리가 어떻게 생성됐는지 모르는 경우, 딕셔너리의 우너소에 접근하려면 우선 get을 사용해야 한다. 하지만 setdefault가 더 짧은 코드를 만들어 내는 몇 가지 경우에는 setdefault를 사용하는 것도 고려해볼 만하다."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}