heap time complexity python - python

import heapq
def getMaxUnit(num,boxes,UnitSize,UnitSize,unitPerBox, truckSize):
if truckSize == 0 or num == 0:
return 0
h = []
for i in range(num):
h.append((-1*unitPerBox[i],boxes[i]))
heapq.heapify(h)
maxCapacity = 0
while truckSize>=0 and len(h) != 0:
popped = heapq.heappop(h)
truckSize = truckSize-popped[1]
available = popped[1]
if truckSize < 0:
available = popped[1]+truckSize
maxCapacity = maxCapacity + available*(-1*popped[0])
return maxCapacity
I'm trying to find the time complexity of the code here. I'm confused with what the time complexity of heapq.heappop here as it needs to maintain heap property every time we pop an element.

Related

Heap Dijkstra Implementation is slower than Naive Dijsktra Implementation

I have tried to implement the Naive and Heap Dijkstra as shown below but somehow my naive Dijkstra implementation is surprisingly faster. I debugged my code but couldn't understand where my problem in my implementations are.
Why is my heap-based implementation is slower than the naive implementation?
Raw data is stored here:
https://www.algorithmsilluminated.org/datasets/problem9.8.txt
Data Import and Manipulation:
import time
with open("DijkstraTest2.txt", 'r') as input:
lines = input.readlines()
lengths = {}
vertices = []
for line in lines:
contents = line.split("\t")
vertices.append(contents[0])
for content in contents:
content = content.replace('\n', '')
if ',' in content:
edge = contents[0] + '-' + content.split(',')[0]
lengths[edge] = int(content.split(',')[1])
Naive Dijkstra:
def NaiveDijkstra(vertices, start_point, lengths):
X = [start_point]
shortest_paths = {}
for vertex in vertices:
if vertex == start_point:
shortest_paths[vertex] = 0
else:
shortest_paths[vertex] = 999999999999
subset = [key for key in lengths.keys() if start_point == key.split('-')[0]
and key.split('-')[0] in X and key.split('-')[1] not in X]
while len(subset) > 0:
temp_min_dict = {}
for edge in subset:
temp_min = shortest_paths[edge.split('-')[0]] + lengths[edge]
temp_min_dict[edge] = temp_min
new_edge = min(temp_min_dict, key=temp_min_dict.get)
X.append(new_edge.split('-')[1])
shortest_paths[new_edge.split('-')[1]] = shortest_paths[new_edge.split('-')[0]] + lengths[new_edge]
subset = []
for key in lengths.keys():
if key.split('-')[0] in X and key.split('-')[1] not in X:
subset.append(key)
return shortest_paths
start_time = time.time()
print(NaiveDijkstra(vertices = vertices, start_point = '1', lengths = lengths)['197'])
print(time.time() - start_time, "seconds")
My Heap based Dijkstra code:
class Heap:
def __init__(self):
self.size = 0
self.lst = []
def swap(self, a):
if self.size == 1:
return self.lst
else:
if a == 1:
i = 1
else:
i = a // 2
while i > 0:
if i * 2 - 1 >= self.size:
break
elif self.lst[i - 1][1] > self.lst[i * 2 - 1][1]:
temp = self.lst[i - 1]
self.lst[i - 1] = self.lst[i * 2 - 1]
self.lst[i * 2 - 1] = temp
elif i * 2 >= self.size:
break
elif self.lst[i - 1][1] > self.lst[i * 2][1]:
temp = self.lst[i - 1]
self.lst[i - 1] = self.lst[i * 2]
self.lst[i * 2] = temp
i -= 1
# print(f"output: {self.lst}")
def insert(self, element):
# print(f"input: {self.lst}")
self.lst.append(element)
self.size += 1
self.swap(self.size)
def extractmin(self):
val = self.lst.pop(0)[0]
self.size -= 1
self.swap(self.size - 1)
return val
def delete(self, deleted):
ix = self.lst.index(deleted)
temp = self.lst[-1]
self.lst[ix] = temp
self.lst[-1] = deleted
self.lst.pop(-1)
self.size -= 1
#self.swap(self.size)
def FastDijkstra(vertices, start_point, lengths):
X = []
h = Heap()
width = {}
shortest_paths = {}
for vertex in vertices:
if vertex == start_point:
width[vertex] = 0
h.insert((vertex, width[vertex]))
else:
width[vertex] = 999999999999
h.insert((vertex, width[vertex]))
while h.size > 0:
w = h.extractmin()
X.append(w)
shortest_paths[w] = width[w]
Y = set(vertices).difference(X)
for x in X:
for y in Y:
key = f"{x}-{y}"
if lengths.get(key) is not None:
h.delete((y, width[y]))
if width[y] > shortest_paths[x] + lengths[key]:
width[y] = shortest_paths[x] + lengths[key]
h.insert((y, width[y]))
return shortest_paths
start_time = time.time()
print(FastDijkstra(vertices=vertices, start_point='1', lengths=lengths)['197'])
print(time.time() - start_time, "seconds")
The way you implemented the heap version is not efficient. Notably the following make it inefficient:
All nodes are put on the heap instead of only the direct neighbors of the visited nodes. This makes the heap large and slower than needed.
Y = set(vertices).difference(X) is a slow operation, and makes Y unnecessary large.
The nested loop that tries every pair in the Cartesian product of X and Y to see if it is an edge. This point together with the previous should be replaced with a collection of edges starting from X, and then discarding edges that lead to already visited nodes.
For every found edge to delete the target node from the heap, and re-insert it, even if the width didn't change! Deletion is a costly operation (see next point). Only if the Heap implementation supports a decrease-key operation, this is an option, but otherwise the heap should just get an extra entry for the same vertex, knowing that the one with the lowest cost will come out of the heap first.
The heap's delete method has a bad time complexity due to the .index() call.
The heap's extractmin method has a bad time complexity, due to the .pop(0) call. This has O(n) time complexity.
The heap's extractmin does not give correct results (again due to that pop(0)). Here is a little script that shows a mistake:
h = Heap()
for value in 4, 3, 5, 2, 1:
h.insert((value, value))
print(h.extractmin()) # 1 = OK
print(h.extractmin()) # 2 = OK
print(h.extractmin()) # 4 = NOK. 3 expected.
The data structure lengths does not allow to quickly find the edges from a particular vertex. But this is a point that is also making the naive implementation slow. I would however suggest to turn that in a dict.
If this is done right it should run faster. Certainly when you would make use of the native heapq module you'll get good running times.
Here is a (much) faster implementation. It doesn't bother about unreachable vertices, and doesn't bother about possibly having multiple entries on the heap for the same node (with different distances). But it does start with only the starting node on the heap, and uses heapq:
from heapq import heappush, heappop
from collections import defaultdict
def FastDijkstra(vertices, start_point, lengths):
# Create a dictionary for the edges, keyed by source node
edges = defaultdict(list)
for key, length in lengths.items():
x, y = key.split("-")
edges[x].append((length, y))
heap = [(0, start_point)]
shortest_paths = {}
while heap:
cost, x = heappop(heap)
if x in shortest_paths:
continue # this vertex had already been on the heap before
shortest_paths[x] = cost
for length, y in edges[x]:
if y not in shortest_paths:
heappush(heap, (cost + length, y))
return shortest_paths
In my tests this ran hundreds times faster.
Thanks to the above answer (wonderful analysis) I adjusted my implementation which is way faster than the previous version. It is shown below.
class Heap:
def __init__(self):
self.size = 0
self.lst = []
def swap(self, a):
if self.size == 1:
return self.lst
else:
if a == 1:
i = 1
else:
i = a // 2
while i > 0:
if i * 2 - 1 >= self.size:
break
elif self.lst[i - 1][1] > self.lst[i * 2 - 1][1]:
temp = self.lst[i - 1]
self.lst[i - 1] = self.lst[i * 2 - 1]
self.lst[i * 2 - 1] = temp
elif i * 2 >= self.size:
break
elif self.lst[i - 1][1] > self.lst[i * 2][1]:
temp = self.lst[i - 1]
self.lst[i - 1] = self.lst[i * 2]
self.lst[i * 2] = temp
elif self.lst[2*i - 1][1] > self.lst[i * 2][1]:
temp = self.lst[2*i - 1]
self.lst[2*i - 1] = self.lst[i * 2]
self.lst[i * 2] = temp
i -= 1
#print(f"output: {self.lst}")
def insert(self, element):
#print(f"input: {self.lst}")
self.lst.append(element)
self.size += 1
self.swap(self.size)
def extractmin(self):
val = self.lst[0][0]
del self.lst[0]
self.size -= 1
self.swap(self.size-1)
return val
def delete(self, deleted):
ix = self.lst.index(deleted)
temp = self.lst[-1]
self.lst[ix] = temp
self.lst[-1] = deleted
del self.lst[-1]
self.size -= 1
#self.swap(self.size)
def FastDijkstra(vertices, start_point, lengths):
X = []
h = Heap()
width = {}
shortest_paths = {}
for vertex in vertices:
if vertex == start_point:
width[vertex] = 0
h.insert((vertex, width[vertex]))
else:
width[vertex] = 999999999999
h.insert((vertex, width[vertex]))
while h.size > 0:
w = h.extractmin()
X.append(w)
shortest_paths[w] = width[w]
Y = set(vertices).difference(X)
for y in Y:
key = f"{w}-{y}"
if lengths.get(key) is not None:
h.delete((y, width[y]))
if width[y] > shortest_paths[w] + lengths[key]:
width[y] = shortest_paths[w] + lengths[key]
h.insert((y, width[y]))
return shortest_paths
start_time = time.time()
print(FastDijkstra(vertices=vertices, start_point='1', lengths=lengths)['197'])
print(time.time() - start_time, "seconds")

