Showing posts with label Algorithms. Show all posts
Showing posts with label Algorithms. Show all posts

Brute force Travelling Salesman Problem in Python

I did not make it into work today. A blanket of snow - dubbed the Pest from the West - hit the North of England causing gridlock in many areas. Instead, I got to stay at home and write a Travelling Salesman simulation in Python.

The Travelling Salesman Problem (TSP) is a problem in Computer Science used to demonstrate intractability and optimisation. In short, imagine a salesman who wishes to visit every one of N cities exactly once each. The salesman does not care which order he visits the cities as he is equally proficient in applying is selling skills in them all regardless. All that matters is that he takes the shortest route between them all before returning back to the start, where, presumably Alan Sugar is waiting in the boardroom to fire him if he has failed in this task.

The Python 3 code below will run a TSP simulation by attempting to solve the problem by brute force. This is, of course, the worst method for solving the problem, being factorial of N time complexity, but it is presented here in the hope that it is of some use to students.

A solution for 10 cities

The simulation in mid-animation

A solution for 9 cities

Here is a photo of me failing to complete the TSP for 2 cities. 
The Python code can be copied from below, or you can download it from my OneDrive. Enjoy!




# Travelling Salesman by Brute force
# superdecade games
# version 2.1
# 2018-03-08

import itertools
import turtle
import time
import math
import random

class Salesman(object):
	"""Travelling Salesman by Brute force"""
	# in this program a route is represented by a list of coordinates
	# where each coordinate is a tuple containing an x,y coordinate
	# in the range 0-1000,0-1000
	def __init__(self):
		self.__city = [] #list for holding city coords
		self.__num = 0 # number of cities in simulation
		self.__animate = False # True if should show animations
		self.__speed = 100 # Speed of animations 100 is fastest
		self.__wn = turtle.Screen() # turtle window
		turtle.setworldcoordinates(0, 0, 1000, 1000)
		self.__wn.title("TRAVELLING SALESMAN")
		self.__cityPen = turtle.Turtle() # pen for drawing cities
		self.__cityPen.shape("circle")
		self.__cityPen.hideturtle()
		self.__routePen = turtle.Turtle() # pen for drawing routes
		self.__routePen.pensize(5) 
		self.__route = [] # list of possible routes
		self.__bestRoute = None # current best route
		self.__bestLength = 1000000 # a big number	
	
	def main(self):
		"""Main test program"""
		self.__title()
		self.__setup()
		self.__generate()
		self.__showCities()
		try:
			self.__getRoutes()
			t0 = time.clock() # start tinmer
			for i in self.__route:
				thisLength = self.__calculateLength(i)
				if thisLength <= self.__bestLength:
					self.__bestLength = thisLength
					self.__bestRoute = i # store the best route so far
				if self.__animate:
					self.__showRoute(i)
					time.sleep(0.01)
					self.__routePen.clear()
			#show the best route
			print("\nDone. Showing best route")
			print(time.clock() - t0, "seconds")
			self.__showRoute(self.__bestRoute, "green")
			input()
			self.__cityPen.clear()
			self.__routePen.clear()
			self.__routePen.hideturtle()
			self.__cityPen.hideturtle()

		except Exception as e:
			print("Ooops, that was an error. Too many cities?")
				
	def __setup(self):
		"""Gets users choices for the simulation"""
		ok = False
		while not(ok):
			try:
				num = int(input("Enter number of cities: "))
				if num>1 and num < 11:
					self.__num = num
					ok = True
				else:
					print("Invalid!")
			except Exception as e:
				pass
		choice = input("Show animation for all? (Y/N): ")
		if choice.lower() in "yes":
			self.__animate = True
			ok = False
			while not(ok):
				try:
					speed = int(input("Select speed (1-10): "))
					if speed >= 0 and speed <= 10:
						self.__speed = speed ** 2
						ok = True
					else:
						print("Try again")
				except Exception as e:
					pass

	def __generate(self):
		"""Creates random cities"""
		for i in range(self.__num):
			x = random.randint(0,1000)
			y = random.randint(0,1000)
			self.__city.append((x,y))
	
	def __showCities(self):
		"""Display cities on the canvas"""
		self.__cityPen.speed(0)
		self.__cityPen.penup()
		for i in range(self.__num):
			self.__cityPen.goto(self.__city[i][0],self.__city[i][1])
			self.__cityPen.stamp()
	
	def __getRoutes(self):
		"""Find all combinations of routes"""
		trylist = list(itertools.permutations(self.__city))
		#remove duplicates
		for i in trylist:
			if i[0] == trylist[0][0]:
				self.__route.append(i)
	
	def __showRoute(self, this, color="red"):
		"""Show a route passed as paramter on the canvas"""
		self.__routePen.color(color)
		self.__routePen.speed(self.__speed)
		self.__routePen.penup()
		for i in this:
			self.__routePen.goto(i[0],i[1])
			self.__routePen.pendown()
		self.__routePen.goto(this[0][0],this[0][1])
			
	def __calculateLength(self, this):
		"""Returns the length of route 'this'"""
		d = 0
		for i in range(1,len(this)):
			d += math.sqrt( (this[i][0]-this[i-1][0])**2 + (this[i][1]-this[i-1][1])**2)
		return d			
	
	def __title(self):
		"""Displays the title"""
		print("Travelling Salesman Problem")
		print("      (by brute force)")
		


while True:
	app = Salesman()
	app.main()

Depth First Search in Python

Well, it has been a while since I posted some code onto these pages, so what follows is some Python 3 code for a depth-first search as I taught it to some students today.

The code uses a stack to implement the depth first search. The output shows the nodes visited.

I shall use the following graph as an example. The graph has been coded as an adjacency list using a dictionary where the nodes are assigned integer keys and their value is a list containing the nodes that they are connected to.

A graph representing a maze with ten nodes featuring closed loops. You can ignore the path leading from 9. I have chosen a maze that cannot be solved by following the walls method (it will ignore node 5)

The code:
(Also available as a download from OneDrive because it is highly likely that it won't work well pasted from the browser.)

class mystack:
    def __init__(self, size):
        self.__stack = []
        self.__size = size
        for i in range (size):
            self.__stack.append("")
        self.__tos = -1

    def peek(self):
        if self.__isEmpty():
            pass
        else:
            return (self.__stack[ self.__tos ])
   
    def push(self, item):
        if not(self.__isFull()):
            self.__stack[ self.__tos + 1 ] = item
            self.__tos += 1
        else:
            print("STACK OVERFLOW")
           
    def pop(self):
        if not(self.__isEmpty()):
            self.__tos -= 1
            return self.__stack[self.__tos + 1]
        else:
            raise StackEmpty("Stack is empty")
           
    def __isEmpty(self):
        return (self.__tos == -1)

       
    def __isFull(self):
        return self.__tos == self.__size-1


    def display(self):
        for i in range (0, self.__tos+1 ):
            print (self.__stack[i], end=", ")



class dfs(object):
    def __init__(self):
        #map is the adjacency list
        #visited keeps track of which nodes have been visited
        #stack is the stack of current nodes under investigation
        self.__map = {0: [1,5,4], 1:[2,0], 2:[3,1,6], 3:[2], 4:[0,8], 5:[0,6,9,8], 6:[2,7,9,5], 7:[6], 8:[4,5,9], 9:[8,5,6]}
        self.__visited = [False, False, False, False, False, False, False, False, False, False]
        self.__stack = mystack(10)

    def main(self):
        start = 0
        current = start
        solved = False
        while not(solved):
            try:
                print("exploring node", current, end='')
                if not(self.__completed(current)):
                    self.__stack.push(current)
                self.__visited[current] = True
                next = self.__findNextNode(current)
                if next != None:
                    current = next
                else:
                    current = self.__stack.pop()
                    print("...deadend...backtracking to", current, end='\n')
                self.__stack.display()
                print()
   
            except StackEmpty as e:
                #assume maze is solved
                solved = True
  
        print("\nMaze fully explored by dfs :-)")
  

    def __findNextNode(self, p):
        #finds next unvisited node in the adjacency list
        nodes = self.__map[p]
        i = 0
        while i<len(nodes):
            if not(self.__visited[ nodes[i] ]):
                return self.__map[p][ i ]
            else:
                i += 1
        return None

    def __completed(self, p):
 #returns true if node p has been fully explored
        nodes = self.__map[p]
        i = 0
        while i<len(nodes):
            if not(self.__visited[ nodes[i] ]):
                return False
            else:
                i += 1
        return True

   
   

class StackEmpty(Exception):
    def __init__(self, value):
        self.value = value
    def toString(self):
        return self.value



app = dfs()
app.main()



The output

exploring node 0
0,
exploring node 1
0, 1,
exploring node 2
0, 1, 2,
exploring node 3...deadend...backtracking to 2
0, 1,
exploring node 2
0, 1, 2,
exploring node 6
0, 1, 2, 6,
exploring node 7...deadend...backtracking to 6
0, 1, 2,
exploring node 6
0, 1, 2, 6,
exploring node 9
0, 1, 2, 6, 9,
exploring node 8
0, 1, 2, 6, 9, 8,
exploring node 4...deadend...backtracking to 8
0, 1, 2, 6, 9,
exploring node 8
0, 1, 2, 6, 9, 8,
exploring node 5...deadend...backtracking to 8
0, 1, 2, 6, 9,
exploring node 8...deadend...backtracking to 9
0, 1, 2, 6,
exploring node 9...deadend...backtracking to 6
0, 1, 2,
exploring node 6...deadend...backtracking to 2
0, 1,
exploring node 2...deadend...backtracking to 1
0,
exploring node 1...deadend...backtracking to 0

exploring node 0
Maze fully explored by dfs :-)



