#!/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)