How do I count the number of recursive calls for n-queens problem?

I have the following code for solving the n-queens problem and I am supposed to count the number of recursive calls made. I have a function called ConstructCandidates to get the candidate solutions and I am trying to count the number of recursive calls it makes. I was wondering if I am counting the recursive calls correctly as I am not sure if I am doing this correctly.
def createChessBoard(boardSize):
chessBoard = [0]*boardSize
for index in range(boardSize):
chessBoard[index] = [0]*boardSize
return chessBoard
def isSolution(results, numberOfQueens):
if numberOfQueens==n:
for r in results:
for row in r:
print(row)
print()
return numberOfQueens>len(results)
def safeToPlaceQueen(chessBoard, boardRow, boardColumn, boardSize):
for indexColumn in range(boardColumn):
if chessBoard[boardRow][indexColumn] == 1:
return False
indexRow, indexColumn = boardRow, boardColumn
while indexRow >= 0 and indexColumn >= 0:
if chessBoard[indexRow][indexColumn] == 1:
return False
indexRow-=1
indexColumn-=1
diagonalRow, diagonalColumn = boardRow,boardColumn
while diagonalRow < boardSize and diagonalColumn >= 0:
if chessBoard[diagonalRow][diagonalColumn] == 1:
return False
diagonalRow+=1
diagonalColumn-=1
return True
def ConstructCandidates(chessBoard, columnLength, boardSize):
global recursionCounter
if columnLength >= boardSize:
return
for row in range(boardSize):
if safeToPlaceQueen(chessBoard, row, columnLength, boardSize):
chessBoard[row][columnLength] = 1
if columnLength == boardSize-1:
Process(chessBoard)
chessBoard[row][columnLength] = 0
return
#recursively call ConstructCandidates to find more candidate solutions
ConstructCandidates(chessBoard, columnLength+1, boardSize)
chessBoard[row][columnLength] = 0
recursionCounter+=1
def Process(chessBoard):
global results
solutions = copy.deepcopy(chessBoard)
results.append(solutions)
global count
count= len(results)
def isFinished():
if count >0:
return False
n = 8
chessBoard = createChessBoard(n)
results = []
recursionCounter =0
ConstructCandidates(chessBoard, 0, n)
isSolution(results, n)
if isFinished() is False:
print("All possible solutions without the Queen being in danger have been found")
print("The total number of possible solutions for",n,"Queens is:",len(results))
print("The total number of recursive calls to solve the problem of",n,"Queens is:",recursionCounter)
If I am not doing this correctly would someone be able to show me how? The current result I get for 8-queens is 1964