Sorting Algorithms in #python

Should it be of use to students of Computing, here are three sorting algorithms in Python:

  • Bubble Sort
  • Insertion Sort
  • Quick Sort

...followed by some videos of sorting algorithms in action.

The Bubble Sort

Not the most efficient algorithm in the computing world.  Really inefficient algorithm- on average it is order n-squared, and at the very best case it is of order n time complexity, and at the worst case it is n-squared order of time complexity (ignoring the case where the list is already sorted, in which case it sorts in order n. In fact this is where the Bubble sort beats all other algorithms as the ability to detect that the list is sorted is built into the algorithm).

Python Implementation:

# Bubble Sort Demo
# Mr Street
# 2014-11-21

def bubbleSort( aList ):
    """Performs the bubble sort on the list passed"""
    leaveLoop = False
    while not(leaveLoop):
        leaveLoop = True # assume we will have it sorted this time
        #for each item in the list
        for i in range(0, len(aList)-1):
            #if this element is greater than the next item,
            #then they should be swapped
            if aList[i] > aList[i + 1]:
                # python shortcut for swap
                aList[i], aList[i+1] = aList[i+1], aList[i]
                leaveLoop = False
            print( aList)

# Test program
someList = [1, 3, 6, 7, 10, 5]

bubbleSort( someList )

Animated bubble sort example from Wikipedia.  The Bubble sort gets its name because the sorted value 'bubbles' to the top of the list.




The Insertion Sort

A very similar algorithm to the Bubble Sort, it is on average an order n squared time complexity.  At the best case it is order (n) time complexity.  The best case is when the list is almost in order, in which case it beats the quick sort.  For this reason you should use the insertion sort to add items to an already sorted list, such as a contacts list.

Python Implementation:

def iSort(aList):
    """ Insertion sort algorithm """
    first = 0
    last = len(aList)-1
    for CurrentPointer in range(first+1, last+1):
        CurrentValue = aList[CurrentPointer]
        Pointer = CurrentPointer - 1
        while aList[Pointer] > CurrentValue and Pointer >= 0:
            aList[Pointer+1] = aList[Pointer]
            Pointer -= 1
        aList[Pointer+1] = CurrentValue

#A nearly sorted list, with one unsorted element to insert
a = [1, 4, 7, 8, 10, 5]
print("Unsorted")
print(a)
print("Sorted")
iSort(a)
print(a)


The insertion sort animation from Wikipedia.  The algorithm works by find the correct place to insert an item.  Unlike the bubble sort it only requires one pass through the list,  although at the worst case each check will require n-1 comparisons.




The Quick Sort

The quick sort algorithm works by 'divide and conquer':  Choose a pivot point (the start of the list will do) and create two lists, one of unsorted items that are smaller than the pivot, and one of unsorted items that are greater than the pivot.  Then perform the quicksort algorithm on each of the two unsorted lists.

The average case time complexity for the quick sort is therefore n log(n).


def quickSort(aList):
    """ Sorts the list by quick sort """
    lesser = []
    pivotList = []
    greater = []
    if len(aList) <= 1:
        return aList
    else:
        #choose first item for the pivot
        pivot = aList[0]
        #compare to pivot
        for i in aList:
            if i < pivot:
                #put items less than pivot into left hand list
                lesser.append(i)
            elif i > pivot:
                #put items greater than pivot into right hand list
                greater.append(i)
            else:
                #put itms same as pivot into pivotList
                pivotList.append(i)
        #quickSort the left hand side
        lesser = quickSort(lesser)
        #quickSort the right hand side
        greater = quickSort(greater)
        return lesser + pivotList + greater
