
# Fichier: python_cheats/cheatsheets/loops_complete.txt
# Python Loops - Boucles et Itérations Complètes



[OK] BOUCLE FOR - BASIQUE


[OK] FOR BASIQUE AVEC RANGE
    # Range simple (0 à n-1)
    for i in range(5):
        print(i)  # 0, 1, 2, 3, 4
    
    # Range avec start et stop
    for i in range(2, 7):
        print(i)  # 2, 3, 4, 5, 6
    
    # Range avec step (pas)
    for i in range(0, 10, 2):
        print(i)  # 0, 2, 4, 6, 8
    
    # Range décroissant
    for i in range(10, 0, -1):
        print(i)  # 10, 9, 8, 7, 6, 5, 4, 3, 2, 1
    
    # Range négatif
    for i in range(-5, 5):
        print(i)  # -5, -4, -3, -2, -1, 0, 1, 2, 3, 4

[OK] RANGE AVANCÉ
    # Range avec float (utiliser numpy)
    import numpy as np
    for i in np.arange(0, 1, 0.1):
        print(f"{i:.1f}")  # 0.0, 0.1, 0.2, ..., 0.9
    
    # Range infini (attention!)
    from itertools import count
    for i in count(start=0, step=1):
        if i > 5:
            break
        print(i)  # 0, 1, 2, 3, 4, 5
    
    # Range avec très grands nombres
    for i in range(10**100, 10**100 + 5):
        print(i)  # Fonctionne même avec des nombres immenses

[OK] ITÉRER SUR UNE LISTE
    fruits = ['apple', 'banana', 'cherry', 'date']
    
    # Méthode 1: Directement sur les éléments
    for fruit in fruits:
        print(fruit)
    
    # Méthode 2: Par index
    for i in range(len(fruits)):
        print(f"{i}: {fruits[i]}")
    
    # Méthode 3: Enumerate (recommandé)
    for index, fruit in enumerate(fruits):
        print(f"{index}: {fruit}")

[OK] ITÉRER SUR UN TUPLE
    coordinates = (10, 20, 30)
    for coord in coordinates:
        print(coord)  # 10, 20, 30

[OK] ITÉRER SUR UN SET
    unique_numbers = {1, 2, 3, 4, 5}
    for num in unique_numbers:
        print(num)  # Ordre non garanti

[OK] ITÉRER SUR UN STRING
    word = "Python"
    
    # Par caractère
    for char in word:
        print(char)  # P, y, t, h, o, n
    
    # Avec index
    for i, char in enumerate(word):
        print(f"{i}: {char}")
    
    # Par mots dans une phrase
    sentence = "Hello world from Python"
    for word in sentence.split():
        print(word)


[OK] ENUMERATE - INDEX ET VALEUR


[OK] ENUMERATE BASIQUE
    fruits = ['apple', 'banana', 'cherry']
    
    # Commence à 0 (par défaut)
    for index, fruit in enumerate(fruits):
        print(f"{index}: {fruit}")
    # 0: apple
    # 1: banana
    # 2: cherry

[OK] ENUMERATE AVEC START
    # Commencer à 1
    for index, fruit in enumerate(fruits, start=1):
        print(f"{index}: {fruit}")
    # 1: apple
    # 2: banana
    # 3: cherry
    
    # Commencer à n'importe quel nombre
    for index, fruit in enumerate(fruits, start=10):
        print(f"{index}: {fruit}")
    # 10: apple, 11: banana, 12: cherry

[OK] ENUMERATE POUR MODIFICATION
    numbers = [1, 2, 3, 4, 5]
    
    # Modifier les éléments
    for i, num in enumerate(numbers):
        numbers[i] = num * 2
    
    print(numbers)  # [2, 4, 6, 8, 10]

[OK] ENUMERATE AVEC UNPACKING
    pairs = [('a', 1), ('b', 2), ('c', 3)]
    
    for index, (letter, number) in enumerate(pairs):
        print(f"{index}: {letter} = {number}")


[OK] DICTIONNAIRES - ITÉRATION


[OK] ITÉRER SUR LES CLÉS
    person = {'name': 'Alice', 'age': 30, 'city': 'Paris'}
    
    # Méthode 1: Implicite (par défaut)
    for key in person:
        print(key)  # name, age, city
    
    # Méthode 2: Explicite avec keys()
    for key in person.keys():
        print(key)
    
    # Accéder aux valeurs via les clés
    for key in person:
        print(f"{key}: {person[key]}")

[OK] ITÉRER SUR LES VALEURS
    for value in person.values():
        print(value)  # Alice, 30, Paris

[OK] ITÉRER SUR CLÉS ET VALEURS (items)
    for key, value in person.items():
        print(f"{key}: {value}")
    # name: Alice
    # age: 30
    # city: Paris

[OK] DICTIONNAIRE AVEC ENUMERATE
    for index, (key, value) in enumerate(person.items()):
        print(f"{index}: {key} = {value}")
    # 0: name = Alice
    # 1: age = 30
    # 2: city = Paris

[OK] ITÉRATION TRIÉE SUR DICTIONNAIRE
    # Trier par clés
    for key in sorted(person.keys()):
        print(f"{key}: {person[key]}")
    
    # Trier par valeurs
    for key, value in sorted(person.items(), key=lambda x: x[1]):
        print(f"{key}: {value}")
    
    # Ordre inverse
    for key in sorted(person.keys(), reverse=True):
        print(f"{key}: {person[key]}")

[OK] DICTIONNAIRE IMBRIQUÉ
    users = {
        'user1': {'name': 'Alice', 'age': 25},
        'user2': {'name': 'Bob', 'age': 30}
    }
    
    for user_id, user_data in users.items():
        print(f"\n{user_id}:")
        for key, value in user_data.items():
            print(f"  {key}: {value}")


[OK] ZIP - ITÉRER SUR PLUSIEURS SÉQUENCES


[OK] ZIP BASIQUE
    names = ['Alice', 'Bob', 'Charlie']
    ages = [25, 30, 35]
    
    for name, age in zip(names, ages):
        print(f"{name} is {age} years old")
    # Alice is 25 years old
    # Bob is 30 years old
    # Charlie is 35 years old

[OK] ZIP AVEC PLUS DE DEUX LISTES
    names = ['Alice', 'Bob', 'Charlie']
    ages = [25, 30, 35]
    cities = ['Paris', 'London', 'Berlin']
    
    for name, age, city in zip(names, ages, cities):
        print(f"{name}, {age} years old, lives in {city}")

[OK] ZIP AVEC LONGUEURS DIFFÉRENTES
    # Par défaut: s'arrête à la plus courte
    list1 = [1, 2, 3, 4, 5]
    list2 = ['a', 'b', 'c']
    
    for num, letter in zip(list1, list2):
        print(num, letter)
    # 1 a
    # 2 b
    # 3 c

[OK] ZIP_LONGEST (itertools)
    from itertools import zip_longest
    
    list1 = [1, 2, 3, 4, 5]
    list2 = ['a', 'b', 'c']
    
    # Remplit avec None par défaut
    for num, letter in zip_longest(list1, list2):
        print(num, letter)
    # 1 a
    # 2 b
    # 3 c
    # 4 None
    # 5 None
    
    # Avec valeur de remplissage personnalisée
    for num, letter in zip_longest(list1, list2, fillvalue='?'):
        print(num, letter)
    # 4 ?
    # 5 ?

[OK] UNZIP (DÉCOMPRESSER)
    pairs = [(1, 'a'), (2, 'b'), (3, 'c')]
    
    numbers, letters = zip(*pairs)
    print(numbers)  # (1, 2, 3)
    print(letters)  # ('a', 'b', 'c')

[OK] ZIP POUR CRÉER UN DICTIONNAIRE
    keys = ['name', 'age', 'city']
    values = ['Alice', 30, 'Paris']
    
    person = dict(zip(keys, values))
    print(person)  # {'name': 'Alice', 'age': 30, 'city': 'Paris'}

[OK] ZIP AVEC ENUMERATE
    names = ['Alice', 'Bob', 'Charlie']
    ages = [25, 30, 35]
    
    for index, (name, age) in enumerate(zip(names, ages), start=1):
        print(f"{index}. {name}: {age} years")


[OK] BOUCLE WHILE


[OK] WHILE BASIQUE
    count = 0
    while count < 5:
        print(count)
        count += 1
    # 0, 1, 2, 3, 4

[OK] WHILE AVEC CONDITION COMPLEXE
    x = 0
    y = 10
    while x < 5 and y > 5:
        print(f"x={x}, y={y}")
        x += 1
        y -= 1

[OK] WHILE TRUE (BOUCLE INFINIE)
    while True:
        user_input = input("Enter 'quit' to exit: ")
        if user_input == 'quit':
            break
        print(f"You entered: {user_input}")

[OK] WHILE AVEC ELSE
    count = 0
    while count < 5:
        print(count)
        count += 1
    else:
        print("Boucle terminée normalement")
    
    # else s'exécute si la boucle n'est PAS interrompue par break

[OK] WHILE POUR VALIDATION
    while True:
        try:
            age = int(input("Enter your age: "))
            if age < 0 or age > 150:
                print("Invalid age")
                continue
            break
        except ValueError:
            print("Please enter a number")
    
    print(f"Your age is {age}")

[OK] WHILE POUR TRAITEMENT DE DONNÉES
    import random
    
    total = 0
    count = 0
    while total < 100:
        value = random.randint(1, 20)
        total += value
        count += 1
        print(f"Added {value}, total: {total}")
    
    print(f"Took {count} iterations")


[OK] BREAK, CONTINUE, PASS


[OK] BREAK (SORTIR DE LA BOUCLE)
    # Break simple
    for i in range(10):
        if i == 5:
            break
        print(i)
    # 0, 1, 2, 3, 4
    
    # Break avec recherche
    numbers = [1, 3, 5, 7, 9, 10, 11]
    for num in numbers:
        if num % 2 == 0:
            print(f"First even number: {num}")
            break
    else:
        print("No even number found")

