""" 
    Exercices 1 : Fonctions anonymes
    En utilisant la notation lambda, écrire une fonction anonyme qui :
"""

# 1. Retourne True si un entier passé en paramètre est pair, False sinon.
# Plusieurs façon de l'écrire :
lambda n: n%2==0
lambda n: True if n%2==0 else False

# On peut les tester
assert (lambda n: n%2==0)(4) == True
assert (lambda n: True if n%2==0 else False)(5) == False

# 2. Retourne True si le paramètre est un entier, False sinon.
lambda n: type(n) == int

assert (lambda n: type(n) == int)(5) == True
assert (lambda n: type(n) == int)(5.0) == False
assert (lambda n: type(n) == int)('Hello') == False

# 3. Retourne la somme de deux entiers passés en paramètre.
lambda x, y: x + y

assert (lambda x, y: x + y)(4, 5) == 9
assert (lambda x, y: x + y)(4.4, 5.6) == 10

# 4. Retourne True si l’entier passé en paramètre représente une année bisextile, False sinon.
bis = lambda annee: (annee%4==0 and not annee%100==0) or annee%400==0

assert bis(2024)
assert not bis(2025)
assert bis(1600)
assert not bis(1700)

"""
    Exercices 2 : Trouve
    Écrire une fonction trouve(propriete, l) qui reçoit en arguments une fonction propriete(x) ->
    bool et une liste l et renvoie le premier élément x de l tel que propriete(x) vaut True. Si aucun élément
    de l ne satisfait propriete, alors la fonction renvoie None.
"""

def trouve(propriete, l):
    for x in l:
        if propriete(x):
            return x
    return None

assert trouve(lambda n: n%2==0, [2025, 1700, 1600, 2024]) == 1700
assert trouve(bis, [2025, 1700, 1600, 2024]) == 1600


"""
Exercices 3 : Filtre
Écrire une fonction filtre(propriete, l) qui reçoit en arguments une fonction propriete(x) ->
bool et une liste l et renvoie une liste contenant tous les éléments x de l tel que propriete(x) vaut True.
Si aucun élément de l ne satisfait propriete, alors la fonction renvoie une liste vide.
"""

def filtre(propriete, l):
    l2 = []
    for x in l:
        if propriete(x):
            l2.append(x)
    return l2

assert filtre(lambda n: n%2==0, [2025, 1700, 1600, 2024]) == [1700, 1600, 2024]
assert filtre(bis, [2025, 1700, 1600, 2024]) == [1600, 2024]
assert filtre(lambda n: n%13==0, [2025, 1700, 1600, 2024]) == []

"""
Exercices 4 : Applique
Écrire une fonction applique(fonction, l) qui reçoit en arguments une fonction fonction et une liste
l et renvoie une nouvelle liste, de même taille, où la fonction fonction a été appliquée à chaque élément de l.
"""

def Applique(fonction, l):
    l2 = []
    for x in l:
        l2.append(fonction(x))
    return l2

assert Applique(lambda n: n**2, [i for i in range(1, 11)]) == [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

"""
Exercice 5 : Tri
En tulisant la fonction sorted, trier liste dans l’ordre croissant du nombre de lettre 'l' (minusucle ou
majuscule) :
    1. En utlisant une fonction nommé
    2. En utilisant une fonction lambda. (On pourra utiliser la fonction len sur une liste en comprehension)
"""

liste = ['Libellule', 'Paille', 'Cool', 'Ada Lovelace', 'Parallèle']

def nb_l_or_L(mot):
    nb = 0
    for c in mot:
        if c == 'L' or c == 'l':
            nb += 1
    return nb
    
assert sorted(liste, key=nb_l_or_L) == ['Cool', 'Paille', 'Ada Lovelace', 'Parallèle', 'Libellule']
assert sorted(liste, key= (lambda m : len([c for c in m if c == 'l' or c == 'L'])) ) == ['Cool', 'Paille', 'Ada Lovelace', 'Parallèle', 'Libellule']



"""
Exercices 6 : Double
Écrire une fonction double(f) qui reçoit une fonction f(x) en argument et renvoie une fonction qui applique
deux fois de suite la fonction f à son argument.
Sans exécuter le code, que vaut double(double(lambda x : x*x))(2) ?
"""

def double(f):
    return lambda x : f(f(x))

# Lambda x: x*x calcule x**2. Donc double(x**2) calcule (x**2)**2 = x**4 et double(x**4) = (x**4)**4 = x**16
# donc 2**16 = 65536
assert double(double(lambda x : x*x))(2) == 65536

"""
Exercices 7 : Composition
Écrire une fonction compose(f, g) qui reçoit en arguments deux fonctions f et g et renvoie leur
composition, c’est à dire la fonction h telle que, pour tout x, h(x) est égale à f(g(x))
"""

def compose(f, g):
    return lambda x: f(g(x))

assert compose(lambda x:x**2, lambda x:x**4)(2) == 2**8

# Exercice 8 : Ensembles comme des fonctions

# Question 1 

def car_S1(x): return x > 0
def car_S2(x): return x >= 0 and x%2 == 0
def car_S3(x): return x in [0,3,7,9]
def car_S4(x): return x >= 10 and x <= 1000
def car_S5(x): return False

# Question 2

def union(car_S1, car_S2):
    return lambda x : car_S1(x) or car_S2(x)

# Question 3

def inter(car_S1, car_S2):
    return lambda x : car_S1(x) and car_S2(x)

# Question 4

def ajout(x, car_S):
    return lambda n: car_S(n) or n==x

# Question 5

def ensemble(l):
    f = car_S5
    for e in l:
        f = ajout(e, f)
    return f
