In [1]:
%%javascript
Jupyter.notebook.config.update({"load_extensions":{"calico-spell-check":true,
"calico-document-tools":true,
"calico-cell-tools":true}})
In [1]:
x = 5
y = 7
In [2]:
x2 = -3 # oops maybe the choice of variable names is not optimal
y2 = 17
In [4]:
x3 = x + x2
y3 = y + y2
print(x3, y3)
In [6]:
from math import sqrt
length_3 = sqrt(x3 * x3 + y3 * y3)
print length_3
In [8]:
length_1 = sqrt(x * x + y * y)
print length_1
Hm, that's kind of annoying.
When you need more than 7 +/- 2 lines of code to describe something, it's very likely that you need to break down the problem in smaller chunks.
In [10]:
def length(x, y):
return sqrt(x* x + y * y)
In [12]:
def vector_sum(vx1, vy1, vx2, vy2):
x = vx1 + vx2
y = vy1 + vy2
return x, y #Nice, there's no restriction on the amount of return values.
In [13]:
def scalar_prod(vx1, vy1, vx2, vy2):
return (vx1 * vx2 + vy2 * vy2)
length
function:
In [14]:
def length(vx1, vy1):
return sqrt(scalar_prod(vx1, vy1, vx1, vy1)) #hmmmm, a function of a function
Let's give it a try:
In [17]:
print length(3, 4)
In [ ]:
def functionname( parameters ):
"function_docstring"
function_suite
return [expression]
In [18]:
def foo( s ):
print(s)
foo()
In [19]:
def foo(name, age):
print("name:", name)
print("age: ", age)
foo('Alice', 35)
print('----')
foo(19, 'Bob')
print('----')
foo(age = 78, name = 'Dave')
In [22]:
def foo(name, age = 23):
print("name:", name)
print("age: ", age)
foo('Alice')
foo('Bob', 29)
In [25]:
def vector_sum(vx1, vy1, vx2, vy2, vz1=None, vz2=None):
if vz1 == None or vz2 == None: # making sure that both values are set
return vx1 + vx2, vy1 + vy2
else:
return vx1 + vx2, vy1 + vy2, vz1 + vz2
In [27]:
print vector_sum(1, 2, 3, 4)
In [28]:
print vector_sum(1, 2, 3, 4, 8,8)
Careful with the amount of code inside a function (7$\pm$2 rule)!
In [31]:
import datetime as dt # What's that?
print dt.datetime.now()
In [33]:
def log_time(message, time=dt.datetime.now()):
print("{0}: {1}".format(time.isoformat(), message))
In [34]:
log_time("message 1")
In [36]:
log_time("message 2")
In [37]:
log_time("message 3")
One more example: default arguments and mutable variables:
In [38]:
def foo(value, a=[]):
a.append(value)
return a
In [39]:
print(foo(1))
In [40]:
print(foo(2))
In [41]:
print(foo('uh oh'))
Here's how it should look like:
In [42]:
def foo(value, a=None):
if a == None:
a = []
a.append(value)
return a
In [43]:
print(foo(1))
In [44]:
print(foo(2))
In [45]:
print(foo('yeah!!!'))
In [46]:
def foo(arg1, *argtuple):
print(arg1)
for arg in argtuple:
print(arg)
foo('Hello World')
foo('x', 1, 'hello', 3.1415, '20')
foo()
In [47]:
class Vehicle:
pass
Obviously this class is pretty boring. Nevertheless, we can now create objects of type Vehicle by instanciating:
In [48]:
car = Vehicle()
print(car)
We just created an object that we called car
of type Vehicle
.
__init__
method:
In [49]:
class Vehicle():
def __init__(self, number_of_wheels, number_of_seats, max_velocity):
self.number_of_wheels = number_of_wheels
self.number_of_seats = number_of_seats
self.max_velocity = max_velocity
In [50]:
car = Vehicle(4, 5, 200.0)
bike = Vehicle(2, 2, 50.0)
print car.number_of_wheels
print bike.number_of_seats
__init__
the constructor method.self
represents the instance of the class. self
keyword we can access the attributes and methods of the class in python.
In [51]:
class Vehicle():
def __init__(self, number_of_wheels, number_of_seats, max_velocity):
self.number_of_wheels = number_of_wheels
self.number_of_seats = number_of_seats
self.max_velocity = max_velocity
def get_max_velocity(self):
return self.max_velocity
def make_noise(self):
print("vrummmmm")
In [52]:
car = Vehicle(4, 5, 200.0)
print car.get_max_velocity()
print car.max_velocity
car.make_noise()
In [53]:
from math import cos, sin, sqrt
class MyVector():
def __init__(self, x=None, y=None, r=None, phi=None):
if x == None or y == None:
self.x = r * cos(phi)
self.y = r * sin(phi)
else:
self.x = x
self.y = y
def get_length(self):
return sqrt(self.x * self.x + self.y * self.y)
def scale_vector(self, a):
self.x *= a
self.y *= a
def add_vector(self, vector):
self.x += vector.x
self.y += vector.y
In [54]:
v1 = MyVector(10, 20)
print v1.get_length()
v2 = MyVector(r=10, phi=2.7)
print v2.get_length()
v1.add_vector(v2)
print v1.get_length()
In [55]:
v1 = MyVector(10, 20)
v2 = MyVector(20, 10)
v3 = v1 + v2
In [64]:
from math import cos, sin, sqrt
class MyVector():
def __init__(self, x=None, y=None, r=None, phi=None):
if x == None or y == None:
self.x = r * cos(phi)
self.y = r * sin(phi)
else:
self.x = x
self.y = y
def get_length(self):
return sqrt(self.x * self.x + self.y * self.y)
def scale_vector(self, a):
self.x *= a
self.y *= a
def add_vector(self, vector):
self.x += vector.x
self.y += vector.y
def __add__(self, other):
x = self.x + other.x
y = self.y + other.y
return MyVector(x, y)
In [63]:
v1 = MyVector(10, 20)
v2 = MyVector(20, 10)
v3 = v1 + v2
print(v3.x, v3.y)
In [71]:
from math import cos, sin, sqrt
class MyVector():
def __init__(self, x, y):
self.x = x
self.y = y
def get_length(self):
return sqrt(self.x * self.x + self.y * self.y)
def scale_vector(self, a):
self.x *= a
self.y *= a
def add_vector(self, vector):
self.x += vector.x
self.y += vector.y
def __add__(self, other):
x = self.x + other.x
y = self.y + other.y
return MyVector(x, y)
def get_r(self):
return self.get_length()
In [72]:
class MyPolarVector(MyVector):
def __init__(self, r, phi):
self.r = r
MyVector.__init__(self, r * cos(phi), r * sin(phi))
def get_length(self):
print "inside MyPolarVector"
return self.r
def get_r(self):
return self.r
In [73]:
v1 = MyVector(10, 20)
v2 = MyPolarVector(12, 2.7)
print (v1 + v2).get_r()
print v1.get_length()
print v2.get_length()
print (v1 + v2).get_length()
Imperative: Computation is performed as a direct change to program state. This style is especially useful when manipulating data structures and produces elegant, but simple, code.
In [74]:
my_list = [1, 2, 3, 4, 5]
sum = 0
for x in my_list:
sum += x
print(sum)
The focus of imperative programming is on how a program operates. It changes state information as needed in order to achieve a goal.
Procedural: Tasks are treated as step-by-step iterations where common tasks are placed in functions that are called as needed. This coding style favors iteration, sequencing, selection, and modularization.
In [75]:
def do_add(a_list):
sum = 0
if type(a_list) is list:
for x in a_list:
sum += x
return sum
my_list = [1, 2, 3, 4, 5]
print(do_add(my_list))
Functional: Any form of state or mutable data are avoided. The main advantage of this approach is that there aren’t any side effects to consider. In addition, this coding style lends itself well to parallel processing because there is no state to consider.
In [76]:
import functools
my_list = [1, 2, 3, 4, 5]
def add(x, y):
return (x + y)
sum = functools.reduce(add, my_list)
# calculates add(add(add(add(1, 2), 3), 4), 5)
print(sum)
The functional coding style treats everything like a math equation.
Object-oriented: Relies on data fields that are treated as objects and manipulated only through prescribed methods. Python doesn’t fully support this coding form because it can’t implement features such as data hiding. This coding style also favors code reuse.
In [77]:
class ChangeList:
def __init__(self, a_list):
self.my_list = []
if type(a_list) is list:
self.my_list = a_list
def do_add(self):
self.my_sum = 0
for x in self.my_list:
self.my_sum += x
create_sum_obj = ChangeList([1, 2, 3, 4, 5])
create_sum_obj.do_add()
print(create_sum_obj.my_sum)
The object-oriented coding style is all about increasing the ability of applications to reuse code and making code easier to understand. The encapsulation that object-orientation provides allows developers to treat code as a black box.
In [78]:
class ListNode:
def __init__(self, data):
"constructor to initiate this object"
# store data
self.data = data
# store reference (next item)
self.next = None
return
def contains_value(self, value):
"method to compare the value with the node data"
if self.data == value:
return True
else:
return False
In [79]:
node1 = ListNode('data point 1')
node2 = ListNode([15, 16 , 3.14])
node3 = ListNode(16.6)
No we want to write a class that represents a linked list. We identify the following methods as required:
__init__()
: initiate an objectlist_length()
: return the number of nodesoutput_list()
: outputs the node valuesadd_list_item()
: add a node at the end of the listunordered_search()
: search the list for the nodes with a specified valueremove_list_item_by_id()
: remove the node according to its id
In [ ]:
class SingleLinkedList:
def __init__(self):
"constructor to initiate this object"
self.head = None
self.tail = None
return
def list_length(self):
pass
def output_list(self):
pass
def add_list_item(self, item):
pass
def unordered_search(self):
pass
def remove_list_item_by_id(self):
pass
In [ ]: