#!/usr/bin/env python
# coding: utf-8
# # A smarter sorting algorithm
# Our sorting algorithm had the following general operation on a list of size $n$:
#
# 1. Find the minimum element and put it at the beginning.
# 2. Sort the last $n-1$ elements.
#
# As we saw the number of steps looked something like this:
# We will show the __merge sort__ algorithm that does the following on a list of size $n$:
#
# 1. Sort the first $n/2$ elements to get a list $L1$ and the last $n/2$ elements to get a list $L2$.
# 2. Merge the two lists together to one sorted list.
#
# It turns out that the number of steps it takes looks like the following:
# In[25]:
# illustration of number of steps sorting 20 numbers in selection sort vs merge sort
****************************** ******************************
***************************** ***************
**************************** *******
*************************** ***
************************** *
************************* *
************************ ***
*********************** *
********************** *
********************* *******
******************** ***
******************* *
****************** *
***************** ***
**************** *
*************** *
************** ***************
************* *******
************ ***
*********** *
********** *
********* ***
******** *
******* *
****** *******
***** ***
**** *
*** *
** ***
* *
*
# In[27]:
# comparison of running time of selection sort and merge sort
..............................plot_steps: False
1.000 micro-seconds per step
..............................plot_steps: True
0.611 micro-seconds per step
#
# ## Merge sort
# In[11]:
#With Recursion
def merge_lists(L1,L2):
i=0
j=0
res = []
while i1:
new_lists = []
if len(lists) % 2:
lists += []
for i in range(0,len(lists)-1,2):
new_lists += merge_lists(lists[i],lists[i+1])
lists = new_lists
return lists[0]
# In[15]:
merge_sort_nr([3,1,4,1,5,9,2])
# ## De-recursion
# Many times, recursion gives us a clean way to __think__ about problems and __solve__ them.
# But a recursive program is often __slower__ than non recursive version.
# So sometimes, after finding a recursive solution, we want to transform it to a non recursive solution.
# ## Remember Selection sort
# In[40]:
def find_min_index(L):
current_index = 0
current_min = L[0]
for j in range(1,len(L)):
if current_min > L[j]:
current_min = L[j]
current_index = j
return current_index
# In[ ]:
def selection_sort(L):
if len(L)<=1:
return L # a one-element list is always sorted
min_idx = find_min_index(L) #non-recursive helper function
L[0], L[min_idx] = L[min_idx], L[0]
return [L[0]] + sort(L[1:len(L)])
# In[ ]:
def selection_sort_nr(L):
for i in range(len(L)):
min_idx = i+find_min_index(L[i:])
L[i], L[min_idx] = L[min_idx], L[i]
return L
# In[41]:
selection_sort_nr([3,1,4,1,5,9,2])
# ## Remember binary search
# In[85]:
#write a function bin_search
#which takes in a sorted list L and an item i
#and returns the index of i if it exists & -1 if not.
#don't use recursion
def binarysearch(li,element):
top=len(li) #li=[0,1,2,3,4,5], element=1
bottom=0
while top>bottom: #top=6, bottom=0
middle=(top+bottom)//2 #middle 3
if element==li[middle]:
return middle
elif element < li[middle]:
top=middle
elif element > li[middle]:
bottom=middle
return -1
binarysearch([0,1,2,3,4,5],4)
# In[34]:
#With recursion
def bin_search_1(L,item):
n = len(L) #L=[1,2,3,4,5], m=2
if n==0:
return -1
m = n//2
if L[m]==item:
return m
#print(m)
if L[m]>item:
return bin_search_1(L[:m],item) #Search left half
res = bin_search_1(L[m+1:],item) #Search right half #what happens if I don't have the following 3 lines?
if res==-1:
return -1
return m+1+res
bin_search_1([1,2,3,4,5], 4)
# In[65]:
#Without recursion
def bin_search_nr(L,item):
left = 0
right= len(L)
while right-left >0:
#print('*')
m = int((left+right)/2)
#print(m) #what would this print?
if L[m]==item:
return m
if L[m]>item: #Search left half
right = m
else:
left = m+1 #Search right half
return -1
# In[71]:
L=range(10) #=[0,1,2,3,4,5,6,7,8,9]
#print(bin_search_nr(L,2))
# # Lab work
# ### Exercise 1
#
# Write a function ```sort4``` that sorts a list of 4 elements. The function should make two calls to ```sort2```
# In[29]:
def sort4(L):
L_first_sorted = sort2(L[0:2])
L_last_sorted = sort2(L[2:4])
#
# do something to return a sorted list
#
# In[ ]:
sort4([10,2,5,7])
# ### Exercise 2
#
# Write a function ```merge_lists``` that takes two sorted lists L1 and L2 and returns a sorted list that of length ```len(L1)+len(L2)``` that contains all their elements.
# ### Exercise 3
#
# Write a function ```sort32``` that sorts a list of 32 elements. The function should make two recursive calls to a provided function ```sort16```
# In[30]:
def sort32(L):
L_first_sorted = sort16(L[0:4])
L_last_sorted = sort16(L[4:8])
#
# do something to return a sorted list
#
# In[31]:
sort16 = sorted
# In[32]:
sort32([32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1])
# ### Exercise 4
#
# Write a function ```merge_sort``` that will sort a list of _any_ size. The function should make two recursive calls to itself
# In[34]:
def merge_sort(L):
#
# do something here
#
L_first_sorted = merge_sort(L[0:int(len(L)/2)])
L_last_sorted = merge_sort(L[int(len(L)/2):len(L)])
#
# do something to return a sorted list
# In[ ]:
merge_sort([78, 39, 50, 43, 3, 30, 34, 75, 33, 7, 30, 71, 76, 44, 27, 4, 68, 21, 51, 78, 11, 53, 71, 60, 64, 9, 28, 63, 55, 34, 44, 52, 28, 52, 43, 44, 4, 41, 40, 17])
# ### Exercise 5
# Use a stopwatch to compare selection sort and merge sort on random lists of 4000 numbers.
# Run each one of them 10 times andrecord the average time.
#
# _(if your computer crashes then you can use smaller lists, you can also use the %timeit option as below)_
# In[36]:
def gen_random_list(n):
return [random.randint(0,2*n) for i in range(n)]
# In[37]:
# run this code so that Python allows you to run the sorts on inputs larger than 100
import sys
sys.setrecursionlimit(10**6)
# In[ ]:
get_ipython().run_line_magic('', 'timeit merge_sort(gen_random_list(4000));')
# In[ ]:
get_ipython().run_line_magic('', 'timeit sort(gen_random_list(4000));')
# your selection sort function from the previous labwork or from the lecture
# ### Exercise 6 (bonus)
#
# Write a function ```cannon_ball_no_gravity(angle,speed,t)``` that on input an angle ```angle``` between $0$ and $90$ and a speed ```speed``` in meters per second, and time $t$ in seconds, returns two values ```height,distance``` .
#
# The value ```height``` will be which are the height in meters that a cannon ball shot at angle $angle$ from the ground and speed $speed$ would be at after $t$ seconds if there is no gravity.
#
# The value ```distance``` will be the distance in meters that the projection of this ball on the ground would be from the initial point after $t$ seconds.
#
# Write a function ```cannon_ball_earth(angle,speed,t)``` that does the same thing if the ball is shot on earth and takes gravity into account.
#
# (You don't need to worry about air resistance nor about whether height becomes negative).
#
# You can use the following helper functions:
# In[39]:
import math
def sine(angle):
return math.sin((angle/360.0)*2*math.pi)
def cosie(angle):
return math.cos((angle/360.0)*2*math.pi)