# =====================================================================
#
#     FUNCTII & BIBLIOTECI in Python      InfoBook.ro · Clasa a IX-a
#
#     Exerseaza EXACT functiile din lectie:
#        built-in   abs, round, int, float, str, len, min, max, sum, sorted
#        math       sqrt, pi, floor, ceil, log10
#        random     randint, choice, shuffle
#
#     CUM IL RULEZI:
#        In VS Code: click dreapta -> "Run Python File"
#        In terminal: python Functii_Biblioteci_Python.py
#
# =====================================================================

import math      # trebuie importat ca sa folosesti math.sqrt, math.pi, ...
import random    # trebuie importat ca sa folosesti random.randint, ...


# =====================================================================
#  PROBLEMA 1 — VARIATIA DE TEMPERATURA
# ---------------------------------------------------------------------
#  Cerinta:         Se dau temperaturile din doua zile. Cu cat s-a
#                   schimbat temperatura? (raspunsul e mereu pozitiv)
#  Date de intrare:   luni = -5 grade, marti = 8 grade
#  Date de iesire:    13 grade
#  Functii folosite:  abs()
#
#  DE RETINUT:  abs() = distanta pana la zero, fara semn. Cand intrebi
#               "cu cat difera?", raspunsul nu poate fi negativ.
# =====================================================================

print("===== PROBLEMA 1: variatia de temperatura =====\n")

temp_luni = -5
temp_marti = 8
print("   luni:", temp_luni, "grade | marti:", temp_marti, "grade")
print("   variatia:", abs(temp_marti - temp_luni), "grade\n")


# =====================================================================
#  PROBLEMA 2 — CAPCANA ROTUNJIRII SI A CONVERSIILOR
# ---------------------------------------------------------------------
#  Cerinta:         Compara round() cu int() pe aceleasi valori si
#                   observa unde te poate insela fiecare.
#  Date de intrare:   2.5, 3.5, 3.9, -3.9
#  Date de iesire:    round(2.5) = 2   (NU 3!)
#                     round(3.5) = 4
#                     int(3.9)   = 3   (taie, nu rotunjeste)
#                     int(-3.9)  = -3  (taie spre zero)
#  Functii folosite:  round(), int(), float(), str()
#
#  POVESTE:  Python foloseste "rotunjirea bancara": cand esti exact la
#            jumatate (.5), rotunjeste spre cifra PARA. Pare ciudat, dar
#            e regula folosita in banci si contabilitate: daca ai rotunji
#            mereu in sus, dupa un milion de operatii ai aduna o eroare
#            uriasa in favoarea unei parti. Asa, erorile se compenseaza.
# =====================================================================

print("===== PROBLEMA 2: rotunjiri si conversii =====\n")

print("   round(2.5)  =", round(2.5), "  <- rotunjire bancara, spre PAR")
print("   round(3.5)  =", round(3.5), "  <- aici para e 4")
print("   round(3.14159, 2) =", round(3.14159, 2))
print()
print("   int(3.9)    =", int(3.9), "  <- TAIE zecimalele")
print("   int(-3.9)   =", int(-3.9), " <- taie spre zero, nu in jos")
print()
print("   conversii intre tipuri:")
print("      int('17') + 1     =", int("17") + 1)
print("      float('3.14') * 2 =", float("3.14") * 2)
print("      str(2024) + ' e anul' ->", str(2024) + " e anul")
print()


# =====================================================================
#  PROBLEMA 3 — STATISTICA NOTELOR UNEI CLASE
# ---------------------------------------------------------------------
#  Cerinta:         Pentru un sir de note, afla: cate sunt, cea mai mica,
#                   cea mai mare, suma, media, notele sortate, media fara
#                   cea mai mica nota si primele 3 note.
#  Date de intrare:   7 9 5 10 8 9 6
#  Date de iesire:    len=7, min=5, max=10, sum=54, media=7.71
#                     sortate: [5, 6, 7, 8, 9, 9, 10]
#                     media fara cea mai mica: 8.17
#                     top 3: [10, 9, 9]
#  Functii folosite:  len(), min(), max(), sum(), sorted(), round()
#
#  DE RETINUT:  sorted() creeaza o lista NOUA — originalul ramane neatins.
#               Asta il face perfect cand vrei sa pastrezi ordinea initiala.
# =====================================================================

