itertools and random
About 18 minutes
Why this matters
Two more useful corners of the standard library: itertools builds combinations without nested loops, and random adds chance to games, simulations and sampling. Both have a trick worth knowing.
The idea
from itertools import combinations, permutations, product, chain
list(combinations("abc", 2)) # [('a','b'), ('a','c'), ('b','c')] order ignored
list(permutations("ab")) # [('a','b'), ('b','a')] order matters
list(product("ab", [1, 2])) # [('a',1), ('a',2), ('b',1), ('b',2)]
list(chain([1, 2], [3])) # [1, 2, 3]
These return iterators (lazy sequences), so wrap them in list(...) to see everything.
import random
rng = random.Random(42) # your own generator, with a seed
rng.randint(1, 6) # a whole number from 1 to 6, both included
rng.choice(["a", "b", "c"]) # one item
rng.shuffle(some_list) # reorders the list IN PLACE
The same seed always gives the same sequence. That makes code that uses chance testable: take the generator as a parameter instead of calling random directly. For passwords and tokens use the secrets module instead; random is not secure.
Try it
Print list(combinations([1, 2, 3, 4], 3)). Count the results, then use permutations and compare. Run random.Random(1).randint(1, 6) twice. Why is the answer the same?
Your turn
Write five functions:
all_pairs(items): every pair of different items as a list of tuples, without repeats in a different order:all_pairs(["a", "b", "c"])is[("a", "b"), ("a", "c"), ("b", "c")].menu_combos(mains, sides): every main with every side as text like"soup + bread", grouped by main.roll_dice(count, rng): a list ofcountdice rolls (1 to 6) using the generatorrng.shuffled_copy(items, rng): a new shuffled list. The original must not change.pick_winner(names, rng): one random name from the list.
Recap
combinations,permutationsandproductreplace nested loops; wrap results inlist().- A seeded
random.Random(seed)repeats exactly; pass it in so your code can be tested. shuffleworks in place and returnsNone.
Common mistakes
shuffled = random.shuffle(items), which setsshuffledtoNone.randint(1, 6)includes 6, butrange(1, 6)stops at 5.- Reading an iterator twice: after one pass over
combinations(...)it is empty.