# TD 23 correction (partie informatique) : Variables aléatoires
# BCPST1 2025-2026
# Lycée Hoche, Versailles
# L.-C. LEFÈVRE

#%% les fonctions

def Bernoulli(p):
    X = [0, 1]
    P = [1-p, p]
    return (X, P)

def uniforme(a, b):
    n = b - a + 1
    P = [1/n] * n
    X = [x for x in range(a, a+n)]
    return (X, P)

def espérance(X, P):
    n = len(X)
    E = 0
    for i in range(n):
        E = E + P[i] * X[i]
    return E

def variance(X, P):
    n = len(X)
    E = 0
    EE = 0
    for i in range(n):
        E = E + P[i] * X[i]
        EE = EE + P[i] * X[i]**2
    return EE - E**2

from math import sqrt
def écarttype(X, P):
    v = variance(X, P)
    return sqrt(v)

def proba_intervalle(X, P, a, b):
    n = len(X)
    p = 0
    for i in range(n):
        if a <= X[i] and X[i] <= b:
            p = p + P[i]
    return p

def cumul(X, P):
    n = len(X)
    C = [0] * n
    C[0] = P[0]
    for i in range(1, n):
        C[i] = C[i-1] + P[i]
    return C

from random import random
def simule(X, P):
    n = len(X)
    C = cumul(X, P)
    p = random()
    i = 0
    while C[i] <= p:
        i = i + 1
    # ici i est le plus petit indice pour lequel C[i] > p
    # p est donc dans l'intervalle C[i-1] <= p < C[i]
    # dans lequel on veut renvoyer la valeur d'indice i
    return X[i]

def somme(X1, P1, X2, P2):
    n = len(X1)
    m = len(X2)
    X = []
    P = []
    for i in range(n):
        for j in range(m):
            X.append(X1[i] + X2[j])
            P.append(P1[i] * P2[j])
    return (X, P)

def produit(X1, P1, X2, P2):
    n = len(X1)
    m = len(X2)
    X = []
    P = []
    for i in range(n):
        for j in range(m):
            X.append(X1[i] * X2[j])
            P.append(P1[i] * P2[j])
    return (X, P)

def binomiale(n, p):
    X = [0]
    P = [1]
    for i in range(n):
        (Y, Q) = Bernoulli(p)
        (X, P) = somme(X, P, Y, Q)
    return (X, P)

#%% quelques tests

(X, P) = Bernoulli(0.3)
for _ in range(20):
    print(simule(X, P))

#%%

(X, P) = uniforme(1, 6)
for _ in range(20):
    print(simule(X, P))

#%%

print(binomiale(3, 1/2))

#%% améliorations
# La liste X peut très bien contenir plusieurs fois la même valeur,
# mais on peut les regrouper en une seule, en sommant leurs probabilités.
# Comparer pour la loi binomiale.

# si x est déjà une valeur dans X, renvoie à quel indice j
# sinon, renvoie None
def est_déjà_dedans(Y, x):
    m = len(Y)
    for j in range(m):
        if Y[j] == x:
            return j
    return None

def regroupe(X, P):
    n = len(X)
    Y = []
    Q = []
    for i in range(n):
        j = est_déjà_dedans(Y, X[i])
        if j == None:
            # pas dedans, l'ajouter à Y
            Y.append(X[i])
            Q.append(P[i])
        else:
            # déjà dedans à l'indice j dans Y :
            # regrouper les valeurs, sommer les probabilités
            Q[j] = Q[j] + P[i]
    return (Y, Q)

(X, P) = binomiale(4, 1/2)
print(X, P)
(X, P) = regroupe(X, P)
print(X, P)
