Trying to Solve Monty Hall in Python - python

I'm trying to understand this solution of the Monty Hall problem, I understand most of the code, but am stuck on two pieces.
Below is the code, but specifically I'm stuck on these two parts
result[bad] = np.random.randint(0,3, bad.sum())
and the entire switch_guess function.
If anyone could explain in plain English for me that would be awesome.
#Simulates picking a prize door
def simulate_prizedoor(nsim):
return np.random.randint(0,3,(nsim))
#Simulates the doors guessed
def simulate_guesses(nsim):
return np.zeros(nsim, dtype=np.int)
#Simulates the "game host" showing whats behind a door
def goat_door(prize_doors, guesses):
result = np.random.randint(0,3, prize_doors.size)
while True:
bad = (result == prize_doors) | (result == guesses)
if not bad.any():
return result
result[bad] = np.random.randint(0,3, bad.sum())
#Used to change your guess
def switch_guess(guesses, goat_doors):
result = np.zeros(guesses.size)
switch = {(0, 1): 2, (0, 2): 1, (1, 0): 2, (1, 2): 1, (2, 0): 1, (2, 1): 0}
for i in [0,1,2]:
#print "i = ", i
for j in [0,1,2]:
#print "j = ", j
mask = (guesses == i) & (goat_doors == j)
#print "mask = ", mask
if not mask.any():
continue
result = np.where(mask, np.ones_like(result) * switch[(i, j)], result)
return result
#Calculates the win percentage
def win_percentage(guesses, prizedoors):
return 100 * (guesses == prizedoors).mean()
#The code to pull everything together
nsim = 10000
#keep guesses
print "Win percentage when keeping original door"
print win_percentage(simulate_prizedoor(nsim), simulate_guesses(nsim))
#switch
pd = simulate_prizedoor(nsim)
guess = simulate_guesses(nsim)
goats = goat_door(pd, guess)
guess = switch_guess(guess, goats)
print "Win percentage when switching doors"
print win_percentage(pd, guess)

… specifically I'm stuck on these two parts
result[bad] = np.random.randint(0,3, bad.sum())
Let's break this down into pieces. It may help to reduce that 10000 to something small, like 5, so you can print out the values (either with print calls, or in the debugger) and see what's going on.
When we start this function, prize_doors is going to have 5 random values from 0 to 2, like 2 2 0 1 2, and guesses will have 5 values, all 0, like 0 0 0 0 0. result will therefore start off with 5 random values from 0 to 2, like 0 2 2 0 1.
Each first time through the loop, bad will be a list of 5 bool values, which are each True if the corresponding value in result matches the corresponding value in either prize_doors or guesses. So, in this example, True True False True False, because guess #1 matches prize_doors, and guesses #0 and #3 match goats.
Unfortunately, we're just going to go around that loop forever, because there's nothing inside the loop that modifies result, and therefore bad is going to be the same forever, and doing the same check forever is always going to return the same values.
But if you indent that result[bad] = … line so it's inside the loop, that changes everything. So, let's assume that's what you were supposed to do, and you just copied it wrong.
When treated as numbers, True and False have values 1 and 0, respectively. So, bad.sum() is a count of how many matches there were in bad—in this case, 3.
So, np.random.randint(0, 3, bad.sum()) picks 3 random values from 0 to 2, let's say 1 0 1.
Now, result[bad] selects all of the elements of result for which the corresponding value in bad is True, so in this example it's result[0], result[1], and result[3].
So we end up assigning that 1 0 1 to those three selected locations, so result is now 1 0 2 1 1.
So, next time through the loop, bad is now True False False False False. We've still got at least one True value, so we run that result[bad] = line again. This time, bad.sum() is 1, so we pick 1 random value, let's say 0, and we then assign that 1 value to result[0], so result is now 0 0 2 1 1.
The next time through, bad is now False False False False False, so bad.any() is False, so we're done.
In other words, each time through, we take all the values that don't match either the prize door or the goat door, and pick a new door for them, until finally there are no such values.

It also confused me, until 5 mins ago when I finally figured it out.
Since the first question has been solved, I will only talk about the second one.
The intuition goes like this : given a sequence of (guesses, goatdoors),in the (i,j) loop, there are always some simulation (e.g., simulation[0] and simulation[5]) that 'hit' by the (i,j), that is the say, the 0th and 5th simulation have guess i and goatdoor j.
Variable mask record 0 and 5 in this example. Then result in 0th and 5th can be decided, because in these simulation, the only possible door to switch to is determined by i and j. So np.where refreshes result in these simulation, leave other simulations unchanged.
Intuition is above. You need to know how np.where work if you want to know what I'm talking about. Good luck.

Related

