{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Week 2: Expanding on the Basics"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Video 2.1: Compound Data Types\n",
    "\n",
    "### Video Outline \n",
    "\n",
    "1. Set Types \n",
    "2. Mapping Types "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Dictionaries \n",
    "\n",
    "A dictionaries are mutable mapping structures that are: \n",
    "\n",
    " - Unordered collections of arbitrary objects that associate sets of keys to values(key-value pairs)\n",
    " - Keys are required to be a hashable type(strings, tuples, integers, etc.) \n",
    " - Values are references to a other object types (numeric values, lists,dictionary, etc.)\n",
    " - Dynamically resizable\n",
    " - Heterogeneous in nature: ordering cannot be assumed unless specified \n",
    " - Implemented using a hashtable for fast lookup of keys to values "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Dictionary Literals \n",
    "\n",
    "  ![alt text](./images/dictionary_literals.png \"Learning Python 2013\") -- <cite>Learning Python 2013</cite>\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Dictionary Operations \n",
    "\n",
    "Basic dictionary operations include: \n",
    " - Accessing a value ``D[key]`` and modifying key-value pairs ``D[key]=new_value``.\n",
    "         \n",
    " - Retrieving the length (``len``), key membership checking (``in``), and creating lists (``list``)\n",
    " "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Creating a dictionary literal \n",
    "test_averages={'test1':84,'test2':76,'test3':94}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Accessing a value \n",
    "print(test_averages['test1'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Changing a key's value\n",
    "test_averages['test3'] = 96"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Now we see the update to the dictionary \n",
    "test_averages"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Using 'in' checks to see if a key inside the dictionary \n",
    "print('test2' in test_averages)\n",
    "\n",
    "# 'Bob' is not a key in the test_averages dictionary\n",
    "print('Bob' in test_averages)\n",
    "\n",
    "# This is false because 96 is a value in the dictionary and not a key!\n",
    "print(96 in test_averages)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Number of (key,value) entries in the dictionary \n",
    "len(test_averages)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Create a list of keys. Use the .keys method to retrieve all the keys \n",
    "# in the dictionary \n",
    "keys = list(test_averages.keys())\n",
    "print(keys)\n",
    "print('---')\n",
    "\n",
    "# Create a list of values. Use the .values method to retrieve all the keys \n",
    "# in the dictionary \n",
    "values = list(test_averages.values())\n",
    "print(values)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Mutability of Dictionaries\n",
    "\n",
    "Dictionaries are mutable, so you can change, expand, and shrink them in place without making new dictionaries."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Lets a define a new dictionary \n",
    "D = {'eggs': 3, 'spam':2, 'ham': 1}\n",
    "D"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Change an entry, we already saw this above\n",
    "D['ham'] = ['grill','bake','fry']\n",
    "D"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Delete an entry in the dictionary \n",
    "# Syntax: del dictionary_name[key]\n",
    "del D['eggs']\n",
    "D"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Add a new entry is just the same syntax as updating an entry \n",
    "# We are going to add 'brunch' as a key and the value of 'Bacon'\n",
    "D['brunch'] = 'Bacon'\n",
    "D"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "### For loop idioms with dictionaries \n",
    "test_averages={'test1':84,'test2':76,'test3':94}\n",
    "\n",
    "# For loop 'in' idiom returns all the keys in the dictionary \n",
    "for key in test_averages:\n",
    "    print(key)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Use the .items() method to return the key-value pair entries \n",
    "for key,value in test_averages.items():\n",
    "    fmt_str = f'Key = {key}, value = {value}'\n",
    "    print(fmt_str)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Dictionary methods \n",
    "\n",
    "  ![alt text](./images/dictionary_methods.png \"Learning Python 2013\") -- <cite>Learning Python 2013</cite>\n",
    "  \n",
    "Documentation:https://docs.python.org/3/library/stdtypes.html#typesmapping2Source: \n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Dictionary View Objects \n",
    "\n",
    "The objects returned by ``dict.keys()``, ``dict.values()`` and ``dict.items()`` are *view objects*:\n",
    "\n",
    " - Provide a dynamic view on the dictionary’s entries, which means that when the dictionary changes the view reflects these changes.\n",
    " \n",
    " - Advantage to these views is that they require a small and fixed amount of memory and processor time."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "dishes = {'eggs':2,'sausage':1,'bacon':1,'spam':500}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Keys is a view object of the keys from the dishes dictionary\n",
    "keys = dishes.keys() \n",
    "values = dishes.values() \n",
    "items  = dishes.items() \n",
    "\n",
    "print(keys)\n",
    "print(values)\n",
    "print(items)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# View objects are dynamic and reflect dictionary changes \n",
    "\n",
    "# Lets delete the 'eggs' entry \n",
    "del dishes['eggs']\n",
    "\n",
    "# Notice the both the views have removed key and its value \n",
    "print(keys)\n",
    "print(values)\n",
    "print(items)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Sets \n",
    "\n",
    "Set are mutable values that contain an unordered collection of unique and immutable values (no duplicates) and also are:\n",
    " \n",
    " - Created by using the either ``{...}`` or ``set(iterable)``\n",
    " - The ``len`` and ``in`` and functions can be used to determine the number of items and test membership in sets. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Defining a set using '{...}' syntax \n",
    "colors={'red','black','white','blue'}\n",
    "colors "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Defining a set using the 'set(iterable)' syntax \n",
    "colors2=set([\"red\",\"blue\",\"black\",\"blue\",\"blue\"])\n",
    "\n",
    "# Notice there are no duplicates{'black','blue','red'}\n",
    "colors2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "####  Set Theory Operations \n",
    "\n",
    "Sets are fundamentally mathematical in nature and contain operations based on set theory.  They allow the following operations:  \n",
    "  \n",
    "  - Union (``union()`` or ``|``}: A set containing all elements that are in both sets\n",
    "  \n",
    "  - Difference (``difference()`` or ``-``): A set that consists of elements that are in one set but not the other. \n",
    "  \n",
    "  - Intersection (``intersection`` or ``&``): A set that consists of all elements that are in both sets. \n",
    " "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#The following creates a set of single strings 'a','b','c','d','e'\n",
    "# and another set of single strings 'b','d','x','y','z'\n",
    "A = set('abcde')\n",
    "B = set('bdxyz')\n",
    "\n",
    "print(A)\n",
    "print(\"--\")\n",
    "print(B)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Union Operation \n",
    "new_set = A | B \n",
    "print(new_set)\n",
    "print('---')\n",
    "new_set = A.union(B) # Same operation as above but using method \n",
    "print(new_set)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Difference Operation \n",
    "new_set = A - B \n",
    "print(new_set)\n",
    "new_set = B.difference(A)\n",
    "print(new_set)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Intersection Operation \n",
    "new_set = A & B \n",
    "print(new_set)\n",
    "print('---')\n",
    "new_set = A.intersection(B) # same operation as above but using method \n",
    "print(new_set)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Set Methods \n",
    "\n",
    "The set object provides methods that support set changes, in-place unions, and deletions of items from the set\n",
    "\n",
    "Documentation:https://docs.python.org/3/library/stdtypes.html#set13/30\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "letters={'a','b','c'} "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Add a new item to the set \n",
    "letters.add('d') \n",
    "letters"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Merge: This is a in-place union \n",
    "letters.update(set(['x','y','a','b']))\n",
    "letters "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Delete on item \n",
    "letters.remove('a') \n",
    "letters "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Simple iteration through a set using a for-loop \n",
    "letters = {'a','b','c'} \n",
    "for item in letters: \n",
    "    print(item)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Immutable Constraints on Sets \n",
    "\n",
    "- Sets can only contain immutable (a.k.a. “hashable”) object types. \n",
    "- Lists and dictionaries cannot be embedded in sets, but tuples can if you need to store compound values"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "S = {1.23}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Running this code section will through an error because lists are\n",
    "# not hashable types \n",
    "S.add([1,2,3])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Running this code section will through an error because dictionaries are\n",
    "# not hashable types \n",
    "S.add({'a':1})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# This is the same for dictionary types. The keys must be immutable \n",
    "# therefore a key cannot be a list or another dictionary. \n",
    "d = {'a': 1, 'b':2}\n",
    "d[['a']] = 3"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# However, tuples work because they immutable. \n",
    "S.add((1,2,3))\n",
    "S"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Sets themselves are mutable too, and so cannot be nested \n",
    "# in other sets directly.\n",
    "S.add(set((1,2,3)))\n",
    "S"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Use the built-in 'frozenset' function (creates an immutable set) \n",
    "# that can embedded other sets. Works just like sets\n",
    "f_set = frozenset([1,2,3])\n",
    "print(f_set) \n",
    "\n",
    "print(\"---\")\n",
    "S.add(f_set)\n",
    "print(S)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Video 2.2:  Python Objects & References\n",
    "\n",
    "### Video Outline \n",
    "\n",
    "1. Overview Python objects \n",
    "2. Referencing and copying  Python objects\n",
    "3. Different ways to assign names to objects "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Python Objects, References & Assignments \n",
    "\n",
    "All data/values in Python takes the form of **objects**, which are pieces of memory with values and sets associated operations. \n",
    "\n",
    "- For example, Numeric objects (ints, float, complex) with their assoicated artmetic operations (+,-,/). "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Types Live With Objects, Not Variables \n",
    "\n",
    "- Variables in Python are referred to as *names* or *identifiers* \n",
    "- Name binding is the association of a name with an object (value)\n",
    " \n",
    "  ![alt text](./images/shared_ref1.png \"Learning Python 2013\") -- <cite>Learning Python 2013</cite>\n",
    "  \n",
    "- A names does not uniquely identify an object! "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Types Live With Objects, Not Variables \n",
    "\n",
    "  ![alt text](./images/shared_ref2.png \"Learning Python 2013\") -- <cite>Learning Python 2013</cite>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Shared References \n",
    "\n",
    "Setting a variable to a new value does not alter the original object, but rather causes the variable to reference an entirely different object."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "x = 10 \n",
    "y = x \n",
    "x = 20 \n",
    "y"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Be careful when working with *mutable* objects and in-place changes:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "x = [1,2,3]\n",
    "y = x \n",
    "x.append(4)\n",
    "print(y)\n",
    "print(x)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Identity \n",
    "\n",
    "The built-in ``id(...)`` function returns the  *identity* of an object, which is an integer value guaranteed to be unique and constant for lifetime of object"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "x = \"MPCS\"\n",
    "print(id(x)) # Unique integer-value for the object pointed by x"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In the CPython Interpeter (i.e., the one we are using in this class), it is the address of the memory location storing the object. \n",
    "\n",
    "Objects having the same value can have different identities:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fruit1 = ('Apples', 4)\n",
    "fruit2 = ('Apples', 4)\n",
    "\n",
    "print(f'Fruit1 id = {id(fruit1)}\\nFruit2 id = {id(fruit2)}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Equality vs. Identity\n",
    "\n",
    "Two different ways of testing for ``equality\":\n",
    "\n",
    " - **Equality operator**(``==``): Returns true if two objects are equal (i.e., have the same value)\n",
    " - **Identity operator**(``is``): Returns true if two objects identities are the same: \n",
    "             a is b <==> id(a) == id(b) \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "a = [1, 2, 3]\n",
    "b = [1, 2, 3]\n",
    "a == b "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(id(a))\n",
    "print(id(b))\n",
    "print(a is b) # The id values are different "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Object Creation \n",
    "\n",
    " > Each time you generate a new value in your script by running an expression, Python creates a new object (i.e., a chunk of memory) to represent that value.  -- <cite>Learning Python 2013</cite>\n",
    " \n",
    "  Not fully true, CPython caches and reuses some immutable objects to save time and memory: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# CPython caches small integers \n",
    "a = 1000 \n",
    "b = 1000 \n",
    "\n",
    "# Makes sense two different integer objects so they have differe ids\n",
    "print(a is b)  \n",
    "\n",
    "a = 100 \n",
    "b = 100 \n",
    "\n",
    "# However, for small integer objects, CPython caches them \n",
    "# this means that a and b point to the same object \n",
    "print(a is b) \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# CPython does the same for short strings \n",
    "str1 = 'MPCS'\n",
    "str2 = 'MPCS'\n",
    "str1 is str2 "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Copying Objects \n",
    "\n",
    "If ``y = x`` does not make a copy, how does one get a copy? \n",
    "\n",
    " - The ``copy`` module provies functions for generating **shallow** and **deep** copies:\n",
    "    - Shallow copy: constructs a new compound object and then inserts references into it of the objects found in the original.\n",
    "    - Deep copy: constructs a new compound object and then, recursively, inserts copies into it from the objects found in the original object."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Shallow Copy example \n",
    "import copy\n",
    "x = [[1, 2], [3, 4]]\n",
    "y = copy.copy(x)\n",
    "print(x is y)\n",
    "print(y[0] is x[0]) "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Deep copy example \n",
    "z = copy.deepcopy(x)\n",
    "z[0] is x[0]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Slicing a list gives you a shallow copy ``mylist[:]`` "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Garbage Collection \n",
    "\n",
    "> Whenever a name is assigned to a new object, the space held by the prior object is reclaimed if it is not referenced by any other name or object. This automatic reclamation of objects' space is known as **garbage collection** -- <cite>Learning Python 2013</cite>\n",
    "\n",
    "Behind the scenes, an object has two header fields: \n",
    " - Type designator: The object's type (a pointer to an object of type type)\n",
    " - Reference counter: Count of names/objects referencing the object\n",
    "\n",
    "Python interpreter will reclaim an object's memory exactly when the objects' reference count drops to zero.\n",
    "  - Reclaimed memory is free to be used for future objects. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Deleting Names \n",
    "\n",
    "Use the ``del`` statement to explicitly remove a predefined variable"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "x = ['Intel', 'AMD', 'Apple']\n",
    "y = x\n",
    "del x\n",
    "y"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Trying to access x will cause an error because you deleted the name.\n",
    "x"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Deleting a name does not delete an object. \n",
    "# Deletion just decreases the reference count for the \n",
    "# associated object.  \n",
    "del y[1:]\n",
    "y"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Multiple Assigment Summary \n",
    "\n",
    "![alt text](./images/assignment_forms.png \"Learning Python 2013\") -- <cite>Learning Python 2013</cite>\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Deep Nesting example \n",
    "((lst,num),y), letter  = [((['a','b'],3), 4), 'a'] \n",
    "print(lst)\n",
    "print(num) "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Video 2.3: Functions (cont.)\n",
    "\n",
    "### Video Outline \n",
    "\n",
    "1. Different ways to define a function header \n",
    "2. Different ways to call a function with arguments "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Functions (cont.) \n",
    "\n",
    "The execution of a function is done by *calling* it.\n",
    " - Call the function by specifying the name followed by any arguments to the function embedded within parenthesis."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import math \n",
    "\n",
    "def distance(pt1,pt2):\n",
    "    x1,y1 = pt1 \n",
    "    x2,y2 = pt2 \n",
    "    return math.sqrt(math.pow(x2-x1,2) + math.pow(y2-y1,2))\n",
    "\n",
    "distance((1,2),(3,4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Arguments Matching: Function Header \n",
    "\n",
    "Python provide multiple ways to define *arguments*. You can pass arguments by name, with default values, and use collectors for extra arguments. \n",
    "\n",
    "By default, arguments are matched by position, from left to right,and you must pass exactly as many arguments as there are argument names (i.e., parameters) in the function header."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Example of Positionally required arguments \n",
    "def func(a,b,c):\n",
    "    print(a,b,c) # prints: 1 Hey! Bye \n",
    "\n",
    "func(1, \"Hey!\", \"Bye\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Python allows for **default arguments**, which allows you to specify a value when defining the argument name. You are not required to specify values for default arguments when calling the function. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# In this example, arguments b and c are default arguments. When \n",
    "# calling the function 'func' are not required to provide a value. If\n",
    "# a value is not given then its default value will be assigned to the \n",
    "# argument. \n",
    "def func (a, b=1, c='spam'):\n",
    "    print(a,b,c)\n",
    "\n",
    "func(33) # Calling func, a=33, b = 1, c='spam'\n",
    "func(33,23) # Calling func, a = 33, b = 23, c = 'spam'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Argument Matching: Function Header \n",
    "\n",
    "You **must** place default arguments after non-default arguments. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# This causes an error because 'b' is a optional argument but 'c' is \n",
    "# a positional argument. All positional arguments must be placed before\n",
    "# optional arguments. \n",
    "def func(a,b='spam',c):\n",
    "    print(a,b,c)    "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Argument Matching: Caller \n",
    "\n",
    "The caller can provide arguments by **position** or by **keyword**:\n",
    "  \n",
    "  - Positional arguments: matched from left to right.\n",
    "  \n",
    "  - Keywords: matched by argument name (``name=value``)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def func(a, b=1, c='spam'):\n",
    "    print(a,b,c)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Calling the function 'func' with all positional arguments \n",
    "func(4,4,4) # a = 4, b = 4, c = 4 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Calling the function 'func' with specifying \n",
    "# the first two positional arguments. 'c' is assigned its default value\n",
    "func(4,4) # a = 4, b = 4, c = 'spam'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Calling the function 'func' with specifying \n",
    "# the only one positional arguments. \n",
    "# 'b' and 'c' are assigned their default value\n",
    "func(4) # a = 4, b = 1, c = 'spam'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# This causes an error because I MUST assign a value to 'a' because \n",
    "# its not a default argument so 'func' must take in at least one value\n",
    "func() "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# You can also assign a value to an argument by using keyword argument\n",
    "# syntax. \n",
    "func(a='Bob',b='Sally',c='Joe')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Does not have to be in the same order specified in the function \n",
    "# header \n",
    "func(b='Sally', c='Joe', a='Bob')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# HOWEVER, you must specify a value for all arguments that do have a \n",
    "# default via a mixture of positional or keyword arguments. \n",
    "func(4, c='Ni')\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# This causes an error since we did not give 'a' a value since its not \n",
    "# a default argument \n",
    "func(b='Sally',c='Joe')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Collecting Keyword Arguments \n",
    "\n",
    "Python allows functions to collect arbitarily many poistional or keyword arguments:\n",
    "\n",
    "  - Preceded by one or two asterisks\n",
    "  - Arguments are collected into a tuple. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# The reduce_add function takes in any number of objects and \n",
    "# args is a tuple of the passed in objects. \n",
    "def reduce_add(*args):\n",
    "    s = 0 \n",
    "    for x in args:\n",
    "        s += x \n",
    "    return s \n",
    "\n",
    "# *args = (1.0,2.0,3.43)\n",
    "print(reduce_add(1.0,2.0,3.43))\n",
    "\n",
    "# *args = (0.0,0.0,3.43)\n",
    "print(reduce_add(0,0,3.43))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Collecting Keyword Arguments \n",
    "\n",
    "Python allows functions to collect arbitrarily many positional or keyword arguments. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def reduce_add2(a,b=2, *args):\n",
    "    s = 0\n",
    "    for x in args:\n",
    "        s += x\n",
    "    return s\n",
    "\n",
    "# a = 0, b = 0, *args = (3.43,), prints: 3.43\n",
    "print(reduce_add2(0,0,3.43))\n",
    "\n",
    "# a = 0, b = 3.43, *args = (), prints: 0.0  \n",
    "print(reduce_add2(0,3.43))\n",
    "\n",
    "# a = 1, b = 2, *args = (1,3.43), prints: 4.43 \n",
    "print(reduce_add2(1,2,1,3.43)) "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### Collecting Keyword Arguments \n",
    "Using ``**`` allows callers to pass key/value pairs as individual keywords. They are collected as a dictionary. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def make_dict(arg=\"Bob\", **kwargs):\n",
    "    print(kwargs)\n",
    "    for key, value in kwargs.items():\n",
    "        print(f'{key} is {value}')\n",
    "\n",
    "# arg = \"Bob\", \n",
    "# **kwargs = {'name': 'John', 'course': 'Python', 'age': 25}\n",
    "make_dict(name='John', course='Python', age=25)\n",
    "\n",
    "# arg = \"Sally\", \n",
    "# **kwargs = {'name': 'John', 'course': 'Python', 'age': 25}\n",
    "make_dict(name = 'John', course='Python', age=25, arg='Sally')\n",
    "\n",
    "# arg = 1, \n",
    "# **kwargs = {'name': 'John', 'course': 'Python', 'age': 25}\n",
    "make_dict(1, name='John', course='Python', age=25)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Unpacking arguments \n",
    "\n",
    "Callers can also use: \n",
    "\n",
    "- ``*iterable``: to pass all objects in the iterable object as individual positional arguments\n",
    "\n",
    "- ``**dict``: to pass all key/value pairs in dict as individual keyword arguments.  \n",
    "        "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def double(x, y, z):\n",
    "    return 2*x, 2*y, 2*z\n",
    "\n",
    "point = (3, -2, 7)\n",
    "\n",
    "# x = 3, y = -2, z = 7\n",
    "print(double(*point)) # prints: (6, -4, 14)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "point = [2,2,2]\n",
    "\n",
    "# x = 2, y = 2, z = 2\n",
    "print(double(*point)) # prints: (4, 4, 4)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "point = [2,2,2,44]\n",
    "#print(double(*point)) # Error too many arguments given"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def double(x, y, z):\n",
    "    return 2*x, 2*y, 2*z\n",
    "\n",
    "point = {'x': 0, 'y': 3, 'z': 4}\n",
    "# x = 0, y = 3, z = 4\n",
    "print(double(**point)) # prints: (0,6,8)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "point = {'t': 0, 'y': 3, 'z': 4}\n",
    "#print(double(**point)) # Error, no keyword arg for x "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "point = {'x': 0, 'y': 3, 'z': 4, 'p': 3}\n",
    "#print(double(**point)) # Error, no function arg for p"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Argument Matching Summary \n",
    "\n",
    "![alt text](./images/argument_matching.png \"Learning Python 2013\") -- <cite>Learning Python 2013</cite>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Week 2 Discussion Session \n",
    "\n",
    "#### Agenda \n",
    "1. Announcements\n",
    "2. Demo: Running Python files as Programs vs Modules \n",
    "3. Demo: Command-line Arguments \n",
    "4. File I/O \n",
    "5. Python Interpreter Explained \n",
    "6. Open Discussion "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### File I/O \n",
    "\n",
    "Often times when programming, you may need to read some data from a file. The common pattern with working with file data is the following. \n",
    "\n",
    "1. Read the contents of a file (or files) and load them into the program (sometimes referrred to as \"loading into memory\"). \n",
    "    - This means we read in the contents from a file and place it inside of a data structure for further processing. \n",
    "    \n",
    "2. Manipulate the data in some way. \n",
    "\n",
    "3. (Optional) Write the data back to disk. \n",
    "\n",
    "**Note**: When working with large files, it may be more efficient to slectively access parts of the file (to avoid reading in the whole dataset into memory); however, we will not worry about that case. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Reading from a File \n",
    "\n",
    "--> This is the content of the file ``uchicago-emails.txt``\n",
    "\n",
    "    bob@uchicago.edu \n",
    "    philb@uchicago.edu \n",
    "    sally@uchicago.edu \n",
    "    rebeccaw@uchicago.edu \n",
    "    joan@uchicago.edu\n",
    "\n",
    "This is a text file with five lines. Files can also contain binary data, but we will not work with that format for now. \n",
    "\n",
    "To access the contents of a file, we first need to open it: \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "f = open('uchicago-emails.txt')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "``f`` is a file object. Here are some command operations on file objects. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Reading the entire contents of the file "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "emails = f.read() "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'bob@uchicago.edu \\nphilb@uchicago.edu \\nsally@uchicago.edu \\nrebeccaw@uchicago.edu \\njoan@uchicago.edu'"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "emails"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see ``.read()`` returns the entire contents of the file as a string string. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "bob@uchicago.edu \n",
      "philb@uchicago.edu \n",
      "sally@uchicago.edu \n",
      "rebeccaw@uchicago.edu \n",
      "joan@uchicago.edu\n"
     ]
    }
   ],
   "source": [
    "print(emails) "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When reading from a file, the OS keeps track of the position it has read. In this case, the file pointer has already reached the end-of-file (or EOF). So, if I call read() again, I don't get the contents of the file."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "more_data = f.read()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "print(more_data)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When you reach EOF, read() returns the empty string."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Closing a File \n",
    "\n",
    "You should close a file when you no longer need it by calling the ``close()`` method on the file object. \n",
    "\n",
    "**Note**: Once you close the file you no longer have access to it. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "f.close() "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*Why is it important to close a file?*\n",
    "\n",
    " 1. It frees resources associated with the file \n",
    " 2. When writing to a file, it ensures the actual contents get written to disk. \n",
    " \n",
    "\n",
    "You can also use the ``with`` statement to ensure that file is closed once we're don with it. \n",
    "\n",
    "**Note**: This is the more common way to work with a file in Python. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['bob@uchicago.edu', 'philb@uchicago.edu', 'sally@uchicago.edu', 'rebeccaw@uchicago.edu', 'joan@uchicago.edu']\n"
     ]
    }
   ],
   "source": [
    "with open('uchicago-emails.txt') as f: \n",
    "    emails = f.read() \n",
    "    print(emails.split())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "At the end of the ``with`` block, file ``f`` is automatically closed. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Reading lines within a File \n",
    "\n",
    "A file object is an iterable object so you can use for loop to iterate over its contents:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "bob@uchicago.edu \n",
      "\n",
      "philb@uchicago.edu \n",
      "\n",
      "sally@uchicago.edu \n",
      "\n",
      "rebeccaw@uchicago.edu \n",
      "\n",
      "joan@uchicago.edu\n"
     ]
    }
   ],
   "source": [
    "with open('uchicago-emails.txt') as f:\n",
    "    for line in f: \n",
    "        print(line)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*Why the extra empty line?*\n",
    " - Each line includes a newline at the end, and print() adds a newline as well.\n",
    "\n",
    "When reading lines from a file, we will usually want to use the strip() method to remove any leading and trailing whitespace (including newlines):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "bob@uchicago.edu\n",
      "philb@uchicago.edu\n",
      "sally@uchicago.edu\n",
      "rebeccaw@uchicago.edu\n",
      "joan@uchicago.edu\n"
     ]
    }
   ],
   "source": [
    "with open(\"uchicago-emails.txt\") as f:\n",
    "    for line in f:\n",
    "        print(line.strip())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Loading data into a data structure "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "emails = []\n",
    "with open(\"uchicago-emails.txt\") as f:\n",
    "    for line in f:\n",
    "        email = line.strip()\n",
    "        emails.append(email)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['bob@uchicago.edu',\n",
       " 'philb@uchicago.edu',\n",
       " 'sally@uchicago.edu',\n",
       " 'rebeccaw@uchicago.edu',\n",
       " 'joan@uchicago.edu']"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "emails"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Writing data to a file \n",
    "\n",
    "You can use the ``open(..)`` function from before to write to a file. You specify an additional argument (i.e., the mode) with the string ``'w'``. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "with open(\"names.txt\", \"w\") as f:\n",
    "    f.write(\"Bob\\n\")\n",
    "    f.write(\"Phil\\n\")\n",
    "    f.write(\"Sally\\n\")\n",
    "    f.write(\"Rebecca\\n\")\n",
    "    f.write(\"Joan\\n\")    "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Very important**: If you open an existing file in write mode, it will wipe all its contents! If you open a file that doesn't exist, it will create the file. \n",
    "\n",
    "You can use `open(\"a_file.txt','a')` to append to a file"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "with open(\"names.txt\", 'a') as f:\n",
    "    f.write(\"Tim\\n\")   "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*Tip*: Use the ``print`` function to include the newline by default.  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "with open(\"names2.txt\", \"w\") as f:\n",
    "    print(\"Bob\", file=f)\n",
    "    print(\"Sally\", file=f)\n",
    "    print(\"Joe\", file=f)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Reading, Manipulating, and Writing Data "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Load data into a data structure (a list of strings)\n",
    "emails = []\n",
    "with open(\"uchicago-emails.txt\") as f:\n",
    "    for line in f:\n",
    "        email = line.strip()\n",
    "        emails.append(email)\n",
    "        \n",
    "# Transform the data\n",
    "cnetids = []\n",
    "for email in emails:\n",
    "    cnetid, domain = email.split(\"@\")\n",
    "    cnetids.append(cnetid)\n",
    "    \n",
    "# Write the data\n",
    "with open(\"uchicago-cnetids.txt\", \"w\") as f:\n",
    "    for cnetid in cnetids:\n",
    "        print(cnetid, file=f)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Other Useful File Methods \n",
    "![alt text](./images/files.png \"Learning Python 2013\") -- <cite>Learning Python 2013</cite>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Interpreter Explained \n",
    "\n",
    "*What’s happening within the interpreter?* \n",
    "\n",
    "1. The interpreter compiles source code to byte code \n",
    "\n",
    "    - Source code: the Python code that is place in the .py files\n",
    "    - Byte code: lower-level, platform-independent representation of the source code\n",
    "    - Caches already compiled bytecode to speedup execution  by saving it inside byte-codefiles(.pyc)are stored in *__pycache__* directory.\n",
    "    \n",
    "2. Next, interpreter routes the byte code to the Python Virtual Machine (PVM)\n",
    "    - The PVM component  is a loop that iterates through the byte code instructions, executing each one"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Other Useful File Methods \n",
    "![alt text](./images/runtime_pvm.png \"Learning Python 2013\") -- <cite>Learning Python 2013</cite>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Interpreted vs. Compiled Languages\n",
    "\n",
    "Languages such as C, and C++ are compiled languages, which means their source code is ultimately translated into machine (native) code before execution.\n",
    " - Machine code runs directly on the CPU. \n",
    " - Machine code is specific to the architecture (Intel, Arm, etc.) of that machine.\n",
    "\n",
    "- Interpreted languages (Python, Java, etc.) \"compile\" to bytecode. \n",
    "    - Once translated, bytecode is ran by a virtual machine.\n",
    "    - Unlike compiled languages, bytecode is portable to other architectures.\n",
    "   "
   ]
  }
 ],
 "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.7.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
