Ruud
Ruud

Reputation: 23

Checking a series of coin tosses for a sequence of 3 in python

I am trying to create a script that flips a coin untill either "heads" is flipped 3 times in a row, or " tails" is flipped 3 times in a row.

My attempt is a quite long snippet of code that does not do what I want it to do. It simply prints out "heads" once and loops on forever:

import random

cointosses = []
total_count = 0

while total_count >= 0:
    tosses = random.randint(1,2)
    total_count += 1
    if tosses == 1:
        cointosses.append("heads")
    if tosses == 2:
        cointosses.append("tails")
    print(cointosses)
    seq_counter1 = 0
    seq_counter2 = 0
    total_seq = 0
    while total_seq <= 3:
        check1 = "heads"
        check2= "tails"
        for toss in cointosses:
            if toss == check1:
                seq_counter1 +=1 
                seq_counter2 = 0
                if seq_counter1 == 3:
                    total_seq = 3
                    break
            if toss == check2:
                seq_counter1 = 0
                seq_counter2 +=1
                if seq_counter2 == 3:
                    total_seq = 3
                    break

    if total_seq == 3:
        break

I'm sure there is some much simpler way of doing this, but I can't seem to figure it out.

Upvotes: 1

Views: 1205

Answers (2)

RobinW
RobinW

Reputation: 322

There are a few problems regarding this code:

1. This code produces an infinite loop:

The inner while loop only terminates if the variable total_seq contains a value greater than 3. Since the only values that could possibly be assigned to it are 0 and 3 (according to your code), this while loop will go on forever.

...
total_seq = 0 #<-----------------------
while total_seq <= 3:
    ...
    for toss in cointosses:
        if toss == check1:
            ...
            if seq_counter1 == 3:
                total_seq = 3 #<-----------------------
                break
        if toss == check2:
            ...
            if seq_counter2 == 3:
                total_seq = 3 #<-----------------------
                break
...

2. You only coinflip once at the beginning and that use this result over and over again

random.randint(...) gives you one value which is stored in the cointosses list (which means: you only flip the coin once). The inner for loop assumes however, that you have a large number of tosses already stored in the list. It only sets total_seq to 3 if it can find 3 consecutive coinflips.

Otherwise it will just repeat the inner while loop and do the same thing again without adding new coinflips (outer while is never reached again)

tosses = random.randint(1,2)
...
if tosses == 1:
    cointosses.append("heads")
if tosses == 2:
    cointosses.append("tails")
...
    for toss in cointosses:
        ...
            if seq_counter1 == 3:
                total_seq = 3
                break
        ...
            if seq_counter2 == 3:
                total_seq = 3
                break
...

3. The sequence counters seq_counter1 and seq_counter2 will only reset if the previous coinflip had a different result.

Since you only coinflip once (as discussed in problem 2) the "previous coinflip" is always just the first one you did. This means that you coinflip once at the beginning and either increment seq_counter1 or seq_counter2 to 3 depending on the result of that first flip.

...
seq_counter1 = 0
seq_counter2 = 0
...
while total_seq < 3:
    ...
        if toss == check1:
            seq_counter1 +=1
            seq_counter2 = 0
            ...
        if toss == check2:
            seq_counter1 = 0
            seq_counter2 +=1
            ...
...

Solution

All three problems can be solved by removing the inner while loop and simply execute its code in the outer one:

import random
cointosses = []
total_count = 0
while total_count >= 0:
    tosses = random.randint(1,2)
    total_count += 1
    if tosses == 1:
        cointosses.append("heads")
    if tosses == 2:
        cointosses.append("tails")
    print(cointosses)
    seq_counter1 = 0
    seq_counter2 = 0
    total_seq = 0
    check1 = "heads"
    check2= "tails"
    for toss in cointosses:
        if toss == check1:
            seq_counter1 +=1
            seq_counter2 = 0
            if seq_counter1 == 3:
                total_seq = 3
                break
        if toss == check2:
            seq_counter1 = 0
            seq_counter2 +=1
            if seq_counter2 == 3:
                total_seq = 3
                break

    if total_seq == 3:
        break

This works because the condition total_seq == 3 is already tested by the last if statement in the outer loop.

Conclusion

This code however is not verry perfomant since you build a list and iterate over it again and again. You are iterating over everything every time you append one cointoss. However if you think about it: You only need to check if the newly appended element creates a consecutive row.

If you want to do this right, you should do it with only one loop (no nested loops) :)

Upvotes: 1

Patrick Artner
Patrick Artner

Reputation: 51633

You do never leave your while loop that checks your list. The break statements only leaves the for-loop (setting total_seq = 3) - your while loops until total_seq is greater then 3 -> endless loop:

while total_seq <= 3:        # this is never been left because <= 3
    check1 = "heads"         #                                 ^^ smaller equal
    check2= "tails"
    for toss in cointosses:
        if toss == check1:
            seq_counter1 +=1 
            seq_counter2 = 0
            if seq_counter1 == 3:
                total_seq = 3
                break              # breaks out of the for but total_seq = 3 so in while
        if toss == check2:
            seq_counter1 = 0
            seq_counter2 +=1
            if seq_counter2 == 3:
                total_seq = 3
                break              # breaks out of the for but total_seq = 3 so in while

You can simplify your code a lot by simply adding to the list and checking if the last 3 elements are equal instead of checking the whole list every time:

import random

def toss():
    """Return randomly 'heads' or 'tails'."""
    return "heads" if (random.randint(1,2) == 1) else "tails"

# need at least 3 tosses to finish
cointosses = []
for _ in range(3):
    cointosses.append(toss())
    print(cointosses)

# repeat until the set(..) of the last 3 elements contains exactly 1 item 
while not len(set(cointosses[-3:]))==1:
    cointosses.append(toss())
    print(cointosses)

print(f"It took {len(cointosses)} tosses to get 3 equal ones.")

Output 2 runs:

['tails']
['tails', 'tails']
['tails', 'tails', 'heads']
['tails', 'tails', 'heads', 'heads']
['tails', 'tails', 'heads', 'heads', 'heads']
It took 5 tosses to get 3 equal ones.

['tails']
['tails', 'tails']
['tails', 'tails', 'heads']
['tails', 'tails', 'heads', 'heads']
['tails', 'tails', 'heads', 'heads', 'tails']
['tails', 'tails', 'heads', 'heads', 'tails', 'heads']
['tails', 'tails', 'heads', 'heads', 'tails', 'heads', 'tails']
['tails', 'tails', 'heads', 'heads', 'tails', 'heads', 'tails', 'tails']
['tails', 'tails', 'heads', 'heads', 'tails', 'heads', 'tails', 'tails', 'heads']
['tails', 'tails', 'heads', 'heads', 'tails', 'heads', 'tails', 'tails', 'heads', 'heads']
['tails', 'tails', 'heads', 'heads', ... snipp ..., 'tails', 'heads', 'heads', 'tails']
['tails', 'tails', 'heads', 'heads', ... snipp ..., 'heads', 'heads', 'tails', 'tails']
['tails', 'tails', 'heads', 'heads', ... snipp ..., 'heads', 'tails', 'tails', 'tails']
It took 13 tosses to get 3 equal ones.

If you dislike set() you could also check:

while not all(i == cointosses[-1] for i in cointosses[-3:-1]):
    # rest identical

Doku:

Upvotes: 2

Related Questions