#Main program
print("Unsorted")
a = [3,7,5,1,2,7,8,11,11,12,10,1,2,3,4,7,8]
print(a)
print("Sorted")
print(quickSort(a))


Sorting videos




Fifteen algorithms in 6 minutes...

Sorting algorithms compared... place your bets now!

A very satisfying video showing sorting algorithms as colour wheel.


Other posts you might be interested in:


Google for ISBN-13 numbers

An ISBN number is a unique identification number for commercial books.  The listing below is for an ISBN-13 validation program in BBC BASIC.  If a valid book number is found the program then performs a Google search (probably bringing up a link to Amazon.com - not that they really need the support).



The algorithm works as follows:


  1. Each of the first 12 digits starting from the left to right, is multiplied by 1 or 3 alternatively.
  2. The sum of the products modulo 10 gives us either zero or a number between 1 to 9.
  3. Subtract the number from 10 and it gives us the checksum.


     REM ISBN-13 Validator program
     REM T Street
     REM 2015-03-19


     INSTALL @lib$+"stringlib"
     REM main program loop
     
REPEAT
       PRINT 
'"ISBN-13 validator."
       PRINT "------------------"'
       REPEAT
         INPUT 
"Enter a 13 digit ISBN number : " isbn$

         isbn$ = FN_convertInput( isbn$ )

       UNTIL LEN(isbn$)=13

       IF FN_validISBN13( isbn$ ) THEN
         PRINT 
"VALID ISBN-13 - opening your browser..."
         SYS "ShellExecute",@hwnd%,0,"https://www.google.co.uk/search?q=ISBN "+isbn$, 0, "", 1
       ELSE
         PRINT 
"INVALID ISBN-13 :-("
       ENDIF
     UNTIL FALSE


     
DEFFN_validISBN13( num$ )
     REM validates an isbn number
     REM pass a 13 digit string as parameter
     REM returns true if the string represents a valid ISBN-13
     REM
     REM Each digit, starting from the left to right, is multiplied by 1 or 3 alternatively.
     REM The sum of the products modulo 10 gives us either zero or a number between 1 to 9.
     REM Subtract the number from 10 and it gives us the checksum.

     
LOCAL i% : REM iterator
     
LOCAL calcCheck% : REM running calculation
     
LOCAL checksum%  : REM checksum
     
LOCAL multiplier% : multiplier% = 3
     i% = 1
     WHILE i%<13
       calcCheck% = calcCheck% + VAL(MID$(num$, i%, 1))
       i% += 1
       calcCheck% = calcCheck% + VAL(MID$(num$, i%, 1)) * multiplier%
       i% += 1
     ENDWHILE

     
calcCheck% = calcCheck% MOD 10

     checksum% = 10 - calcCheck%

     IF checksum% = VAL(RIGHT$(num$,1)) THEN
       
TRUE
     ENDIF

     
FALSE


     
DEFFN_convertInput( astring$ )
     REM pass an ISBN-13 number as a string
     REM removes any spaces or dashes
     REM which are sometimes located
     REM in ISBN numbers
     REM returns the string with dash/space removed
     
LOCAL a$ : a$ = astring$
     IF FN_findreplacei( a$, "-""", 0)
     IF FN_findreplacei( a$, " """, 0)
     = a$

#isbn #bbcbasicforwindows #algoithms

A whole load of linear search algorithm videos

The linear search: a simple algorithm for searching for something.  It is what you do when you are searching for a matching sock in your sock drawer.  Nevertheless, students find it difficult to code, so presented here is a load of videos to help.

In its simplest form, the linear search is as follows.

i = 0         // the first item in your list
found = false // you haven't found it yet
answer = null // you haven't found it
while i < numberOfItems and not(found)
    if currentItem(i) == theThingYouAreLookingFor then
        found = true  // you found it
        answer = i    // the position of the thing
    else
        i ++          // increment i
    endif
endwhile
return ( answer )

In English:

Look through all the items in your list in turn, from the start, until you find the one you are looking for.  There are two ways a linear search can end: either you find the thing (so you stop searching), or you check everything and don't find it.

#linearSearch #algorithms

dd

Label