Python calls the same function in a loop, and uses the return value of the last call as the parameter of this call

This is kmeans I wrote. I want to call this function cyclically to repeatedly calculate the distance to the center point and enter the return as parameter
the first input is (b[0,:],b[1,:])
def DIV(point1,point2):
plt.figure()
c = []
d = []
for i in range(1000):
dist1 = np.linalg.norm(Z[i] - point1)
dist2 = np.linalg.norm(Z[i] - point2)
if dist1 > dist2:
c.append(Z[i])
else:
d.append(Z[i])
C = np.array(c)
D = np.array(d)
plt.scatter(C[:,0],C[:,1],s=16.,color='green')
plt.scatter(D[:,0],D[:,1],s=16.,color='yellow')
center1 = C.mean(axis=0)
center2 = D.mean(axis=0)
plt.scatter(center1[0],center1[1],marker ='x',s=16.,color='red')
plt.scatter(center2[0],center2[1],marker ='x',s=16.,color='red')
return center1,center2
Your function and background don't matter much, sounds like you are basically trying to do a "fixed point" function based on your response to my comment:
def fix(func, initial, max_iter):
prev = func(initial)
iteration = 1
while True:
curr = func(prev)
if curr == prev or iteration > max_iter:
break
prev = curr
iteration += 1
return curr
For your specific case, you could then write
div = lambda (a, b): DIV(a, b)
result = fix(div, (b[0,:],b[1,:]), max_iter=1000)
if you wanted to use the same logic inline, instead of using more generic logic, you could write
prev = DIV(b[0,:],b[1,:])
iteration = 1
while True:
curr = DIV(*prev)
if curr == prev or iteration > 1000:
break
prev = curr
iteration += 1
result = curr
I used very cumbersome loops and functions to control its iteration
def paste(head, start=0, stop=0, step=1, sep=''):
return [f'{head}{sep}{i}' for i in range(start, stop, step)]
def loop(n):
k = paste('a', 0, n)
k[0],k[1] = DIV(b[0,:],b[1,:])
for i in range(0,n,2):
k[i],k[i+1] = DIV(k[i],k[i+1])
for j in range(2,n,2):
k[j],k[j+1] = DIV(k[i],k[i+1])
loop(6)

