CoCalc Public FilesIntroduction to Python with Sage.ipynb
Author: Aram Dermenjian
Views : 306

# Introduction to Python

#### using sage

Sage + Python = Awesomeness

## First things first:

#### Tabbing & Questions

If you don't know what a function does, type the function, add a ? and hit shift+enter for a quick description.

Try typing:

fibonacci?
In [ ]:
# Try it here!


Congrats! For the next parts, go ahead and read the information and hit shift+enter each time you want to test something out.

Try doing shift+enter on the following to test it out. (The first lines makes everything look pretty by using latex =D)

In [ ]:
%display latex
var('x','i','u')
f = (9*x - 7*i > 3 * (3*x - 7*u))
3*f.solve(i)[0][0]


## Second:

#### Data Types

Luckily there are a finite number of data types in Python. This list includes
• Booleans
• Integers
• Floats
• Strings
• Sets
• Lists
• Tuples
• Dictionaries

Python is a dynamic language, so there is no need to declare what variable type you are using. You can also change the type of your variables. To know what type a variable is you can just ask for it's type using the type function. Or you can ask directly if the object is of a certain type using isinstance.

Full details can be found in the python docs.

Let's first set-up a string and an integer. Notice how their types are different.

In [ ]:
a = "123"
print type(a)

a = 123
print type(a)

In [ ]:
print isinstance(a, Integer)
print isinstance(a, str)


### Booleans

bool has one of two values: True and False.

There are three boolean operators: and, or, & not.

You can convert something into a boolean by the bool function.

Notice in the following that True returns 'bool' as its type.

In [ ]:
type(True)


Notice that when converting something to boolean, we normally get True, except for when we input $0$.

In [ ]:
print bool(1)
print bool(0)
print bool(-1)


In particular, the following give us False:

• The integer 0
• The string ''

We can also do operations on booleans using and, or, not, etc.

In [ ]:
True and False

In [ ]:
True and True

In [ ]:
False and False

In [ ]:
not True

In [ ]:
False or True


### Integers/Floats

int is the python built-in integer type. The problem with the python version is the integer conversion problem. Due to this restriction, Sage uses its own type called Integer.

float is the python built-in rational number type. Due to similar restrictions, Sage uses the RealLiteral type.

Look at what happens if we force numbers to use python's integers.

In [ ]:
int(5)/int(2)


This is definitely not true!!!

In order to get around this, sage uses its own definition of integer using the integer ring.

In [ ]:
print type(1)
print type(1.2)


### Strings

The str type is fairly common. You can use single or double quotes. Additionally you can use triple quotes if you want a multi-line string. You can also fill a string using dictionary substitutions as can be seen in the following example.

In [ ]:
aram = 'Aram'
print """Hello %s,
You're a pretty cool person.
- %s
""" % ('human', aram)


## Concatenation

You can concatenate strings either using , or +. The difference is that , will auto-type convert and will add spaces automatically. + on the other hand assumes everything is a str variable and will not add spaces.

In [ ]:
print a, "factors into", a.factor()

In [ ]:
print str(a) + " factors into " + str(a.factor())


### Sets, lists, tuples, and dictionaries

##### Because, why not.

There are four more (complicated) types:

• set - is a set of items with no multiplicites or order.
• list - is an ordered list of items
• tuple - is the same as a list, but it is immutable (it cannot be changed).
• dict - Is an associative array which associates values with keys.

### Some basic commands

#### Constructing stuff

This is how you can construct a set, list, tuple and dict.

In [ ]:
S = set([1,2,3])
L = [1,2,3]
T = (1,2,3)
D = {2:1, 3:2, 4:3}

print S
print L
print T
print D


To have only one element in your object, it's generally normal:

In [ ]:
print set([1])
print [1]
print {1:2}


Note that for tuples, you can't just have 1 element! Look at what happens if we try and define a tuple with just one element.

In [ ]:
print (1)
print (1,2)


If you reaaaaalllly want a tuple with one element, you need to add a comma after the first element to force it to be a tuple.

In [ ]:
(1,)


#### How many stuff does it have?

Use the len function.
In [ ]:
print len(S)
print len(L)
print len(T)
print len(D)