print("===== PROBLEMA 3: statistica notelor =====\n")

note = [7, 9, 5, 10, 8, 9, 6]
print("   notele:      ", note)
print("   cate sunt:   ", len(note))
print("   cea mai mica:", min(note))
print("   cea mai mare:", max(note))
print("   suma:        ", sum(note))
print("   media:       ", round(sum(note) / len(note), 2))
print()
print("   sortate crescator:  ", sorted(note))
print("   originalul:         ", note, "<- neschimbat")
print("   sortate descrescator:", sorted(note, reverse=True))
print()

# Media fara cea mai mica nota (ca la concursuri, unde se elimina extrema)
fara_minim = sorted(note)[1:]        # sortez si arunc prima valoare
print("   media fara cea mai mica nota:", round(sum(fara_minim) / len(fara_minim), 2))
print("   primele 3 note:              ", sorted(note, reverse=True)[:3])
print()


# =====================================================================
#  PROBLEMA 4 — CERCUL: ARIE SI CIRCUMFERINTA
# ---------------------------------------------------------------------
#  Cerinta:         Pentru o raza data, calculeaza aria si circumferinta.
#  Date de intrare:   raza = 5
#  Date de iesire:    aria = 78.54 ; circumferinta = 31.42
#  Functii folosite:  math.pi, round()
#
#  POVESTE:  Numarul pi nu poate fi scris exact — are o infinitate de
#            zecimale care nu se repeta niciodata. ARHIMEDE (sec. III i.Hr.)
#            l-a calculat desenand poligoane cu 96 de laturi in interiorul
#            si in exteriorul unui cerc, si a demonstrat ca pi este intre
#            3.1408 si 3.1429 — o precizie uimitoare pentru acum 2200 de ani.
#            Python il are gata calculat in math.pi.
# =====================================================================

print("===== PROBLEMA 4: aria si circumferinta cercului =====\n")

r = 5
print("   math.pi =", math.pi)
print("   raza:", r)
print("   aria          =", round(math.pi * r ** 2, 2))
print("   circumferinta =", round(2 * math.pi * r, 2))
print()


# =====================================================================
#  PROBLEMA 5 — DISTANTA DINTRE DOUA PUNCTE
# ---------------------------------------------------------------------
#  Cerinta:         Se dau coordonatele a doua puncte in plan. Calculeaza
#                   distanta dintre ele.
#  Date de intrare:   A(0, 0) si B(3, 4)
#  Date de iesire:    5.0
#  Functii folosite:  math.sqrt()
#
#  DE RETINUT:  formula vine tot din teorema lui Pitagora: diferenta pe x
#               si diferenta pe y sunt catetele, distanta e ipotenuza.
#               Poti scrie radicalul si fara import:  x ** 0.5
# =====================================================================

print("===== PROBLEMA 5: distanta dintre doua puncte =====\n")

x1, y1 = 0, 0
x2, y2 = 3, 4
d = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
print("   A(", x1, ",", y1, ")  B(", x2, ",", y2, ")")
print("   distanta =", d)
print("   math.sqrt(25) =", math.sqrt(25), "| 25 ** 0.5 =", 25 ** 0.5)
print()


# =====================================================================
#  PROBLEMA 6 — CATE CUTII SUNT NECESARE?
# ---------------------------------------------------------------------
#  Cerinta:         Ai un numar de obiecte si cutii cu capacitate fixa.
#                   De cate cutii ai nevoie ca sa incapa toate?
#  Date de intrare:   100 de obiecte, 12 intr-o cutie
#  Date de iesire:    9 cutii
#  Functii folosite:  math.ceil(), math.floor()
#
#  ATENTIE:  100 / 12 = 8.33...  Cu round() ai obtine 8 cutii si 4 obiecte
#            ar ramane pe dinafara. Cand intrebi "de cate imi trebuie ca sa
#            incapa TOT?", raspunsul e intotdeauna math.ceil().
# =====================================================================

