In [31]:

```
from __future__ import division
```

Yesterday and this morning we saw how we can solve linear equations in 3,4 variables, and some of you have even seen how to do it in general.

We will now go over this more carefully.

In [8]:

```
### Helper functions
def multiply_equation(eq,num):
"""Multiply all coefficients of equation eq by number num.
Return result"""
res = []
for x in eq:
res += [x*num]
return res
def add_equations(eq1,eq2):
"""Add eq1 and eq2. Return result"""
res = []
for i in range(len(eq1)):
res.append(eq1[i]+eq2[i])
return res
```

**Input:** List of $n$ equations $eqs= [ eqs[0], \ldots, eqs[n-1] ]$ in $n$ variables. For every $i=0..n-1$, $eqs[i]$ is a list of $n+1$ numbers.

**Output:** List of $n$ numbers $[x_0,\ldots,x_n-1]$ that are a solution to the equations.
That is, for every $i=0..n-1$, $eqs[i][0]x_0+...+eqs[i][n-1]x_{n-1} + eqs[i][n] = 0$

**Assumption:** We already know how to solve $n-1$ equations in $n-1$ variables.

**Operation:**

- Ensure that the first coefficient of the first equation is nonzero
- Divide the first equation by its first coefficient to make it one.
- For every $i=1..n-1$, add to the $i^{th}$ equation a copy of the first equation multiplied by $-eqs[i][0]$ so now the first coefficients of equations $1,..,n-1$ is zero.
- Run a solver for the last $n-1$ equations and last $n-1$ variables.
- Use solution to get solution for first variable.

In [24]:

```
def solve100(eqs):
n = len(eqs)
make_first_coeff_nonzero_general(eqs) # make 1st coef of 1st equation nonzero
eqs[0] = multiply_equation(eqs[0],1/eqs[0][0])
# make 1st coef of 1st equation equal 1
for i in range(1,n-1):
eqs[i] = add_equations(eqs[i],multiply_equation(eqs[0],-eqs[i][0])) # zero out first coefficient in eqs 1,2
# make 1st coef of 2nd .. n-th equation equal zero
rest_equations = []
for i in range(1,n):
rest_equations.append(eqs[i][1:n+1])
solutions = solve99(rest_equations)
# solve remainder of equations for remainder of variables
x = - eqs[0][n]
for i in range(1,n):
x -= eqs[0][i]*solutions[i-1]
# solve 1st variable using solution for 2nd and 3rd variable
return [x] + solutions
```

In [23]:

```
def solve99(eqs):
n = len(eqs)
make_first_coeff_nonzero_general(eqs) # make 1st coef of 1st equation nonzero
eqs[0] = multiply_equation(eqs[0],1/eqs[0][0])
# make 1st coef of 1st equation equal 1
for i in range(1,n-1):
eqs[i] = add_equations(eqs[i],multiply_equation(eqs[0],-eqs[i][0])) # zero out first coefficient in eqs 1,2
# make 1st coef of 2nd .. n-th equation equal zero
rest_equations = []
for i in range(1,n):
rest_equations.append(eqs[i][1:n+1])
solutions = solve98(rest_equations)
# solve remainder of equations for remainder of variables
x = - eqs[0][n]
for i in range(1,n):
x -= eqs[0][i]*solutions[i-1]
# solve 1st variable using solution for 2nd and 3rd variable
return [x] + solutions
```

So, we could write functions `solve1`

,`solve2`

,...,`solve10000`

But we can see that they are very similar. The solution for `solve`

$n$ uses `solve`

$n-1$
This suggests that we should use **recursion**

In [22]:

```
def solve(eqs):
n = len(eqs)
make_first_coeff_nonzero_general(eqs) # make 1st coef of 1st equation nonzero
eqs[0] = multiply_equation(eqs[0],1/eqs[0][0])
# make 1st coef of 1st equation equal 1
for i in range(1,n-1):
eqs[i] = add_equations(eqs[i],multiply_equation(eqs[0],-eqs[i][0])) # zero out first coefficient in eqs 1,2
# make 1st coef of 2nd .. n-th equation equal zero
rest_equations = []
for i in range(1,n):
rest_equations.append(eqs[i][1:n+1])
solutions = solve(rest_equations)
# solve remainder of equations for remainder of variables
x = - eqs[0][n]
for i in range(1,n):
x -= eqs[0][i]*solutions[i-1]
# solve 1st variable using solution for 2nd and 3rd variable
return [x] + solutions
```

Let's see if it works:

In [17]:

```
solve([[1,2,3],[4,5,6]])
```

Since it didn't work let's try to see where the problem was: let's print the length of equations so we understand where in the recursion it fails.

In [20]:

```
def solve(eqs):
n = len(eqs)
print "Solving ", n, " equations in ", n, "variables"
make_first_coeff_nonzero_general(eqs) # make 1st coef of 1st equation nonzero
eqs[0] = multiply_equation(eqs[0],1/eqs[0][0])
# make 1st coef of 1st equation equal 1
for i in range(1,n-1):
eqs[i] = add_equations(eqs[i],multiply_equation(eqs[0],-eqs[i][0])) # zero out first coefficient in eqs 1,2
# make 1st coef of 2nd .. n-th equation equal zero
rest_equations = []
for i in range(1,n):
rest_equations.append(eqs[i][1:n+1])
solutions = solve(rest_equations)
# solve remainder of equations for remainder of variables
x = - eqs[0][n]
for i in range(1,n):
x -= eqs[0][i]*solutions[i-1]
# solve 1st variable using solution for 2nd and 3rd variable
return [x] + solutions
```

In [25]:

```
solve([[1,2,3],[4,5,6]])
```

We tried to solve **zero equations in zero variables!**
No wonder we ran into trouble.

The problem is that we need to always have a **base** for the recursion. Just like we need a base for *proofs by inductions* in mathematics.

Here is an updated version:

In [35]:

```
def solve(eqs):
n = len(eqs)
print "Solving ", n, " equations in ", n, "variables"
if n==1:
return [ -eqs[0][1]/eqs[0][0] ]
make_first_coeff_nonzero_general(eqs) # make 1st coef of 1st equation nonzero
eqs[0] = multiply_equation(eqs[0],1/eqs[0][0])
# make 1st coef of 1st equation equal 1
for i in range(1,n):
eqs[i] = add_equations(eqs[i],multiply_equation(eqs[0],-eqs[i][0])) # zero out first coefficient in eqs 1,2
# make 1st coef of 2nd .. n-th equation equal zero
rest_equations = []
for i in range(1,n):
rest_equations.append(eqs[i][1:n+1])
solutions = solve(rest_equations)
# solve remainder of equations for remainder of variables
x = - eqs[0][n]
for i in range(1,n):
x -= eqs[0][i]*solutions[i-1]
# solve 1st variable using solution for 2nd and 3rd variable
return [x] + solutions
```

In [36]:

```
solve([[1,2,3],[4,5,6]])
```

Out[36]:

Let's check if it can solve 25 equations in 25 variables.

In [42]:

```
n= 25
solutions = []
for j in range(n):
solutions.append(j)
```

In [43]:

```
solutions
```

Out[43]:

In [44]:

```
import random
equations = []
for i in range(n):
constant_term = 0
eq = []
for j in range(n):
x = random.randint(-100,+100)
eq.append(x)
constant_term -= x*solutions[j]
eq.append(constant_term)
equations.append(eq)
```

In [49]:

```
my_solutions = solve(equations)
```

In [50]:

```
my_solutions
```

Out[50]:

In [51]:

```
round_solutions = []
for x in my_solutions:
round_solutions.append(round(x,3))
round_solutions
```

Out[51]:

In [53]:

```
[ round(x,3) for x in my_solutions ]
```

Out[53]:

We have now obtained a function `solve`

that solves general linear equations.
This is still not enough however.
Eventually, we want to be able to achieve a function that reads the equations and outputs the solution, like the following:

In [66]:

```
solve_eqs()
```

Note that the equations now are given in arbitrary order, so we will need to **sort** them to make them into the standard format of $ax+by+cy+d=0$ so we can extract the coefficients $[a,b,c,d]$ for our `solve`

function.

So, we will now talk about sorting lists. That is, coming up with a function `sort_list`

In [77]:

```
sort_list([9,8,7,6,5])
```

Out[77]:

In [78]:

```
sort_list([100,3,4,8,7])
```

Out[78]:

In [79]:

```
sort_list([3,1,4,1,5,9,2])
```

Out[79]:

As usual, we will start by writing `sort2`

:

In [7]:

```
def sort2(L):
if L[0]>L[1]:
L[0],L[1] = L[1],L[0]
return L
```

In [8]:

```
sort2([1,2])
```

Out[8]:

In [9]:

```
sort2([2,1])
```

Out[9]:

And then `sort3`

:

In [17]:

```
def sort3(L):
if L[0]>L[1]:
L[0],L[1] = L[1],L[0]
if L[0]>L[2]:
L[0],L[2] = L[2],L[0]
return [L[0]] + sort2(L[1:3])
```

In [18]:

```
sort3([9,5,8])
```

Out[18]:

**Theorem:** For every three numbers $x_0,x_1,x_2$, `sort3(`

$[ x_0,x_1,x_2 ]$ `)`

returns a list $[ x_i,x_j,x_k ]$ such that $x_i \leq x_j \leq x_k$ and $i,j,k$ are distinct numbers in $\{0,1,2\}$.

**Proof:** Suppose we run `sort3(`

$[x_0,x_1,x_2]$`)`

. Let's split into cases:

**Case 1:** $x_0 \leq \min \{ x_1,x_2\}$.
Then both `if`

's don't execute, and we output $[ x_0 ] + $ `sort2(`

$[x_1,x_2]$ `)`

.
Since $x_0$ is the smallest element then this output will be sorted.

**Case 2:** $x_0 > x_1$ but $x_1 \leq x_2$.
Then the first `if`