Python - Probability of 6 or more Tails/Heads Code

I assigned my students in comp sci to write a code that simulates flipping a coin 100 times, storing the results in a list. Look for a streak of 6 heads ( or more ), or 6 tails ( or more ). If you find a streak, then consider the trial a success. Repeat this experiment 10,000 times. Use this to determine the probability of finding a streak of 6 heads or 6 tails.
Theoretically this probability should be ~80%.
EDIT: It is possible I misinterpreted this theoretical probability. I found this probability here: https://math.stackexchange.com/questions/2736117/what-is-the-probability-of-getting-6-or-more-heads-or-tails-in-a-row-after-flipp
My code is giving me a probability of about 54%, the probability of getting exactly 6 in a row. However, if I got 7, 8, 9, or more in a row, my code should mark this as a success, correct?
I understand my code checks for steaks of 6, but if there is a streak of 7, 8, 9, ... it would still mark it as a success. There must be something I'm missing here...
Attached is my code:
import random
numberofstreaks = 0
for experimentnumber in range(10000):
result = []
for i in range(100):
flip = random.randint(0,1)
result.append(flip)
for i in range(len(result)-6):
if result[i:i+6] == ([0,0,0,0,0,0] or [1,1,1,1,1,1]):
numberofstreaks += 1
break
print(numberofstreaks)
print('Chance of steak:',(numberofstreaks/100))
Note: They are currently learning about lists, which is why their code must contain the use of lists.
Thanks ahead of time!
A slight correction:
import random
numberofstreaks = 0
for experimentnumber in range(10000):
result = []
for i in range(100):
flip = random.randint(0,1)
result.append(flip)
for i in range(len(result)-6):
if result[i:i+6] == [0,0,0,0,0,0] or result[i: i+6] == [1,1,1,1,1,1]:
numberofstreaks += 1
break
print(numberofstreaks)
print('Chance of steak:',(numberofstreaks/100))
The answer is now 80.22%. (A or B) returns A, if A cannot be deduced to be false, otherwise B. A list of all 0s isn't False. So you were only checking for streaks of 0s.
From what I understood, your code doesn't account for multiple streaks in a given 100 throws of a coin.
You can do that by shifting i by 6 places once it has found a streak so that it doesn't count a >6 streak as multiple streaks.

If, else return else value even when the condition is true, inside a for loop

Here is the function i defined:
def count_longest(field, data):
l = len(field)
count = 0
final = 0
n = len(data)
for i in range(n):
count = 0
if data[i:i + l] is field:
while data[i - l: i] == data[i:i + l]:
count = count + 1
i = i + 1
else:
print("OK")
if final == 0 or count >= final:
final = count
return final
a = input("Enter the field - ")
b = input("Enter the data - ")
print(count_longest(a, b))
It works in some cases and gives incorrect output in most cases. I checked by printing the strings being compared, and even after matching the requirement, the loop results in "OK" which is to be printed when the condition is not true! I don't get it! Taking the simplest example, if i enter 'as', when prompted for field, and 'asdf', when prompted for data, i should get count = 1, as the longest iteration of the substring 'as' is once in the string 'asdf'. But i still get final as 0 at the end of the program. I added the else statement just to check the if the condition was being satisfied, but the program printed 'OK', therefore informing that the if condition has not been satisfied. While in the beginning itself, data[0 : 0 + 2] is equal to 'as', 2 being length of the "field".
There are a few things I notice when looking at your code.
First, use == rather than is to test for equality. The is operator checks if the left and right are referring to the very same object, whereas you want to properly compare them.
The following code shows that even numerical results that are equal might not be one and the same Python object:
print(2 ** 31 is 2 ** 30 + 2 ** 30) # <- False
print(2 ** 31 == 2 ** 30 + 2 ** 30) # <- True
(note: the first expression could either be False or True—depending on your Python interpreter).
Second, the while-loop looks rather suspicious. If you know you have found your sequence "as" at position i, you are repeating the while-loop as long as it is the same as in position i-1—which is probably something else, though. So, a better way to do the while-loop might be like so:
while data[i: i + l] == field:
count = count + 1
i = i + l # <- increase by l (length of field) !
Finally, something that might be surprising: changing the variable i inside the while-loop has no effect on the for-loop. That is, in the following example, the output will still be 0, 1, 2, 3, ..., 9, although it looks like it should skip every other element.
for i in range(10):
print(i)
i += 1
It does not effect the outcome of the function, but when debugging you might observe that the function seems to go backward after having found a run and go through parts of it again, resulting in additional "OK"s printed out.
UPDATE: Here is the complete function according to my remarks above:
def count_longest(field, data):
l = len(field)
count = 0
final = 0
n = len(data)
for i in range(n):
count = 0
while data[i: i + l] == field:
count = count + 1
i = i + l
if count >= final:
final = count
return final
Note that I made two additional simplifications. With my changes, you end up with an if and while that share the same condition, i.e:
if data[i:i+1] == field:
while data[i:i+1] == field:
...
In that case, the if is superfluous since it is already included in the condition of while.
Secondly, the condition if final == 0 or count >= final: can be simplified to just if count >= final:.

