Skip to content

Commit

Permalink
Implementation in Python 3
Browse files Browse the repository at this point in the history
  • Loading branch information
Subangkar committed Nov 22, 2018
1 parent 492846a commit 1a85702
Show file tree
Hide file tree
Showing 10 changed files with 529 additions and 1 deletion.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,5 @@ docs/_build/

# PyBuilder
target/

\.vscode/
17 changes: 17 additions & 0 deletions Constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
class Direction:
OLD_TO_NEW = 1
NEW_TO_OLD = 0


class CONST:
def __init__(self, MAX_M, MAX_C, CAP_BOAT, MAX_TIME_S, MAX_NODES):
self.MAX_M = MAX_M
self.MAX_C = MAX_C
self.CAP_BOAT = CAP_BOAT

self.MAX_TIME = MAX_TIME_S
self.MAX_NODES = MAX_NODES

# TERMINAL_STATE = State(-1, -1, Direction.NEW_TO_OLD, -1, -1, 0)
# INITIAL_STATE = None
# # State(MAX_M, MAX_C, Direction.OLD_TO_NEW, 0, 0,0)
111 changes: 111 additions & 0 deletions Graph.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
from collections import defaultdict

from State import TERMINAL_STATE

import time


class Graph:

def __init__(self):

self.bfs_parent = {}
self.dfs_parent = {}

self.expandedBFS = 0
self.expandedDFS = 0

def BFS(self, s):
self.expandedBFS = 0
self.bfs_parent[s] = None
visited = {(s.missionaries, s.cannibals, s.dir): True}
s.level = 0

start_time = time.time()
queue = [s]
while queue:
self.expandedBFS += 1

u = queue.pop(0)

if u.isGoalState():
print("No of Expanded Nodes: " + str(self.expandedBFS))
print("No of Explored Nodes: " + str(visited.__len__()))
queue.clear()
self.bfs_parent[TERMINAL_STATE] = u
return self.bfs_parent

# Stops searching after a certain time/node limit
t = time.time() - start_time
if t > u.CONSTANTS.MAX_TIME or self.expandedBFS > u.CONSTANTS.MAX_NODES:
if t > u.CONSTANTS.MAX_TIME:
print("%.2fs EXCEEDED TIME LIMIT of %.2fs" % (t, u.CONSTANTS.MAX_TIME))
else:
print("EXCEEDED NODE LIMIT of %d" % u.CONSTANTS.MAX_NODES)
print("No of Expanded Nodes: " + str(self.expandedBFS))
print("No of Explored Nodes: " + str(visited.__len__()))
queue.clear()
return {}

for v in reversed(u.successors()):
if (v.missionaries, v.cannibals, v.dir) not in visited.keys():
self.bfs_parent[v] = u
v.level = u.level + 1
queue.append(v)
visited[(v.missionaries, v.cannibals, v.dir)] = True

return {}

def DFS(self, s):
self.expandedDFS = 0
self.dfs_parent[s] = None
visited = {(s.missionaries, s.cannibals, s.dir): True}

start_time = time.time()
stack = [s]
while stack:
u = stack.pop()
self.expandedDFS += 1

if u.isGoalState():
print("No of Expanded Nodes: " + str(self.expandedDFS))
print("No of Explored Nodes: " + str(visited.__len__()))
self.dfs_parent[TERMINAL_STATE] = u
stack.clear()
return self.dfs_parent

t = time.time() - start_time
# Stops searching after a certain time/node limit
if t > u.CONSTANTS.MAX_TIME or self.expandedDFS > u.CONSTANTS.MAX_NODES:
if t > u.CONSTANTS.MAX_TIME:
print("%.2fs EXCEEDED TIME LIMIT of %.2fs" % (t, u.CONSTANTS.MAX_TIME))
else:
print("EXCEEDED NODE LIMIT of %d" % u.CONSTANTS.MAX_NODES)
print("No of Expanded Nodes: " + str(self.expandedDFS))
print("No of Explored Nodes: " + str(visited.__len__()))
stack.clear()
return {}

for v in u.successors():
if (v.missionaries, v.cannibals, v.dir) not in visited.keys():
visited[(v.missionaries, v.cannibals, v.dir)] = True
self.dfs_parent[v] = u
stack.append(v)
return {}

# Prints the path returned by BFS/DFS
def printPath(self, parentList, tail):
if tail is None:
return
if parentList == {} or parentList is None: # tail not in parentList.keys():
return
if tail == TERMINAL_STATE: tail = parentList[tail]

stack = []

while tail is not None:
stack.append(tail)
tail = parentList[tail]

while stack:
print(stack.pop())
8 changes: 8 additions & 0 deletions Problem Statement.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

The missionaries and cannibals problem, which is a famous problem in AI, is usually stated as follows. Three missionaries and three cannibals are on one side of a river, along with a boat that can hold one or two people. Find a way to get everyone to the other side without ever leaving a group of missionaries in one place outnumbered by the cannibals in that place.

In this assignment, your task is to do the problem formulation so it can be solved by searching, and do the computer implementation in order to experimentally compare the performance of the BFS and the DFS search strategy. For performance comparison, you may use time, number of nodes explored, number of nodes expanded, effective branching factor etc.