print("===== PROBLEMA 6: cate cutii? =====\n")

obiecte, per_cutie = 100, 12
print("   ", obiecte, "obiecte,", per_cutie, "intr-o cutie")
print("   impartire exacta:", round(obiecte / per_cutie, 2))
print("   cutii necesare:  ", math.ceil(obiecte / per_cutie), " <- cu ceil")
print("   (cu round ai gresi:", round(obiecte / per_cutie), "cutii)")
print()
print("   numarul de cifre al lui 47382:", int(math.log10(47382)) + 1)
print()


# =====================================================================
#  PROBLEMA 7 — 1000 DE ARUNCARI DE ZAR
# ---------------------------------------------------------------------
#  Cerinta:         Simuleaza 1000 de aruncari cu zarul. Numara de cate
#                   ori a iesit fiecare fata si calculeaza media.
#  Date de intrare:   1000 de aruncari, zar cu 6 fete
#  Date de iesire:    numarul de aparitii pentru fiecare fata (aprox. 167)
#                     media aruncarilor, aproape de 3.5
#  Functii folosite:  random.randint(), random.choice(), random.shuffle()
#
#  POVESTE:  Metoda asta — sa afli un raspuns aruncand "zaruri" de mii de
#            ori — se numeste METODA MONTE CARLO, dupa cazinoul din Monaco.
#            A fost inventata in anii '40 de matematicieni care lucrau la
#            primele calculatoare si aveau nevoie sa rezolve probleme prea
#            grele pentru formule. Se foloseste si azi in fizica si finante.
#
#  Nota: random.seed(9) face ca rezultatele sa fie ACELEASI la fiecare
#        rulare — util la ora, ca sa vada toti elevii aceleasi numere.
#        Sterge linia daca vrei rezultate diferite de fiecare data.
# =====================================================================

print("===== PROBLEMA 7: 1000 de aruncari de zar =====\n")

random.seed(9)

print("   o aruncare:", random.randint(1, 6))
print()

frecvente = [0] * 7                    # folosim pozitiile 1..6
for _ in range(1000):
    fata = random.randint(1, 6)
    frecvente[fata] += 1               # vector de frecventa (Modulul 04!)

total = 0
for f in range(1, 7):
    print("   fata", f, ":", frecvente[f], "aparitii")
    total += f * frecvente[f]

print()
print("   media aruncarilor:", round(total / 1000, 3), " (teoretic 3.5)")
print()

elevi = ["Ana", "Bogdan", "Cristina", "Dan"]
print("   elev ales la tabla:", random.choice(elevi))

raspunsuri = ["A", "B", "C", "D"]
random.shuffle(raspunsuri)             # amesteca lista PE LOC
print("   variante amestecate:", raspunsuri)
print()


# =====================================================================
#  PROBLEMA 8 — CEL MAI APROPIAT PUNCT DE ORIGINE
# ---------------------------------------------------------------------
#  Cerinta:         Se dau mai multe puncte in plan. Care e cel mai
#                   apropiat de origine (0, 0)?
#  Date de intrare:   (3,4), (1,1), (6,8), (0,2)
#  Date de iesire:    (1, 1), la distanta 1.41
#  Functii folosite:  math.sqrt(), round()
# =====================================================================

print("===== PROBLEMA 8: cel mai apropiat punct de origine =====\n")

puncte = [(3, 4), (1, 1), (6, 8), (0, 2)]
cea_mai_mica = -1
cel_mai_aproape = None

for (px, py) in puncte:
    dist = math.sqrt(px ** 2 + py ** 2)
    print("   punctul (", px, ",", py, ") -> distanta", round(dist, 2))
    if cea_mai_mica == -1 or dist < cea_mai_mica:
        cea_mai_mica = dist
        cel_mai_aproape = (px, py)

print()
print("   cel mai aproape:", cel_mai_aproape, "la distanta", round(cea_mai_mica, 2))
print()


