Last active
November 5, 2024 17:01
-
-
Save UmbreLu/b12c4fe79a54619804c928dc8224e014 to your computer and use it in GitHub Desktop.
My answers in Python 3.8 for oskarkv/exercises.org on functional programing (https://gist.github.com/oskarkv/3168ea3f8d7530ccd94c97c19aafe266).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from functools import reduce | |
import operator | |
# The proposed exercises are expected to be solven with a | |
# pure functional aproach. This means using recursion, map, | |
# reduce, filter, clist and other functions. | |
# As my focus is learning the "Python Way", I'll also | |
# write some functions the most pythonic fashion I can. | |
# In currect state python, the proper pythonistic way | |
# would be to use recursion, generators, generator expressions | |
# and list comprehetions mostly. | |
# Defining local variables is tolerable if you can't substitute | |
# the reduce function with sum(), all(), any() or similar | |
# to update states (not great but readable at least). | |
# That's why those functions are now in functools or itertools | |
# and are not ready-to-use anymore. | |
x = [1, [[2], 3], [4], 5, [6, 100, [[7], [[8]], 9]], 10] | |
x = [1, [[2], 3], [4], 5, [6, 100, [[7], [[8]], 9]], 10] | |
# tree sum | |
def tree_sum(tree): | |
if not isinstance(tree, list): | |
return tree | |
else: | |
return reduce(operator.add, map(tree_sum, tree)) | |
print(tree_sum(x)) | |
#pythonic tree_sum | |
def tree_sum2(tree): | |
if not isinstance(tree, list): | |
return tree | |
else: | |
return sum(tree_sum2(x) for x in tree) | |
print(tree_sum2(x)) | |
# calculate the depth of a tree (in the tree above, 8 is the deepest element) | |
def how_deep(tree): | |
if isinstance(tree, int): | |
return 0 | |
else: | |
return max(map(how_deep, tree)) + 1 | |
print(how_deep(x)) # => maximun depth is 5 | |
# pythonic how_deep | |
def how_deep2(tree): | |
if isinstance(tree, int): | |
return 0 | |
else: | |
return max(how_deep(x) for x in tree) + 1 | |
print(how_deep2(x)) # => maximun depth is 5 | |
# find the largest value in a tree | |
def largest_value(tree): | |
if isinstance(tree, int): | |
return tree | |
else: | |
return max(map(largest_value, tree)) | |
print(largest_value(x)) # => 100 | |
# pythonic largest_value | |
def largest_value2(tree): | |
if isinstance(tree, int): | |
return tree | |
else: | |
return max(largest_value2(x) for x in tree) | |
print(largest_value2(x)) # => 100 | |
# Tower of Hanoi with prints inside | |
def hanoi(a, b, c, count): | |
def move(a, b): | |
print("Move disk from", a, "to", b) | |
if count == 1: | |
move(a, c) | |
else: | |
hanoi(a, c, b, count - 1) | |
move(a, c) | |
hanoi(b, a, c, count - 1) | |
print('Hanoi with print') | |
print(hanoi('A', 'B', 'C', 6)) | |
# Tower of Hanoi returning string (totally pure) | |
def hanoi_string(a, b, c, count): | |
def move(a, b): | |
return f'Move disk from {a} to {b}.\n' | |
if count == 1: | |
return move(a, c) | |
else: | |
return (hanoi_string(a, c, b, count - 1) + move(a, c) + hanoi_string(b, a, c, count - 1)) | |
print('Hanoi with string') | |
print(hanoi_string('A', 'B', 'C', 6)) | |
def clist(*args): # or use list | |
return [*args] | |
print(clist(1, 2, 3)) | |
def add_many(*args): | |
return reduce(operator.add, args) | |
print(add_many(1, 2, 3)) | |
def sub_many(*args): | |
return reduce(operator.sub, args) | |
print(sub_many(1, 2, 3)) | |
def negate(arg): | |
return -arg | |
def negate_many(*args): | |
return list(map(lambda x: -x, args)) | |
print(negate_many(1, 2, 3)) | |
def double(arg): | |
return arg * 2 | |
def double_many(*args): | |
return reduce(double, args) | |
def compose(function1, function2): | |
def closure(*arg): | |
return function2(function1(*arg)) | |
return closure | |
def compose_many(*funcs): | |
return reduce(compose, funcs) | |
# add_many and sub_many must come first because they take multiple args | |
# and reduce it to a single output, while negate and double take only | |
# one arg. Otherwise it would bug. | |
print(compose_many(add_many, negate, double)(1, 2, 3)) # => -12 | |
print(compose_many(sub_many, double, clist)(1, 2, 3)) # => [-8] | |
# just change packing method to return list of lists | |
def zip_two(iter1, iter2): | |
def gen(x, y): | |
if isinstance(iter1[0], int): | |
for n in range(len(iter1)): | |
yield iter1[n], iter2[n] | |
else: | |
for n in range(len(iter1)): | |
yield iter1[n] + (iter2[n],) | |
return list(gen(iter1, iter2)) | |
def myzip(*iterables): | |
return reduce(zip_two, iterables) | |
print(myzip([1, 2, 3], [4, 5, 6])) | |
print(myzip([1, 2, 3], [4, 5, 6], [7, 8, 9])) | |
print(list(zip([1, 2, 3], [4, 5, 6]))) # zip built-in function returns a zip object | |
print(zip([1, 2, 3], [4, 5, 6], [7, 8, 9])) # instead of just tuples | |
def zipmap(iter1, iter2): | |
def gen(x, y): | |
for n in range(len(iter1)): | |
yield iter1[n], iter2[n] | |
return dict(gen(iter1, iter2)) | |
print(zipmap([1, 2, 3], [4, 5, 6])) # => {1: 4, 2: 5, 3: 6} | |
def zipwith(f, *iters): | |
def gen(x, *y): | |
for n in range(len(iters[0])): | |
yield reduce(x, (z[n] for z in y)) | |
return list(gen(f, *iters)) | |
print(zipwith(operator.add, [1, 2, 3], [4, 5, 6])) # => [5, 7, 9] | |
print(zipwith(operator.add, [1, 2, 3], [4, 5, 6], [1, 1, 1])) # => [6, 8, 10] | |
# car and cdr | |
# given cons: | |
def cons(a, b): | |
def pair(f): | |
return f(a, b) | |
return pair | |
# car(cons(3, 4)) returns 3, and cdr(cons(3, 4)) returns 4 | |
def car(f): | |
return f(lambda x, y: x) | |
def cdr(f): | |
return f(lambda x, y: y) | |
car_imp = car(cons(1, 2)) | |
print(car_imp) | |
cdr_imp = cdr(cons(1, 2)) | |
print(cdr_imp) | |
def partial(f, *args): | |
def closure(*new_args): | |
return f(*args, *new_args) | |
return closure | |
print(partial(add_many, 1, 2)(3, 4)) # => 10 | |
print(partial(clist, 1, 2)(3, 4)) # => [1, 2, 3, 4] | |
print(partial(sub_many, 10)(1, 2)) # => 7 | |
def transpose(mat): | |
def inter(pos, mat): | |
for row in mat: | |
yield row[pos] | |
for n in range(len(mat[0])): | |
yield list(inter(n, mat)) | |
print(list(transpose([[1, 2, 3], [4, 5, 6]]))) # => [[1, 4], [2, 5], [3, 6]] | |
def flip(f): | |
def closure(arg1, arg2, *args): | |
return f(arg2, arg1, *args) | |
return closure | |
print(flip(clist)(1, 2, 3)) # => [2, 1, 3] | |
print(flip(sub_many)(10, 1)) # => -9 | |
def flips(f): | |
def closure(*args): | |
return f(*args[::-1]) | |
return closure | |
print(flips(clist)(1, 2, 3)) # => [3, 2, 1] | |
print(flips(sub_many)(1, 2, 3)) # => 0 | |
def take(n, seq): | |
return seq[:n] | |
print(take(3, list(range(10)))) # => [0, 1, 2] | |
def drop(n, seq): | |
return seq[n:] | |
print(drop(3, list(range(6)))) # => [3, 4, 5] | |
# functional pure flatten | |
def flatten(tree): | |
if isinstance(tree, int): | |
return clist(tree) | |
else: | |
return reduce(operator.add, map(flatten, tree)) | |
print(flatten([1, [2, [3, 4], [5, 6], 7], 8, [9, 10]])) | |
# => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] | |
# pythonic flatten | |
def flatten2(tree): | |
if isinstance(tree, int): | |
return [tree] | |
else: | |
concatenated = [] | |
for listed in (flatten2(x) for x in tree): | |
concatenated += listed | |
return concatenated | |
print(flatten2([1, [2, [3, 4], [5, 6], 7], 8, [9, 10]])) | |
# => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] | |
# generator | |
def interleave(*seqs): | |
for n in range(len(seqs[0])): | |
for s in range(len(seqs)): | |
yield seqs[s][n] | |
# genexp | |
def interleave2(*seqs): | |
return (seqs[s][n] for s in range(len(seqs)) for n in range(len(seqs[0]))) | |
print(list(interleave([1, 2, 3], [10, 20, 30]))) | |
# => [1, 10, 2, 20, 3, 30] | |
print(list(interleave([1, 2, 3], [10, 20, 30], "abc"))) | |
# => [1, 10, "a", 2, 20, "b", 3, 30, "c"] | |
print(list(interleave2([1, 2, 3], [10, 20, 30]))) | |
# => [1, 10, 2, 20, 3, 30] | |
print(list(interleave2([1, 2, 3], [10, 20, 30], "abc"))) | |
# => [1, 10, "a", 2, 20, "b", 3, 30, "c"] | |
# every_pred pythonic functions (doesn't need to be high order function) | |
# and with multiple args | |
positive = lambda x: x > 0 | |
even = lambda x: x % 2 == 0 | |
def every_pred(*nums, **funcs): | |
return all(f(n) for f in funcs.values() for n in nums) | |
# every_pred(positive, even)(8) => True | |
print(every_pred(8, 10, 12, 14, f1=positive, f2=even)) | |
# every_pred(positive, even)(7) => False | |
print(every_pred(7, positive, even)) | |
# without defining functions | |
pos_even_nums = (2, 4, 6, 8) | |
every_pred_1 = all((x > 0 and x % 2 == 0 for x in pos_even_nums)) # => True | |
print(every_pred_1) | |
every_pred_2 = all(positive(x) and even(x) for x in pos_even_nums) | |
print(every_pred_2) | |
# single expression with single argument | |
every_pred_3 = 7 > 0 and 7 % 2 == 0 | |
print(every_pred_3) | |
###### From this point the exercises are not updated | |
###### and are done in a beginner fashion | |
###### check only if you have no clue on how to solve | |
###### the problems | |
def frequencies(list_): | |
dict_ = {} | |
for x in list_: | |
if x not in dict_: | |
dict_[x] = 1 | |
else: | |
dict_[x] += 1 | |
return dict_ | |
freq1 = frequencies("aabcbcac") #=> {'a': 3, 'c': 3, 'b': 2} | |
freq2 = frequencies([1, 2, 2, 2]) #=> {1: 1, 2: 3} | |
print(freq1) | |
print(freq2) | |
def partition(n, step, seq): | |
partit = [] | |
for this_step in range(len(seq) - (len(seq) % 3)): | |
partit.append(seq[this_step:this_step + 3]) | |
return partit | |
partit = partition(3, 1, [1, 2, 3, 4, 5]) #=> [[1, 2, 3], [2, 3, 4], [3, 4, 5]] | |
print(partit) | |
def merge_with(func, *dicts): | |
new_dict = dict(dicts[0]) | |
for dict_ in dicts[1:]: | |
for key in dict_.keys(): | |
if key in new_dict: | |
new_dict[key] = func(new_dict[key], dict_[key]) | |
else: | |
new_dicts[key] = dict_[key] | |
return new_dict | |
def list_more(*args): | |
return list(args) | |
merge1 = merge_with(add, {"a": 1, "b": 2}, {"b": 2}) #=> {"a": 1, "b": 4} | |
merge2 = merge_with(list_more, {"a": 1, "b": 2}, {"b": 2}) #=> {"a": 1, "b": [2 2]} | |
print(merge1) | |
print(merge2) | |
def memoize(func): | |
def closure(*args): | |
if len(args) == 1: | |
true_arg = args[0] | |
else: | |
true_arg = args | |
memo = dict() | |
if not true_arg in memo: | |
memo[true_arg] = func(true_arg) | |
return memo[true_arg] | |
return closure | |
new_add2 = memoize(add) | |
n_add3 = new_add2(1, 2, 3) #=> 6 | |
n_add4 = new_add2(1, 2, 3) #=> 6 (Did not actually compute add(1, 2, 3).) | |
print(n_add3) | |
print(n_add4) | |
def group_by(func, seq): | |
dict_ = dict() | |
for n in seq: | |
m = func(n) | |
if m in dict_: | |
dict_[m].append(n) | |
else: | |
dict_[m] = [n] | |
return dict_ | |
print(group_by(len, ["hi", "dog", "me", "bad", "good"])) | |
#=> {2: ["hi", "me"], 3: ["dog", "bad"], 4: ["good"]} | |
def update(dict_, k, func, *args): | |
if len(args) == 1: | |
true_args = args[0] | |
else: | |
true_args = args | |
if k in dict_: | |
dict_[k] = func((dict_[k]) + (true_args)) | |
else: | |
dict_[k] = func(true_args) | |
return dict_ | |
bob = {"name": "bob", "hp": 3} | |
print(update(bob, "hp", myadd, 2)) | |
#=> {"name": "bob", "hp": 5} | |
nohp = {"name": "bob"} | |
print(update(nohp, "hp", myadd, 2)) | |
#=> {"name": "bob", "hp": 2} | |
def update_in(dict_, ks, func, *args): | |
if len(args) == 1: | |
true_args = args[0] | |
else: | |
true_args = args | |
if len(ks) > 1: | |
update_in(dict_[ks[0]], ks[1:], func, true_args) | |
else: | |
g = ks[0] | |
if g in dict_: | |
dict_[g] = func((dict_[g]) + (true_args)) | |
else: | |
dict_[g] = func(true_args) | |
return dict_ | |
a = {"a": 1, "b": {"c": 2, "d": {"e": 3}}} | |
print(update_in(a, ["b", "d", "e"], myadd, 10)) | |
#=> {"a": 1, "b": {"c": 2, "d": {"e": 13}}} | |
print(update_in(a, ["b", "d", "f"], myadd, 10)) | |
#=> {"a": 1, "b": {"c": 2, "d": {"e": 3, "f": 10}}} | |
def balanced(string): #returns true/false for balanced marks in string | |
match = {')': '(', ']': '[', '}': '{'} | |
last_open = '@' | |
print(string) | |
for pos, char in enumerate(string): | |
if char in '([{': | |
last_open = char | |
last_pos = pos | |
if char in ')]}': | |
if last_open != match[char]: | |
return False | |
else: | |
if balanced(string[:last_pos] + string[pos + 1:]): | |
return True | |
else: | |
return False | |
if last_open == '@': | |
return True | |
else: | |
return False | |
balanced0 = balanced('aaaaaaa(a)aaaaaa') | |
balanced1 = balanced("abc(def{g}hi[jk]((()))l)m") #=> True | |
balanced2 = balanced("a(b") #=> False | |
balanced3 = balanced("([)]") #=> False | |
print(balanced0) | |
print(balanced1) | |
print(balanced2) | |
print(balanced3) | |
def wrap_unless_zero(n): | |
if isinstance(n, int) and n > 0: | |
return [n - 1] | |
else: | |
return n | |
def postwalk(f, tree): | |
for p, n in enumerate(tree): | |
if not isinstance(n, list): | |
tree[p] = f(n) | |
else: | |
postwalk(f, n) | |
return tree | |
def prewalk(f, tree): | |
for p, n in enumerate(tree): | |
if not isinstance(n, list): | |
tree[p] = f(n) | |
if isinstance(tree[p], list): | |
prewalk(f, tree[p]) | |
else: | |
prewalk(f, n) | |
return tree | |
print('walkers') | |
tree = [1, [2, [3]]] | |
postwalk(wrap_unless_zero, tree) #=> [[0] [[1] [[2]]]] | |
print(tree) | |
print(prewalk(wrap_unless_zero, [1, [2, [3]]])) #=> [[0] [[[0]] [[[[0]]]]]] | |
def mymap(f, list_): | |
temp = [] | |
for n in list_: | |
temp.append(f(n)) | |
return temp | |
print(mymap(lambda x: x+3, [1, 2, 3, 4, 50, 60, 70, 80])) | |
def myfilter(f, list_): | |
temp = [] | |
for n in list_: | |
if f(n): | |
temp.append(n) | |
return temp | |
print(myfilter(lambda x: x%2 == 0, range(10))) | |
def myreduce(f, list_): | |
current = list_[0] | |
for n in list_[1:]: | |
current = f(current, n) | |
return current | |
print(myreduce(lambda x, y: x + y, range(10))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment