mirror of
				https://github.com/python/cpython.git
				synced 2025-10-30 21:21:22 +00:00 
			
		
		
		
	
		
			
	
	
		
			635 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			635 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
|   | #! /usr/bin/env python | ||
|  | 
 | ||
|  | """Sorting algorithms visualizer using Tkinter.
 | ||
|  | 
 | ||
|  | This module is comprised of three ``components'': | ||
|  | 
 | ||
|  | - an array visualizer with methods that implement basic sorting | ||
|  | operations (compare, swap) as well as methods for ``annotating'' the | ||
|  | sorting algorithm (e.g. to show the pivot element); | ||
|  | 
 | ||
|  | - a number of sorting algorithms (currently quicksort, insertion sort, | ||
|  | selection sort and bubble sort, as well as a randomization function), | ||
|  | all using the array visualizer for its basic operations and with calls | ||
|  | to its annotation methods; | ||
|  | 
 | ||
|  | - and a ``driver'' class which can be used as a Grail applet or as a | ||
|  | stand-alone application. | ||
|  | 
 | ||
|  | """
 | ||
|  | 
 | ||
|  | 
 | ||
|  | from Tkinter import * | ||
|  | from Canvas import Line, Rectangle | ||
|  | import random | ||
|  | 
 | ||
|  | 
 | ||
|  | XGRID = 10 | ||
|  | YGRID = 10 | ||
|  | WIDTH = 6 | ||
|  | 
 | ||
|  | 
 | ||
|  | class Array: | ||
|  | 
 | ||
|  |     def __init__(self, master, data=None): | ||
|  | 	self.master = master | ||
|  | 	self.frame = Frame(self.master) | ||
|  | 	self.frame.pack(fill=X) | ||
|  | 	self.label = Label(self.frame) | ||
|  | 	self.label.pack() | ||
|  | 	self.canvas = Canvas(self.frame) | ||
|  | 	self.canvas.pack() | ||
|  | 	self.report = Label(self.frame) | ||
|  | 	self.report.pack() | ||
|  | 	self.left = Line(self.canvas, 0, 0, 0, 0) | ||
|  | 	self.right = Line(self.canvas, 0, 0, 0, 0) | ||
|  | 	self.pivot = Line(self.canvas, 0, 0, 0, 0) | ||
|  | 	self.items = [] | ||
|  | 	self.size = self.maxvalue = 0 | ||
|  | 	if data: | ||
|  | 	    self.setdata(data) | ||
|  | 
 | ||
|  |     def setdata(self, data): | ||
|  | 	olditems = self.items | ||
|  | 	self.items = [] | ||
|  | 	for item in olditems: | ||
|  | 	    item.delete() | ||
|  | 	self.size = len(data) | ||
|  | 	self.maxvalue = max(data) | ||
|  | 	self.canvas.config(width=(self.size+1)*XGRID, | ||
|  | 			   height=(self.maxvalue+1)*YGRID) | ||
|  | 	for i in range(self.size): | ||
|  | 	    self.items.append(ArrayItem(self, i, data[i])) | ||
|  | 	self.reset("Sort demo, size %d" % self.size) | ||
|  | 
 | ||
|  |     speed = "normal" | ||
|  | 
 | ||
|  |     def setspeed(self, speed): | ||
|  | 	self.speed = speed | ||
|  | 
 | ||
|  |     def destroy(self): | ||
|  | 	self.frame.destroy() | ||
|  | 
 | ||
|  |     in_mainloop = 0 | ||
|  |     stop_mainloop = 0 | ||
|  | 
 | ||
|  |     def cancel(self): | ||
|  | 	self.stop_mainloop = 1 | ||
|  | 	if self.in_mainloop: | ||
|  | 	    self.master.quit() | ||
|  | 
 | ||
|  |     def step(self): | ||
|  | 	if self.in_mainloop: | ||
|  | 	    self.master.quit() | ||
|  | 
 | ||
|  |     Cancelled = "Array.Cancelled"	# Exception | ||
|  | 
 | ||
|  |     def wait(self, msecs): | ||
|  | 	if self.speed == "fastest": | ||
|  | 	    msecs = 0 | ||
|  | 	elif self.speed == "fast": | ||
|  | 	    msecs = msecs/10 | ||
|  | 	elif self.speed == "single-step": | ||
|  | 	    msecs = 1000000000 | ||
|  | 	if not self.stop_mainloop: | ||
|  | 	    self.master.update() | ||
|  | 	    id = self.master.after(msecs, self.master.quit) | ||
|  | 	    self.in_mainloop = 1 | ||
|  | 	    self.master.mainloop() | ||
|  | 	    self.master.after_cancel(id) | ||
|  | 	    self.in_mainloop = 0 | ||
|  | 	if self.stop_mainloop: | ||
|  | 	    self.stop_mainloop = 0 | ||
|  | 	    self.message("Cancelled") | ||
|  | 	    raise Array.Cancelled | ||
|  | 
 | ||
|  |     def getsize(self): | ||
|  | 	return self.size | ||
|  | 
 | ||
|  |     def show_partition(self, first, last): | ||
|  | 	for i in range(self.size): | ||
|  | 	    item = self.items[i] | ||
|  | 	    if first <= i < last: | ||
|  | 		item.item.config(fill='red') | ||
|  | 	    else: | ||
|  | 		item.item.config(fill='orange') | ||
|  | 	self.hide_left_right_pivot() | ||
|  | 
 | ||
|  |     def hide_partition(self): | ||
|  | 	for i in range(self.size): | ||
|  | 	    item = self.items[i] | ||
|  | 	    item.item.config(fill='red') | ||
|  | 	self.hide_left_right_pivot() | ||
|  | 
 | ||
|  |     def show_left(self, left): | ||
|  | 	if not 0 <= left < self.size: | ||
|  | 	    self.hide_left() | ||
|  | 	    return | ||
|  | 	x1, y1, x2, y2 = self.items[left].position() | ||
|  | ##	top, bot = HIRO | ||
|  | 	self.left.coords([(x1-2, 0), (x1-2, 9999)]) | ||
|  | 	self.master.update() | ||
|  | 
 | ||
|  |     def show_right(self, right): | ||
|  | 	if not 0 <= right < self.size: | ||
|  | 	    self.hide_right() | ||
|  | 	    return | ||
|  | 	x1, y1, x2, y2 = self.items[right].position() | ||
|  | 	self.right.coords(((x2+2, 0), (x2+2, 9999))) | ||
|  | 	self.master.update() | ||
|  | 
 | ||
|  |     def hide_left_right_pivot(self): | ||
|  | 	self.hide_left() | ||
|  | 	self.hide_right() | ||
|  | 	self.hide_pivot() | ||
|  | 
 | ||
|  |     def hide_left(self): | ||
|  | 	self.left.coords(((0, 0), (0, 0))) | ||
|  | 
 | ||
|  |     def hide_right(self): | ||
|  | 	self.right.coords(((0, 0), (0, 0))) | ||
|  | 
 | ||
|  |     def show_pivot(self, pivot): | ||
|  | 	x1, y1, x2, y2 = self.items[pivot].position() | ||
|  | 	self.pivot.coords(((0, y1-2), (9999, y1-2))) | ||
|  | 
 | ||
|  |     def hide_pivot(self): | ||
|  | 	self.pivot.coords(((0, 0), (0, 0))) | ||
|  | 
 | ||
|  |     def swap(self, i, j): | ||
|  | 	if i == j: return | ||
|  | 	self.countswap() | ||
|  | 	item = self.items[i] | ||
|  | 	other = self.items[j] | ||
|  | 	self.items[i], self.items[j] = other, item | ||
|  | 	item.swapwith(other) | ||
|  | 
 | ||
|  |     def compare(self, i, j): | ||
|  | 	self.countcompare() | ||
|  | 	item = self.items[i] | ||
|  | 	other = self.items[j] | ||
|  | 	return item.compareto(other) | ||
|  | 
 | ||
|  |     def reset(self, msg): | ||
|  | 	self.ncompares = 0 | ||
|  | 	self.nswaps = 0 | ||
|  | 	self.message(msg) | ||
|  | 	self.updatereport() | ||
|  | 	self.hide_partition() | ||
|  | 
 | ||
|  |     def message(self, msg): | ||
|  | 	self.label.config(text=msg) | ||
|  | 
 | ||
|  |     def countswap(self): | ||
|  | 	self.nswaps = self.nswaps + 1 | ||
|  | 	self.updatereport() | ||
|  | 
 | ||
|  |     def countcompare(self): | ||
|  | 	self.ncompares = self.ncompares + 1 | ||
|  | 	self.updatereport() | ||
|  | 
 | ||
|  |     def updatereport(self): | ||
|  | 	text = "%d cmps, %d swaps" % (self.ncompares, self.nswaps) | ||
|  | 	self.report.config(text=text) | ||
|  | 
 | ||
|  | 
 | ||
|  | class ArrayItem: | ||
|  | 
 | ||
|  |     def __init__(self, array, index, value): | ||
|  | 	self.array = array | ||
|  | 	self.index = index | ||
|  | 	self.value = value | ||
|  | 	x1, y1, x2, y2 = self.position() | ||
|  | 	self.item = Rectangle(array.canvas, x1, y1, x2, y2, | ||
|  | 			      fill='red', outline='black', width=1) | ||
|  | 	self.item.bind('<Button-1>', self.mouse_down) | ||
|  | 	self.item.bind('<Button1-Motion>', self.mouse_move) | ||
|  | 	self.item.bind('<ButtonRelease-1>', self.mouse_up) | ||
|  | 
 | ||
|  |     def delete(self): | ||
|  | 	item = self.item | ||
|  | 	self.array = None | ||
|  | 	self.item = None | ||
|  | 	item.delete() | ||
|  | 
 | ||
|  |     def mouse_down(self, event): | ||
|  | 	self.lastx = event.x | ||
|  | 	self.lasty = event.y | ||
|  | 	self.origx = event.x | ||
|  | 	self.origy = event.y | ||
|  | 	self.item.tkraise() | ||
|  |     	 | ||
|  |     def mouse_move(self, event): | ||
|  | 	self.item.move(event.x - self.lastx, event.y - self.lasty) | ||
|  | 	self.lastx = event.x | ||
|  | 	self.lasty = event.y | ||
|  | 
 | ||
|  |     def mouse_up(self, event): | ||
|  | 	i = self.nearestindex(event.x) | ||
|  | 	if i >= self.array.getsize(): | ||
|  | 	    i = self.array.getsize() - 1 | ||
|  | 	if i < 0: | ||
|  | 	    i = 0 | ||
|  | 	other = self.array.items[i] | ||
|  | 	here = self.index | ||
|  | 	self.array.items[here], self.array.items[i] = other, self | ||
|  | 	self.index = i | ||
|  | 	x1, y1, x2, y2 = self.position() | ||
|  | 	self.item.coords(((x1, y1), (x2, y2))) | ||
|  | 	other.setindex(here) | ||
|  | 
 | ||
|  |     def setindex(self, index): | ||
|  | 	nsteps = steps(self.index, index) | ||
|  | 	if not nsteps: return | ||
|  | 	if self.array.speed == "fastest": | ||
|  | 	    nsteps = 0 | ||
|  | 	oldpts = self.position() | ||
|  | 	self.index = index | ||
|  | 	newpts = self.position() | ||
|  | 	trajectory = interpolate(oldpts, newpts, nsteps) | ||
|  | 	self.item.tkraise() | ||
|  | 	for pts in trajectory: | ||
|  | 	    self.item.coords((pts[:2], pts[2:])) | ||
|  | 	    self.array.wait(50) | ||
|  | 
 | ||
|  |     def swapwith(self, other): | ||
|  | 	nsteps = steps(self.index, other.index) | ||
|  | 	if not nsteps: return | ||
|  | 	if self.array.speed == "fastest": | ||
|  | 	    nsteps = 0 | ||
|  | 	myoldpts = self.position() | ||
|  | 	otheroldpts = other.position() | ||
|  | 	self.index, other.index = other.index, self.index | ||
|  | 	mynewpts = self.position() | ||
|  | 	othernewpts = other.position() | ||
|  | 	myfill = self.item['fill'] | ||
|  | 	otherfill = other.item['fill'] | ||
|  | 	self.item.config(fill='green') | ||
|  | 	other.item.config(fill='yellow') | ||
|  | 	self.array.master.update() | ||
|  | 	if self.array.speed == "single-step": | ||
|  | 	    self.item.coords((mynewpts[:2], mynewpts[2:])) | ||
|  | 	    other.item.coords((othernewpts[:2], othernewpts[2:])) | ||
|  | 	    self.array.master.update() | ||
|  | 	    self.item.config(fill=myfill) | ||
|  | 	    other.item.config(fill=otherfill) | ||
|  | 	    self.array.wait(0) | ||
|  | 	    return | ||
|  | 	mytrajectory = interpolate(myoldpts, mynewpts, nsteps) | ||
|  | 	othertrajectory = interpolate(otheroldpts, othernewpts, nsteps) | ||
|  | 	if self.value > other.value: | ||
|  | 	    self.item.tkraise() | ||
|  | 	    other.item.tkraise() | ||
|  | 	else: | ||
|  | 	    other.item.tkraise() | ||
|  | 	    self.item.tkraise() | ||
|  | 	try: | ||
|  | 	    for i in range(len(mytrajectory)): | ||
|  | 		mypts = mytrajectory[i] | ||
|  | 		otherpts = othertrajectory[i] | ||
|  | 		self.item.coords((mypts[:2], mypts[2:])) | ||
|  | 		other.item.coords((otherpts[:2], otherpts[2:])) | ||
|  | 		self.array.wait(50) | ||
|  | 	finally: | ||
|  | 	    mypts = mytrajectory[-1] | ||
|  | 	    otherpts = othertrajectory[-1] | ||
|  | 	    self.item.coords((mypts[:2], mypts[2:])) | ||
|  | 	    other.item.coords((otherpts[:2], otherpts[2:])) | ||
|  | 	    self.item.config(fill=myfill) | ||
|  | 	    other.item.config(fill=otherfill) | ||
|  | 
 | ||
|  |     def compareto(self, other): | ||
|  | 	myfill = self.item['fill'] | ||
|  | 	otherfill = other.item['fill'] | ||
|  | 	outcome = cmp(self.value, other.value) | ||
|  | 	if outcome < 0: | ||
|  | 	    myflash = 'white' | ||
|  | 	    otherflash = 'black' | ||
|  | 	elif outcome > 0: | ||
|  | 	    myflash = 'black' | ||
|  | 	    otherflash = 'white' | ||
|  | 	else: | ||
|  | 	    myflash = otherflash = 'grey' | ||
|  | 	try: | ||
|  | 	    self.item.config(fill=myflash) | ||
|  | 	    other.item.config(fill=otherflash) | ||
|  | 	    self.array.wait(500) | ||
|  | 	finally: | ||
|  | 	    self.item.config(fill=myfill) | ||
|  | 	    other.item.config(fill=otherfill) | ||
|  | 	return outcome | ||
|  | 
 | ||
|  |     def position(self): | ||
|  | 	x1 = (self.index+1)*XGRID - WIDTH/2 | ||
|  | 	x2 = x1+WIDTH | ||
|  | 	y2 = (self.array.maxvalue+1)*YGRID | ||
|  | 	y1 = y2 - (self.value)*YGRID | ||
|  | 	return x1, y1, x2, y2 | ||
|  | 
 | ||
|  |     def nearestindex(self, x): | ||
|  | 	return int(round(float(x)/XGRID)) - 1 | ||
|  | 
 | ||
|  | 
 | ||
|  | # Subroutines that don't need an object | ||
|  | 
 | ||
|  | def steps(here, there): | ||
|  |     nsteps = abs(here - there) | ||
|  |     if nsteps <= 3: | ||
|  | 	nsteps = nsteps * 3 | ||
|  |     elif nsteps <= 5: | ||
|  | 	nsteps = nsteps * 2 | ||
|  |     elif nsteps > 10: | ||
|  | 	nsteps = 10 | ||
|  |     return nsteps | ||
|  | 
 | ||
|  | def interpolate(oldpts, newpts, n): | ||
|  |     if len(oldpts) != len(newpts): | ||
|  | 	raise ValueError, "can't interpolate arrays of different length" | ||
|  |     pts = [0]*len(oldpts) | ||
|  |     res = [tuple(oldpts)] | ||
|  |     for i in range(1, n): | ||
|  | 	for k in range(len(pts)): | ||
|  | 	    pts[k] = oldpts[k] + (newpts[k] - oldpts[k])*i/n | ||
|  | 	res.append(tuple(pts)) | ||
|  |     res.append(tuple(newpts)) | ||
|  |     return res | ||
|  | 
 | ||
|  | 
 | ||
|  | # Various (un)sorting algorithms | ||
|  | 
 | ||
