Naloge - generatorji

Pri loteriji so lansirali novo igro. Ker proizvajalec ni pravočasno dostavil bobna za žreb, so se odločili, da za žreb napišejo kar generator v Pythonu. To nalogo so zaupali Jaki. Ker se ta še ni naučil, kako generirati naključna števila, se je odločil, da bo števila zgeneriral tako, da vzame prvih 6 števil in vsako izmed njih pomnoži s 3. Torej, generator bo generiral naslednje zaporedje: 3, 6, 9, 12, 15, 18. Jaka si je sicer pogledal YouTube tutorial o generatorjih, vendar še vedno ne ve točno, kako bi ga napisal, zato rabi pomoč. Napiši funkcijo loto_generator_zaporedna(), ki vrne generator opisan zgoraj.

Primer klica generatorja:

>>> list(loto_generator_zaporedna())
[3, 6, 9, 12, 15, 18]

Rešitev

def loto_generator_zaporedna():
    return (x * 3 for x in range(1, 7))

Da boš lahko preverjal pravilnost rešitve, napiši funkcijo izpisi_izzrebane(generator), ki prejme generator in izpiše izžrebane številke ločene z vejico in presledkom v konzolo.

Rešitev

def izpisi_izzrebane(generator):
    print(", ".join(str(x) for x in generator))

Po prvem žrebu se je sodelavcu Vidu zdel rezultat žreba nekoliko sumljiv. Stopil je do Jake in skupaj sta preverila program. Vid je Jaki predlagal, da uporabi funkcijo randint iz modula random, s katero lahko zgenerira naključna števila.

Napiši funkcijo loto_generator_nakljucna(), ki vrne generator, ki zgenerira 6 naključnih števil med 1 in 20 vključno.

Rešitev

def loto_generator_nakljucna():
    return (random.randint(1, 20) for _ in range(6))

Ker po 100 žrebih še nihče ni zadel glavne nagrade, so pri loteriji začeli razmišljati, kaj bi bil razlog za to. Ugotovili so, da je verjetnost, da so izbrane številke na listku dejansko izžrebane 1/64 000 00 -- vsaka številka je lahko na listku izbrana večkrat in pomemben je vrstni red izbranih števil.

Da bi nekoliko povečali to verjetnost, so se odločili spremeniti sistem. Vsak sodelujoč sedaj izbere 6 različnih številk, vrstni red izbranih števil pa ni pomemben. Za glavni dobitek morajo biti izžrebane vse številke na listku ne glede na vrstni red.

Napiši funkcijo loto_generator_brez_ponavljanja(), ki generira številke tako, da se te ne ponovijo. Namig: če je bila številka že izžrebana, generiraj novo, oziroma generiraj nove toliko časa, da dobiš še ne-izžrebano številko.

Rešitev

import random


def loto_generator_brez_ponavljanja():
    izzrebane = set()
    for i in range(6):
        stevilka = random.randint(1, 20)
        while stevilka in izzrebane:
            stevilka = random.randint(1, 20)
        izzrebane.add(stevilka)
        yield stevilka

ali nekoliko krajša rešitev

import random


def loto_generator_brez_ponavljanja():
    izzrebane = set()
    while len(izzrebane) < 6:
        stevilka = random.randint(1, 20)
        if stevilka not in izzrebane:
            izzrebane.add(stevilka)
            yield stevilka

Zadnja sprememba je povzročila, da je že v naslednjem krogu nekdo zadel glavno nagrado. Zaposleni v loteriji so se takrat odločili, da preverijo verjetnost za glavni dobitek. Verjetnost, da so številke na listku izžrebane, je sedaj 1/38 760. Ker se jim je zdela ta nekoliko prevelika, so se odločili, da dodajo dodatno (sedmo) številko. Ta je izžrebana kot zadnja in je lahko enaka katerikoli od prej izžrebanih številk. Generatorju dodaj žrebanje dodatne številke. Napiši funkcijo loto_generator_dodatna(), ki vrne tak generator, pri tem si pomagaj z generatorjem loto_generator_brez_ponavljanja.

Rešitev

def loto_generator_dodatna():
    yield from loto_generator_brez_ponavljanja()
    yield random.randint(1, 20)

Pri loteriji pripravljajo še drugo igro, kjer bodo žrebali števila med 0 in 9. Ta se lahko ponavljajo. Da bo igra bolj zanimiva, bodo žrebali toliko števil, kolikor je ostanek pri deljenju dneva v mesecu z 8, vendar nikoli manj kot 5.

Da bo to mogoče, najprej pripravi generator. Napiši funkcijo loto_generator_neskoncni, ki vrne generator, ki žreba števila (med 1 in 9) v neskončnost.

Rešitev

import random


def loto_generator_neskoncni():
    while True:
        yield random.randint(1, 9)

Sedaj pa napiši še funkcijo zrebaj(generator, stevilka_dneva), ki kot argument prejme tak generator in zaporedno številko dneva v mesecu in vrne seznam izžrebanih števil.

Rešitev

def zrebaj(generator, stevilka_dneva):
    n = max(5, stevilka_dneva % 8)
    return [next(generator) for _ in range(n)]

ali

import itertools


def zrebaj(generator, stevilka_dneva):
    n = max(5, stevilka_dneva % 8)
    return list(itertools.islice(generator, n))

Na loteriji so prosili še za malo pomoči. Potrebujejo še generator loto_generator_brez_ponavljanja2(generator), ki prejme neskončni generator števil kot argument (na primer loto_generator_neskoncni()) in vrne prvih šest številk tega generatorja, vendar se ta ne smejo ponavljati. Ponovljeno število naj preprosto preskoči.

Rešitev

def loto_generator_brez_ponavljanja2(generator):
    izzrebane = set()
    while len(izzrebane) < 6:
        st = next(generator)
        if st not in izzrebane:
            yield st
            izzrebane.add(st)

ali

def loto_generator_brez_ponavljanja2(generator):
    izzrebane = set()
    for st in generator:
        if st not in izzrebane:
            yield st
            izzrebane.add(st)
        if len(izzrebane) >= 6:
            break