# import other modules this way. # qualified version: must use sys.args, sys.whatever, always sys.something. import sys # import every name from the math module, but not the name math itself. # can use pi, but not math.pi from math import * import functools # reduce ################################################################################ print("\n\nControl Flow:\n------------") # if-else example. With nesting. x,y,z = 1,5,10 if x= 90: print("A") elif score >= 80: print("B") elif score >= 70: print("C") elif score >= 60: print("D") else: print("F") ################################################################################ # simple function example. Note multiple returns. def max3(a, b, c): if a>=b and a>= c: return a if b >= c: return b return c def add(*xs): s = 0 for x in xs: s += x return s #------------------------------------------------------------------------------- # Look Out! complex default values: created once at def'n time, shared always!! def put_stuff (these_vals, here = []): def helper(): y = 1 def helperhelper(): x = 1 print("so helpful!") helperhelper() helper() here.extend(these_vals) return here put_stuff([1,2,3]) # gives back [1,2,3] put_stuff([4,5]) # gives back [1,2,3,4,5] put_stuff([6,7],["test"]) # gives back ["test",6,7] #------------------------------------------------------------------------------- # our version of printing. def p(*vargs, the_sep=" ", the_end="\n", flush=True, **kwargs): print(*vargs, **kwargs, sep=the_sep, end=the_end, flush=flush) # paste and compare: ours looks faster because it flushes by default. """ for i in range(1000000): p(i, the_end=" ") for i in range(1000000): print(i, end=" ") """ ################################################################################ # CLASSES class Person: min_age = 0 # class variable: shared; Person.min_age # constructor. def __init__(self, name, age): self.name = name self.age = max(age, Person.min_age) def greet(self): return ("Hi, I'm {}. I'm {} years old.".format(self.name, self.age)) # to-string functionality. str is for people or whatever you'd like; # repr is for code cut-pasting. def __str__(self): return "Person(\"{}\",{})".format(self.name, self.age) # if it can return a valid constructor call, do that. def __repr__(self): return str(self) # str looks for __str__ def'n, finds it, calls it. def next_age(self): return self.age+1 def have_birthday(self): self.age += 1 def n_birthdays (self, n=1): for i in range(n): self.have_birthday() # gratuitous method-calling-method print("\n\n\nPerson class:\n-------------") george = Person("George", 67) newborn = Person("newbie", -123) # will become zero years old. print("first birthday: ", newborn.next_age()) newborn.n_birthdays(6) print("not that newborn anymore: ", newborn) # look out! #------------------------------------------------------------------------------- class Point: def __init__(self, x=0,y=0): self.x = x self.y = y def magnitude(self): """this is a docstring. magnitude: dist. to origin.""" return (self.x*self.x + self.y*self.y) ** 0.5 def __str__(self): return f"({self.x},{self.y})" def shift(self, xshift=0, yshift=0): self.x += xshift self.y += yshift def __repr__(self): return f"Point({self.x}, {self.y})" # common pattern of "other" param for another of same type. def slope(self, other): rise = other.y - self.y run = other.x - self.x return rise / run def __lt__(self, other): if self.x!= other.x: return self.x < other.x return self.y < other.y print("\n\n\nPoints\n------") p1 = Point(3,4) p2 = Point(9,12) print(p1.slope(p2)) """ p1.z = 13 # now p1 has an extra instance variable! p2 does not... print(p1.z) # works print(p2.z) # crashes del p1.z print(p1.z) # crashes now """ ################################################################################ #------------------------------------------------------------------------------- # create subclass of Point. Yeah, 2D/3D points isn't a great example... class Point3D(Point): def __init__(self, x, y, z): # manually call parent's __init__ # preferably first!! super().__init__(x,y) self.z = z def __str__(self): return "(%s,%s,%s)" % (self.x, self.y, self.z) def __repr__(self): return str(self) def project_down(self): # go to 2D; make new object. return Point(self.x, self.y) print("\n\n\nsubclasses:\n-----------") # very useful: help(). # print(help(Point3D)) tri = Point3D(1,2,3) print("tri",tri) print("2d tri:", tri.project_down()) tri.shift(10,20) # z not included... print("shifted tri:",tri) # getting weird: attach this function to the type! def shift3(self, xshift=0, yshift=0, zshift=0): self.x += xshift self.y += yshift self.z += zshift # now it'll be available for all objects in the type, # whether created before or after. Point3D.shift3 = shift3 # this hurts my static-code brain. tri.shift3(100,200,300) print("tri, shifted in 3D: ",tri) # x = 5 # don't check if type(x, int): # True # use isinstance: if isinstance(x, int): # True # inspect classes: if issubclass(Point3D, Point): # True ################################################################################ #------------------------------------------------------------------------------- def fact(n): if n<=1: return 1 return n*fact(n-1) def even(n): if n==0: return True return odd(n-1) def odd(n): if n==0: return False return even(n-1) def fib(n): if n<=1: return 1 return fib(n-1)+fib(n-2) # fib(40) is slow. every time. def fastfib(n, ans={0:1,1:1}): if n<=1: return 1 n1 = ans[n-1] if (n-1) in ans else fastfib(n-1) n2 = ans[n-2] ans[n] = n1+n2 return ans[n] """ # fastfib is pretty quick! unless we run out of stack-depth... fastfib(400) fastfib(2500) # fails fastfib( 500) # succeeds fastfib(1000) # succeeds fastfib(1500) # succeeds fastfib(2000) # succeeds fastfib(2500) # succeeds ?!? ans is learning, saving more n-answers each time. """ def iterfib(n): # todo is our "stack"... todo = [] ans = {} ans[0] = 0 ans[1] = 1 # put work on the todo-list. todo.append(n) # work until there's nothing todo. while len(todo)>0: # look at an item. v = todo.pop(0) # been done before? skip. if v in ans: continue # check if we can look up this one's subterms. ready = True if (v-1) not in ans: todo.insert(0,v-1) ready = False if (v-2) not in ans: todo.insert(0,v-2) ready = False # if it's not ready, try later. if not ready: todo.append(v) # if it's ready now, save the answer! else: ans[v] = ans[v-1] + ans[v-2] # nothing left todo, look up the answer! return ans[n] # now call it on stuff like 50000. As long as your heap doesn't run out, you're fine. # of course... if you can find a more efficient solution do so! # (lowest memory footprint; still O(n)). def loopfib(n): if n<2: return 1 (a,b) = (1,1) while n!=0: (a,b) = (b,a+b) n -= 1 return a ################################################################################ #------------------------------------------------------------------------------- # concatenating strings is slow, the longer they get! s = "" for i in range(10): s += str(i) """ # evolution of s: "" "0" "01" "012" "0123" "01234" "012345" "0123456" "01234567" "012345678" "0123456789" # this gets bad if i gets large. """ # so build a list, and join them once at the end: """ parts = [] for i in range(n) : parts.append(str(n)) s = "".join(parts) """ ################################################################################ #------------------------------------------------------------------------------- # Higher Order Functions: functions that accept other functions as parameters. # This parameterizes the functionality, not just the data, when it is called. # the ability to pass functions around explicitly (without calling them) is called # "functions as first class values." # from a question on how to keep the leftovers: # we make another parameter for what list to put the # extras into; if it's the default None, we don't # give them back. If not None, we put them all at the end # of that file. def my_zip(xs,ys, leftovers = None): size = min(len(xs),len(ys)) if leftovers!= None and len(xs)!=len(ys): if len(xs)>len(ys): leftovers.extend(xs[len(ys):]) else: leftovers.extend(ys[len(xs):]) ans = [] for i in range(size): ans .append ( (xs[i], ys[i]) ) return ans def simple_filter(f,xs): ans = [] for x in xs: if f(x): ans.append(x) return ans # first, some small functions to play with. def double(x): return x*2 def smaller(x,y): if x