2
\$\begingroup\$

Like title says programs shows you the number of streaks and how many of each. Is there anyway to break this down. Feels like it's too many variables.

numberOfStreaks = 0
flip = []
numofT = 0
numofH = 0
tStreak = 0
hStreak = 0
for experimentNumber in range(20):
    if random.randint(0, 1) == 0:
        flip.append('H') # Add a living cell.
        hStreak +=1
        tStreak = 0
    else:
        flip.append('T')
        tStreak += 1
        hStreak = 0
    if tStreak == 3:
          numberOfStreaks += 1
          numofT +=1
          tStreak, hStreak = 0, 0
    elif hStreak == 3:
         numberOfStreaks += 1
         tStreak, hStreak = 0, 0
         numofH += 1
\$\endgroup\$

1 Answer 1

5
\$\begingroup\$

Organize code in functions. This is the most important point. Make it a personal rule to put all code in functions. It's easy to do, even if you don't fully appreciate all of the benefits yet. But with that simple rule in place, you now have a mechanism to organize the code better.

When feasible, do one thing at a time. Don't generate data (eg, flip coins) and try to perform multiple kinds of data analysis (counting head/tails, counting streaks, etc) at the same time. It's too much complexity. Break things down. For example, one function can focus solely on generating the coin flips.

from random import randint

def coin_flips(n):
    return [randint(0, 1) for _ in range(n)]

If you need to count things, consider using a Counter. Super easy.

from collections import Counter

def tally_flips(flips):
    return Counter(flips)

If you need to group things, consider using groupby. If given no argument other than data values, groupby() will organize the data based on equality – exactly what you need to find streaks of any size.

from itertools import groupby

def find_streaks(flips):
    return [list(group) for _, group in groupby(flips)]

Find just the big streaks. That's just another function.

def find_big_streaks(streaks, size = 3):
    return [s for s in streaks if len(s) >= size]

Orchestration. Again, another function.

import sys

def main(args):
    flips = coin_flips(20)
    tally = tally_flips(flips)
    streaks = find_streaks(flips)
    bigs = find_big_streaks(streaks)
    print(flips, tally, streaks, bigs, sep = '\n')

if __name__ == '__main__':
    main(sys.argv[1:])

Should one really make such tiny functions? Maybe not. It depends on the context. Will you need to re-use any of the functionality somewhere else in the program? If so, a separate function, even a tiny one, can be a good idea. If not, it might make more sense to ditch the function and move the single line of code back to the calling location. If we do that in the case at hand, we end up with the code below. Even though it contains only a single function, it still retains the benefits of a functional mindset because we are taking care to separate the tasks. It's very easy to read code like this because each step is distinct, small, and simple.

def main(args):
    flips = [randint(0, 1) for _ in range(20)]
    tally = Counter(flips)
    streaks = [list(group) for _, group in groupby(flips)]
    bigs = [s for s in streaks if len(s) >= 3]
    print(flips, tally, streaks, bigs, sep = '\n')
\$\endgroup\$
1
  • 2
    \$\begingroup\$ Another benefit of creating functions is that you have to give them a name that describes what the function does. In a way, this automatically documents the code. Instead of looking at some list comprehension, you can now see "ah, this is for finding streaks!" \$\endgroup\$
    – G. Sliepen
    Commented Aug 18, 2023 at 5:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.