|  | def uniform(array): | ||
|  |     size = array.getsize() | ||
|  |     array.setdata([(size+1)/2] * size) | ||
|  |     array.reset("Uniform data, size %d" % size) | ||
|  | 
 | ||
|  | def distinct(array): | ||
|  |     size = array.getsize() | ||
|  |     array.setdata(range(1, size+1)) | ||
|  |     array.reset("Distinct data, size %d" % size) | ||
|  | 
 | ||
|  | def randomize(array): | ||
|  |     array.reset("Randomizing") | ||
|  |     n = array.getsize() | ||
|  |     for i in range(n): | ||
|  | 	j = random.randint(0, n-1) | ||
|  | 	array.swap(i, j) | ||
|  |     array.message("Randomized") | ||
|  | 
 | ||
|  | def insertionsort(array): | ||
|  |     size = array.getsize() | ||
|  |     array.reset("Insertion sort") | ||
|  |     for i in range(1, size): | ||
|  | 	j = i-1 | ||
|  | 	while j >= 0: | ||
|  | 	    if array.compare(j, j+1) <= 0: | ||
|  | 		break | ||
|  | 	    array.swap(j, j+1) | ||
|  | 	    j = j-1 | ||
|  |     array.message("Sorted") | ||
|  | 
 | ||
|  | def selectionsort(array): | ||
|  |     size = array.getsize() | ||
|  |     array.reset("Selection sort") | ||
|  |     try: | ||
|  | 	for i in range(size): | ||
|  | 	    array.show_partition(i, size) | ||
|  | 	    for j in range(i+1, size): | ||
|  | 		if array.compare(i, j) > 0: | ||
|  | 		    array.swap(i, j) | ||
|  | 	array.message("Sorted") | ||
|  |     finally: | ||
|  | 	array.hide_partition() | ||
|  | 
 | ||
|  | def bubblesort(array): | ||
|  |     size = array.getsize() | ||
|  |     array.reset("Bubble sort") | ||
|  |     for i in range(size): | ||
|  | 	for j in range(1, size): | ||
|  | 	    if array.compare(j-1, j) > 0: | ||
|  | 		array.swap(j-1, j) | ||
|  |     array.message("Sorted") | ||
|  | 
 | ||
|  | def quicksort(array): | ||
|  |     size = array.getsize() | ||
|  |     array.reset("Quicksort") | ||
|  |     try: | ||
|  | 	stack = [(0, size)] | ||
|  | 	while stack: | ||
|  | 	    first, last = stack[-1] | ||
|  | 	    del stack[-1] | ||
|  | 	    array.show_partition(first, last) | ||
|  | 	    if last-first < 5: | ||
|  | 		array.message("Insertion sort") | ||
|  | 		for i in range(first+1, last): | ||
|  | 		    j = i-1 | ||
|  | 		    while j >= first: | ||
|  | 			if array.compare(j, j+1) <= 0: | ||
|  | 			    break | ||
|  | 			array.swap(j, j+1) | ||
|  | 			j = j-1 | ||
|  | 		continue | ||
|  | 	    array.message("Choosing pivot") | ||
|  | 	    j, i, k = first, (first+last)/2, last-1 | ||
|  | 	    if array.compare(k, i) < 0: | ||
|  | 		array.swap(k, i) | ||
|  | 	    if array.compare(k, j) < 0: | ||
|  | 		array.swap(k, j) | ||
|  | 	    if array.compare(j, i) < 0: | ||
|  | 		array.swap(j, i) | ||
|  | 	    pivot = j | ||
|  | 	    array.show_pivot(pivot) | ||
|  | 	    array.message("Pivot at left of partition") | ||
|  | 	    array.wait(1000) | ||
|  | 	    left = first | ||
|  | 	    right = last | ||
|  | 	    while 1: | ||
|  | 		array.message("Sweep right pointer") | ||
|  | 		right = right-1 | ||
|  | 		array.show_right(right) | ||
|  | 		while right > first and array.compare(right, pivot) >= 0: | ||
|  | 		    right = right-1 | ||
|  | 		    array.show_right(right) | ||
|  | 		array.message("Sweep left pointer") | ||
|  | 		left = left+1 | ||
|  | 		array.show_left(left) | ||
|  | 		while left < last and array.compare(left, pivot) <= 0: | ||
|  | 		    left = left+1 | ||
|  | 		    array.show_left(left) | ||
|  | 		if left > right: | ||
|  | 		    array.message("End of partition") | ||
|  | 		    break | ||
|  | 		array.message("Swap items") | ||
|  | 		array.swap(left, right) | ||
|  | 	    array.message("Swap pivot back") | ||
|  | 	    array.swap(pivot, right) | ||
|  | 	    n1 = right-first | ||
|  | 	    n2 = last-left | ||
|  | 	    if n1 > 1: stack.append((first, right)) | ||
|  | 	    if n2 > 1: stack.append((left, last)) | ||
|  | 	array.message("Sorted") | ||
|  |     finally: | ||
|  | 	array.hide_partition() | ||
|  | 
 | ||
|  | def demosort(array): | ||
|  |     while 1: | ||
|  | 	for alg in [quicksort, insertionsort, selectionsort, bubblesort]: | ||
|  | 	    randomize(array) | ||
|  | 	    alg(array) | ||
|  | 
 | ||
|  | 
 | ||
|  | # Sort demo class -- usable as a Grail applet | ||
|  | 
 | ||
|  | class SortDemo: | ||
|  | 
 | ||
|  |     def __init__(self, master, size=15): | ||
|  | 	self.master = master | ||
|  | 	self.size = size | ||
|  | 	self.busy = 0 | ||
|  | 	self.array = Array(self.master) | ||
|  | 
 | ||
|  | 	self.botframe = Frame(master) | ||
|  | 	self.botframe.pack(side=BOTTOM) | ||
|  | 	self.botleftframe = Frame(self.botframe) | ||
|  | 	self.botleftframe.pack(side=LEFT, fill=Y) | ||
|  | 	self.botrightframe = Frame(self.botframe) | ||
|  | 	self.botrightframe.pack(side=RIGHT, fill=Y) | ||
|  | 
 | ||
|  | 	self.b_qsort = Button(self.botleftframe, | ||
|  | 			      text="Quicksort", command=self.c_qsort) | ||
|  | 	self.b_qsort.pack(fill=X) | ||
|  | 	self.b_isort = Button(self.botleftframe, | ||
|  | 			      text="Insertion sort", command=self.c_isort) | ||
|  | 	self.b_isort.pack(fill=X) | ||
|  | 	self.b_ssort = Button(self.botleftframe, | ||
|  | 			      text="Selection sort", command=self.c_ssort) | ||
|  | 	self.b_ssort.pack(fill=X) | ||
|  | 	self.b_bsort = Button(self.botleftframe, | ||
|  | 			      text="Bubble sort", command=self.c_bsort) | ||
|  | 	self.b_bsort.pack(fill=X) | ||
|  | 
 | ||
|  | 	# Terrible hack to overcome limitation of OptionMenu... | ||
|  | 	class MyIntVar(IntVar): | ||
|  | 	    def __init__(self, master, demo): | ||
|  | 		self.demo = demo | ||
|  | 		IntVar.__init__(self, master) | ||
|  | 	    def set(self, value): | ||
|  | 		IntVar.set(self, value) | ||
|  | 		if str(value) != '0': | ||
|  | 		    self.demo.resize(value) | ||
|  | 
 | ||