optimizing Knight's tour on a chess board

my code below
I have a little knight's tour problem I'm trying to solve: find the smallest number of moves from point A to point B on an N*N chess board.
I created a board, and used a simple algorithm:
1. add point A to candidate list and start loop:
2. pop first element in candidate list and check it:
3. if end - return counter
4. else - add the candidate 's "sons" to end of candidate list
5. go to step 2 (counter is incremented after all previous level sons are popped)
This algorithm works as I expected (used it on a few test cases), but it was very slow:
The call f = Find_route(20, Tile(4,4), Tile(14,11)) (20 is the board dimensions, Tile(4,4) and Tile(14,11) are the start & end positions, respectively) checked 201590 (!!) tiles before reaching the answer.
I tried optimizing it by sorting the candidates list with sorted(tiles, key = lambda e : abs(e.x - end.x)+abs(e.y - end.y)) where tiles is the candidates list. That works for some of the cases but for some it is kind of useless.
helpful cases:
f = Find_route(20, Tile(1,4), Tile(1,10)) from 459 to 309 (~33% !!)
f = Find_route(20, Tile(7,0), Tile(1,11)) from 87738 to 79524 (~10% :( )
unhelpful cases:
f = Find_route(20, Tile(4,4), Tile(14,11)): from 201891 to 201590
f = Find_route(20, Tile(1,4), Tile(1,11)) from 2134 to 2111
I want eventually to have a list of near-end cases, from which the algorithm would know exactly what to do, (something like a 5 tiles radius), and I think that could help, but I am more interested in how to improve my optimize_list method. Any tips?
Code
class Tile(object):
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
tmp = '({0},{1})'.format(self.x, self.y)
return tmp
def __eq__(self, new):
return self.x == new.x and self.y == new.y
def get_horse_jumps(self, max_x , max_y):
l = [(1,2), (1,-2), (-1,2), (-1,-2), (2,1), (2,-1), (-2,1), (-2,-1)]
return [Tile(self.x + i[0], self.y + i[1]) for i in l if (self.x + i[0]) >= 0 and (self.y + i[1]) >= 0 and (self.x + i[0]) < max_x and (self.y + i[1]) < max_y]
class Board(object):
def __init__(self, n):
self.dimension = n
self.mat = [Tile(x,y) for y in range(n) for x in range(n)]
def show_board(self):
print('-'*20, 'board', '-'*20)
n = self.dimension
s = ''
for i in range(n):
for j in range(n):
s += self.mat[i*n + j].__str__()
s += '\n'
print(s,end = '')
print('-'*20, 'board', '-'*20)
class Find_route(Board):
def __init__(self, n, start, end):
super(Find_route, self).__init__(n)
#self.show_board()
self.start = start
self.end = end
def optimize_list(self, tiles, end):
return sorted(tiles, key = lambda e : abs(e.x - end.x)+abs(e.y - end.y))
def find_shortest_path(self, optimize = False):
counter = 0
sons = [self.start]
next_lvl = []
num_of_checked = 0
while True:
curr = sons.pop(0)
num_of_checked += 1
if curr == self.end:
print('checked: ', num_of_checked)
return counter
else: # check sons
next_lvl += curr.get_horse_jumps(self.dimension, self.dimension)
# sons <- next_lvl (optimize?)
# next_lvl <- []
if sons == []:
counter += 1
if optimize:
sons = self.optimize_list(next_lvl, self.end)
else:
sons = next_lvl
next_lvl = []
optimize = True
f = Find_route(20, Tile(7,0), Tile(1,11))
print(f.find_shortest_path(optimize))
print(f.find_shortest_path())
EDIT
I added another optimization level - optimize list at any insertion of new candidate tiles, and it seems to work like a charm, for some cases:
if optimize == 2:
if sons == []:
#counter += 1
sons = self.optimize_list(next_lvl, self.end)
else:
sons = self.optimize_list(sons + next_lvl, self.end)
else:
if sons == []:
counter += 1
if optimize == 1:
sons = self.optimize_list(next_lvl, self.end)
else:
sons = next_lvl
next_lvl = []
optimize = 2
f = Find_route(20, Tile(1,4), Tile(8,18)) # from 103761 to 8 ( optimal!!! )
print(f.find_shortest_path(optimize))
print(f.find_shortest_path())
I have a problem with calculating the number-of-jumps because I don't know when to increment the counter (maybe at each check?), but it seems to at least converge faster. Also, for other cases (e.g. f = Find_route(20, Tile(1,4), Tile(8,17))) it does not improve at all (not sure if it stops...)
Don't reinvent the wheel.
Build a graph with tiles as vertices. Connect tiles with an edge if a knight can get from one tile to another in one step.
Use a standard path finding algorithm. The breadth-first search looks like the best option in you're looking for a shortest path in an unweighted graph.

How to implement the random pairs pairwise interchange and steepest descent pairwise interchange (SDPI) heuristic for an LAP?

I am struggling with these problems (codes are provided):
Using the lap.py code (and lap_h1.py and lap_h2.py as needed), implement the random pairs pairwise interchange heuristic for the LAP. Use 100,000 random pairs for your procedure.
Using the code developed as part of Problem 1, implement the steepest descent, pairwise interchange (SDPI) heuristic for the LAP.
ANY HELP WOULD BE APPRECIATED! Thank you!
#parseCSV.py
#
def parseCSV(filename, dataType='float', returnJagged=False, fillerValue=0, delimiter=',', commentChar='%'):
# define the matrix
matrix = []
# open the file
with open(filename, 'U') as csvfile :
# read all the lines
csvFile = csvfile.readlines()
maxSize = 0
# iterate through each line
for line in csvFile :
# check for comments, go to next line if this is a comment
if(line.startswith(commentChar)):
continue
# make sure it's not a blank line
if line.rstrip():
# Check for the data type (float or int)
if dataType == 'float':
row = map(float, filter(None, line.rstrip().split(delimiter)))
elif dataType == 'int':
row = map(int, filter(None, line.rstrip().split(delimiter)))
matrix.append(row)
if len(row) > maxSize :
maxSize = len(row)
# unless returnJagged is true, fill the blank values
if not returnJagged :
for row in matrix :
row += [fillerValue] * (maxSize - len(row))
if len(matrix) == 1 :
# This is a vector, just return a 1-D vector
matrix = matrix[0]
return matrix
def printMatrix(matrix):
for row in matrix:
for cell in row:
print cell,
#lap.py
#
import sys
import copy
# need to update this to point to the location of parseCSV.py
sys.path.append('c:\\svns\\code\\python')
from parseCSV import parseCSV
#
# initialize the problem - fname is the csv file with the cost matrix
#
def initialize(fname, show):
# read the costs matrix from the csv file
costs = parseCSV(fname)
# people[i] is the index of the task assigned to person i
# or -1 if the person does not have an assigned task
people = []
# tasks[j] is the index of the person assigned to task j
# or -1 if the task is unassigned
tasks = []
# create the people and tasks lists
for k in range(len(costs)):
people.append(-1)
tasks.append(-1)
if show:
print '{} people, {} tasks, {}x{} costs, lb = {:.2f}'.format(
len(people),
len(tasks),
len(costs),
len(costs),
simple_lb(costs))
return costs, people, tasks
#
# show_solution - displays the current solution
#
def show_solution(costs, people, tasks):
for k in range(len(people)):
if people[k] > -1:
task = 'T{}'.format(people[k])
cost = costs[k][people[k]]
else:
task = 'n/a'
cost = 0.0
print '\tP{}, {}, {:.2f} '.format(k, task, cost)
print '\nTotal cost: {:.2f} (lower bound: {:.2f})'.format(
calc_cost(costs, people)[0],
simple_lb(costs)
)
#
# calc_cost - calculates the current solution cost
#
def calc_cost(costs, people):
total_cost = 0
num_assigned = 0
# for each person
for k in range(len(people)):
# make sure the person has an assigned task
if people[k] != -1:
total_cost += costs[k][people[k]]
num_assigned += 1
return total_cost, num_assigned
#
# low_cost_task - finds the lowest cost available task for the
# specified person
#
def low_cost_task(costs, person, tasks):
# initialize with the biggest possible number
min_cost = 1e308
# index of the low-cost task
min_idx = -1
# loop through all tasks
for k in range(len(tasks)):
# if the task is currently unassigned
if tasks[k] == -1:
# is the task lower cost than the current minimum?
if costs[person][k] < min_cost:
min_cost = costs[person][k]
min_idx = k
return min_idx, min_cost
#
# simple_lb - calculates a simple lower bound based on low-cost assignment
#
def simple_lb(costs):
# min cost task for each person
total_cost1 = 0;
for k in range(len(costs)) :
total_cost1 += min(costs[k])
# min cost person for each task
total_cost2 = 0;
for k in range(len(costs)):
total_cost2 += min([c[k] for c in costs])
# return the better of the two bounds
return max(total_cost1, total_cost2)
#
# clear_solution - clears the incumbent solution
#
def clear_solution(people, tasks):
for k in range(len(people)):
people[k] = -1
for k in range(len(tasks)):
tasks[k] = -1
#
# store_solution
#
def store_solution(people, tasks, cost, seq):
# create the dictonary
solution = {}
# need to use copy since the lists are mutable
solution['people'] = copy.copy(people)
solution['tasks'] = copy.copy(tasks)
solution['obj_val'] = cost
solution['seq'] = copy.copy(seq)
return solution
#
# main
#
def main():
# default values
fname = 'r.csv'
# argv[0] - file name
if len(sys.argv) > 1:
fname = sys.argv[1]
# initialize
costs, people, tasks = initialize(fname, 1)
# Simple assignment - person k gets task k for all k
for k in range(len(people)):
people[k] = k;
tasks[k] = k;
print '\nSolution:'
show_solution(costs, people, tasks)
# if cmd line, execute main
if __name__ == '__main__' : main()
#lap_h1.py
#
import sys
# need to update this to point to the location of parseCSV.py
sys.path.append('c:\\svns\\code\\python')
from parseCSV import parseCSV
#
# initialize the problem - fname is the csv file with the cost matrix
#
def initialize(fname):
# read the costs
costs = parseCSV(fname)
# people[i] is the index of the task assigned to person i
# or -1 if the person does not have an assigned task
people = []
# tasks[j] is the index of the person assigned to task j
# or -1 if the task is unassigned
tasks = []
# create the people and tasks lists
for k in range(len(costs)):
people.append(-1)
tasks.append(-1)
return costs, people, tasks
#
# show_solution - displays the current solution
#
def show_solution(costs, people, tasks):
for k in range(len(people)):
if people[k] > -1:
task = 'T{}'.format(people[k])
cost = costs[k][people[k]]
else:
task = 'n/a'
cost = 0.0
print '\tP{}, {}, {:.2f} '.format(k, task, cost)
print '\nTotal cost: {:.2f} (lower bound: {:.2f})'.format(
calc_cost(costs, people)[0],
simple_lb(costs)
)
#
# calc_cost - calculates the current solution cost
#
def calc_cost(costs, people):
total_cost = 0
num_assigned = 0
# for each person
for k in range(len(people)):
# make sure the person has an assigned task
if people[k] != -1:
total_cost += costs[k][people[k]]
num_assigned += 1
return total_cost, num_assigned
#
# low_cost_task - finds the lowest cost available task for the
# specified person
#
def low_cost_task(costs, person, tasks):
# initialize with the biggest possible number
min_cost = 1e308
# index of the low-cost task
min_idx = -1
# loop through all tasks
for k in range(len(tasks)):
# if the task is currently unassigned
if tasks[k] == -1:
# is the task lower cost than the current minimum?
if costs[person][k] < min_cost:
min_cost = costs[person][k]
min_idx = k
return min_idx, min_cost
#
# simple_lb - calculates a simple lower bound based on low-cost assignment
#
def simple_lb(costs):
# min cost task for each person
total_cost1 = 0;
for k in range(len(costs)) :
total_cost1 += min(costs[k])
# min cost person for each task
total_cost2 = 0;
for k in range(len(costs)):
total_cost2 += min([c[k] for c in costs])
# return the better of the two bounds
return max(total_cost1, total_cost2)
#
# main
#
def main():
# initialize parameters then check for command line changes
show_intermediate = 1
fname = 'r.csv'
if len(sys.argv) > 1:
fname = sys.argv[1]
if len(sys.argv) > 2:
show_intermediate = int(sys.argv[2])
# initialize the data structures using the cost matrix file
costs, people, tasks = initialize(fname)
print '{} people, {} tasks, {}x{} costs, lb = {:.2f}'.format(
len(people),
len(tasks),
len(costs),
len(costs),
simple_lb(costs))
# for each person, assign their low-cost task
for k in range(len(people)):
# find the low cost task for this person
task, min_cost = low_cost_task(costs, k, tasks)
# assign task to person and person to task
people[k] = task
tasks[task] = k
# show current assignment
if show_intermediate:
if people[k] != -1:
print 'Assigned P{} task T{} at cost {:.2f}'.format(
k,
people[k],
min_cost
)
# show solution
print '\nFinal solution:'
show_solution(costs, people, tasks)
# if cmd line, execute main
if __name__ == '__main__' : main()
#lap_h2.py
#
import sys
from random import sample
import lap
#
# solve - solves the problem for a given assignment sequence
#
def solve(costs, people, tasks, seq):
total_cost = 0
for k in seq:
# find the low cost task for this person
task, min_cost = lap.low_cost_task(costs, k, tasks)
# assign task to person and person to task
people[k] = task
tasks[task] = k
total_cost += min_cost
return total_cost
#
# main
#
def main():
# default values
nreps = 100
fname = 'r.csv'
# argv[0] - file name
# argv[1] - number of replications
if len(sys.argv) > 1:
fname = sys.argv[1]
if len(sys.argv) > 2:
try:
nreps = int(sys.argv[2])
except:
print 'Invalid parameters. Syntax: lab_h2.py fname nreps'
exit()
# initialize
costs, people, tasks = lap.initialize(fname, 1)
# initial solution with the "natural" sequence
cost = solve(costs, people, tasks, range(len(people)))
# store the solution -- need to use deepcopy so the best
# incumbent solution is not overwritten
solution = lap.store_solution(people, tasks, cost, range(len(people)))
print 'Iteration {:3d} Cost: {:.2f}'.format(-1, solution['obj_val'])
# iterate
for k in range(nreps):
# clear the current assignments
lap.clear_solution(people, tasks)
# sample a random sequence
seq = sample(range(len(people)), len(people))
# solve with the random sequence
cost = solve(costs, people, tasks, seq)
print 'Iteration {:3d} Cost: {:.2f}'.format(k, cost)
# if this solution is better than the best incumbent,
# make it the best incumbent.
if cost < solution['obj_val'] :
solution = lap.store_solution(people, tasks, cost, seq)
# show solution
print '\nFinal solution:'
print 'Sequence: {}'.format(solution['seq'])
lap.show_solution(costs, solution['people'], solution['tasks'])
# if cmd line, execute main
if __name__ == '__main__' : main()
The programming community at Stack Overflow is here to try to help you with your coding errors and other queries regarding the particular programming language. Let me state it clearly, we are not here to do your homework for you.
Next time you post something, have something of your own.
Thanks,
Yedurag

Categories

Resources