#### Is this object present?

Use the in operator.

In [ ]:
S = set([1,2,3])
L = [1,2,3]
T = (1,2,3)
D = {2:1, 3:2, 4:3}

print 1 in S
print 1 in L
print 1 in T
print 1 in D

In [ ]:
print 4 not in S
print 4 not in L
print 4 not in T
print 4 not in D


Did you notice how for dictionaries the in operator is looking at the key and not the value? Remember this forever!

#### How do I access elements?

Use square brackets to access individual items. For dictionaries you use the key. For lists and tuples the counting begins at $0$. Sets have no order so you can't "access" things.
In [ ]:
L = [1,2,3]
T = (1,2,3)
D = {2:1, 3:2, 4:3}
print L[0]
print T[0]
print D[2]


#### Can I modify things?

Only lists and dictionaries! Tuples are immutable, so they can't be altered.
In [ ]:
L[0] = 5
D[2] = 5
print L
print D


#### How do I add new things?

There are many ways my young padawan.

### set example for adding new things

In [ ]:
S = set([1,2,3])
S


### list example for adding new things

In [ ]:
L = [1,2,3]
L2 = ["a", "b"]
L3 = [(1,2), (4,5)]

L.append(4)
L.extend(L2)
print L
L4 = L2 + L3
print L4


### tuple example for adding new things

Recall that since they are immutable, you can't add directly to a tuple

In [ ]:
T = (1,2,3)
T2 = (0,0)
T3 = T + T2
print T3


### dict example for adding new things

In [ ]:
D = {2:1, 3:2, 4:3}
D[10] = fibonacci(10)
D[(3,2,1)] = Permutation([3,2,1])
D


Note: For lists we can't add additional items like we do in dictionaries.

In [ ]:
L[10] = fibonacci(10)


Note: We also can't add mutable items as keys for dictionaries.

In [ ]:
D[[1]] = 'fail'


##### Sets of sets can't happen
In [ ]:
S1 = set([1,2,3])
S2 = set([S1])
S2


If you want a set of a set you need to create a frozenset. Frozen sets are immutable and therefore they can be placed in a set.

In [ ]:
S1 = frozenset([1,2,3])
S2 = set([S1])
S2


Exercise: Look at the following code and make a guess as to what will be displayed.

In [ ]:
L = [1,"z",3,4,5]
L.append("c")
L = L + [9,10]
L[6]


#### But I added too much! How do I delete things I don't want?

For sets we can just discard the item we want. Note that we must discard the actual element and not the 'index' as indices don't exist in a set.

In [ ]:
S = set([1,2,3])
S


For a list we can either delete by index using del, we can pop an item out, or we can remove an item by its value.

For del you must specify the list with the index you are trying to remove. For pop you specify the index as well, but unlike del, it will actually return the item removed so you can use it later if you want. For remove you specify the value of the thing you want to remove. This is useful if you don't know the index.

In [ ]:
L = [1,2,3,4,5]
del L[1]
poppedItem = L.pop(0)
L.remove(3)
L


Note: remove will only remove the first time it sees an object. It won't remove all of them.

In [ ]:
L = [1, 2, 3, 2]
L.remove(2)
L


You can also use splicing. Splicing won't be covered in full here, but if you're interested, here's a good article on it.

In [ ]:
L2 = [1,2,3,4,5]
L2 = L2[1:3]
L2

In [ ]:
T = (1,2,3,4,5)
T = T[1:3]
T


For dict we can do similar things as list. We can del or we can pop. Recall that we are using the keys and not the values.

In [ ]:
D = {2:1, 3:2, 4:3}
del D[3]
poppedItem = D.pop(4)
D

##### Some other useful commands (Lists only!):
• sort
• reverse
In [ ]:
L = [5,4,1,2,3]
L.sort()
L

In [ ]:
L = [5,4,1,2,3]
L.reverse()
L


### Some basic commands

