{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "de726aba-1fb3-4b2e-be01-825ced7a9093",
   "metadata": {},
   "source": [
    "# Functional Programming\n",
    "\n",
    "The style of programming we've been doing is called **imperative** or **procedural**.  Statements run in sequence and change a program's state.\n",
    "\n",
    "**state** can be thought of as the status of all variables at a given time. Imperative programming relies heavily on functions updating state.\n",
    "\n",
    "As we said early on, Python is multi-paradigm. \n",
    "\n",
    "> \"[...] practicality beats purity.\"\n",
    "> \n",
    "> - The Zen of Python\n",
    "\n",
    "Languages like LISP, Haskell, and Racket are purely functional & differ significantly from procedural & object-oriented languages.\n",
    "\n",
    "Functional programming uses a definition of functions more compatible with the mathematical definition. Instead of the recipe model of procedural programming, mathematical functions take input(s) and return an output. \n",
    "\n",
    "These functions do not have the concept of \"state\", that is, calling a function in math creates a mapping from inputs to outputs.\n",
    "\n",
    "When we call `sin(x)` we do not speak of it modifying its inputs, just returning a value.\n",
    "\n",
    "Similarly, when we workin a functional style we'll often write smaller functions that we chain together, instead of long procedures that rely on internal state.\n",
    "\n",
    "Python has many features that stem from pure functional languages & support functional programming:\n",
    "\n",
    "- Functions as first class objects\n",
    "- Lambda expressions\n",
    "- map/filter\n",
    "- `functools`\n",
    "- comprehensions\n",
    "\n",
    "## Functions are \"first-class objects\"\n",
    "\n",
    "A key feature of Python that makes it possible to write code in the functional style is the fact that functions are objects. (Everything in Python is an object.)\n",
    "\n",
    "This means functions don't have special rules about how they can be used, any variable can reference a function. (Remember, a variable is an association between a name & object.)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "72bee260-b4d1-4c61-a689-8799d4dfb7c6",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def echo(message):\n",
    "    print(message)\n",
    "    print(message)\n",
    "    \n",
    "print(f\"echo = {echo}\")\n",
    "print(f\"type(echo) = {type(echo)}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "39ee1708-c3ec-47ed-9c87-2124fa55b57f",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# we can assign other names to objects, including functions\n",
    "\n",
    "x = echo\n",
    "\n",
    "x(\"hello\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5a175699-fd00-4e69-a534-2d25085550d2",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "id(x), id(echo)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "52de62a9-af2f-4fbe-becf-2c315ddb9c77",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# we can also store functions in other types\n",
    "\n",
    "func_list = [print, echo, print, echo]\n",
    "for i, func in enumerate(func_list):\n",
    "    func(i)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6cad8105-8a71-4b8a-926c-dfa0f635e7ad",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# dictionaries too\n",
    "func_mapping = {False: print, True: echo}\n",
    "\n",
    "print_twice = f()\n",
    "func_mapping[True](\"twice\")\n",
    "\n",
    "print_twice = False\n",
    "func_mapping[print_twice](\"once\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "54247b2d-d5fe-4bbb-990a-bbaf6660f89d",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d89416fb-e0e7-48ca-9ca7-9fea5525b273",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# we can pass functions into other functions\n",
    "\n",
    "def add(a, b):\n",
    "    return a + b\n",
    "\n",
    "def sub(a, b):\n",
    "    return a - b\n",
    "\n",
    "def perform_op(op_func, a, b):\n",
    "    return op_func(a, b)\n",
    "\n",
    "print(\"add, 3, 4 = \", perform_op(add, 3, 4))\n",
    "print(\"sub, 3, 4 = \", perform_op(sub, 3, 4))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d0192ae3-d175-4140-a8a9-ea664ee1e015",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# and we can return functions from other functions\n",
    "\n",
    "def get_op(name):\n",
    "    if name == \"div\":\n",
    "        def f(a, b):\n",
    "            return a / b\n",
    "    elif name == \"mod\":\n",
    "        def f(a, b):\n",
    "            return a % b\n",
    "    return f"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0f558df5-c1ae-4a20-9820-8a6d83878302",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "fn = get_op(\"mod\")\n",
    "fn(100, 5)\n",
    "#perform_op(fn, 10, 3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c898e181-1fcd-477e-b6bf-573544ed6170",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "x = [(\"Nick\", 1), (\"Nick\", -100), (\"Yusong\", 9000), (\"Emma\", 100)]\n",
    "\n",
    "def negate(y):\n",
    "    return -y[1]\n",
    "\n",
    "def second_key(item):\n",
    "    return item[1]\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ff9cfd44-8a22-4c83-98a6-4420ce651238",
   "metadata": {},
   "outputs": [],
   "source": [
    "x.sort(key=negate)\n",
    "print(x)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9422326e-37ad-4ffe-8e03-4a0bd76df9de",
   "metadata": {},
   "outputs": [],
   "source": [
    "help(sorted)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "73f8aee0-8a13-43cf-b565-96285891dd4f",
   "metadata": {},
   "outputs": [],
   "source": [
    "second_key(x[0])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8c3c8074-4696-483a-9a16-238caec889f6",
   "metadata": {},
   "outputs": [],
   "source": [
    "x.sort()\n",
    "print(x)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3b13f2e4-f269-482a-b0b4-ed1831fd6e1b",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def second_key(item):\n",
    "    return item[1]\n",
    "    \n",
    "x.sort(key=lambda item: item[1])\n",
    "print(x)\n",
    "\n",
    "#x.sort(key=negate)\n",
    "#print(x)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "06fdddbe-0903-452c-8528-e5a0b8489f50",
   "metadata": {},
   "source": [
    "## lambda functions\n",
    "\n",
    "Python also provides another way to generate function objects.\n",
    "\n",
    "These are called lambda functions (aka anonymous functions), which:\n",
    "\n",
    "- Are expressions that return a function object that can be called later without providing a name (hence ``anonymous\")\n",
    "- Can be used in places where def statement is not syntactically legal (inside a literal list, inlined as a function argument, etc.)\n",
    "\n",
    "The body of an lambda function is a single expression, not a block of statements.  The body is similar to a return statement in a def statement.\n",
    "\n",
    "```python\n",
    "\n",
    "lambda arg1, arg2: expression\n",
    "\n",
    "# essentially the same as\n",
    "\n",
    "def __(arg1, arg2):\n",
    "    return expression\n",
    "```\n",
    "\n",
    "(0 or more arguments, but *must* have an expression)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bf3e3782-2baf-4a04-8a23-252d39ad5dd3",
   "metadata": {},
   "source": [
    "## Reminder: expressions vs. statements\n",
    "\n",
    "Everything in Python is either an expression or a statement. \n",
    "\n",
    "An expression evaluates to a value, examples include:\n",
    "\n",
    "* `42`\n",
    "* `\"hello world\"`\n",
    "* `10 * 5`\n",
    "* `f(1, 2, 3)`\n",
    "* `[1, 2, 3]`\n",
    "* `l[0]` \n",
    "* `lambda arg1, arg2: arg1 + arg2`\n",
    "\n",
    "Notice that all of these could be found on the right hand side of an assignment (e.g. `x = 10 * 5`)\n",
    "\n",
    "Expresssions are valid in assignment, function calls, sequence values, etc.  (Anywhere a value is needed.)\n",
    "\n",
    "```\n",
    "# in assignment\n",
    "x = 42\n",
    "x = 10 * 5\n",
    "x = [1, 2, 3]\n",
    "\n",
    "# in function calls\n",
    "f(42)\n",
    "f(10 * 5)\n",
    "f([1, 2, 3])\n",
    "\n",
    "# in complex types\n",
    "[42, [1, 2, 3], lambda x: x**2]\n",
    "{10*5: f(10, 5)}\n",
    "```\n",
    "\n",
    "To contrast, statements perform an action.\n",
    "\n",
    "* `x = 1`\n",
    "* `if x: ...`\n",
    "* `def f(a): ...` \n",
    "* `import math`\n",
    "\n",
    "They are prohibted where types are required:\n",
    "\n",
    "```\n",
    " # not allowed\n",
    "x = if y > 0: \n",
    "   7\n",
    "\n",
    "z = def f(a): \n",
    "   ...\n",
    "```\n",
    "\n",
    "A statement will often have multiple expressions within it. Many statements (but not all) use indented blocks.\n",
    "\n",
    "When it comes to `lambda`:\n",
    "* a `lambda` defines a function that maps input to a single expression, `def` can be used if more was needed\n",
    "* a `lambda` is itself an expression, it can be used anywhere other expresssions are needed"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "68f51efb-9a62-4303-afe7-3639be9cc395",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# can fit places a function definition can't\n",
    "# such as being used as a parameter\n",
    "perform_op(lambda a, b: a * b, 5, 6)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "15e0c8eb-d8af-4e85-a53d-06820961b5e8",
   "metadata": {},
   "outputs": [],
   "source": [
    "lambda s: s.upper()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9b6748b0-9fb0-4664-86c6-bc35e4c5d9e2",
   "metadata": {},
   "outputs": [],
   "source": [
    "sorted([\"abc\", \"Abc\", \"ABC\", \"AbC\"], key=lambda s: s.upper())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "39bf652e-f85d-4a66-96bf-0ae21054be26",
   "metadata": {},
   "outputs": [],
   "source": [
    "# can be assigned to a variable\n",
    "mul = lambda a, b: a * b\n",
    "mul(5, 6)\n",
    "\n",
    "# same as\n",
    "def mul2(a, b):\n",
    "    return a * b"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1eb1c524-30e1-4b20-b2a9-7b078e906c61",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "type(mul), type(mul2)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "51a501aa-4533-4d34-bb6d-8185521cf8e7",
   "metadata": {},
   "source": [
    "General rule: If you're giving a lambda a name, use a function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "425bb2b5-bb3a-448e-a6fb-9060579f588a",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# common use case\n",
    "names = [\"adam\", \"Ziwe\", \"Bo\", \"JENNY\"]\n",
    "names.sort()\n",
    "print(names)  # case sensitive, lower-case a comes after Z"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a1477f21-65e6-4925-8da7-8321cb801eac",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "names.sort(key = lambda x: x.upper())\n",
    "print(names)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a773d32c-b061-410a-a4e4-f7afe8a83529",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "308a3691-8873-4166-afec-089a653333d4",
   "metadata": {},
   "source": [
    "## Functional Methods\n",
    "\n",
    "Python also has several built in methods that are useful when writing programs with a functional mindset.\n",
    "\n",
    "`map`, `filter`, `functools`\n",
    "\n",
    "#### `map(function, iterable1, [...iterableN])`\n",
    "\n",
    "Returns a new iterable that calls `function` with parameters from `iterable1 ... iterableN`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "3e2768f5-8817-4dfc-8ff5-72a774f499fa",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "called add_two 1\n",
      "3\n",
      "called add_two 2\n",
      "4\n",
      "called add_two 3\n",
      "5\n"
     ]
    }
   ],
   "source": [
    "def add_two(x):\n",
    "    print(\"called add_two\", x)\n",
    "    return x + 2\n",
    "\n",
    "for x in map(add_two, [1, 2, 3]):\n",
    "    print(x)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "cd940ce1-c28a-4646-8846-f199f586cad8",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Help on class map in module builtins:\n",
      "\n",
      "class map(object)\n",
      " |  map(func, *iterables) --> map object\n",
      " |  \n",
      " |  Make an iterator that computes the function using arguments from\n",
      " |  each of the iterables.  Stops when the shortest iterable is exhausted.\n",
      " |  \n",
      " |  Methods defined here:\n",
      " |  \n",
      " |  __getattribute__(self, name, /)\n",
      " |      Return getattr(self, name).\n",
      " |  \n",
      " |  __iter__(self, /)\n",
      " |      Implement iter(self).\n",
      " |  \n",
      " |  __next__(self, /)\n",
      " |      Implement next(self).\n",
      " |  \n",
      " |  __reduce__(...)\n",
      " |      Return state information for pickling.\n",
      " |  \n",
      " |  ----------------------------------------------------------------------\n",
      " |  Static methods defined here:\n",
      " |  \n",
      " |  __new__(*args, **kwargs) from builtins.type\n",
      " |      Create and return a new object.  See help(type) for accurate signature.\n",
      "\n"
     ]
    }
   ],
   "source": [
    "help(map)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "04106e30-95df-432a-b6b0-40fcc58f51e5",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "called add_two 1\n",
      "called add_two 2\n",
      "called add_two 3\n",
      "[3, 4, 5]\n"
     ]
    }
   ],
   "source": [
    "x = list(map(add_two, [1, 2, 3]))\n",
    "print(x)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "fba295ed-9dcb-4cbb-8b7e-3b0ff2965639",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "A!\n",
      "B?\n",
      "C.\n"
     ]
    }
   ],
   "source": [
    "# commonly used with lambdas\n",
    "for x in map(lambda x, y: x+y, (\"A\", \"B\", \"C\"), [\"!\", \"?\", \".\"]):\n",
    "    print(x)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "18f5223a-b85a-44c6-9f49-40183a1e3208",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "A!!\n",
      "B???\n",
      "C....\n"
     ]
    }
   ],
   "source": [
    "# number of parameters must match number of iterables\n",
    "for x in map(lambda x, y, z: x+(y*z), (\"A\", \"B\", \"C\"), [\"!\", \"?\", \".\"], [2, 3, 4]):\n",
    "    print(x)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "c4e1d7f9-c111-44ff-8525-de147f90aa56",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "15"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# operator module contains all of the common operators in function form\n",
    "import operator\n",
    "operator.sub(20, 5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "e6383ce1-6e06-44a4-beb2-1e946b81c483",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{10}"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "set(map(operator.sub, [20, 19], [10, 9]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "b58441e4-ba95-43e4-8151-dd25e1c7efd1",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('AAA', 'BBB', 'CCC')"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# the result of `map` is a new kind of iterable (in fact a generator, which we'll cover next)\n",
    "\n",
    "# possible to pass into set or list \n",
    "#  or use anywhere you can use an iterable\n",
    "tuple(map(lambda x: x * 3, (\"A\", \"B\", \"C\")))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a58bbdf5-afb7-466b-886e-618ea0411ab3",
   "metadata": {},
   "source": [
    "#### `filter(function, iterable)` \n",
    "\n",
    "returns an iterable that contains all items from iterable for which `function(item)` returns True"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "62cac0ce-56e4-4ca7-9223-ed9be407428a",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['ABC', 'XYZ']"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "list(filter(lambda s: s.isupper(), [\"a\", \"ABC\", \"AbCdeF\", \"XYZ\", \"\"]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "7eaab05d-2b8e-45fa-ad97-565c2edac0f6",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['ABCABC', 'XYZXYZ']"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "list(map(lambda s: s*2, filter(str.isupper, [\"a\", \"ABC\", \"AbCdeF\", \"XYZ\"])))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "c4a69097-c30c-4096-b69b-f3fa89a1bc7f",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['A']"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "list(filter(str.isupper, map(lambda s: s.title(), [\"a\", \"ABC\", \"AbCdeF\", \"XYZ\"])))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6852f961-f20d-4c82-8da4-c7bdcfc1ae4b",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "list(map(lambda s: s.lower(), filter(lambda s: s.isupper(), [\"a\", \"ABC\", \"AbCdeF\", \"XYZ\"])))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a195957c-0e89-4d3e-9b85-ace9e1f84e5d",
   "metadata": {},
   "outputs": [],
   "source": [
    "g = (x**2 for x in filter(lambda x: x % 2 != 0, range(20)))\n",
    "list(g)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aab35319-a066-414f-a0b5-d66412d41061",
   "metadata": {},
   "source": [
    "#### functools"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f2529705-fd60-45ed-ae13-0a77f745c201",
   "metadata": {},
   "outputs": [],
   "source": [
    "import functools\n",
    "[name for name in dir(functools) if name[0].islower()]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "67d3bc6f-f3a4-4b7e-984f-87d22ad3925f",
   "metadata": {},
   "source": [
    "``functools.reduce(function, iterable[, initializer])``\n",
    "\n",
    "Apply ``function`` to pairs of items successively and return a single value as the result. You can optionally specify the initial value.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "a623edc8-08cb-41d0-8b2c-fd99619fed73",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "10"
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import functools \n",
    "import operator \n",
    "\n",
    "# accumulator = 0\n",
    "# for item in my_list:\n",
    "#     accumulator += item\n",
    "\n",
    "# 1st iteration: Call operator.add(1,2) -> 3 \n",
    "# 2nd iteration: Call operator.add(3,3) -> 6 \n",
    "# 3rd iteration: Call operator.add(6,4) -> 10 \n",
    "# final result = 10 \n",
    "functools.reduce(operator.add, [1,2,3,4])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "5081e0e6-424b-472b-b0b0-f0c2bfa1344e",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "14"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "names = [\"Ben\", \"Martha\", \"Susan\"]\n",
    "# 1st iteration: call f(0, \"Ben\") -> 0 + len(\"Ben\") -> 3\n",
    "# 2nd iteration: call f(3, \"Martha\") -> 3 + len(\"Martha\") -> 9\n",
    "# 3rd iteration: call f(9, \"Susan\") -> 9 + len(\"Susan\") -> 14\n",
    "functools.reduce(lambda accumulator, new_val: accumulator + len(new_val), \n",
    "                 names, \n",
    "                 0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "id": "6fd2afda-b07f-45f6-9a82-6bc50665af6c",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "48"
      ]
     },
     "execution_count": 30,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# What happens if you pass in an initial value \n",
    "# 1st iteration: Call operator.mul(2,1) -> 2 \n",
    "# 2nd iteration: Call operator.mul(2,2) -> 4 \n",
    "# 3rd iteration: Call operator.mul(4,3) -> 12 \n",
    "# 4th iteration: Call operator.mul(12,4) -> 48 \n",
    "# Final result = 48 \n",
    "functools.reduce(operator.mul, [1,2,3,4], 2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "74e7a581-3f79-4be3-ba9d-f354b1df13a1",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "functools.reduce(lambda a,b: a+b, [1, 2, 3])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b0dea67f-aaf8-43aa-a166-7bbfd4d2496d",
   "metadata": {},
   "source": [
    "```functools.partial(func, *args, **kwargs)```\n",
    "\n",
    "`functools.partial` returns a new function that \"binds\" any passed args & kwargs, and leaves other parameters unbound."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "5a23eb2c-8324-4ff1-beb2-bca7ff5dbbe1",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "20"
      ]
     },
     "execution_count": 31,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import operator\n",
    "operator.mul(2, 10)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "70988dc5-1a32-4316-bb9a-d4be646dd2c2",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "-5"
      ]
     },
     "execution_count": 32,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import functools\n",
    "negate = functools.partial(operator.mul, -1)\n",
    "negate(5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "id": "995d61df-dfcf-411a-b371-7633ec9ed649",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[-1, -2, -3, -4]"
      ]
     },
     "execution_count": 35,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "list(map(negate, [1, 2, 3, 4]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "id": "7b2d87c2-903a-4b48-a012-3e3b0b03584f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "16\n",
      "16\n"
     ]
    }
   ],
   "source": [
    "def calls_twice(f):\n",
    "    print(f())\n",
    "    print(f())\n",
    "    \n",
    "\n",
    "g = functools.partial(operator.mul, 4, 4)\n",
    "#print(g())\n",
    "calls_twice(g)\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "id": "0e2ffbc6-e05f-401e-b569-273c61f6d514",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "a!b!c\n",
      "x!a!b!c\n"
     ]
    }
   ],
   "source": [
    "print_ex = functools.partial(print, sep=\"!\")\n",
    "print_ex(\"a\", \"b\", \"c\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6e9be6b5-0a68-4de6-8289-31fb5095d31f",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# parameters must be valid\n",
    "print_foo = functools.partial(print, foo=\"x\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "10c4769b-7b1f-4fbc-924d-8b3e5de7fb93",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "print_foo(\"hello\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "318556fb-eafa-44af-bcfd-0848b87b6e18",
   "metadata": {},
   "outputs": [],
   "source": [
    "# another way to deal with functions we're calling with the same args repeatedly\n",
    "def request_page(url, verify, cache=True, send_cookies=False, https_only=True):\n",
    "    pass\n",
    "\n",
    "secure_request = functools.partial(request_page, verify=True, https_only=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9b4c5678-2525-4822-93e3-77dd0f40213b",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "secure_request(\"\", verify=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "157ff803-8809-481e-bff4-1fa06d067963",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "5ba68ff2-42fd-441b-ac44-9d51bd9b6d8b",
   "metadata": {},
   "source": [
    "## List Comprehensions\n",
    "\n",
    "Generate a new list from an existing iterable.\n",
    "\n",
    "Same syntax as generator expression but inside `[]`:\n",
    "\n",
    "```python\n",
    "new_list = [expression for var in iterable]\n",
    "\n",
    "# or \n",
    "\n",
    "new_list = [expression for var in iterable if condition]\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "id": "79029822-694a-445d-9776-48e05e94733b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[1, 2, 4, 8, 16, 32, 64, 128, 256, 512]\n"
     ]
    }
   ],
   "source": [
    "#f = lambda n: n ** 2\n",
    "powers_of_two = [2**n for n in range(10)]\n",
    "print(powers_of_two)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "id": "fe7d9640-165c-4887-a9f0-9e411fc8e031",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['Q♠', 'Q♣', 'Q♦', 'Q♥', 'J♠', 'J♣', 'J♦', 'J♥']\n"
     ]
    }
   ],
   "source": [
    "# possible to nest comprehensions, but beware readability\n",
    "faces = (\"K\", \"Q\", \"J\")\n",
    "suits = (\"♠\", \"♣\", \"♦\", \"♥\")\n",
    "cards = [(face + suit) for face in faces for suit in suits if face != \"K\"]\n",
    "print(cards)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "94a7b169-7930-4df5-a1ed-76f6bb0df14c",
   "metadata": {},
   "source": [
    "## Set & Dict Comprehensions\n",
    "\n",
    "Also possible to make `set` and `dict` comprehensions by using `{}`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "id": "24213ab6-3c6e-45df-8bb9-93b2d69c10fa",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{8, 16, 2, 4}\n"
     ]
    }
   ],
   "source": [
    "powers_of_two_set = {2 ** n for n in [1,1,2,2,3,3,3,4,4,4]}\n",
    "print(powers_of_two_set)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "id": "95f3d657-39aa-4803-8fc7-4e736ebc5b20",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{3: 2, 4: 3, 5: 4, 6: 5}\n"
     ]
    }
   ],
   "source": [
    "\n",
    "powers_of_two_mapping = {n+2:n+1 for n in range(5) if n > 0}\n",
    "print(powers_of_two_mapping)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "id": "7139c632-97cf-49a5-8abf-5b7516c1a401",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<generator object <genexpr> at 0x1037de5e0>\n"
     ]
    }
   ],
   "source": [
    "p2gen = (2 ** n for n in [1,1,2,2,3,3,3,4,4,4])\n",
    "print(p2gen)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "16f356d0-5147-455d-b7b4-385bd544c56a",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "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.10.15"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
