# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: Python 3.7.2 64-bit
#     language: python
#     name: python37264bit4a2e9be422294fc3a9a080314b1a5773
# ---

# # Anonymous Functions
#
# - [Download the lecture notes](https://philchodrow.github.io/PIC16A/content/functions/functions_3.ipynb). 
#
#
# $\lambda$-expressions provide a concise way to create very simple functions. While the syntax is somewhat odd, it's quite readable once you know the correct idiom. 

# +
# "double is the function that takes x and multiplies it by 2"
double = lambda x: 2*x 

# same as:
# def double(x):
#     return 2*x

double(4)
# -

# "second_char is the function that returns the second character of a string s"
second_char = lambda s: s[1] 

second_char("Picard")

# $\lambda$ expressions are extremely useful when a relatively simple function is required, for example when sorting lists. 

# +
# sort a list into even and odd:

L = [4, 6, 9, 3, 4, 6, 7, 3, 2, 0, 9, 5]
L.sort(key = lambda x: (x % 2) == 1)
L

# +
# decreasing order within even and odd groups

L.sort(key = lambda x: ((x % 2) == 1, -x))
L

# +
# lambda functions also accept multiple arguments

multiply = lambda x,y: x*y
multiply(2, 3)
# -

# Don't let your $\lambda$-expressions get too complicated. Generally speaking, these expressions should not contain control flow statements, and should not be longer than a single, 80-character line of code. If your $\lambda$-expression is getting complex, use an explicitly-defined function instead. 

                                                                                # 80 characters


# # Arguments and Keyword Arguments
#
# In some cases, we might not know how many arguments a function will accept. For example, consider the following function:

def add(a, b):
    return(a+b)
add(2, 2)

# This works, but only for two numbers. 

add(2, 2, 2)
# ---

# Of course, we could define a version of "add" that works with three numbers, or four....but there's a better way. 
#
# The special `*args` argument can be passed to the function. Within the function scope, `args` (no `*` asterisk) is then a list of all **positional** arguments passed to the function. So, we can write a general `add` this way:

# +
def better_add(*args):
    total = 0
    # args is a list containing all of the inputs
    for a in args:
        total += a
    return(total)

better_add(2, 2), better_add(2, 2, 2), better_add(2, 2, 2, 2, 2)


# -

# In some cases, you might not be sure how many **keyword** arguments will be used in your function. In this case, use `**kwargs` (with two `*` asterisks). Having done so, `kwargs` will be available as a dictionary within the function scope. 

# +
def favorites(**kwargs):    
    for key, val in kwargs.items():
        print("My favorite " + key + " is " + val + ".")

favorites(TV = "Star Trek", captain = "Picard")
# ---
# -

# It is possible to use `*args` and `**kwargs` together. Since positional arguments always come first, it's necessary to use `*args` before `**kwargs`. 

# An especially useful application of `**kwargs` occurs when one is using functions from modules with many optional arguments. For example, the `scatter` command in the `matplotlib` library has a [large number of keyword arguments](https://matplotlib.org/api/_as_gen/matplotlib.pyplot.scatter.html). If you want to call this function in a custom function, then you don't have to enumerate all of them: use `**kwargs` and it just works. 

# +
from matplotlib import pyplot as plt
import random

def random_scatter(n, **kwargs):
    """
    produce a scatter plot of n random 2d points 
    """
    x, y = [], []
    for i in range(n):
        x.append(random.random())
        y.append(random.random())
    plt.scatter(x, y, **kwargs)


# -

random_scatter(100)
# image: a scatterplot of 100 blue, filled-in circles, with x and y coordinates each between 0 and 1. 
# ---

random_scatter(100, color = "orange")
# image: a scatterplot of 100 orange, filled-in circles, with x and y coordinates each between 0 and 1. 
# ---

# make the points very large and slightly transparent
random_scatter(100, color = "orange", s = 200, alpha = 0.4)
# image: a scatterplot of 100 large, orange, slightly transparent circles, with x and y coordinates each between 0 and 1. 
# ---

StarcoderdataJupyterScriptsDedupFiltered