{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Functional Programming in Python " ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Collecting toolz\n", " Downloading toolz-0.7.4.tar.gz\n", "Building wheels for collected packages: toolz\n", " Running setup.py bdist_wheel for toolz ... \u001b[?25l-\b \b\\\b \bdone\n", "\u001b[?25h Stored in directory: /home/jovyan/.cache/pip/wheels/3e/e9/72/b9e24c6b4c0347670b9a20afeba6b2534655f5dc714b30cb4e\n", "Successfully built toolz\n", "Installing collected packages: toolz\n", "Successfully installed toolz-0.7.4\n", "\u001b[33mYou are using pip version 8.1.1, however version 8.1.2 is available.\n", "You should consider upgrading via the 'pip install --upgrade pip' command.\u001b[0m\n" ] } ], "source": [ "!pip install toolz" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import toolz\n", "import operator\n", "from operator import methodcaller, itemgetter, attrgetter" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Purity " ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "2" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# pure\n", "def add(a, b):\n", " return a + b\n", "\n", "# impure\n", "additions_made = 0\n", "def add(a, b):\n", " global additions_made\n", " additions_made += 1\n", " return a + b\n", "\n", "add(3, 5)\n", "add(6, 8)\n", "additions_made" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## First class functions" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "2" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def add(a, b):\n", " return a + b\n", "\n", "add_function = add\n", " \n", "add = lambda a,b: a + b\n", "\n", "add(1,1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Higher order functions" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "took 1.002180814743042\n" ] } ], "source": [ "from time import time, sleep\n", "\n", "def timer(fn):\n", " def timed(*args, **kwargs):\n", " t = time()\n", " fn(*args, **kwargs)\n", " print(\"took {time}\".format(time=time()-t))\n", "\n", " return timed\n", "\n", "def compute():\n", " sleep(1)\n", "\n", "timed_compute = timer(compute)\n", "timed_compute()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Decorators" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "took 1.0032811164855957\n" ] } ], "source": [ "@timer\n", "def compute():\n", " sleep(1)\n", " \n", "compute()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Partial function application " ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "2" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def add1(num):\n", " return add(1, num)\n", "add1(1)" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "2" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from functools import partial\n", "add1 = partial(add, 1)\n", "add1(1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Currying" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "2" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def curried_add(a):\n", " def inner(b):\n", " return add(a,b)\n", " return inner\n", "\n", "curried_add(1)\n", "curried_add(1)(1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Function composition " ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[0, 4, 16, 36, 64]" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from toolz.curried import compose, map, filter, pipe, curry\n", "\n", "compute = compose(map(lambda x: x**2), filter(lambda x: x%2==0))\n", "\n", "list(compute(range(10)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Example: Simplified CSV parsing" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "collapsed": false }, "outputs": [], "source": [ "csv = \"\"\"firstName;lastName\n", "Jim;Drake\n", "Ben;James\n", "Tim;Banes\"\"\"" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[{'firstName': 'Jim', 'lastName': 'Drake'},\n", " {'firstName': 'Ben', 'lastName': 'James'},\n", " {'firstName': 'Tim', 'lastName': 'Banes'}]" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "lines = csv.split(\"\\n\")\n", "matrix = [line.split(';') for line in lines]\n", "header = matrix.pop(0)\n", "records = []\n", "for row in matrix:\n", " record = {}\n", " for index, key in enumerate(header):\n", " record[key] = row[index]\n", " records.append(record)\n", "records" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[{'firstName': 'Jim', 'lastName': 'Drake'},\n", " {'firstName': 'Ben', 'lastName': 'James'},\n", " {'firstName': 'Tim', 'lastName': 'Banes'}]" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from toolz.curried import compose, map\n", "from functools import partial\n", "from operator import methodcaller\n", "\n", "split = partial(methodcaller, 'split')\n", "split_lines = split(\"\\n\")\n", "split_fields = split(';')\n", "dict_from_keys_vals = compose(dict, zip)\n", "csv_to_matrix = compose(map(split_fields), split_lines)\n", "\n", "matrix = csv_to_matrix(csv)\n", "keys = next(matrix)\n", "records = map(partial(dict_from_keys_vals, keys), matrix)\n", "list(records)" ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "['foo', 'bar']" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from operator import methodcaller\n", "methodcaller('split', ';')('foo;bar')" ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[('a', 1), ('b', 2)]" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "list(zip(['a','b'], [1,2]))" ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "functools.partial(, ['firstName', 'lastName'])" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "partial(dict_from_keys_vals, keys)" ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "{'firstName': 'John', 'lastName': 'Doe'}" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "partial(dict_from_keys_vals, keys)(['John', 'Doe'])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## fn.py" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Collecting fn\n", " Downloading fn-0.4.3.tar.gz\n", "Building wheels for collected packages: fn\n", " Running setup.py bdist_wheel for fn ... \u001b[?25l-\b \b\\\b \bdone\n", "\u001b[?25h Stored in directory: /home/jovyan/.cache/pip/wheels/ec/0b/f1/2e09d08831712bc4d4dd920ea1c966ebbca788264b0dda4dde\n", "Successfully built fn\n", "Installing collected packages: fn\n", "Successfully installed fn-0.4.3\n", "\u001b[33mYou are using pip version 8.1.1, however version 8.1.2 is available.\n", "You should consider upgrading via the 'pip install --upgrade pip' command.\u001b[0m\n" ] } ], "source": [ "!pip install fn" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[0, 1, 4, 9, 16]" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from fn import _\n", "\n", "list(map(_**2, range(5)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# PySpark" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import pyspark\n", "sc = pyspark.SparkContext('local[*]')" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "collapsed": false, "scrolled": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Pi is roughly 3.138912\n" ] } ], "source": [ "import random\n", "NUM_SAMPLES = 1000000\n", "\n", "def sample(p):\n", " x, y = random.random(), random.random()\n", " return 1 if x*x + y*y < 1 else 0\n", "\n", "count = sc.parallelize(range(0, NUM_SAMPLES)).map(sample) \\\n", " .reduce(lambda a, b: a + b)\n", "print(\"Pi is roughly %f\" % (4.0 * count / NUM_SAMPLES))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## K-Means\n", "mostly stolen and adapted from https://github.com/joelgrus/stupid-itertools-tricks-pydata/blob/master/src/k_means.py" ] }, { "cell_type": "code", "execution_count": 38, "metadata": { "collapsed": false, "scrolled": true }, "outputs": [], "source": [ "import random\n", "from toolz.curried import iterate, accumulate, curry, groupby, last, compose" ] }, { "cell_type": "code", "execution_count": 39, "metadata": { "collapsed": false }, "outputs": [], "source": [ "def kmeans(k, points):\n", " return until_convergence(iterate(find_new_means(points), random.sample(points, k)))" ] }, { "cell_type": "code", "execution_count": 40, "metadata": { "collapsed": false }, "outputs": [], "source": [ "@curry\n", "def find_new_means(points, old_means):\n", " k = len(old_means)\n", " clusters = groupby(compose(str, closest_mean(old_means)), points).values()\n", " return list(map(cluster_mean, clusters))" ] }, { "cell_type": "code", "execution_count": 41, "metadata": { "collapsed": true }, "outputs": [], "source": [ "@curry\n", "def closest_mean(means, point):\n", " return min(means, key=squared_distance(point))\n", "\n", "@curry\n", "def squared_distance(p, q):\n", " return sum((p_i - q_i)**2 for p_i, q_i in zip(p, q))" ] }, { "cell_type": "code", "execution_count": 42, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def cluster_mean(points):\n", " num_points = len(points)\n", " dim = len(points[0]) if points else 0\n", " sum_points = [sum(point[j] for point in points)\n", " for j in range(dim)]\n", " return [s / num_points for s in sum_points]" ] }, { "cell_type": "code", "execution_count": 43, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def no_repeat(prev, curr):\n", " if prev == curr: raise StopIteration\n", " else: return curr\n", "\n", "def until_convergence(it):\n", " return last(accumulate(no_repeat, it))" ] }, { "cell_type": "code", "execution_count": 44, "metadata": { "collapsed": false }, "outputs": [], "source": [ "data = [(random.random(), random.random()) for _ in range(500)]" ] }, { "cell_type": "code", "execution_count": 45, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[[0.21952859841448114, 0.25481402534954994],\n", " [0.7652565865929, 0.7483653171083438],\n", " [0.264055940070528, 0.7509097028831765],\n", " [0.7225812094669304, 0.24038806923701367]]" ] }, "execution_count": 45, "metadata": {}, "output_type": "execute_result" } ], "source": [ "k = 4\n", "means = list(kmeans(k, data))\n", "means" ] }, { "cell_type": "code", "execution_count": 46, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "%matplotlib inline" ] }, { "cell_type": "code", "execution_count": 47, "metadata": { "collapsed": false }, "outputs": [ { "data": { "image/png": "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\nV9LytAHx9gAAAABJRU5ErkJggg==\n", "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "colors = iter(['r', 'g', 'b', 'c', 'm'])\n", "clusters = groupby(compose(str, closest_mean(means)), data)\n", "for mean in means:\n", " x, y = zip(*clusters[str(mean)])\n", " color = next(colors)\n", " plt.scatter(x, y, color=color)\n", " plt.plot(*mean, color=color, marker='*', markersize=20)" ] } ], "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.5.1" }, "widgets": { "state": {}, "version": "1.1.1" } }, "nbformat": 4, "nbformat_minor": 0 }