Is there a way to increment the iterator if an 'if' condition is met

I'm solving this HackerRank challenge:
Alice has a binary string. She thinks a binary string is beautiful if and only if it doesn't contain the substring '010'.
In one step, Alice can change a 0 to a 1 or vice versa. Count and print the minimum number of steps needed to make Alice see the string as beautiful.
So basically count the number of '010' occurrences in the string 'b' passed to the function.
I want to increment i by 2 once the if statement is true so that I don't include overlapping '010' strings in my count.
And I do realize that I can just use the count method but I wanna know why my code isn't working the way I want to it to.
def beautifulBinaryString(b):
count = 0
for i in range(len(b)-2):
if b[i:i+3]=='010':
count+=1
i+=2
return count
Input: 0101010
Expected Output: 2
Output I get w/ this code: 3
You are counting overlapping sequences. For your input 0101010 you find 010 three times, but the middle 010 overlaps with the outer two 010 sequences:
0101010
--- ---
---
You can't increment i in a for loop, because the for loop construct sets i at the top. Giving i a different value inside the loop body doesn't change this.
Don't use a for loop; you could use a while loop:
def beautifulBinaryString(b):
count = 0
i = 0
while i < len(b) - 2:
if b[i:i+3]=='010':
count += 1
i += 2
i += 1
return count
A simpler solution is to just use b.count("010"), as you stated.
If you want to do it using a for loop, you can add a delta variable to keep track of the number of positions that you have to jump over the current i value.
def beautifulBinaryString(b):
count = 0
delta = 0
for i in range(len(b)-2):
try:
if b[i+delta:i+delta+3]=='010':
count+=1
delta=delta+2
except IndexError:
break
return count
You don't need to count the occurrences; as soon as you find one occurrence, the string is "ugly". If you never find one, it's beautiful.
def is_beautiful(b):
for i in range(len(b) - 2):
if b[i:i+3] == '010':
return False
return True
You can also avoid the slicing by simply keeping track of whether you've started to see 010:
seen_0 = False
seen_01 = False
for c in b:
if seen_01 and c == '0':
return False
elif seen_1 and c == '1':
seen_01 = True
elif c == '0':
seen_0 = True
else:
# c == 1, but it doesn't follow a 0
seen_0 = False
seen_01 = False
return True

Python while sampling

I'm searching to make a loop to go throw samples till I reach the result. Example:
i=(list(np.random.randint(2, size=5)),list(np.random.randint(2, size=2)),list(np.random.randint(2, size=7)),list(np.random.randint(2, size=7)),list(np.random.randint(2, size=9)),list(np.random.randint(2, size=8)),list(np.random.randint(2, size=7)))
tot=0
for j in range(0,len(anytable)):
if resp(i,(anytable.iloc[j,0:7].tolist())): #this function "resp" gives me True and False and add 1 to variable "tot"
tot=tot+1
Now I want to stop till I have to tot>=100 .
So I have to generate many "i" samples lists till I get to tot>=100.
How can I do this?
Thank you very much
From guessing I would say, this could be your solution.
j, tot = 1, 1
while j<len(anytable) and tot<100 :
tot += int( resp(i,(anytable.iloc[j,0:7].tolist())) )
j += 1
The condition is false if one of the inequations is false. The incementing operator += on tot is adding the integer representation of a boolean True = 1 or False = 0 to the value of tot.

Restart loop and add values in python?