[OK] CONTINUE (PASSER À L'ITÉRATION SUIVANTE)
    # Continue simple
    for i in range(10):
        if i % 2 == 0:
            continue  # Sauter les pairs
        print(i)
    # 1, 3, 5, 7, 9
    
    # Continue avec condition
    numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    for num in numbers:
        if num % 3 == 0:
            continue  # Sauter les multiples de 3
        print(num)

[OK] PASS (NE RIEN FAIRE)
    # Pass comme placeholder
    for i in range(5):
        if i == 2:
            pass  # TODO: implémenter plus tard
        else:
            print(i)
    
    # Pass dans une classe vide
    class EmptyClass:
        pass
    
    # Pass dans une fonction vide
    def empty_function():
        pass

[OK] BREAK DANS BOUCLES IMBRIQUÉES
    # Break ne sort que de la boucle interne
    for i in range(3):
        for j in range(3):
            if j == 1:
                break
            print(f"({i}, {j})")
    # (0, 0), (1, 0), (2, 0)
    
    # Pour sortir de toutes les boucles: utiliser un flag
    found = False
    for i in range(3):
        for j in range(3):
            if i == 1 and j == 1:
                found = True
                break
        if found:
            break
        print(f"Row {i} completed")

[OK] CONTINUE AVEC BOUCLES IMBRIQUÉES
    for i in range(3):
        for j in range(3):
            if j == 1:
                continue
            print(f"({i}, {j})")
    # (0, 0), (0, 2), (1, 0), (1, 2), (2, 0), (2, 2)


[OK] ELSE AVEC BOUCLES


[OK] FOR...ELSE
    # else s'exécute si la boucle se termine normalement (sans break)
    for i in range(5):
        if i == 10:
            break
    else:
        print("Boucle terminée normalement")  # S'exécute
    
    # Avec break: else ne s'exécute PAS
    for i in range(5):
        if i == 3:
            break
    else:
        print("Ceci ne s'affichera pas")

[OK] WHILE...ELSE
    count = 0
    while count < 5:
        print(count)
        count += 1
    else:
        print("While terminé normalement")

[OK] UTILISATION PRATIQUE: RECHERCHE
    # Rechercher un élément
    numbers = [1, 3, 5, 7, 9]
    search = 4
    
    for num in numbers:
        if num == search:
            print(f"Trouvé: {search}")
            break
    else:
        print(f"{search} non trouvé dans la liste")
    
    # Vérifier si un nombre est premier
    def is_prime(n):
        if n < 2:
            return False
        for i in range(2, int(n ** 0.5) + 1):
            if n % i == 0:
                return False
        else:
            return True  # Boucle terminée sans break


[OK] BOUCLES IMBRIQUÉES


[OK] BOUCLES IMBRIQUÉES BASIQUES
    # Table de multiplication
    for i in range(1, 6):
        for j in range(1, 6):
            print(f"{i} x {j} = {i*j}")

[OK] MATRICE/GRILLE
    # Créer une matrice
    rows = 3
    cols = 4
    
    for i in range(rows):
        for j in range(cols):
            print(f"({i},{j})", end=" ")
        print()  # Nouvelle ligne
    
    # Parcourir une matrice existante
    matrix = [
        [1, 2, 3],
        [4, 5, 6],
        [7, 8, 9]
    ]
    
    for row in matrix:
        for element in row:
            print(element, end=" ")
        print()

[OK] BOUCLES IMBRIQUÉES AVEC ENUMERATE
    matrix = [
        [1, 2, 3],
        [4, 5, 6],
        [7, 8, 9]
    ]
    
    for i, row in enumerate(matrix):
        for j, element in enumerate(row):
            print(f"matrix[{i}][{j}] = {element}")

[OK] COMBINAISONS ET PERMUTATIONS
    # Toutes les paires
    colors = ['red', 'blue', 'green']
    sizes = ['S', 'M', 'L']
    
    for color in colors:
        for size in sizes:
            print(f"{color}-{size}")
    
    # Éviter les doublons (i < j)
    numbers = [1, 2, 3, 4]
    for i in range(len(numbers)):
        for j in range(i + 1, len(numbers)):
            print(f"({numbers[i]}, {numbers[j]})")

[OK] BOUCLES IMBRIQUÉES - PYRAMIDE
    # Pyramide de nombres
    n = 5
    for i in range(1, n + 1):
        for j in range(1, i + 1):
            print(j, end=" ")
        print()
    # 1
    # 1 2
    # 1 2 3
    # 1 2 3 4
    # 1 2 3 4 5
    
    # Pyramide d'étoiles
    for i in range(1, n + 1):
        print(" " * (n - i) + "*" * (2 * i - 1))


[OK] COMPREHENSIONS


[OK] LIST COMPREHENSION - BASIQUE
    # Créer une liste
    squares = [x**2 for x in range(10)]
    # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
    
    # Avec transformation
    names = ['alice', 'bob', 'charlie']
    upper_names = [name.upper() for name in names]
    # ['ALICE', 'BOB', 'CHARLIE']

[OK] LIST COMPREHENSION - AVEC CONDITION
    # Filtrer avec if
    evens = [x for x in range(20) if x % 2 == 0]
    # [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
    
    # Condition complexe
    numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    filtered = [x for x in numbers if x % 2 == 0 and x > 5]
    # [6, 8, 10]

[OK] LIST COMPREHENSION - IF/ELSE
    # if/else (ternaire)
    numbers = [-2, -1, 0, 1, 2]
    absolute = [x if x >= 0 else -x for x in numbers]
    # [2, 1, 0, 1, 2]
    
    # Catégoriser
    ages = [15, 25, 35, 45]
    categories = ['minor' if age < 18 else 'adult' for age in ages]
    # ['minor', 'adult', 'adult', 'adult']

[OK] LIST COMPREHENSION - IMBRIQUÉE
    # Matrice aplatie
    matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
    flat = [element for row in matrix for element in row]
    # [1, 2, 3, 4, 5, 6, 7, 8, 9]
    
    # Créer une matrice
    matrix = [[i*j for j in range(3)] for i in range(3)]
    # [[0, 0, 0], [0, 1, 2], [0, 2, 4]]
    
    # Toutes les paires
    pairs = [(x, y) for x in range(3) for y in range(3)]
    # [(0,0), (0,1), (0,2), (1,0), (1,1), (1,2), (2,0), (2,1), (2,2)]

[OK] LIST COMPREHENSION - AVANCÉE
    # Avec fonction
    def square(x):
        return x ** 2
    
    squares = [square(x) for x in range(5)]
    
    # Avec multiple conditions
    numbers = range(20)
    filtered = [x for x in numbers if x % 2 == 0 if x % 3 == 0]
    # [0, 6, 12, 18]
    
    # Avec enumerate
    words = ['apple', 'banana', 'cherry']
    indexed = [f"{i}: {word}" for i, word in enumerate(words, 1)]
    # ['1: apple', '2: banana', '3: cherry']

[OK] DICT COMPREHENSION
    # Basique
    squares_dict = {x: x**2 for x in range(6)}
    # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
    
    # À partir de deux listes
    keys = ['a', 'b', 'c']
    values = [1, 2, 3]
    dictionary = {k: v for k, v in zip(keys, values)}
    # {'a': 1, 'b': 2, 'c': 3}
    
    # Filtrer un dictionnaire
    person = {'name': 'Alice', 'age': 30, 'city': 'Paris'}
    filtered = {k: v for k, v in person.items() if k != 'city'}
    # {'name': 'Alice', 'age': 30}
    
    # Inverser un dictionnaire
    original = {'a': 1, 'b': 2, 'c': 3}
    inverted = {v: k for k, v in original.items()}
    # {1: 'a', 2: 'b', 3: 'c'}

[OK] SET COMPREHENSION
    # Basique
    squares_set = {x**2 for x in range(10)}
    # {0, 1, 4, 9, 16, 25, 36, 49, 64, 81}
    
    # Filtrer les doublons automatiquement
    numbers = [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
    unique = {x for x in numbers}
    # {1, 2, 3, 4}
    
    # Avec condition
    unique_evens = {x for x in range(20) if x % 2 == 0}
    # {0, 2, 4, 6, 8, 10, 12, 14, 16, 18}

[OK] GENERATOR EXPRESSION
    # Similaire à list comprehension mais avec ()
    gen = (x**2 for x in range(10))
    
    # Itérer
    for value in gen:
        print(value)
    
    # Convertir en liste
    squares = list(gen)
    
    # Avantage: économise la mémoire (lazy evaluation)
    # Bon pour grandes données
    large_gen = (x**2 for x in range(10**6))
    
    # Utiliser avec sum, max, min
    total = sum(x**2 for x in range(100))
    maximum = max(x**2 for x in range(100))


[OK] ITÉRATION AVANCÉE


[OK] REVERSED (ITÉRATION INVERSE)
    # Liste
    numbers = [1, 2, 3, 4, 5]
    for num in reversed(numbers):
        print(num)  # 5, 4, 3, 2, 1
    
    # Range
    for i in reversed(range(5)):
        print(i)  # 4, 3, 2, 1, 0
    
    # String
    word = "Python"
    for char in reversed(word):
        print(char)  # n, o, h, t, y, P

[OK] SORTED (ITÉRATION TRIÉE)
    # Liste
    numbers = [3, 1, 4, 1, 5, 9, 2, 6]
    for num in sorted(numbers):
        print(num)  # 1, 1, 2, 3, 4, 5, 6, 9
    
    # Ordre décroissant
    for num in sorted(numbers, reverse=True):
        print(num)  # 9, 6, 5, 4, 3, 2, 1, 1
    
    # Trier par clé personnalisée
    words = ['apple', 'pie', 'a', 'banana']
    for word in sorted(words, key=len):
        print(word)  # a, pie, apple, banana
    
    # Trier dictionnaire par valeur
    scores = {'Alice': 85, 'Bob': 92, 'Charlie': 78}
    for name, score in sorted(scores.items(), key=lambda x: x[1], reverse=True):
        print(f"{name}: {score}")

[OK] FILTER
    # Filtrer avec fonction
    numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    evens = filter(lambda x: x % 2 == 0, numbers)
    
    for num in evens:
        print(num)  # 2, 4, 6, 8, 10
    
    # Convertir en liste
    even_list = list(filter(lambda x: x % 2 == 0, numbers))

[OK] MAP
    # Appliquer une fonction à chaque élément
    numbers = [1, 2, 3, 4, 5]
    squared = map(lambda x: x**2, numbers)
    
    for num in squared:
        print(num)  # 1, 4, 9, 16, 25
    
    # Avec plusieurs iterables
    list1 = [1, 2, 3]
    list2 = [4, 5, 6]
    sums = map(lambda x, y: x + y, list1, list2)
    print(list(sums))  # [5, 7, 9]

[OK] ITERTOOLS - CYCLE
    from itertools import cycle, repeat, chain, islice
    
    # Cycle infini
    colors = ['red', 'green', 'blue']
    color_cycle = cycle(colors)
    
    for i, color in enumerate(color_cycle):
        if i >= 7:
            break
        print(color)
    # red, green, blue, red, green, blue, red

[OK] ITERTOOLS - REPEAT
    # Répéter une valeur
    for x in repeat('Hello', 3):
        print(x)
    # Hello
    # Hello
    # Hello

[OK] ITERTOOLS - CHAIN
    # Chaîner plusieurs iterables
    list1 = [1, 2, 3]
    list2 = [4, 5, 6]
    list3 = [7, 8, 9]
    
    for num in chain(list1, list2, list3):
        print(num)  # 1, 2, 3, 4, 5, 6, 7, 8, 9

[OK] ITERTOOLS - ISLICE
    # Slice d'un itérable
    numbers = range(100)
    
    # Prendre les 5 premiers
    for num in islice(numbers, 5):
        print(num)  # 0, 1, 2, 3, 4
    
    # Prendre de 10 à 15
    for num in islice(numbers, 10, 15):
        print(num)  # 10, 11, 12, 13, 14
    
    # Avec step
    for num in islice(numbers, 0, 20, 2):
        print(num)  # 0, 2, 4, 6, ..., 18

[OK] ITERTOOLS - COMBINATIONS & PERMUTATIONS
    from itertools import combinations, permutations, product
    
    # Combinations (ordre ne compte pas)
    items = ['A', 'B', 'C']
    for combo in combinations(items, 2):
        print(combo)
    # ('A', 'B'), ('A', 'C'), ('B', 'C')
    
    # Permutations (ordre compte)
    for perm in permutations(items, 2):
        print(perm)
    # ('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'C'), ('C', 'A'), ('C', 'B')
    
    # Product (produit cartésien)
    for item in product(['A', 'B'], [1, 2]):
        print(item)
    # ('A', 1), ('A', 2), ('B', 1), ('B', 2)

[OK] ITERTOOLS - GROUPBY
    from itertools import groupby
    
    # Grouper par clé
    data = [
        ('Alice', 25),
        ('Bob', 30),
        ('Charlie', 25),
        ('Diana', 30)
    ]
    
    # Trier d'abord par la clé de groupement
    data.sort(key=lambda x: x[1])
    
    for age, group in groupby(data, key=lambda x: x[1]):
        print(f"Age {age}:")
        for person in group:
            print(f"  {person[0]}")
    # Age 25:
    #   Alice
    #   Charlie
    # Age 30:
    #   Bob
    #   Diana

[OK] ITERTOOLS - ACCUMULATE
    from itertools import accumulate
    import operator
    
    # Somme cumulative
    numbers = [1, 2, 3, 4, 5]
    for total in accumulate(numbers):
        print(total)  # 1, 3, 6, 10, 15
    
    # Produit cumulatif
    for product in accumulate(numbers, operator.mul):
        print(product)  # 1, 2, 6, 24, 120


[OK] ITÉRATION SUR FICHIERS


[OK] ITÉRER SUR LES LIGNES D'UN FICHIER
    # Méthode recommandée (context manager)
    with open('file.txt', 'r') as f:
        for line in f:
            print(line.strip())  # strip() enlève les \n
    
    # Avec numéro de ligne
    with open('file.txt', 'r') as f:
        for line_num, line in enumerate(f, start=1):
            print(f"{line_num}: {line.strip()}")

[OK] LIRE TOUTES LES LIGNES
    # readlines() - charge tout en mémoire
    with open('file.txt', 'r') as f:
        lines = f.readlines()
        for line in lines:
            print(line.strip())
    
    # read().splitlines() - alternative
    with open('file.txt', 'r') as f:
        for line in f.read().splitlines():
            print(line)

[OK] ITÉRER PAR CHUNKS (GROS FICHIERS)
    def read_in_chunks(file_path, chunk_size=1024):
        with open(file_path, 'r') as f:
            while True:
                chunk = f.read(chunk_size)
                if not chunk:
                    break
                yield chunk
    
    for chunk in read_in_chunks('large_file.txt'):
        # Traiter le chunk
        pass

[OK] ITÉRER SUR PLUSIEURS FICHIERS
    import glob
    
    # Tous les fichiers .txt
    for filepath in glob.glob('*.txt'):
        print(f"Processing {filepath}")
        with open(filepath, 'r') as f:
            for line in f:
                print(line.strip())

[OK] ITÉRER SUR UN CSV
    import csv
    
    with open('data.csv', 'r') as f:
        reader = csv.reader(f)
        for row in reader:
            print(row)  # row est une liste
    
    # Avec DictReader (dictionnaire par ligne)
    with open('data.csv', 'r') as f:
        reader = csv.DictReader(f)
        for row in reader:
            print(row['column_name'])

[OK] ITÉRER SUR UN JSON
    import json
    
    with open('data.json', 'r') as f:
        data = json.load(f)
        
        # Si c'est une liste
        if isinstance(data, list):
            for item in data:
                print(item)
        
        # Si c'est un dictionnaire
        elif isinstance(data, dict):
            for key, value in data.items():
                print(f"{key}: {value}")


[OK] ITÉRATION NUMÉRIQUE AVANCÉE


[OK] ITÉRATION AVEC NUMPY
    import numpy as np
    
    # Array 1D
    arr = np.array([1, 2, 3, 4, 5])
    for element in arr:
        print(element)
    
    # Array 2D - par ligne
    matrix = np.array([[1, 2, 3], [4, 5, 6]])
    for row in matrix:
        print(row)
    
    # Array 2D - par élément
    for element in matrix.flat:
        print(element)
    
    # Itération avec index
    for index, value in np.ndenumerate(matrix):
        print(f"matrix{index} = {value}")

[OK] ITÉRATION AVEC PANDAS
    import pandas as pd
    
    # DataFrame
    df = pd.DataFrame({
        'name': ['Alice', 'Bob', 'Charlie'],
        'age': [25, 30, 35],
        'city': ['Paris', 'London', 'Berlin']
    })
    
    # Itérer sur les lignes (retourne Series)
    for index, row in df.iterrows():
        print(f"{row['name']}: {row['age']} years")
    
    # Itérer sur les lignes (retourne tuple) - plus rapide
    for row in df.itertuples():
        print(f"{row.name}: {row.age} years")
    
    # Itérer sur les colonnes
    for col_name, col_data in df.items():
        print(f"Column: {col_name}")
        print(col_data)


[OK] PATTERNS ET TECHNIQUES AVANCÉES


[OK] ITÉRATION PARALLÈLE AVEC THREADING
    from concurrent.futures import ThreadPoolExecutor
    
    def process_item(item):
        # Traitement de l'item
        return item ** 2
    
    items = range(10)
    
    with ThreadPoolExecutor(max_workers=4) as executor:
        results = executor.map(process_item, items)
        for result in results:
            print(result)

[OK] ITÉRATION AVEC PROGRESS BAR
    from tqdm import tqdm
    import time
    
    # Avec tqdm
    for i in tqdm(range(100)):
        time.sleep(0.01)
        # Traitement
    
    # Avec liste
    items = ['a', 'b', 'c', 'd', 'e']
    for item in tqdm(items, desc="Processing"):
        time.sleep(0.5)

[OK] ITÉRATION AVEC RETRY
    import time
    
    def retry_iterator(iterable, max_retries=3):
        for item in iterable:
            for attempt in range(max_retries):
                try:
                    # Traitement qui peut échouer
                    yield process_with_error(item)
                    break  # Succès
                except Exception as e:
                    if attempt == max_retries - 1:
                        raise
                    time.sleep(1)  # Attendre avant retry

[OK] ITÉRATION CONDITIONNELLE (TAKEWHILE, DROPWHILE)
    from itertools import takewhile, dropwhile
    
    numbers = [1, 2, 3, 4, 5, 1, 2, 3]
    
    # Prendre tant que condition vraie
    for num in takewhile(lambda x: x < 4, numbers):
        print(num)  # 1, 2, 3
    
    # Ignorer tant que condition vraie
    for num in dropwhile(lambda x: x < 4, numbers):
        print(num)  # 4, 5, 1, 2, 3

[OK] ITÉRATION AVEC FENÊTRE GLISSANTE
    def sliding_window(iterable, n):
        """Fenêtre glissante de taille n"""
        from collections import deque
        it = iter(iterable)
        window = deque(maxlen=n)
        
        # Remplir la première fenêtre
        for _ in range(n):
            window.append(next(it))
        
        yield tuple(window)
        
        # Faire glisser
        for element in it:
            window.append(element)
            yield tuple(window)
    
    numbers = [1, 2, 3, 4, 5, 6, 7]
    for window in sliding_window(numbers, 3):
        print(window)
    # (1, 2, 3)
    # (2, 3, 4)
    # (3, 4, 5)
    # (4, 5, 6)
    # (5, 6, 7)

[OK] ITÉRATION PAR BATCH
    def batch_iterator(iterable, batch_size):
        """Itérer par lots"""
        batch = []
        for item in iterable:
            batch.append(item)
            if len(batch) == batch_size:
                yield batch
                batch = []
        
        if batch:  # Dernier batch partiel
            yield batch
    
    numbers = range(10)
    for batch in batch_iterator(numbers, 3):
        print(batch)
    # [0, 1, 2]
    # [3, 4, 5]
    # [6, 7, 8]
    # [9]

[OK] ITÉRATION RÉCURSIVE (ARBRES)
    class TreeNode:
        def __init__(self, value, children=None):
            self.value = value
            self.children = children or []
        
        def __iter__(self):
            """Itération en profondeur"""
            yield self.value
            for child in self.children:
                yield from child
    
    # Créer un arbre
    tree = TreeNode(1, [
        TreeNode(2, [TreeNode(4), TreeNode(5)]),
        TreeNode(3, [TreeNode(6)])
    ])
    
    # Itérer sur tous les nœuds
    for value in tree:
        print(value)  # 1, 2, 4, 5, 3, 6

[OK] ITÉRATION AVEC CACHE/MEMOIZATION
    from functools import lru_cache
    
    @lru_cache(maxsize=None)
    def fibonacci(n):
        if n < 2:
            return n
        return fibonacci(n-1) + fibonacci(n-2)
    
    # Itérer sur la séquence de Fibonacci
    for i in range(10):
        print(fibonacci(i))

[OK] ITÉRATION LAZY (GÉNÉRATEURS)
    def infinite_sequence():
        """Générateur infini"""
        num = 0
        while True:
            yield num
            num += 1
    
    # Utiliser avec islice pour limiter
    from itertools import islice
    
    for num in islice(infinite_sequence(), 5):
        print(num)  # 0, 1, 2, 3, 4

[OK] ITÉRATION AVEC WALRUS OPERATOR (:=, Python 3.8+)
    # Lire jusqu'à condition
    data = [1, 2, 3, 0, 4, 5, 6, 0, 7, 8]
    i = 0
    
    while (value := data[i] if i < len(data) else None) is not None:
        if value == 0:
            break
        print(value)
        i += 1
    
    # Avec fichier
    # while (line := file.readline()):
    #     process(line)


[OK] ITÉRATION ALÉATOIRE


[OK] ITÉRATION EN ORDRE ALÉATOIRE
    import random
    
    items = [1, 2, 3, 4, 5]
    
    # Copier et mélanger
    shuffled = items.copy()
    random.shuffle(shuffled)
    for item in shuffled:
        print(item)
    
    # Avec sample (sans doublon)
    for item in random.sample(items, len(items)):
        print(item)

[OK] ITÉRATION AVEC SÉLECTION ALÉATOIRE
    import random
    
    items = ['a', 'b', 'c', 'd', 'e']
    
    # Sélectionner k éléments aléatoires
    for item in random.sample(items, 3):
        print(item)
    
    # Avec remplacement (choices)
    for item in random.choices(items, k=5):
        print(item)


[OK] ITÉRATION AVEC RECHERCHE


[OK] RECHERCHE LINÉAIRE
    def linear_search(iterable, target):
        for index, item in enumerate(iterable):
            if item == target:
                return index
        return -1
    
    numbers = [3, 1, 4, 1, 5, 9, 2, 6]
    index = linear_search(numbers, 5)
    print(f"Found at index {index}")

[OK] RECHERCHE AVEC ALL/ANY
    numbers = [2, 4, 6, 8, 10]
    
    # Vérifier si tous pairs
    all_even = all(num % 2 == 0 for num in numbers)
    print(all_even)  # True
    
    # Vérifier si au moins un pair
    any_even = any(num % 2 == 0 for num in numbers)
    print(any_even)  # True

[OK] RECHERCHE DU PREMIER ÉLÉMENT
    # Avec next()
    numbers = [1, 3, 5, 6, 7, 9]
    
    # Premier pair
    first_even = next((x for x in numbers if x % 2 == 0), None)
    print(first_even)  # 6
    
    # Avec valeur par défaut
    first_negative = next((x for x in numbers if x < 0), "Not found")
    print(first_negative)  # Not found


[OK] TECHNIQUES D'OPTIMISATION


[OK] ÉVITER LES BOUCLES AVEC OPÉRATIONS VECTORISÉES
    # [X] Lent: boucle Python
    numbers = list(range(1000000))
    result = []
    for num in numbers:
        result.append(num * 2)
    
    # [OK] Rapide: list comprehension
    result = [num * 2 for num in numbers]
    
    # [OK] Très rapide: NumPy (si applicable)
    import numpy as np
    arr = np.array(numbers)
    result = arr * 2

[OK] UTILISER ITERTOOLS AU LIEU DE BOUCLES
    from itertools import chain
    
    # [X] Avec boucles
    result = []
    for list1 in [list_a, list_b, list_c]:
        for item in list1:
            result.append(item)
    
    # [OK] Avec itertools
    result = list(chain(list_a, list_b, list_c))

[OK] GÉNÉRATEURS POUR GRANDES DONNÉES
    # [X] Charge tout en mémoire
    def get_numbers():
        return [i for i in range(1000000)]
    
    # [OK] Génère à la demande
    def get_numbers():
        for i in range(1000000):
            yield i

[OK] BREAK EARLY (SORTIR TÔT)
    # [X] Continue même après avoir trouvé
    found = False
    for item in large_list:
        if item == target:
            found = True
        # Continue à boucler...
    
    # [OK] Sort dès que trouvé
    found = False
    for item in large_list:
        if item == target:
            found = True
            break

[OK] UTILISER enumerate() AU LIEU DE range(len())
    items = ['a', 'b', 'c', 'd']
    
    # [X] Moins pythonique
    for i in range(len(items)):
        print(f"{i}: {items[i]}")
    
    # [OK] Pythonique
    for i, item in enumerate(items):
        print(f"{i}: {item}")


[OK] EXEMPLES PRATIQUES


[OK] TRAITER UN LOG FILE
    def parse_log(filename):
        error_count = 0
        warning_count = 0
        
        with open(filename, 'r') as f:
            for line_num, line in enumerate(f, start=1):
                line = line.strip()
                
                if 'ERROR' in line:
                    error_count += 1
                    print(f"Line {line_num}: {line}")
                elif 'WARNING' in line:
                    warning_count += 1
        
        return error_count, warning_count

[OK] CALCULER DES STATISTIQUES
    def calculate_stats(numbers):
        total = 0
        count = 0
        minimum = float('inf')
        maximum = float('-inf')
        
        for num in numbers:
            total += num
            count += 1
            minimum = min(minimum, num)
            maximum = max(maximum, num)
        
        average = total / count if count > 0 else 0
        
        return {
            'sum': total,
            'count': count,
            'average': average,
            'min': minimum,
            'max': maximum
        }

[OK] GROUPER DES DONNÉES
    def group_by_category(items):
        groups = {}
        
        for item in items:
            category = item['category']
            if category not in groups:
                groups[category] = []
            groups[category].append(item)
        
        return groups
    
    # Utilisation
    products = [
        {'name': 'Apple', 'category': 'Fruit'},
        {'name': 'Carrot', 'category': 'Vegetable'},
        {'name': 'Banana', 'category': 'Fruit'},
    ]
    
    grouped = group_by_category(products)

[OK] PAGINATION
    def paginate(items, page_size=10):
        """Générer des pages d'items"""
        for i in range(0, len(items), page_size):
            yield items[i:i + page_size]
    
    # Utilisation
    all_items = list(range(1, 101))
    
    for page_num, page in enumerate(paginate(all_items, 10), start=1):
        print(f"Page {page_num}: {page}")

[OK] VALIDER DES DONNÉES
    def validate_emails(emails):
        valid = []
        invalid = []
        
        for email in emails:
            if '@' in email and '.' in email:
                valid.append(email)
            else:
                invalid.append(email)
        
        return valid, invalid

[OK] TRANSFORMER DES DONNÉES
    def normalize_names(names):
        """Normaliser une liste de noms"""
        normalized = []
        
        for name in names:
            # Strip whitespace, capitalize
            clean_name = name.strip().title()
            
            # Skip empty strings
            if not clean_name:
                continue
            
            normalized.append(clean_name)
        
        return normalized

[OK] PIPELINE DE TRANSFORMATION
    def pipeline(data, *functions):
        """Appliquer plusieurs transformations"""
        result = data
        for func in functions:
            result = [func(item) for item in result]
        return result
    
    # Utilisation
    numbers = [1, 2, 3, 4, 5]
    result = pipeline(
        numbers,
        lambda x: x * 2,
        lambda x: x + 10,
        lambda x: x ** 2
    )


[OK] BONNES PRATIQUES


"""
1. UTILISER LES BONNES STRUCTURES
   - for ... in pour itérer sur séquences
   - while pour conditions complexes
   - enumerate() pour index + valeur
   - zip() pour plusieurs listes
   - items() pour dictionnaires

2. PRÉFÉRER LES COMPREHENSIONS
   - Plus lisibles et rapides que les boucles
   - [x for x in ...] au lieu de append()
   - Mais pas au détriment de la lisibilité

3. GÉNÉRATEURS POUR GRANDES DONNÉES
   - Économise mémoire
   - Lazy evaluation
   - yield au lieu de return liste

4. ÉVITER LES BOUCLES INUTILES
   - Utiliser built-ins: sum(), max(), min(), any(), all()
   - Utiliser itertools pour opérations complexes
   - Vectorisation avec NumPy si possible

5. BREAK EARLY
   - Sortir dès que possible
   - Utiliser else avec boucles pour signaler fin normale

6. NOMMAGE CLAIR
   - for item in items (pas for i in l)
   - for row in matrix (pas for r in m)
   - for key, value in dict.items()

7. ÉVITER LES MUTATIONS PENDANT L'ITÉRATION
   - [X] for item in list: list.remove(item)
   - [OK] Créer une nouvelle liste ou itérer sur copie

8. UTILISER CONTEXT MANAGERS POUR FICHIERS
   - with open() as f: garantit fermeture
   - Pas besoin de f.close()

9. INDEXATION
   - range(len(list)) uniquement si nécessaire
   - Préférer enumerate()

10. PERFORMANCE
    - Profile avant d'optimiser
    - Générateurs pour lazy evaluation
    - NumPy/Pandas pour calculs numériques
"""


[OK] PIÈGES COURANTS À ÉVITER


"""
[X] MODIFIER UNE LISTE PENDANT L'ITÉRATION
    items = [1, 2, 3, 4, 5]
    for item in items:
        if item % 2 == 0:
            items.remove(item)  # [X] Comportement imprévisible
    
    [OK] Solution: itérer sur copie ou list comprehension
    items = [item for item in items if item % 2 != 0]

[X] UTILISER range(len()) INUTILEMENT
    items = ['a', 'b', 'c']
    for i in range(len(items)):
        print(items[i])  # [X] Pas pythonique
    
    [OK] Solution:
    for item in items:
        print(item)

[X] OUBLIER QUE RANGE EST EXCLUSIF
    for i in range(10):
        pass  # i va de 0 à 9, pas 10!

[X] RÉUTILISER UN GÉNÉRATEUR
    gen = (x**2 for x in range(5))
    list(gen)  # [0, 1, 4, 9, 16]
    list(gen)  # [] [X] Vide! Générateur épuisé

[X] CONFONDRE is ET ==
    for i in range(1000):
        if i is 500:  # [X] Utiliser is pour comparaison
            pass
    
    [OK] Solution:
    if i == 500:

[X] BOUCLE INFINIE ACCIDENTELLE
    i = 0
    while i < 10:
        print(i)
        # Oubli: i += 1  [X] Boucle infinie!

[X] NE PAS GÉRER LES ITÉRABLES VIDES
    items = []
    max_item = max(items)  # [X] ValueError
    
    [OK] Solution:
    max_item = max(items, default=None)
"""


[OK] RESSOURCES


"""
Documentation Python:
- Loops: https://docs.python.org/3/tutorial/controlflow.html
- Itertools: https://docs.python.org/3/library/itertools.html
- Built-in Functions: https://docs.python.org/3/library/functions.html

PEP 289 - Generator Expressions:
https://peps.python.org/pep-0289/

PEP 202 - List Comprehensions:
https://peps.python.org/pep-0202/

Itertools Recipes:
https://docs.python.org/3/library/itertools.html#itertools-recipes
"""


# FIN DU CHEATSHEET LOOPS COMPLET
