Essentially I am making a probability calculator using Python. It is meant to find the probability to finding a set number of balls after performing a set number of experiments (could be a large number), where in each experiment, you draw a certain number of balls randomly from a hat. I used object oriented programming to do this under the class Hat.
A hat object can be created this way:
hat = Hat(blue=3,red=2,green=6)
The class should take a variable number of arguments that specify the number of balls of each color that are in the hat. Here the hat contains 3 blue balls, 2 red balls and 6 green balls.
Outside the class, there is a function called experiment that works out the probability of drawing certain type of balls (expected_balls) from a argument called 'hat' when you decide to draw a set number of balls (num_balls_drawn) after performing a certain number of experiments (num_experiments). The certain balls can be balls of different colors or styles. So a typical way/example of calling the experiment function is:
probability = experiment(hat=hat, expected_balls={"blue":2,"green":1}, num_balls_drawn=4, num_experiments=1000)
The probabilities produced each time the code is run should vary slightly. I was testing my code with this object and specific function call:
hat = Hat(blue=3,red=2,green=6)
probability = experiment(hat=hat, expected_balls={"blue":2,"green":1}, num_balls_drawn=4, num_experiments=1000)
While my probabilities varied slightly and produce probabilities between 0.31 and 0.39, the expected probability is actually 0.272 or values close to that probability (with a difference of 0.01). So it appears that I am far off. However, I can't quite work out what the problem is and how to fix it.
Any help will be appreciated! Thank you in advance.
THE CODE
import copy
import random
class Hat:
def __init__(self,**ball):
self.contents = list() #holds the balls
ball_and_count = list() #list for colour of ball and its count
for key, value in ball.items():
ball_and_count.append(f"{key}= {value}")
#print(ball_and_count)
for i in ball_and_count:
equal_pos = i.find("=")
ball_type = i[:equal_pos] #using string splicing to find ball type
count = int(i[equal_pos+1:])#using string splicing to find the number of balls of that type
c = 0
while c < count:
self.contents.append(ball_type)
c = c + 1
def draw(self,num)
self.num = num
c = 0 #used in a while loop
drawed_ball = list() #this is where all the balls that were drawed out from contents will stay
try:
while c < self.num:
drawed_ball.append(self.contents.pop(random.randint(0,len(self.contents)-1)))
c = c + 1
return drawed_ball
except:
return drawed_ball
def experiment(hat, expected_balls,num_balls_drawn, num_experiments):
M = 0
exp_done = 0
while exp_done < num_experiments:
drawn = 0
drawn_balls = list()
while drawn < num_balls_drawn:
dc_contents = copy.deepcopy(hat.contents) # we are creating a deep copy of hat contents so that hat.contents stays the same
drawn_balls.append(dc_contents.pop(random.randint(0,len(dc_contents)-1))) #append to the drawn_balls list
v = 0
for key, val in expected_balls.items():
if key in drawn_balls:
k = drawn_balls.count(key)
if k >= val:#here we are checking if for one ball type, we are drew the expected number of balls, then we increment the variable v
v = v + 1
if v == len(expected_balls):#we check if we have drawn the expected no. balls for all the balls and types, either we did or we did not, no in between, then we increment the variable M
M = M + 1
#incrementing the number of balls drawn
drawn = drawn + 1
exp_done = exp_done + 1 #incrementing the number of experiments drawn
N = num_experiments
prob = M / N
return prob
There's more efficient ways to approach the problem, but following your original structure:
def experiment(hat, expected_balls, num_balls_drawn, num_experiments):
exp_done = 0
M = 0
while exp_done < num_experiments:
drawn = 0
drawn_balls = list()
dc_contents = copy.deepcopy(
hat.contents) # we are creating a deep copy of hat contents so that hat.contents stays the same
while drawn < num_balls_drawn:
drawn_balls.append(
dc_contents.pop(random.randint(0, len(dc_contents) - 1))) # append to the drawn_balls list
for key, val in expected_balls.items():
if drawn_balls.count(key) < val:
break
else:
M += 1
break
drawn += 1
exp_done += 1 # incrementing the number of experiments drawn
N = num_experiments
prob = M / N
return prob
Problems that had to be fixed:
you were resetting the hat after every ball drawn, the deepcopy needs to be outside the loop
your check routine counted a success as soon as each required type was drawn
Runnable:
import copy
import random
class Hat:
def __init__(self,**ball):
self.contents = list() #holds the balls
ball_and_count = list() #list for colour of ball and its count
for key, value in ball.items():
ball_and_count.append(f"{key}= {value}")
#print(ball_and_count)
for i in ball_and_count:
equal_pos = i.find("=")
ball_type = i[:equal_pos] #using string splicing to find ball type
count = int(i[equal_pos+1:])#using string splicing to find the number of balls of that type
c = 0
while c < count:
self.contents.append(ball_type)
c = c + 1
def draw(self,num):
self.num = num
c = 0 #used in a while loop
drawed_ball = list() #this is where all the balls that were drawed out from contents will stay
try:
while c < self.num:
drawed_ball.append(self.contents.pop(random.randint(0,len(self.contents)-1)))
c = c + 1
return drawed_ball
except:
return drawed_ball
def experiment(hat, expected_balls, num_balls_drawn, num_experiments):
exp_done = 0
M = 0
while exp_done < num_experiments:
drawn = 0
drawn_balls = list()
dc_contents = copy.deepcopy(
hat.contents) # we are creating a deep copy of hat contents so that hat.contents stays the same
while drawn < num_balls_drawn:
drawn_balls.append(
dc_contents.pop(random.randint(0, len(dc_contents) - 1))) # append to the drawn_balls list
for key, val in expected_balls.items():
if drawn_balls.count(key) < val:
break
else:
M += 1
break
drawn += 1
exp_done += 1 # incrementing the number of experiments drawn
N = num_experiments
prob = M / N
return prob
hat = Hat(blue=3,red=2,green=6)
probability = experiment(hat=hat, expected_balls={"blue":2,"green":1}, num_balls_drawn=4, num_experiments=1000)
print(probability)
This prints values like 0.288 - how you'd be getting values like 8.0 is a mystery to me, but I'm fairly certain this isn't actually the code you're running. (and thus you weren't running the solution given with your code - that still has a typo and won't run on def draw(self,num) anyway)
I finally fixed it! I'm relieved. I just simply made some adjustments on my draw method in the Hat class. Then I used the draw method in the experiment function, as well as the changes that you (Grismar) mentioned. Thank you so so much!
import copy
import random
class Hat:
def __init__(self,**ball):
self.contents = list() #holds the balls
ball_and_count = list() #list for colour of ball and its count
for key, value in ball.items():
ball_and_count.append(f"{key}= {value}")
#print(ball_and_count)
for i in ball_and_count:
equal_pos = i.find("=")
ball_type = i[:equal_pos] #using string splicing to find ball type
count = int(i[equal_pos+1:])#using string splicing to find the number of balls of that type
c = 0
while c < count:
self.contents.append(ball_type)
c = c + 1
def draw(self,num):
self.num = num
c = 0 #used in a while loop
drawed_ball = list() #this is where all the balls that were drawed out from contents will stay
if self.num <= len(self.contents):
while c < self.num:
drawed_ball.append(self.contents.pop(random.randint(0,len(self.contents)-1)))
c = c + 1
return drawed_ball
else:
drawed_ball = self.contents
return drawed_ball
def experiment(hat, expected_balls, num_balls_drawn, num_experiments):
exp_done = 0
M = 0
while exp_done < num_experiments:
dc_contents = copy.deepcopy(hat) # we are creating a deep copy of hat contents so that hat.contents stays the same
drawn_balls = dc_contents.draw(num_balls_drawn)
for key,val in expected_balls.items():
if drawn_balls.count(key) < val:
break
else:
M += 1
exp_done += 1 # incrementing the number of experiments drawn
N = num_experiments
prob = M / N
return prob
Related
Title. Essentially I want to replace the characters that would be randomly generated in a 2d List I made. Here's an example, I'd like to replace 0 with 9
0 0 0 0
1 1 1 1
2 2 2 2
3 3 3 3
9 9 9 9
1 1 1 1
2 2 2 2
3 3 3 3
But I am not entirley familiar with how I am supposed to do this because I am rather new at programming.
My first attempt was to make a function with a parameter that wiuld Identify and characters that had 0 and then make a seperate funtinon that would return it and replace it with 9, but it wouldn't even start it, and I have no idea where to go from there. Any help would be
appreciated.
code:
import random
SIZE = 4
EMPTY = " "
PERSON = "P"
PET = "T"
POOP = "O"
ERROR = "!"
CLEANED = "."
MAX_RANDOM = 10
def clean(world,endRow,endColumn):
if world == POOP, POOP == CLEAN:
print("Scooping the poop")
# #args(reference to a alist of characters,integer,integer)
# Function will count the number of occurences of the character specified by the user up the specified
# end point (row/column specified by the user).
# #return(character,integer)
def count(world,endRow,endColumn):
print("Counting number of occurances of a character")
number = 0
element = input("Enter character: ")
return(element,number)
# Randomly generates an 'occupant' for a location in the world
# #args(none)
# Randomly generates and returns a character according to the specified probabilities.
# *50% probability empty space
# *20% probability of a person
# *10% probability of a pet
# *20% probability of fecal matter
# #return(character)
def createElement():
tempNum = random.randrange(MAX_RANDOM)+1
# 50% chance empty
if ((tempNum >= 1) and (tempNum <= 5)):
tempElement = EMPTY
# 20% of a person
elif ((tempNum >= 6) and (tempNum <= 7)):
tempElement = PERSON
# 10% chance of pet
elif (tempNum == 8):
tempElement = PET
# 20% chance of poop in that location (lots in this world)
elif ((tempNum >= 9) and (tempNum <= 10)):
tempElement = POOP
# In case there's a bug in the random number generator
else:
tempElement = ERROR
return(tempElement)
# Creates the SIZExSIZE world. Randomly populates it with the
# return values from function createElement
# #args(none)
# Randomly populates the 2D list which is returned to the caller.
# #return(reference to a list of characters)
def createWorld():
world = [] # Create a variable that refers to a 1D list.
r = 0
# Outer 'r' loop traverses the rows.
# Each iteration of the outer loop creates a new 1D list.
while (r < SIZE):
tempRow = []
world.append(tempRow) # Create new empty row
c = 0
# The inner 'c' loop traverses the columns of the newly
# created 1D list creating and initializing each element
# to the value returned by createElement()
while (c < SIZE):
element = createElement()
tempRow.append(element)
c = c + 1
r = r + 1
return(world)
# Shows the elements of the world. All the elements in a row will
# appear on the same line.
# #args(reference to a list of characters)
# Displays the 2D list with each row on a separate line.
# #return(nothing)
def display(world):
print("OUR WORLD")
print("========")
r = 0
while (r < SIZE): # Each iteration accesses a single row
c = 0
while (c < SIZE): # Each iteration accesses an element in a row
print(world[r][c], end="")
c = c + 1
print() # Done displaying row, move output to the next line
r = r + 1
# #args(none)
# #return(integer,integer)
def getEndPoint():
#Declaring local variables
endRow = -1
endColumn = -1
return(endRow,endColumn)
# Starting execution point for the program.
def start():
world = createWorld()
display(world)
endRow,endColumn = getEndPoint()
element,number = count(world,endRow,endColumn)
print("# occurances of %s=%d" %(element,number))
clean(world,endRow,endColumn)
display(world)
start()
I am trying to solve this question from the USACO website. Problem Link: http://www.usaco.org/index.php?page=viewproblem2&cpid=1061
Farmer John has recently expanded the size of his farm, so from the perspective of his cows it is effectively now infinite in size! The cows think of the grazing area of the farm as an infinite 2D grid of square "cells", each filled with delicious grass (think of each cell as a square in an infinite chessboard). Each of Farmer John's N cows (1≤N≤50) starts out in a different cell; some start facing north, and some start facing east.
Every hour, every cow either
Stops if the grass in her current cell was already eaten by another
cow.
Eats all the grass in her current cell and moves one cell forward
according to the direction she faces.
Over time, each cow therefore leaves a barren "rut" of empty cells behind her.
If two cows move onto the same grassy cell in the same move, they share the cell and continue moving in their respective directions in the next hour.
Please determine the amount of grass eaten by each cow. Some cows never stop, and therefore eat an infinite amount of grass.
INPUT FORMAT (input arrives from the terminal / stdin):
The first line of input contains N. Each of the next N lines describes the starting location of a cow, in terms of a character that is either N (for north-facing) or E (for east-facing) and two nonnegative integers x and y (0≤x≤1000000000, 0≤y≤1000000000) giving the coordinates of a cell. All x-coordinates are distinct from each-other, and similarly for the y-coordinates.
To be as clear as possible regarding directions and coordinates, if a cow is in cell (x,y) and moves north, she ends up in cell (x,y+1). If she instead had moved east, she would end up in cell (x+1,y).
OUTPUT FORMAT (print output to the terminal / stdout):
Print N lines of output. Line i in the output should describe the number of cells worth of grass that the ith cow in the input eats. If a cow eats an infinite amount of grass, output "Infinity" for that cow.
SAMPLE INPUT:
6
E 3 5
N 5 3
E 4 6
E 10 4
N 11 2
N 8 1
SAMPLE OUTPUT:
5
3
Infinity
Infinity
2
5
SCORING:
In test cases 2-5, all coordinates are at most 100.
In test cases 6-10, there are no additional constraints.
My logic is that since simulating the collisions would be too slow because the field is huge, we can sort the cows by their x values, iterate over all the collisions/intersections of cows and stop the ones that should be stopped, and after iterating, print out the distances of the stopped cows. And if a cow hasn't stopped, print "Infinity".
My code:
# Defining the cow class with the original order position, x, y, distance,
# and whether or not it stopped.
class Cow:
def __init__(self, i, x, y):
self.i = i
self.x = x
self.y = y
self.dist = 0
self.stopped = False
# Get input from console and split cows into east facing and north facing cows.
n = int(input().strip())
hor = []
ver = []
ans = [0] * n
for i in range(n):
line = input().strip().split()
if line[0] == 'E':
hor.append(Cow(i, int(line[1]), int(line[2])))
else:
ver.append(Cow(i, int(line[1]), int(line[2])))
hor.sort(key = lambda c: c.x)
ver.sort(key = lambda c: c.x)
# Iterate over every possible collision. Logic problem here:
for h in hor:
for v in ver:
vdist = abs(h.y - v.y)
hdist = abs(h.x - v.x)
if h.stopped and v.stopped:
continue
elif h.stopped:
if v.x >= h.x and v.x <= h.x + h.dist and v.y <= h.y:
if vdist > hdist:
v.dist = vdist
v.stopped = True
elif v.stopped:
if v.x >= h.x and h.y <= v.y + v.dist and v.y <= h.y:
if hdist > vdist:
h.dist = hdist
h.stopped = True
else:
if v.x >= h.x and v.y <= h.y:
if vdist > hdist:
v.dist = vdist
v.stopped = True
if hdist > vdist:
h.dist = hdist
h.stopped = True
# Combine the lists and put them back into the original order.
cows = hor + ver
cows.sort(key = lambda c: c.i)
# Print out all the cows' distances, and it a cow hasn't stopped, replace distance with Infinity.
for i in cows:
if not i.stopped:
i.dist = "Infinity"
print(i.dist)
I'm not sure if it's just my code that isn't correct, or if it's my basic logic. If anyone can provide a fix, it would be appreciated.
Try this revised approach, using set to add the movements and check intersection.
from collections import deque
import sys
class Cow:
def __init__(self, d, x, y, amt):
self.d = d
self.x = x
self.y = y
self.amt = amt
lines = sys.stdin.read().strip().split('\n')
n = int(lines[0])
EMPTY = set()
COW = []
for line in lines[1:]:
d, x, y = line.split()
x, y = int(x), int(y)
COW.append(Cow(d, x, y, 0))
S = set()
for i in range(n):
for j in range(n):
S.add(abs(COW[i].x - COW[j].x))
S.add(abs(COW[i].y - COW[j].y))
S2 = set()
for k in S:
S2.add(k -1)
S2.add(k)
S2.add(k + 1)
S2.add(max(S) + 1)
dq = deque(sorted(S2)) #
SCORE = [None for _ in range(n)]
t = 0
while dq:
#nt += 1
dt = dq.popleft() - t
dt = max(dt, 1)
t += dt
VOID = []
for i in range(n):
if SCORE[i] is None:
if (COW[i].x, COW[i].y) in EMPTY:
SCORE[i] = COW[i].amt
continue
VOID.append((COW[i].x, COW[i].y))
if COW[i].d == 'N': COW[i].y += dt
elif COW[i].d == 'E': COW[i].x += dt
COW[i].amt += dt
for spot in VOID: EMPTY.add(spot)
for i in range(n):
print(SCORE[i] if SCORE[i] else 'Infinity')
To keep track of your algorithm you could split 'intersection-finding' and 'cow-stopping' into separate parts.
import sys
from collections import namedtuple
Cow = namedtuple('Cow', ['distance','facing','x','y','number'])
lines = sys.stdin.read().strip().split('\n')
cows = [Cow(0,*[int(x) if x.isnumeric() else x for x in i.split()], e)
for e,i in enumerate(lines[1:])]
# finding intersections
# save if distances differ, sorted descending by distance
intersections = []
for cowA, cowB in [(cowA, cowB)
for cowB in cows if cowB.facing == 'N'
for cowA in cows if cowA.facing == 'E'
]:
if cowA.x < cowB.x and cowA.y > cowB.y:
d1, d2 = cowB.x - cowA.x, cowA.y - cowB.y
if d1 != d2:
intersections.append(
sorted([Cow(d1, *cowA[1:]),Cow(d2, *cowB[1:])], reverse=True))
# sorting intersections by larger distance
# checking if a cow reached the intersection or stopped earlier
distances = [int(10E9)] * len(cows)
for i in sorted(intersections):
if i[1].distance < distances[i[1].number] and i[0].distance < distances[i[0].number]:
distances[i[0].number] = i[0].distance
for i in distances:
print('Infinity' if i==int(10E9) else i)
Output
5
3
Infinity
Infinity
2
5
My mistake was hor.sort(key = lambda c: c.x) where I sorted the list by the 1st element instead of the 2nd.
It should be hor.sort(key = lambda c: c.y) since that's what matters at the intersections.
I do apologize if I'm not looking in the right places, but I cannot for the life of me figure out how to get a value from say
list[[1,2,3][4,5,6.01]] , list[1][2] integrated into code as anything but a list.
import random
fruits = [
['mango',7],
['apple',4],
['kiwi',6],
['grape',12],
['pear',3]
]
#Finding Probability
def setup():
fsum = 0;
prob = 0;
i = 0
#Finding the sum
while i < len(fruits):
fsum += fruits[i][1]
i += 1
i = 0
#Calculating Probability
while i < len(fruits):
prob = [fruits[i][1] / fsum]
fruits[i].append(prob)
i += 1
print(fsum)
print(fruits)
setup()
def pick(x):
rand = random.random()
index = 0
while rand > 0:
#How do I get the value of the float in the list from the next line
#(fruits[index][2])
#to be stored in a variable that I can plug into this.
#rand = rand - (var)
index+=1
pick (fruits)
Any feedback would be greatly appreciated.
Your problem is this line:
prob = [fruits[i][1] / fsum]
You are defining prob to be a list with one value, just eliminate the unnecessary list, e.g.:
prob = fruits[i][1] / fsum
Then fruits[index][2] will be the probability.
You should consider replacing your while loops with for loops, e.g.:
while i < len(fruits):
fsum += fruits[i][1]
i += 1
i = 0
Is equivalent to:
for fruit in fruits:
fsum += fruit[1]
Which could be be accomplished with a generator expression:
fsum = sum(fruit[1] for fruit in fruits)
But if what you are looking to do is just pick the fruit based on the relative weights (fruits[i][1]) then there is an easier way to do this in Py3.6, without the setup(), e.g.:
def pick(fruits):
items, weights = zip(*fruits)
return random.choices(items, weights)[0]
Prior to Py3.6 you could do:
def pick(fruits):
return random.choice([f for fruit in fruits for f in [fruit[0]]*fruit[1]])
Just access the first item of the list/array, using the index access and the index 0:
var = fruits[index][2][0]
Think of the Unit Circle x 2. What I have done is create two lists, one for x and one for y, producing 500 pairs of random (x,y). Then I created r=x2+y2 in my while loop, where r is the radius and x2=x**2 and y2=y**2. What I want to be able to do is count the number of times r=<2. I assume my if statement needs to be in the while loop, but I don't know how to actually count the number of times the condition r=<2is met. Do I need to create a list for the r values?
import random
from math import *
def randomgen(N):
rlg1=[]
rlg2=[]
a=random.randint(0,N)
b=float(a)/N
return b
i=0
rlg=[]
rlg2=[]
countlist=[]
while i<500:
x=randomgen(100)*2
y=randomgen(100)*2
x2=x**2
y2=y**2
r=x2+y2
rlg.append(x)
rlg2.append(y)
print rlg[i],rlg2[i]
i+=1
if r<=2:
import random
from math import *
def randomgen(N):
rlg1=[]
rlg2=[]
a=random.randint(0,N)
b=float(a)/N
return b
i=0
rlg=[]
rlg2=[]
countlist=[]
amount = 0
while i<500:
x=randomgen(100)*2
y=randomgen(100)*2
x2=x**2
y2=y**2
r=x2+y2
rlg.append(x)
rlg2.append(y)
print rlg[i],rlg2[i]
i+=1
if r<=2:
amount += 1
You need two counters here. One for the total number of points (i) and one for the number of points that lie within your circle r <= 2 (I'm calling this one isInside). You only want to increment the isInside counter if the point lies within your circle (r <= 2).
i = 0
rlg = []
rlg2 = []
countlist = []
isInside = 0
while i < 500:
x=randomgen(100)*2
y=randomgen(100)*2
x2=x**2
y2=y**2
r=x2+y2
rlg.append(x)
rlg2.append(y)
print rlg[i],rlg2[i]
i+=1
if r <= 2:
# increment your isInside counter
isInside += 1
Here is my code for the karger min cut algorithm.. To the best of my knowledge the algorithm i have implemented is right. But I don get the answer right. If someone can check what's going wrong I would be grateful.
import random
from random import randint
#loading data from the text file#
with open('data.txt') as req_file:
mincut_data = []
for line in req_file:
line = line.split()
if line:
line = [int(i) for i in line]
mincut_data.append(line)
#extracting edges from the data #
edgelist = []
nodelist = []
for every_list in mincut_data:
nodelist.append(every_list[0])
temp_list = []
for temp in range(1,len(every_list)):
temp_list = [every_list[0], every_list[temp]]
flag = 0
for ad in edgelist:
if set(ad) == set(temp_list):
flag = 1
if flag == 0 :
edgelist.append([every_list[0],every_list[temp]])
#karger min cut algorithm#
while(len(nodelist) > 2):
val = randint(0,(len(edgelist)-1))
print val
target_edge = edgelist[val]
replace_with = target_edge[0]
should_replace = target_edge[1]
for edge in edgelist:
if(edge[0] == should_replace):
edge[0] = replace_with
if(edge[1] == should_replace):
edge[1] = replace_with
edgelist.remove(target_edge)
nodelist.remove(should_replace)
for edge in edgelist:
if edge[0] == edge[1]:
edgelist.remove(edge)
print ('edgelist remaining: ',edgelist)
print ('nodelist remaining: ',nodelist)
The test case data is :
1 2 3 4 7
2 1 3 4
3 1 2 4
4 1 2 3 5
5 4 6 7 8
6 5 7 8
7 1 5 6 8
8 5 6 7
Please copy it in a text file and save it as "data.txt" and run the program
The answer should be :
the number of min cuts is 2 and
the cuts are at edges [(1,7), (4,5)]
This code also works.
import random, copy
data = open("***.txt","r")
G = {}
for line in data:
lst = [int(s) for s in line.split()]
G[lst[0]] = lst[1:]
def choose_random_key(G):
v1 = random.choice(list(G.keys()))
v2 = random.choice(list(G[v1]))
return v1, v2
def karger(G):
length = []
while len(G) > 2:
v1, v2 = choose_random_key(G)
G[v1].extend(G[v2])
for x in G[v2]:
G[x].remove(v2)
G[x].append(v1)
while v1 in G[v1]:
G[v1].remove(v1)
del G[v2]
for key in G.keys():
length.append(len(G[key]))
return length[0]
def operation(n):
i = 0
count = 10000
while i < n:
data = copy.deepcopy(G)
min_cut = karger(data)
if min_cut < count:
count = min_cut
i = i + 1
return count
print(operation(100))
So Karger's algorithm is a `random alogorithm'. That is, each time you run it it produces a solution which is in no way guaranteed to be best. The general approach is to run it lots of times and keep the best solution. For lots of configurations there will be many solutions which are best or approximately best, so you heuristically find a good solution quickly.
As far as I can see, you are only running the algorithms once. Thus the solution is unlikely to be the optimal one. Try running it 100 times in for loop and holding onto the best solution.
As stated by Phil, I had to run my program 100 times. And one more correction in the code was :
for edge in edgelist:
if edge[0] == edge[1]:
edgelist.remove(edge)
This part of the code did not correctly eliminate the self loops. So I had to change the code like :
for i in range((len(edgelist)-1),-1,-1):
if edgelist[i][0] == edgelist[i][1]:
edgelist.remove(edgelist[i])
And this line was not needed. since the target node would be automatically changed to self loop and it would be removed.
edgelist.remove(target_edge)
Then as said earlier, the program was looped for 100 times, and I got the minimum cut by randomization. :)
While looking at this post's answers, I came across Joel's comment. According to Karger's algorithm, the edge must be chosen uniformly at random. You can find my implementation which is based on Oscar's answer and Joel's comment below:
class KargerMinCutter:
def __init__(self, graph_file):
self._graph = {}
self._total_edges = 0
with open(graph_file) as file:
for index, line in enumerate(file):
numbers = [int(number) for number in line.split()]
self._graph[numbers[0]] = numbers[1:]
self._total_edges += len(numbers[1:])
def find_min_cut(self):
min_cut = 0
while len(self._graph) > 2:
v1, v2 = self._pick_random_edge()
self._total_edges -= len(self._graph[v1])
self._total_edges -= len(self._graph[v2])
self._graph[v1].extend(self._graph[v2])
for vertex in self._graph[v2]:
self._graph[vertex].remove(v2)
self._graph[vertex].append(v1)
self._graph[v1] = list(filter(lambda v: v != v1, self._graph[v1]))
self._total_edges += len(self._graph[v1])
self._graph.pop(v2)
for edges in self._graph.values():
min_cut = len(edges)
return min_cut
def _pick_random_edge(self):
rand_edge = randint(0, self._total_edges - 1)
for vertex, vertex_edges in self._graph.items():
if len(vertex_edges) <= rand_edge:
rand_edge -= len(vertex_edges)
else:
from_vertex = vertex
to_vertex = vertex_edges[rand_edge]
return from_vertex, to_vertex
Note, my response is in Python3 as it has been a few years since this post last received a response.
Further iterating upon #sestus' helpful answer above, I wanted to address three features:
Within the _pick_random_edge method of the class KarmgerMinCut(), rand_edge will ultimately match the length of vertex_edges. I adjusted the code to subtract 1 from rand_edge so rand_edge does not attempt to grab an element at a location 1 greater than the array length.
Understand which vertices comprise the two subgroupings representing the minimum cut. The functions implemented upon the "supervertices" dict achieve this.
Run this algorithm a large number of times (in my case, 100 times) and keep track of the smallest min_cut and its related supervertices. That's what my outside function full_karger() achieves. I am not clever enough to implement this as an internal
from random import randint
from math import log
class KargerMinCut():
# 0: Initialize graph
def __init__(self, graph_file):
# 0.1: Load graph file
self.graph = {}
self.total_edges = 0
self.vertex_count = 0
with open(graph_file, "r") as file:
for line in file:
numbers = [int(x) for x in line.split('\t') if x!='\n']
vertex = numbers[0]
vertex_edges = numbers[1:]
self.graph[vertex] = vertex_edges
self.total_edges+=len(vertex_edges)
self.vertex_count+=1
self.supervertices = {}
for key in self.graph:
self.supervertices[key] = [key]
# 1: Find the minimum cut
def find_min_cut(self):
min_cut = 0
while len(self.graph)>2:
# 1.1: Pick a random edge
v1, v2 = self.pick_random_edge()
self.total_edges -= len(self.graph[v1])
self.total_edges -= len(self.graph[v2])
# 1.2: Merge the edges
self.graph[v1].extend(self.graph[v2])
# 1.3: Update all references to v2 to point to v1
for vertex in self.graph[v2]:
self.graph[vertex].remove(v2)
self.graph[vertex].append(v1)
# 1.4: Remove self loops
self.graph[v1] = [x for x in self.graph[v1] if x != v1]
# 1.5: Update total edges
self.total_edges += len(self.graph[v1])
self.graph.pop(v2)
# 1.6: Update cut groupings
self.supervertices[v1].extend(self.supervertices.pop(v2))
# 1.7: Calc min cut
for edges in self.graph.values():
min_cut = len(edges)
# 1.8: Return min cut and the two supervertices
return min_cut, self.supervertices
# 2: Pick a truly random edge:
def pick_random_edge(self):
rand_edge = randint(0, self.total_edges-1)
for vertex, vertex_edges in self.graph.items():
if len(vertex_edges) < rand_edge:
rand_edge -= len(vertex_edges)
else:
from_vertex = vertex
to_vertex = vertex_edges[rand_edge-1]
return from_vertex, to_vertex
# H.1: Helpful young man who prints our graph
def print_graph(self):
for key in self.graph:
print("{}: {}".format(key, self.graph[key]))
graph = KargerMinCut('kargerMinCut.txt')
def full_karger(iterations):
graph = KargerMinCut('kargerMinCut.txt')
out = graph.find_min_cut()
min_cut = out[0]
supervertices = out[1]
for i in range(iterations):
graph = KargerMinCut('kargerMinCut.txt')
out = graph.find_min_cut()
if out[0] < min_cut:
min_cut = out[0]
supervertices = out[1]
return min_cut, supervertices
out = full_karger(100)
print("min_cut: {}\nsupervertices: {}".format(out[0],out[1]))
I totally agree with the above answers. But when I run your code with Python 3.x, it turns out to produce Code 1. This code sometimes works, but sometimes fails. In fact, as #user_3317704 mentioned above, there is a mistake in your code:
for edge in edgelist:
if edge[0] == edge[1]:
edgelist.remove(edge)
You should not change the items of the list when you do a for-do. It raises mistakes. For your reference, it could be
newEdgeList = edgeList.copy();
for edge in edgeList:
if edge[0] == edge[1]:
newEdgeList.remove(edge);
edgeList = newEdgeList;