Here's a summary of everything we just talked about.
Operation set list tuple dict
Construction set([]) [] () {}
One element construction set([1]) [1] (1,) {1:1}
Size len(set) len(list) len(tuple) len(dict)
Contains x in set x in list x in tuple key in dict
Accessing entries - list[0] tuple[0] dict[key]
Adding stuff set.add(x) list.append(x)
list.extend(list2)
list3 = list1 + list2
tuple3 = tuple1 + tuple2 dict[key] = x
key must be immutable
Deleting stuff set.discard(item) list.pop(i)
del list[i]
list = list[start:end]
list.remove(value)
tuple = tuple[start:end] dict.pop(key)
del dict[key]

Exercise:

1. Create the list [5,1,3,6,7,2,12].
2. Remove the element 12.
4. Sort the list and reverse it.
In [ ]:
# Try it out!


### The modification problem.

##### Or things that should be illegal.
Changing lists and dictionaries can have unintended consequences. To see this, let's start off with constructing a list.

Note that for theatrical purposes, I'll have you do the heavy work of hitting shift+enter for each code. Try and think about what you would expect to happen each time before running the code.

In [ ]:
L = ["a", "b", "c"]
L2 = [L, L]
L2


This seems fairly normal. We have a list composed with two lists. But now watch what happens when we add an element to our list L.

In [ ]:
L.append("d")
L2


WHAT?! That's mental right? So what happens if we change L to something altogether?

In [ ]:
L = ["a", "b", "c"]
L2


But L2 didn't change!

So what happened? Basically variables in Python work a little differently than we might otherwise think. When creating a variable Python assigns the variable to a slot in your RAM with the data inside of it. When you then use that variable to construct other variables (such as for L2 = [L,L]) Python doesn't duplicate the data. Instead, it says "create an array with two elements where the information for these elements contained in the slot where L is". So in essence this new memory slot points to the original slot! Therefore when we alter L by appending a d to it, L2 still is pointing to that memory slot and therefore will show the d now as well.

But what happened when we reconstructed L?! That is because whenever we do a = it resets the memory slot. So now L is pointing to a different slot in RAM and altering it won't affect L2 anymore.

BUT that doesn't mean we can't get weird things happening still! L[0] is nothing more than just a pointer to the original memory slot that used to contain L. So what do yu think will happen when we alter this?

In [ ]:
L2[0].remove("c")
L2


### deepcopy to the rescue!

Basically, what deepcopy does is makes a copy of the data instead of just pointing! Like this we won't get a lot of this alteration problems.
In [ ]:
L = ["a", "b", "c"]
L2 = [deepcopy(L), deepcopy(L)]
L2

In [ ]:
L.append("d")
L2


### The None type

If you want to set a variable but don't want to set it as any particular type, you can use None

In [ ]:
v = None
print v


### Ranges

Ranges allow us to make numerical lists easily. We use the range function to construct a range.

In [ ]:
range?


If we want all the numbers between 1 and 10 we would do the following:

In [ ]:
range(1,10)


Note that 10 is not included! This is because range is "starting" from 1 and going up to (but not including) 10. Something to note!.

Also, you can skip over every other element if we want.

In [ ]:
range(1,10,2)


Or we can go backwards!

In [ ]:
range(20,1,-1)


Exercise: Use range to construct the list of even numbers between $1$ and $40$ (including $40$).
Bonus: Put it in reverse order.

In [ ]:
# Try it out!


## Third parts the charm:

#### Control Flows

• if - Does something if the condition is true.
• for - Loops through a range of objects.
• while - Keeps looping until condition is false.

For more details you can visit the control flow page on the Python docs.

#### Quick aside: Comparisons

• or    and    not - Boolean operators
• == - Equal
• != - Not equal
• <   >   <=   >= - Inequalities
• is - Object identity
• is not - Negated object identity

#### if

##### with its siblings elif and else.

An if statement is exactly like what it sounds like. If something is true, then do something.

In [ ]:
if 2 in (1,2,3,4):
print "2 is in our tuple!"


If we have multiple things we can use elif and else to help. elif stands for "else if". The rest should be easy to understand.

In [ ]:
R = range(1,10,2)
if 2 in R:
print "2 is in range."
elif 4 in R:
print "4 is in range."
else:
print "By faulty induction our range is only odd numbers."


#### for

for is used for constructing loops. It does "something" for each item in something.

In [ ]:
for i in set([3,2,1]):
print i

In [ ]:
for i in range(1,5):
print i