# =====================================================================
#  PROBLEMA 9 — CARE NUMERE SUNT PATRATE PERFECTE?
# ---------------------------------------------------------------------
#  Cerinta:         Dintr-un sir de numere, afla care sunt patrate
#                   perfecte (1, 4, 9, 16, 25, ...).
#  Date de intrare:   16 20 25 30 49 50
#  Date de iesire:    [16, 25, 49]
#  Functii folosite:  math.sqrt(), int()
#
#  IDEEA:  extragi radacina, o TRUNCHIEZI la intreg, apoi o inmultesti cu
#          ea insasi. Daca obtii inapoi numarul initial, era patrat perfect.
# =====================================================================

print("===== PROBLEMA 9: patrate perfecte =====\n")

valori = [16, 20, 25, 30, 49, 50]
patrate = []

for x in valori:
    rad = int(math.sqrt(x))            # radacina, trunchiata la intreg
    if rad * rad == x:
        patrate.append(x)
        print("   ", x, "= ", rad, "*", rad, " -> patrat perfect")
    else:
        print("   ", x, "-> nu")

print()
print("   rezultat:", patrate)
print()

print("=====================================================")
print("  GATA! Ai exersat toate functiile lectiei.")
print("=====================================================")


# =====================================================================
#  JOC FINAL — GHICESTE NUMARUL (1 - 100) in cel mult 7 incercari
# ---------------------------------------------------------------------
#  Cerinta:         Calculatorul alege un numar secret intre 1 si 100.
#                   Tu incerci sa-l ghicesti. Dupa fiecare incercare iti
#                   spune daca numarul tau e prea mic sau prea mare.
#                   Ai la dispozitie 7 incercari.
#  Date de intrare:   numerele tale, cate unul pe rand
#  Date de iesire:    "prea mic" / "prea mare" / "BRAVO"
#  Functii folosite:  random.randint(), int(), input()
#
#  DE CE EXACT 7 INCERCARI?
#     Daca la fiecare pas ghicesti MIJLOCUL intervalului ramas, injumatatesti
#     de fiecare data numarul de variante:
#         100 -> 50 -> 25 -> 13 -> 7 -> 4 -> 2 -> 1
#     Adica sunt necesare cel mult 7 pasi, pentru ca 2^7 = 128 > 100.
#     Matematic: ceil(log2(100)) = 7.  (Ai calculat log2 la Problema 2!)
#     Strategia asta se numeste CAUTARE BINARA si o vei studia la clasa a X-a.
# =====================================================================

print()
print("=====================================================")
print("       JOC: GHICESTE NUMARUL (1 - 100)")
print("=====================================================")
print()

# Anulam seed-ul fix pus la Problema 7, ca jocul sa fie cu adevarat aleator
# (seed() fara argument porneste de la ceasul calculatorului)
random.seed()

secret = random.randint(1, 100)      # calculatorul alege numarul secret
MAX_INCERCARI = 7
incercari = 0
ghicit = False

print("M-am gandit la un numar intre 1 si 100.")
print("Ai", MAX_INCERCARI, "incercari. Sfat: incearca mereu MIJLOCUL!")
print()

while incercari < MAX_INCERCARI:
    incercari = incercari + 1
    n = int(input("Incercarea " + str(incercari) + "/" + str(MAX_INCERCARI) + " - numarul tau: "))

    if n == secret:
        print("   BRAVO! Ai ghicit din", incercari, "incercari!")
        ghicit = True
        break                         # iesim din bucla, jocul s-a terminat
    elif n < secret:
        print("   Numarul meu e mai MARE decat", n)
    else:
        print("   Numarul meu e mai MIC decat", n)

    # ii spunem cate incercari i-au ramas
    ramase = MAX_INCERCARI - incercari
    if ramase > 0:
        print("   (ti-au ramas", ramase, "incercari)")
    print()

if not ghicit:
    print()
    print("   Ai terminat incercarile. Numarul meu era:", secret)

print()
print("=====================================================")
