# A first class function just means that functions can be passed as arguments to functions.


def calculate(*values, operator):
    return operator(*values)


def divide(dividend, divisor):
    if divisor != 0:
        return dividend / divisor
    else:
        return "You fool!"


# We pass the `divide` function as an argument
result = calculate(20, 4, operator=divide)
print(result)


def average(*values):
    return sum(values) / len(values)


result = calculate(10, 20, 30, 40, operator=average)
print(result)

# -- searching with first-class functions --


def search(sequence, expected, finder):
    for elem in sequence:
        if finder(elem) == expected:
            return elem
    raise RuntimeError(f"Could not find an element with {expected}")


friends = [
    {"name": "<NAME>", "age": 24},
    {"name": "<NAME>", "age": 30},
    {"name": "<NAME>", "age": 27},
]


def get_friend_name(friend):
    return friend["name"]


print(search(friends, "<NAME>", get_friend_name))

# -- using lambdas since this can be simple enough --


def search(sequence, expected, finder):
    for elem in sequence:
        if finder(elem) == expected:
            return elem
    raise RuntimeError(f"Could not find an element with {expected}")


friends = [
    {"name": "<NAME>", "age": 24},
    {"name": "<NAME>", "age": 30},
    {"name": "<NAME>", "age": 27},
]

print(search(friends, "<NAME>", lambda friend: friend["name"]))


# -- or as an extra, using built-in functions --

from operator import itemgetter


def search(sequence, expected, finder):
    for elem in sequence:
        if finder(elem) == expected:
            return elem
    raise RuntimeError(f"Could not find an element with {expected}")


friends = [
    {"name": "<NAME>", "age": 24},
    {"name": "<NAME>", "age": 30},
    {"name": "<NAME>", "age": 27},
]

print(search(friends, "<NAME>", itemgetter("name")))

# A first class function just means that functions can be passed as arguments to functions.


def calculate(*values, operator):
    return operator(*values)


def divide(dividend, divisor):
    if divisor != 0:
        return dividend / divisor
    else:
        return "You fool!"


# We pass the `divide` function as an argument
result = calculate(20, 4, operator=divide)
print(result)


def average(*values):
    return sum(values) / len(values)


result = calculate(10, 20, 30, 40, operator=average)
print(result)

# -- searching with first-class functions --


def search(sequence, expected, finder):
    for elem in sequence:
        if finder(elem) == expected:
            return elem
    raise RuntimeError(f"Could not find an element with {expected}")


friends = [
    {"name": "<NAME>", "age": 24},
    {"name": "<NAME>", "age": 30},
    {"name": "<NAME>", "age": 27},
]


def get_friend_name(friend):
    return friend["name"]


print(search(friends, "<NAME>", get_friend_name))

# -- using lambdas since this can be simple enough --


def search(sequence, expected, finder):
    for elem in sequence:
        if finder(elem) == expected:
            return elem
    raise RuntimeError(f"Could not find an element with {expected}")


friends = [
    {"name": "<NAME>", "age": 24},
    {"name": "<NAME>", "age": 30},
    {"name": "<NAME>", "age": 27},
]

print(search(friends, "<NAME>", lambda friend: friend["name"]))


# -- or as an extra, using built-in functions --

from operator import itemgetter


def search(sequence, expected, finder):
    for elem in sequence:
        if finder(elem) == expected:
            return elem
    raise RuntimeError(f"Could not find an element with {expected}")


friends = [
    {"name": "<NAME>", "age": 24},
    {"name": "<NAME>", "age": 30},
    {"name": "<NAME>", "age": 27},
]

print(search(friends, "<NAME>", itemgetter("name")))

# A first class function just means that functions can be passed as arguments to functions. # We pass the `divide` function as an argument # -- searching with first-class functions -- # -- using lambdas since this can be simple enough -- # -- or as an extra, using built-in functions --