|  | 	self.v_size = MyIntVar(self.master, self) | ||
|  | 	self.v_size.set(size) | ||
|  | 	sizes = [1, 2, 3, 4] + range(5, 55, 5) | ||
|  | 	if self.size not in sizes: | ||
|  | 	    sizes.append(self.size) | ||
|  | 	    sizes.sort() | ||
|  | 	self.m_size = apply(OptionMenu, | ||
|  | 			    (self.botleftframe, self.v_size) + tuple(sizes)) | ||
|  | 	self.m_size.pack(fill=X) | ||
|  | 	 | ||
|  | 	self.v_speed = StringVar(self.master) | ||
|  | 	self.v_speed.set("normal") | ||
|  | 	self.m_speed = OptionMenu(self.botleftframe, self.v_speed, | ||
|  | 				  "single-step", "normal", "fast", "fastest") | ||
|  | 	self.m_speed.pack(fill=X) | ||
|  | 	 | ||
|  | 	self.b_step = Button(self.botleftframe, | ||
|  | 			     text="Step", command=self.c_step) | ||
|  | 	self.b_step.pack(fill=X) | ||
|  | 	 | ||
|  | 	self.b_randomize = Button(self.botrightframe, | ||
|  | 				  text="Randomize", command=self.c_randomize) | ||
|  | 	self.b_randomize.pack(fill=X) | ||
|  | 	self.b_uniform = Button(self.botrightframe, | ||
|  | 				  text="Uniform", command=self.c_uniform) | ||
|  | 	self.b_uniform.pack(fill=X) | ||
|  | 	self.b_distinct = Button(self.botrightframe, | ||
|  | 				  text="Distinct", command=self.c_distinct) | ||
|  | 	self.b_distinct.pack(fill=X) | ||
|  | 	self.b_demo = Button(self.botrightframe, | ||
|  | 			     text="Demo", command=self.c_demo) | ||
|  | 	self.b_demo.pack(fill=X) | ||
|  |  	self.b_cancel = Button(self.botrightframe, | ||
|  | 			       text="Cancel", command=self.c_cancel) | ||
|  |  	self.b_cancel.pack(fill=X) | ||
|  | 	self.b_cancel.config(state=DISABLED) | ||
|  | 	self.b_quit = Button(self.botrightframe, | ||
|  | 			     text="Quit", command=self.c_quit) | ||
|  | 	self.b_quit.pack(fill=X) | ||
|  | 
 | ||
|  |     def resize(self, newsize): | ||
|  | 	if self.busy: | ||
|  | 	    self.master.bell() | ||
|  | 	    return | ||
|  | 	self.size = newsize | ||
|  | 	self.array.setdata(range(1, self.size+1)) | ||
|  | 
 | ||
|  |     def c_qsort(self): | ||
|  | 	self.run(quicksort) | ||
|  | 
 | ||
|  |     def c_isort(self): | ||
|  | 	self.run(insertionsort) | ||
|  | 
 | ||
|  |     def c_ssort(self): | ||
|  | 	self.run(selectionsort) | ||
|  | 
 | ||
|  |     def c_bsort(self): | ||
|  | 	self.run(bubblesort) | ||
|  | 
 | ||
|  |     def c_demo(self): | ||
|  | 	self.run(demosort) | ||
|  | 
 | ||
|  |     def c_randomize(self): | ||
|  | 	self.run(randomize) | ||
|  | 
 | ||
|  |     def c_uniform(self): | ||
|  | 	self.run(uniform) | ||
|  | 
 | ||
|  |     def c_distinct(self): | ||
|  | 	self.run(distinct) | ||
|  | 
 | ||
|  |     def run(self, func): | ||
|  | 	if self.busy: | ||
|  | 	    self.master.bell() | ||
|  | 	    return | ||
|  | 	self.busy = 1 | ||
|  | 	self.array.setspeed(self.v_speed.get()) | ||
|  | 	self.b_cancel.config(state=NORMAL) | ||
|  | 	try: | ||
|  | 	    func(self.array) | ||
|  | 	except Array.Cancelled: | ||
|  | 	    pass | ||
|  | 	self.b_cancel.config(state=DISABLED) | ||
|  | 	self.busy = 0 | ||
|  | 
 | ||
|  |     def c_cancel(self): | ||
|  | 	if not self.busy: | ||
|  | 	    self.master.bell() | ||
|  | 	    return | ||
|  | 	self.array.cancel() | ||
|  | 
 | ||
|  |     def c_step(self): | ||
|  | 	if not self.busy: | ||
|  | 	    self.master.bell() | ||
|  | 	    return | ||
|  | 	self.v_speed.set("single-step") | ||
|  | 	self.array.setspeed("single-step") | ||
|  | 	self.array.step() | ||
|  | 
 | ||
|  |     def c_quit(self): | ||
|  | 	if self.busy: | ||
|  | 	    self.array.cancel() | ||
|  | 	self.master.after_idle(self.master.quit) | ||
|  | 
 | ||
|  | 
 | ||
|  | # Main program -- for stand-alone operation outside Grail | ||
|  | 
 | ||
|  | def main(): | ||
|  |     root = Tk() | ||
|  |     demo = SortDemo(root) | ||
|  |     root.protocol('WM_DELETE_WINDOW', demo.c_quit) | ||
|  |     root.mainloop() | ||
|  | 
 | ||
|  | if __name__ == '__main__': | ||
|  |     main() |