executes and after it is done, $L[0]=x_1$.
Because $x_1 \leq x_2$, the second `if`

does not execute, and we output $[ x_1 ] + $ `sort2(`

$[x_0,x_2]$ `)`

.
Since $x_1$ is the smallest element then this output will be sorted.

**Case 3:** $x_0 > x_1$ and $x_1 > x_2$.
Then the first `if`

executes, and after it $L[0]=x_1$ and then the second `if`

executes and after it, $L[0]=x_2$. We output $[ x_2 ] + $ `sort2(`

$[x_1,x_0]$ `)`

which will be sorted since $x_2$ is the smallest element.

In [19]:

```
sort3(['cat','apple','dog'])
```

Out[19]:

In [21]:

```
'apple' < 'cat'
```

Out[21]:

In [22]:

```
'car' > 'cat'
```

Out[22]:

Write the function `sort4(L)`

that takes a list of 4 elements and sorts it.
The last line of the function must be `return [L[0]]+sort3(L[1:4])`

In [30]:

```
sort4 = sort_list
```

In [31]:

```
def sort4(L):
#
# your code goes below
#
return [L[0]]+ sort3(L[1:4])
```

Here are some output examples:

In [27]:

```
sort4([7,8,1,2])
```

Out[27]:

In [28]:

```
sort4([1,9,2,3])
```

Out[28]:

In [29]:

```
sort4(['Mickey','Donald','Goofy','Minney'])
```

Out[29]:

Suppose that you are given the function `sort9`

that sorts a list of 9 elements. Write a function `sort10(L)`

that sorts a list L of 10 elements. The last line of the function must be `return [L[0]]+sort9(L[1,4])`

In [32]:

```
# you can use this function as a "black box" but there's no need to read it or understand its code
def sort9(L):
return sorted(L[0:9])
```

In [33]:

```
def sort10(L):
#
# your code goes below
#
return [L[0]] + sort9(L[1:10])
```

In [37]:

```
sort10([0, 1, 6, 10, 9, 3, 3, 9, 9, 5])
```

Out[37]:

In [40]:

```
sort10([15, 16, 19, 13, 5, 1, 7, 19, 12, 4])
```

Out[40]:

In [42]:

```
sort10([5, 9, 13, 8, 15, 17, 20, 9, 10, 8])
```

Out[42]:

Use *recursion* to write the general `sort_list(L)`

function that works for lists of *any* length.
Again, the last line of your code must be a recursive call to `sort_list`

of the form `return [L[0]]+sort_list(L[1:len(L)])`

In [43]:

```
def sort_list(L):
#
# your code goes below
#
return [L[0]] + sort_list(L[1:len(L)])
```

The array below contains the names of all the students that were registered to the course. Compute an array that contains these students in alphabetical order by first name. Use the function you wrote to sort it by first name.

In [56]:

```
L = ['abinet mulugeta', 'urgie huseien', 'yonatan wosenyeleh', 'amanuel asfaw', 'tibebu solomon', 'hailegbrel wudneh', 'gatluk chuol', 'elsabet buzuneh', 'eden ketema', 'maeden seid', 'mikyas legese', 'meskerem birhanu demeke', 'kumneger worku', 'shambel abate', 'hailmeskel shimeles', 'tsega hailu', 'dawit fikeru', 'asmare habitamo', 'zelalem ades', 'betelehem eshetu', 'yosef tadiwos', 'haymanot gidena', 'henock mersha', 'binyam kidane', 'mohammed nur', 'bethelehem walelegn', 'lewi mekonnen', 'wondimu yohanes', 'hodo mukitar', 'yonas adugna', 'tigabu gebrecherkos', 'nardos gesese', 'mohammed nur', 'abdurezak temam', 'shambel elena', 'adem mohamed', 'zakira tebarek', 'lidya gegnaw', 'knesa desta', 'ibrahim ahmed', 'betlehem desalegn', 'adonay geremew', 'kalkidan muluneh', 'haile gebreselasie', 'eden tekilu tilahun', 'ayantu aleneh', 'yosef nosha', 'mebrihity girmay', 'finet hailu', 'elisa feloh', 'bezawit gebremariam', 'nigusu terefe', 'amina bedrie', 'kiflom leuel', 'hana tariku', 'nejat beshir', 'mesfen tamiru', 'shafi abdi', 'kelbesa ambesa', 'abrham tuna', 'daniel hagos', 'yordanos jemberu', 'aman musa', 'habene abdi', 'kawuser jemal', 'tariku erina', 'mesigina gebretsadik', 'yetnayet birhanu', 'semer abrar', 'nur ahmed', 'eman hasen', 'natol gizaw', 'banchayehu asrat', 'hilina thewodros', 'hasen ali', 'mebrihatu lebelo', 'yosef enawgaw', 'nesera teyib', 'mekdes muluneh', 'surafel sewutu', 'mentesenot tefera']
```

Sort the array above in *reverse alphabetical order* by first name (so that L[0] will be the name that is last in alphabetical order and L[80] will be the name that is first)

Sort the array in alphabetical order by **last name**.