""" Code for reading "CS1110: Worked examples regarding loops and invariants" This code includes ASCII-art visualization of the process. Authors: D. Gries, L. Lee, S. Marschner, W. White Date: November 1, 2017 (Python 3 Version) """ import inspect import cornell def min_p1(b, h, k, verbose=False): """Returns: An int i such that b[i] is the minimum value in b[h..k] This is an illustration of min with invariant P1 from the reading. If verbose is True, it prints out incremental output showing the internal state of variables at each loop iteration. Parameter b: The list to search Precondition: b is a list of numbers Parameter h: The starting position in the list Precondition: h an int, 0 <= h <= k < len(b) Parameter k: The ending position in the list Precondition: k an int, 0 <= h <= k < len(b) Parameter verbose: Whether to display any animation Precondition: verbose is a bool (False by default) """ # Inv says b[t+1..k] are '???'. # Start: b[h+1..k] '???'. End: b[k+1..k] '???'. i = h; t = h if verbose: print('Displaying '+_whoami()+' loop\n') d = {'h':h,'k':k,'i':i,'t':t} _print_indices(b,d,2) while (t < k): # still have '???' to process # peek at next '???' if b[t+1] < b[i]: i = t+1 # location of smaller value t = t+1 # update knowledge, progress towards termination else: # the next unknown is at least as big as the known min t = t+1 # update knowledge, progress towards termination if verbose: d = {'h':h,'k':k,'i':i,'t':t} _print_indices(b,d,2) if verbose: print('Done with ' + _whoami() + ' loop\n') return i def min_p2(b, h, k, verbose=False): """Returns: An int i such that b[i] is the minimum value in b[h..k] This is an illustration of min with invariant P2 from the reading. If verbose is True, it prints out incremental output showing the internal state of variables at each loop iteration. Parameter b: The list to search Precondition: b is a list of numbers Parameter h: The starting position in the list Precondition: h an int, 0 <= h <= k < len(b) Parameter k: The ending position in the list Precondition: k an int, 0 <= h <= k < len(b) Parameter verbose: Whether to display any animation Precondition: verbose is a bool (False by default) """ # Inv says b[s..k] are '???'. # Start: b[h+1..k] '???'; End: b[k+1..k] '???'. i = h; s = h+1 if verbose: print('Displaying '+_whoami()+' loop\n') d = {'h':h,'k':k,'i':i,'s':s} _print_indices(b,d,2) while (s < k+1): # still have '???' to process # peek at next '???' if b[s] < b[i]: i = s # location of smaller value s = s+1 # update knowledge, progress towards termination else: # the next unknown is at least as big as the known min s = s+1 # update knowledge, progress towards termination if verbose: d = {'h':h,'k':k,'i':i,'s':s} _print_indices(b,d,2) if verbose: print('Done with ' + _whoami() + ' loop\n') return i def min_p3(b, h, k, verbose=False): """Returns: An int i such that b[i] is the minimum value in b[h..k] This is an illustration of min with invariant P3 from the reading. If verbose is True, it prints out incremental output showing the internal state of variables at each loop iteration. Parameter b: The list to search Precondition: b is a list of numbers Parameter h: The starting position in the list Precondition: h an int, 0 <= h <= k < len(b) Parameter k: The ending position in the list Precondition: k an int, 0 <= h <= k < len(b) Parameter verbose: Whether to display any animation Precondition: verbose is a bool (False by default) """ # Inv says b[h..r-1] are '???'. # Start: b[h..k-1] '???'; End: b[h..h-1] '???'. i = k; r = k if verbose: print('Displaying '+_whoami()+' loop\n') d = {'h':h,'k':k,'i':i,'r':r} _print_indices(b,d,2) while (r > h): # still have '???' to process # peek at next '???' if b[r-1] < b[i]: i = r-1 # location of smaller value r = r-1 # update knowledge, progress towards termination else: # the next unknown is at least as big as the known min r = r-1 # update knowledge, progress towards termination if verbose: d = {'h':h,'k':k,'i':i,'r':r} _print_indices(b,d,2) if verbose: print('Done with ' + _whoami() + ' loop\n') return i def part_3dot1(b, verbose=False): """Returns: An int k such that b[0..k] <= 6 and b[k+1] > 6. This function will rearrange b to make the existence of such a k possible. If verbose is True, it prints out incremental output showing the internal state of variables at each loop iteration. Parameter b: The list to partition Precondition: b is a list of numbers Parameter verbose: Whether to display any animation Precondition: verbose is a bool (False by default) """ # Inv says b[h+1..k] are '?'. # Start: b[0..len(b)-1] '?'; End: b[h+1..h] '?'. h = -1; k = len(b)-1 if verbose: print('Displaying '+_whoami()+' loop\n') d = {'h':h,'k':k} _print_indices(b,d,2) while k > h: # still have '?' to process # peek at next '?' if b[h+1] <= 6: h = h+1 # update knowledge, progress towards termination else: b[h+1], b[k] = b[k], b[h+1] # swap to put big val with right-hand brethren k = k-1 # update knowledge, progress towards termination if verbose: d = {'h':h,'k':k} _print_indices(b,d,2) if verbose: print('Done with ' + _whoami() + ' loop\n') return k def part_4dot1(b, h, k, verbose=False): """ Returns: An int j such that b[h..j-1] <= b[j] <= b[j+1..k]. If x = b[h] at the start of the function, this function guarantees that x == b[j]. It rearranged the list so that j is possible. If verbose is True, it prints out incremental output showing the internal state of variables at each loop iteration. Parameter b: The list to search Precondition: b is a list of numbers Parameter h: The starting position in the list Precondition: h an int, 0 <= h <= k < len(b) Parameter k: The ending position in the list Precondition: k an int, 0 <= h <= k < len(b) Parameter verbose: Whether to display any animation Precondition: verbose is a bool (False by default) """ # Inv: b[j] is pivot, b[j+1..q] '?'. # Start: b[h+1..k] '?'; end: b[j+1..j] '?' j=h; q=k if verbose: print('Displaying '+_whoami()+' loop\n') d = {'h':h,'k':k,'j':j,'q':q} _print_indices(b,d,2) while q > j: # still have '?' to process # peek at next '?' if b[j+1] <= b[j]: b[j+1], b[j] = b[j], b[j+1] j = j+1 # pivot value has moved else: b[j+1], b[q] = b[q], b[j+1] # swap to put big val with right-hand brethren q = q-1 # update knowledge, progress towards termination if verbose: d = {'h':h,'k':k,'j':j,'q':q} _print_indices(b,d,2) if verbose: print('Done with ' + _whoami() + ' loop\n') return j # TEST FUNCTIONS def test_min(): """ Tests the three min functions on various lists """ # Change the verbose settings to False if you don't want the 'animation' verbose = True lists = [[1,5,5,2,9,7,8], [1,2], [1,5,5,2,1,7,1]] for fn in [min_p1,min_p2,min_p3]: for j in range(len(lists)): cl = lists[j][:] # copy of current list, for short end = len(cl)-1 # last position in list in question # Perform the test m1 = min(cl[1:end+1]) m2 = cl[fn(cl,1,end,verbose=verbose)] cornell.assert_equals(m1,m2) if verbose: input('Press any key') def test_part(): """ Tests the two partition functions on various lists """ # Change the verbose settings to False if you don't want the 'animation' verbose = True # Test first partition lists = [[1,5,5,2,9,7,8], [1,2], [1,5,5,2,1,7,1], []] for j in range(len(lists)): cl = lists[j][:] # copy of current list k = part_3dot1(cl, verbose=verbose) # Test the contents of the list are partitioned for m in range(k+1): cornell.assert_true(cl[m] <=6) for m in range(k+1,len(cl)): cornell.assert_true(cl[m] > 6) if verbose: input('Press any key') # Test second partition list1 = [4, 6, 1, 5, 0, 7, 3, 1, 4, 9, 0] list2 = [5,8,7,5,7,6,4,0,7] lists = [list1, list2, [7,1], [7,8]] for n in range(len(lists)): for h in [0,1]: cl = lists[n][:] # copy of current list, for short j = part_4dot1(cl,h,len(cl)-1, verbose=True) # Test the contents of the list are partitioned cornell.assert_equals(cl[j], lists[n][h]) for k in range(h,j): # verify first partition range cornell.assert_true(cl[k] <= cl[j]) for k in range(j+1, len(cl)): # verify second partition range cornell.assert_true(cl[k] >= cl[j]) if verbose: input('Press any key') # HELPERS def _whoami(): """ Returns: name of function it is called in """ return inspect.stack()[1][3] def _print_indices(b,d,offset=0): """ Prints b with the indices above it, as in the invariant diagrams. The indices are provided by the dictionary d, which maps strings (the index name) to ints (the index position). The indices are each drawn on their own line. This allows us to visualize the case where two indices have the same value. Parameter b: The list to annotate Precondition: b is a list Parameter d: The dictionary of indices. Precondition: d maps strings to ints. The values of d are in 0..len(b) Parameter offset: The amount to indent the illustration (OPTIONAL) Precondition: offset is an int >= 0 """ # Create an index template s = str(b) p = s.split(',') line = [' '*(offset+len(p[0])-1)] for x in p[1:]: line.append('.') line.append(' '*len(x)) line[-1] = line[-1][:-1] line.append('.') # Just in case we go beyond line.append(' ') line.append(' ') # print out indices for k in d: line[2*d[k]+1] = k print(''.join(line)) line[2*d[k]+1] = '.' # print string print((' '*offset)+s) print() if __name__ == '__main__': test_min() test_part() print('Loop invariants completed with no errors')