The problem:
There are 3 paths where only one leads home.
The first path gets you lost for 3 days, then you're back to the beginning where you have to pick another path.
The second path gets you lost for 2 days, then you're back at the beginning and you have to pick another path.
The last door leads you home in 1 day.
Basically you keep going until you pick the last path. I'm trying to find the average time it takes to get home by simulating 1000 tries.
Here is what I have thus far:
days=0
for i in range(1000):
door=["a","b","c"]
numpy.random.choice(path)
if numpy.random.choice(path)=="a":
days=+2
if numpy.random.choice(path)=="b":
days=+3
if numpy.random.choice(path)=="c":
days=+1
print(steps)
As is, my code will just print out a value from 1-3 as the days.
I'm having trouble figuring out how to pick one and then accumulate it into the days and then restarting the loop until it picks path C.
I've done research and think a while loop might work but I don't know how to apply that.
You can use a while loop that keeps iterating while you are stuck and then when door 'a' is selected it adds the 1 to get home but then the person is no longer stuck so it drops out of the while loop. Then before entering the while loop again, just set stuck = True and the process continues always adding to the total number of days, then at the end just take the average.
import numpy
days=0
door=["a","b","c"]
N = 1000
for i in range(N):
stuck = True
while stuck:
if numpy.random.choice(door)=="a":
days += 2
if numpy.random.choice(door)=="b":
days += 3
if numpy.random.choice(door)=="c":
days += 1
stuck = False
print('Average number of days taken to get home: ', days / N)
I hope this helps!
Here's the code you're looking for:
import numpy
def runOnce():
days = 0
door=["a","b","c"]
while(True):
path = numpy.random.choice(door)
if path=="a":
days+=2
if path=="b":
days+=3
if path=="c":
days+=1
return days
total = 0
for i in range(1000):
total += runOnce()
print(total / 1000.0)
This code must solve your problem:
import random
doors = ['a', 'b', 'c']
total_days = 0
runs = 1000
for i in range(runs):
days = 0
choice = None
while choice != 'c':
choice = random.choice(doors)
if choice == 'a':
days += 2
if choice == 'b':
days += 3
if choice == 'c':
days += 1
total_days += days
avg_days = total_days / runs
print(avg_days)
I'm not quite sure on your rules, but this is my attempt
import numpy as np
def choose_path():
p =np.random.randint(3)
#print("Path = {}".format(p))
return p
N = 100000
days=0.0
for i in range(N):
#make sure you don't take the wrong path twice
taken = [False, False, False]
path = choose_path()
while(path != 2):
if(path==0):
if(not(taken[path])):
taken[path] = True
days += 2.0
if(path==1):
if(not(taken[path])):
taken[path] = True
days += 3.0
path = choose_path()
days += 1.0
# print("Days = {}".format(days))
print("Average for {} iterations = {}".format(N, days/N))
In contrast to the some of the other codes my guy doesn't take the same route twice. I'm not sure how you problem is defined. My solution seems to be 3.5.
Some of the mistakes you made are:
=+ is an assignment of a positive number a = +3 or a = -3
+= is an increment a = a + 3 <=> a += 3
you define door, but never use it
you never define steps but you use it
I think you should come up with an algorithm first and then implement it.
There are a few problems with your code. For example, you define a door list of possible choices, but then you pass path to the choice function. At the end of your program you print steps, but that's not defined anywhere. Instead, you should be printing days, or days / 1000. You need to pay attention to things like that when you're programming!
As others have shown, you need to do this with two loops. Each iteration of the outer loop performs a trial. The inner loop chooses paths until you get home and adds the day counts to the current total.
In your code, each if test generates a fresh random choice on top of the one you make at the start of the loop. That's not right. Just make the choice at the top of the loop, determine how many days to add to the count, and if you're home, break out of the loop.
We can do this in a simpler way. Rather than choosing from 'a', 'b', or 'c', just choose from 1, 2, or 3, the number of days each path takes. And as I said earlier, there's no need to use Numpy for this, we can just call the random module functions directly instead of letting Numpy do it on our behalf.
Here's a short demo.
from random import randint
trials = 10000
days = 0
for n in range(trials):
while True:
path = randint(1, 3)
days += path
if path == 1:
break
print(days, days / trials)
typical output
59996 5.9996
We can get a more accurate estimate to the true expected time by performing multiple runs and averaging the results. We could do that by wrapping the previous code in an extra loop, but it makes the code more readable if instead we wrap the old code in a function, and call that function in a loop. Like this:
from random import randint
def sim(trials):
days = 0
for n in range(trials):
while True:
path = randint(1, 3)
days += path
if path == 1:
break
return days
num = 10
trials = 10000
total = 0
for i in range(num):
days = sim(trials)
x = days / trials
print(i, x)
total += x
print('Final', total / num)
typical output
0 5.9732
1 6.007
2 6.0555
3 5.9943
4 5.9964
5 5.9514
6 6.0689
7 6.0457
8 5.9859
9 5.9685
Final 6.00468
It looks like the true expected value is 6 days. Actually, it's not hard to show that mathematically.
Let d equal the expected number of days to get home. 1/3 of the time we get home in 1 day, 1/3 of the time we get back to the start in 2 days and so we still have d days before we get home, and 1/3 of the time we get back to the start in 3 days and so, once again we still have d days before we get home.
We can put that into an equation:
d = (1/3)*1 + (1/3)*(2 + d) + (1/3)*(3 + d)
3*d = 1 + 2 + d + 3 + d
3*d = 6 + 2*d
d = 6

Categories

Resources