In addition, your computer implementation need to be able to deal with a scaled-up version of this problem (for example, a problem with
five missionaries and five cannibals). The implementation may have m number of missionaries, c number of cannibals, k number of maximum allowable passengers in the boat. There should a search cut-off limit (for example, termination after 30 seconds, or after 1,000,000 nodes have been expanded) which you should be able to vary.

20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,19 @@
Missionaries-and-Cannibals
The missionaries and cannibals problem, which is a famous problem in AI, is usually stated as follows. Three missionaries and three cannibals are on one side of a river, along with a boat that can hold one or two people. Find a way to get everyone to the other side without ever leaving a group of missionaries in one place outnumbered by the cannibals in that place.


Here the problem formulation has been solved by BFS and the DFS search strategy.


This implementation is able to deal with a scaled-up version of this problem (for example, a problem with five missionaries and five cannibals). The implementation may have m number of missionaries, c number of cannibals, k number of maximum allowable passengers in the boat. There is a search cut-off limit (for example, termination after 30 seconds, or after 1,000,000 nodes have been expanded) which was passed as input via "in.txt".

>> Run "main.py"(in Python3)
>> Input format("in.txt"):
>Line1:m
>Line2:c
>Line3:k
>Line4:TIME_LIMIT_IN_SECONDS
>Line5:NO_OF_EXPLORED_NODES_LIMIT
>> Output:
"outBFS.txt" contains the output generated by Breadth First Search and
"outDFS.txt" contains the output generated by Depth First Search.
91 changes: 91 additions & 0 deletions State.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
from Constants import Direction

MAX_M = 30
MAX_C = 30
CAP_BOAT = 20
CNST = None


class State(object):

def __init__(self, missionaries, cannibals, dir, missionariesPassed, cannibalsPassed, level, CONSTS,moves):
self.missionaries = missionaries
self.cannibals = cannibals
self.dir = dir
self.action = ""
self.level = level
self.missionariesPassed = missionariesPassed
self.cannibalsPassed = cannibalsPassed
self.CONSTANTS = CONSTS

self.moves = moves

global MAX_M
global MAX_C
global CAP_BOAT
global CNST

if not CONSTS is None:
CNST = CONSTS

MAX_M = CONSTS.MAX_M
MAX_C = CONSTS.MAX_C
CAP_BOAT = CONSTS.CAP_BOAT

# pass True to count forward
def successors(self):
listChild = []
if not self.isValid() or self.isGoalState():
return listChild
if self.dir == Direction.OLD_TO_NEW:
sgn = -1
direction = "from the original shore to the new shore"
else:
sgn = 1
direction = "back from the new shore to the original shore"
for i in self.moves:
(m, c) = i
self.addValidSuccessors(listChild, m, c, sgn, direction)
return listChild

def addValidSuccessors(self, listChild, m, c, sgn, direction):
newState = State(self.missionaries + sgn * m, self.cannibals + sgn * c, self.dir + sgn * 1,
self.missionariesPassed - sgn * m, self.cannibalsPassed - sgn * c, self.level + 1,
self.CONSTANTS,self.moves)
if newState.isValid():
newState.action = " take %d missionaries and %d cannibals %s." % (m, c, direction)
listChild.append(newState)

def isValid(self):
# obvious
if self.missionaries < 0 or self.cannibals < 0 or self.missionaries > MAX_M or self.cannibals > MAX_C or (
self.dir != 0 and self.dir != 1):
return False

# then check whether missionaries outnumbered by cannibals in any shore
if (self.cannibals > self.missionaries > 0) or (
self.cannibalsPassed > self.missionariesPassed > 0): # more cannibals then missionaries on original shore
return False

return True

def isGoalState(self):
return self.cannibals == 0 and self.missionaries == 0 and self.dir == Direction.NEW_TO_OLD

def __repr__(self):
return "\n%s\n\n< @Depth:%d State (%d, %d, %d, %d, %d) >" % (
self.action, self.level, self.missionaries, self.cannibals, self.dir, self.missionariesPassed,
self.cannibalsPassed)

def __eq__(self, other):
return self.missionaries == other.missionaries and self.cannibals == other.cannibals and self.dir == other.dir

def __hash__(self):
return hash((self.missionaries, self.cannibals, self.dir))

def __ne__(self, other):
return not (self == other)


TERMINAL_STATE = State(-1, -1, Direction.NEW_TO_OLD, -1, -1, 0, CNST,None)
# INITIAL_STATE = State(MAX_M, MAX_C, Direction.OLD_TO_NEW, 0, 0, 0, CNST)
80 changes: 80 additions & 0 deletions in.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
3
3
2
30
100000

10000
10000
5
120
100000

10000
10000
20
120
100000

300
300
4
30
100000

329
297
17
30
100000

300
300
200
120
100000

200
200
11
120
100000

30
30
20
30
100000

4
4
3
120
100000

77
47
11
30
10000

2
2
2
120
100000

79
47
23
120
100000



9000
8000
200
2
10000

Loading

0 comments on commit 1a85702

Please sign in to comment.