In [ ]:
for i in range(1,10):
if i % 2 == 0:
print i

In [ ]:
for i in range(1,10):
if is_prime(i):
print i


Exercise: Use a for loop to print out all the non-even primes from $1$ to $100$.

In [ ]:
# Try it yourself


#### while

while is a loop that keeps going while a certain condition is true.

Note: be careful of creating infinite loops! Always make sure that your loop WILL end at some point.

Let's look at this through the Collatz conjecture.

In [ ]:
C = 3
while C != 1:
print C
if C % 2 == 0:
C /= 2
else:
C = C*3 + 1


#### for and list

##### The super team
We can use for loops in order to create super awesome lists.
In [ ]:
[i^2 for i in range(1,10)]


We can even combine if statements!

In [ ]:
[i for i in range(1,50) if i%7 == 0 or i%5 == 0]


And take sums!

In [ ]:
sum([p for p in range(1,10) if is_prime(p)])


Exercise: Find the sume of the non-even primes from $1$ to $100$.

In [ ]:
# Try it yourself!


#### Wait, it gets better.... Nesting!

We can nest these lists in order to construct lists of tuples, dictionaries, matrices, etc.
In [ ]:
[(x,y,z) for x in range(1,3) for y in range(1,3) for z in range (1,3)]

In [ ]:
[[i^j for j in range(1,3)] for i in range(1,5)]

In [ ]:
matrix([[i^j for j in range(1,3)] for i in range(1,5)])


#### These special lists also allow us to grab the keys/values of dictionaries!

In [ ]:
D = {2:1, 3:2, 4:3}
[value for value in D.itervalues()]

In [ ]:
[key for key in D.iterkeys()]

In [ ]:
[(key,value) for key,value in D.iteritems()]


### Integers vs ints

##### Revisited

So do you recall how integers in Python have a division problem? Check out this matrix....

In [ ]:
matrix(QQ, [[i/j for i in range(1,10)] for j in range(1,10)])


That looks wrong... Basically, range will return Python integers and NOT sage integers! This is obviously a problem if we actually want to work with these numbers. So, sage includes with it srange which is a copy of range but instead returns sage integers and not Python integers. Let's see this in action.

In [ ]:
matrix(QQ, [[i/j for i in srange(1,10)] for j in srange(1,10)])


## Part $2^2$:

#### Functions

Functions are defined using the def statement. We can return things from functions using the return keyword.
In [ ]:
def f(x):
return x^2

f(2)


#### Recursion - Catalan Numbers

$C_0 = 1$
$C_{n} = \frac{2(2n-1)}{n+1} C_{n-1}$
In [ ]:
def catalanNumber(x):
if x == 0:
return 1
else:
return (2 * (2 * x - 1) / (x+1)) * catalanNumber(x-1)

[catalanNumber(n) for n in range(0,10)]


#### Complicated recursion - Catalan Numbers part 2

$C_0 = 1$
$C_n = \sum_{i = 0}^{n-1} C_{i} C_{n-1-i}$
In [ ]:
def catalanNumber(x):
if x == 0:
return 1
else:
return sum([ (catalanNumber(i) * catalanNumber(x-1 - i)) for i in range(0,x)])

[catalanNumber(n) for n in range(0,10)]


Exercise: Create a function for the fibonacci series and display the first $20$.

In [ ]:
# Try it!


#### Functions can also have default values.

In [ ]:
# By default we choose 1
def choose(n, k = 1):
return binomial(n,k)

print choose(5)
print choose(5,2)


#### Functions can also return multiple values if desired.

In [ ]:
def fibAndCat(n):
return fibonacci(n), catalan_number(n)

F, C = fibAndCat(5)
print F
print C

In [ ]:
[fibAndCat(i) for i in range(0,10)]


### Digraphs!

Sage allows us to construct digraphs using dictionaries. Let's take a sidestep and look at how to construct digraphs and some operations we can do on them.

In [ ]:
D = {0:[1,2,3], 1:[0,3], 2:[3,4], 4:[5,5,5,1], 5:[5]}
G = DiGraph(D)
G.plot()

In [ ]:
G.adjacency_matrix()


# Test this out here.