{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Python for STEM Teachers<br/>[Oregon Curriculum Network](http://4dsolutions.net/ocn/)\n",
    "\n",
    "# Atoms R Us\n",
    "\n",
    "![Periodic Table](http://www.chemicool.com/images/periodic-table.png)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Atom(protons=3, symbol='Li', long_name='Lithium', mass=6.941, series='Alkali metal')\n",
      "{'protons': 3, 'symbol': 'Li', 'long_name': 'Lithium', 'mass': 6.941, 'series': 'Alkali metal'}\n",
      "3\n"
     ]
    }
   ],
   "source": [
    "import json\n",
    "\n",
    "series_types = [\"Don't Know\", \"Other nonmetal\", \"Alkali metal\", \n",
    "                \"Alkaline earth metal\", \"Nobel gas\", \"Metalloid\", \n",
    "                \"Halogen\", \"Transition metal\", \"Post-transition metal\", \n",
    "                \"Lanthanoid\", \"Actinoid\"]\n",
    "                \n",
    "class Element:\n",
    "    fields = \"protons symbol long_name mass series\"\n",
    "    repstr = (\"Atom(protons={protons}, symbol='{symbol}', \"\n",
    "    \"long_name='{long_name}', \"\n",
    "    \"mass={mass}, series='{series}')\")\n",
    "   \n",
    "    def __init__(self, protons: int, symbol: str,\n",
    "                 long_name: str, mass: float, series: str):\n",
    "\n",
    "        # build self.__dict__       \n",
    "        self.protons = protons\n",
    "        self.symbol = symbol\n",
    "        self.long_name = long_name\n",
    "        self.__dict__['mass'] = mass # same idea\n",
    "        self.series = series\n",
    "       \n",
    "    def __getitem__(self, idx):  # simulates collection.namedtuple behavior\n",
    "        return self.__dict__[self.fields[idx]]\n",
    "       \n",
    "    def __repr__(self):\n",
    "        return self.repstr.format(**self.__dict__)\n",
    "\n",
    "Atom = Element # synonyms\n",
    "\n",
    "lithium = Atom(3, \"Li\", \"Lithium\", 6.941, \"Alkali metal\")\n",
    "print(lithium)  # __str__, then __repr__\n",
    "print(lithium.__dict__)\n",
    "print(lithium.protons)  # print(lithium.__getattr__('protons'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      ".\n",
      "----------------------------------------------------------------------\n",
      "Ran 1 test in 0.002s\n",
      "\n",
      "OK\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<unittest.runner.TextTestResult run=1 errors=0 failures=0>"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import unittest\n",
    "\n",
    "class Test_Element(unittest.TestCase):\n",
    "    \n",
    "    def test_instance(self):\n",
    "        lithium = Atom(3, \"Li\", \"Lithium\", 6.941, \"Alkali metal\")\n",
    "        self.assertEqual(lithium.protons, 3, \"Houston, we have a problem\")\n",
    "\n",
    "a = Test_Element()  # the test suite\n",
    "suite = unittest.TestLoader().loadTestsFromModule(a) # fancy boilerplate\n",
    "unittest.TextTestRunner().run(suite)  # run the test suite"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "File: periodic_table.json loaded.\n"
     ]
    }
   ],
   "source": [
    "class ElementEncoder(json.JSONEncoder):\n",
    "    \"\"\"\n",
    "    See:  https://docs.python.org/3.5/library/json.html\n",
    "    \"\"\"\n",
    "    def default(self, obj):\n",
    "        if isinstance(obj, Element):  # how to encode an Element\n",
    "            return [obj.protons, obj.symbol, obj.long_name, obj.mass, obj.series]\n",
    "        return json.JSONEncoder.default(self, obj) # just do your usual\n",
    "            \n",
    "# Element = namedtuple(\"Atom\", \"protons abbrev long_name mass\")\n",
    "\n",
    "def load_elements():\n",
    "    global all_elements  # <--- will be visible to entire module\n",
    "    try:\n",
    "        the_file = \"periodic_table.json\"\n",
    "        f = open(the_file, \"r\") # <--- open the_file instead\n",
    "    except IOError:\n",
    "        print(\"Sorry, no such file!\")\n",
    "    else:\n",
    "        the_dict = json.load(f)\n",
    "        f.close()\n",
    "        all_elements = {}\n",
    "        for symbol, data in the_dict.items():\n",
    "            all_elements[symbol] = Atom(*data) # \"explode\" data into 5 inputs\n",
    "        print(\"File:\", the_file, 'loaded.')\n",
    "\n",
    "load_elements()  # actually do it"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![by Kenneth Snelson](http://www.kennethsnelson.net/atom/6-deBrogAtm.jpg)\n",
    "<div align=\"center\">graphic by Kenneth Snelson</div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Selected: 1\n",
      "PERIODIC TABLE OF THE ELEMENTS\n",
      "----------------------------------------------------------------------\n",
      "Symbol |Long Name             |Protons |Mass   |Series  \n",
      "----------------------------------------------------------------------\n",
      "H      | Hydrogen             |      1 |  1.01 | Other nonmetal \n",
      "He     | Helium               |      2 |  4.00 | Nobel gas      \n",
      "Li     | Lithium              |      3 |  6.94 | Alkali metal   \n",
      "Be     | Beryllium            |      4 |  9.01 | Alkaline earth metal\n",
      "B      | Boron                |      5 | 10.81 | Metalloid      \n",
      "C      | Carbon               |      6 | 12.01 | Noble gas      \n",
      "N      | Nitrogen             |      7 | 14.01 | Other nonmetal \n",
      "O      | Oxygen               |      8 | 16.00 | Other nonmetal \n",
      "F      | Fluorine             |      9 | 19.00 | Metalloid      \n",
      "Ne     | Neon                 |     10 | 20.18 | Noble gas      \n",
      "Na     | Sodium               |     11 | 22.99 | Alkali metal   \n",
      "Mg     | Magnesium            |     12 | 24.30 | Alkaline earth metal\n",
      "Al     | Aluminum             |     13 | 26.98 | Post-transition metal\n",
      "Si     | Silicon              |     14 | 28.09 | Metalloid      \n",
      "P      | Phosphorous          |     15 | 30.97 | Other nonmetal \n",
      "S      | Sulfur               |     16 | 32.06 | Other nonmetal \n",
      "Cl     | Chlorine             |     17 | 35.45 | Halogen        \n",
      "Ar     | Argon                |     18 | 39.95 | Nobel gas      \n",
      "K      | Potassium            |     19 | 39.10 | Alkali metal   \n"
     ]
    }
   ],
   "source": [
    "def print_periodic_table(sortby=1):\n",
    "    \"\"\"\n",
    "    sort all_elements by number of protons, ordered_elements local only\n",
    "    What about series?\n",
    "    Sort Order:\n",
    "    1. protons\n",
    "    2. symbol\n",
    "    3. series\n",
    "    \"\"\"\n",
    "    print(\"Selected:\", sortby)\n",
    "    \n",
    "    if sortby == 1:\n",
    "        ordered_elements = sorted(all_elements.values(), key = lambda k: k.protons)\n",
    "    elif sortby == 2:\n",
    "        ordered_elements = sorted(all_elements.values(), key = lambda k: k.symbol)        \n",
    "    elif sortby == 3:\n",
    "        ordered_elements = sorted(all_elements.values(), key = lambda k: k.series)\n",
    "        \n",
    "    print(\"PERIODIC TABLE OF THE ELEMENTS\")\n",
    "    print(\"-\" * 70)\n",
    "    print(\"Symbol |Long Name             |Protons |Mass   |Series  \" )\n",
    "    print(\"-\" * 70)\n",
    "   \n",
    "    for the_atom in ordered_elements:\n",
    "        print(\"{:6} | {:20} | {:6} | {:5.2f} | {:15}\".format(the_atom.symbol,\n",
    "                          the_atom.long_name,\n",
    "                          the_atom.protons,\n",
    "                          the_atom.mass,\n",
    "                          the_atom.series))\n",
    "\n",
    "print_periodic_table()  # do it for real"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![by Kenneth Snelson](http://www.kennethsnelson.net/atom/3-Atom.jpg)\n",
    "<div align=\"center\">by Kenneth Snelson</div>"
   ]
  }
 ],
 "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.6.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}