Sage + Python = Awesomeness

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]

- 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)

`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

`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)

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)

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())

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.

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,)

`len`

function.In [ ]:

print len(S) print len(L) print len(T) print len(D)

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!

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]

In [ ]:

L[0] = 5 D[2] = 5 print L print D

`add`

function
`append`

a list at the end, `extend`

a list with another list, or just plain add two lists together.
`set`

example for adding new thingsIn [ ]:

S = set([1,2,3]) S.add(2) S.add(4) S

`list`

example for adding new thingsIn [ ]:

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 thingsRecall 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 thingsIn [ ]:

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'

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]

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.discard(2) 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

`sort`

`reverse`

In [ ]:

L = [5,4,1,2,3] L.sort() L

In [ ]:

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

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:**

- Create the list
`[5,1,3,6,7,2,12]`

. - Remove the element 12.
- Add the element 4.
- Sort the list and reverse it.

In [ ]:

# Try it out!

**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!
In [ ]:

L = ["a", "b", "c"] L2 = [deepcopy(L), deepcopy(L)] L2

In [ ]:

L.append("d") L2

`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 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!

`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.

`or`

`and`

`not`

- Boolean operators`==`

- Equal`!=`

- Not equal`<`

`>`

`<=`

`>=`

- Inequalities`is`

- Object identity`is not`

- Negated object identity

`if`

`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`

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!

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)])

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()]

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)])

`def`

statement. We can return things from functions using the `return`

keyword.In [ ]:

def f(x): return x^2 f(2)

$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)]

$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!

In [ ]:

# By default we choose 1 def choose(n, k = 1): return binomial(n,k) print choose(5) print choose(5,2)

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)]

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()

**Exercise:** Plot your favourite digraph.

In [ ]:

# Test this out here.

Thanks for reading this tutorial!!!

If you have any suggestions, just email me and let me know =D I'm more than happy to make this more accessible to everyone =) Also, feel free to use this for anything =) The more humans using sage the better =D

**Email:** [email protected]
**Website:** dermenjian.com