33from fractions import Fraction
44from random import randint
55
6- # This example demonstrates a bunch of Python features for functional
7- # programming. This means that functions are treated as data that can
8- # be stored to variables, passed to other functions, returned from
9- # functions as results, created on the fly etc.
6+ # This example demonstrates some of Python features for functional
7+ # programming. This means that functions are treated as data that
8+ # can be stored to variables, passed to other functions, returned
9+ # from functions as results, created on the fly as needed, etc.
1010
1111
1212def is_positive (n ):
@@ -21,13 +21,15 @@ def square(n):
2121# Note that these both return iterator objects instead of doing
2222# the computation right away, so we force the computation to
2323# take place with list().
24+
2425print ("The positive elements are: " , end = '' )
2526print (list (filter (is_positive , [- 7 , 8 , 42 , - 1 , 0 ])))
27+
2628print ("The squares of first five positive integers are: " , end = '' )
2729print (list (map (square , [1 , 2 , 3 , 4 , 5 ])))
2830
29- # Reduce is a handy operation to repeatedly combine first two elements
30- # of the given sequence into one, until only one element remains.
31+ # Reduce is a handy operation to repeatedly combine first two
32+ # elements of given sequence into one, until only one remains.
3133
3234print (reduce (mul , [1 , 2 , 3 , 4 , 5 ], 1 )) # 120 = 5!
3335
@@ -57,20 +59,24 @@ def is_eventually_periodic(f, x, giveup=1000):
5759 giveup -= 1
5860 return tortoise == hare
5961
60- # Next, let's examine how functions can be given to other functions as
61- # parameters, and returned as results to the caller. To demonstrate this,
62- # let's write a function negate that can be given any predicate function,
63- # and it creates and returns a function that behaves as the negation of
62+ # Next, let's examine how functions can be given to other functions
63+ # as parameters, and returned as results to the caller. To demo this,
64+ # let's write a function negate that can be given any predicate, and
65+ # it creates and returns a function that behaves as the negation of
6466# its parameter.
6567
6668
6769def negate (f ):
6870 # Nothing says that we can't define a function inside a function.
69- # Since we don't know what parameters f takes, we write our function
70- # to accept any arguments and keyword arguments.
71- def result (* args , ** kwargs ):
71+ # Since we don't know what parameters f takes, we write this
72+ # function to accept any arguments and keyword arguments that
73+ # it simply passes down to the original f without further ado.
74+
75+ def negated_f (* args , ** kwargs ):
7276 return not f (* args , ** kwargs )
73- return result # return the function that we just defined
77+
78+ # Return the function that was just defined.
79+ return negated_f
7480
7581# Let's try this out by negating the following simple function.
7682
@@ -79,30 +85,32 @@ def is_odd(x):
7985 return x % 2 != 0
8086
8187
82- is_even = negate (is_odd )
83- print (is_even ) # <function result at ....>
88+ is_even = negate (is_odd ) # I can't even...
89+ print (is_even ) # <function result at ....>
8490print (f"Is 2 even? { is_even (2 )} " ) # True
8591print (f"Is 3 even? { is_even (3 )} " ) # False
8692
87- # Partial evaluation or "currying" means fixing some of the parameter
88- # values of a function to create a function that takes fewer parameters
89- # but is otherwise the same.
9093
94+ # Partial evaluation or "currying" means setting some of the
95+ # arguments of a function to stone, to create a new function
96+ # that takes fewer parameters but works otherwise the same as
97+ # the original function.
9198
9299def foo (a , b , c ):
93100 return a + (b * c )
94101
95102
96103# Create a version of foo with parameter b fixed to -1.
104+
97105foob = partial (foo , b = - 1 )
98- print (f"After partial application , foob(2, 5)={ foob (a = 2 , c = 5 )} ." )
106+ print (f"After partial, foob(2, 5)={ foob (a = 2 , c = 5 )} ." )
99107
100108# Sometimes functions can take a long time to calculate, but we
101109# know that they will always return the same result for the same
102- # parameters . The function memoize takes an arbitrary function as
103- # a parameter , and creates and returns a new function that gives
104- # the same results as the original, but remembers the results that
105- # it has already computed and simply looks up those results that
110+ # arguments . The function memoize takes an arbitrary function as
111+ # an argument , and creates and returns a new function that gives
112+ # the same results as the original. However, it remembers what
113+ # it has already computed, and simply looks up those results that
106114# have previously been calculated.
107115
108116
@@ -199,9 +207,10 @@ def wine(barrel, age, year, pour=Fraction(1, 2)):
199207 print (f"Barrel { b } : { ', ' .join (comp )} ." )
200208
201209
202- # Wouldn't it be "groovy" to memoize the memoize function itself, so that
203- # if some function has already been memoized, the same function won't be
204- # memoized again but its previously memoized version is returned?
210+ # Wouldn't it be "groovy" to memoize the memoize function itself, so
211+ # that if some function has already been memoized, the same function
212+ # won't be redundantly memoized again, but its previously memoized
213+ # version is returned? Isn't duck typing just dandy?
205214
206215memoize = memoize (memoize )
207216
@@ -214,10 +223,11 @@ def divisible_by_3(x):
214223f2 = memoize (divisible_by_3 ) # already did that one
215224print (f"f1 is f2 = { f1 is f2 } ." ) # True
216225
217- # We can use the memoization technique to speed up checking whether the
218- # so-called Collatz sequence starting from the given number eventually
219- # reaches 1. The function collatz(n) tells how many steps this takes.
220226
227+ # We can use the memoization technique to speed up checking whether
228+ # the so-called Collatz sequence starting from the given number
229+ # eventually reaches 1. The function collatz(n) tells how many
230+ # steps this needs.
221231
222232@memoize
223233def collatz (n ):
@@ -233,7 +243,7 @@ def collatz(n):
233243print (f"Collatz sequence from { lc [1 ]} contains { lc [0 ]} steps." )
234244print (f"Stored { len (collatz .results )} results. Some of them are:" )
235245
236- for i in range (10 ):
246+ for _ in range (10 ):
237247 v = randint (1 , 10 ** 6 - 1 )
238248 print (f"From { v } , sequence contains { collatz .results [(v ,)]} steps." )
239249
@@ -261,7 +271,7 @@ def thue_morse(n, sign):
261271 if n == 1 :
262272 return '0' if sign == 0 else '1'
263273 else :
264- return thue_morse (n - 1 , sign ) + thue_morse (n - 1 , 1 - sign )
274+ return thue_morse (n - 1 , sign ) + thue_morse (n - 1 , 1 - sign )
265275
266276
267277print ("Thue-Morse sequences from 2 to 10 are:" )
@@ -271,21 +281,22 @@ def thue_morse(n, sign):
271281# Memoization of recursions that have repeating subproblems is awesome.
272282print (f"Computing those required { __tm_call_count } recursive calls." )
273283
284+
274285# The next function can be given any number of functions as parameters,
275286# and it creates and returns a function whose value is the maximum of
276287# the results of any of these functions.
277288
278-
279289def max_func (* args ):
280290 funcs = args
281291
282- def result (* args ):
292+ def our_max_f (* args ):
283293 return max ((f (* args ) for f in funcs ))
284- return result
294+
295+ return our_max_f
285296
286297
287- # Try that one out with some polynomials that we create as a lambda
288- # to be used as throwaway arguments of max_func.
298+ # Try that one out with some polynomials created as lambdas, to be
299+ # used as throwaway arguments of max_func.
289300
290301f = max_func (lambda x : - (x * x ) + 3 * x - 7 , # -x^2+3x-7
291302 lambda x : 4 * x * x - 10 * x + 10 , # 4x^2-10x+10
@@ -318,14 +329,15 @@ def get_count():
318329 def reset_count ():
319330 count [0 ] = 0
320331
332+ # This is ugly, but necessary. As so many other things in life.
321333 cf .get_count = get_count
322334 cf .reset_count = reset_count
323335 return cf
324336
337+
325338# Demonstrate the previous decorator by counting how many times the
326339# sorting algorithm computes the given key...
327340
328-
329341def kf (x ):
330342 return x
331343
@@ -341,7 +353,7 @@ def kf(x):
341353sorted (range (- 100000 , 100000 ), key = kf , reverse = True )
342354print (f"The key was computed { kf .get_count ()} times." )
343355
344- # Another sometimes handy feature of Python is the ability to compile
356+ # Another sometimes handy feature of Python is its ability to compile
345357# and execute new code dynamically on the fly, in any desired context.
346358# However, be careful using eval and exec to strings that originate
347359# from outside your program, as this opens a doorway for a malicious
0 commit comments