Skip to main content

Advent of Code 2021 Day 3

Le problème

Partie 1

Aujourd’hui, on nous dit désormais que notre sous-marin fait de drôles de bruits, et qu’il faut maintenant analyser un rapport diagnostique qui consiste en une liste de nombres binaires.

On nous explique qu’il faut analyser la consommation d’énergie du sous-marin,qui se calcule en multipliant deux valeurs : la valeur gamma et la valeur epsilon.

À partir de notre liste de nombres binaires, on peut déterminer les valeurs en binaire de gamma et de epsilon de la façon suivante :

  • Chaque bit de gamma est déterminé en récupérant la valeur la plus fréquemment trouvée pour cette position pour chaque ligne du rapport diagnostique.
  • Chaque bit de epsilon est déterminé en récupérant la valeur la moins fréquemment trouvées pour cette position pour chaque ligne du rapport diagnostique.

D’ores et déjà, on peut constater que epsilon est le complément à un de gamma. Cela signifie gamma + epsilon = 2^n - 1, où n est le nombre de bits.

Il faut donc être en mesure de déterminer la valeur de bit la plus fréquente à chaque position. Pour ça, j’applique l’algorithme suivant :

Pour i allant de 0 à taille_message:
    Détermine la valeur de bit la plus fréquente
    Ajoute cette valeur à la variable gamma
Convertit gamma en entier
Calcule epsilon

Voici donc les deux fonctions que j’ai implémentées (une pour déterminer la valeur de bit la plus fréquente, une autre pour résoudre l’exercice) :

def most_recurrent_digit(lines, index):
    values = [int(line[index]) for line in lines]
    # Puisqu'il n'y a que des '0' et '1', on peut simplement compter le nombre de 1
    # et le comparer à la moitié de la taille du message.
    return '1' if sum(values) >= len(lines)/2 else '0'
    
    
def solve_part_one():
    with open('input-small', 'r') as data:
        lines = data.read().splitlines()
        size_message = len(lines[0])
        gamma = ''
        for char_index in range(size_message):
            gamma += most_recurrent_digit(lines, char_index)
        gamma = int(gamma, 2)
        epsilon = (2**size_message - 1) - gamma
        print(gamma * epsilon)

Partie 2

La seconde partie du problème reprend à peu près le même principe, sauf qu’au lieu de déterminer la valeur du bit la plus fréquente dans l’ensemble des données, on procède d’abord à un filtre pour ne garder que les lignes où cette valeur est effectivement présente. Il y a également une petite subtilité quand à la valeur à garder lorsqu’il y a un nombre égal de 0 et de 1, selon que l’on cherche à déterminer la valeur en oxygène ou en CO2.

Pour cela, l’algorithme est quasiment identique, il faut simplement faire attention à bien filtrer les bonnes lignes, et à rappliquer l’algorithme sur les lignes filtrées. Cela donne les deux fonctions suivantes :

# On note l'ajout du paramètre pour distinguer les valeurs d'oxygène ou de CO2
def most_recurrent_digit(lines, index, is_oxygen):
    values = [int(line[index]) for line in lines]
    if is_oxygen: 
        return '1' if sum(values) >= len(lines)/2 else '0'
    return '0' if sum(values) >= len(lines)/2 else '1'
    
    
def solve_part_two():
    with open('input-large', 'r') as data:
        lines = data.read().splitlines()
        size_message = len(lines[0])
        # On crée les listes de lignes qui serviront au filtrage
        oxygen_lines, co2_lines = lines, lines
        for char_index in range(size_message):
            # Ici, on fait bien attention à chercher le bit le plus fréquent 
            # dans les listes filtrées
            oxygen_digit = most_recurrent_digit(oxygen_lines, char_index, True)
            co2_digit = most_recurrent_digit(co2_lines, char_index, False)
            if len(oxygen_lines) > 1:
                oxygen_lines = [line for line in oxygen_lines
                                     if line[char_index] == oxygen_digit]
            if len(co2_lines) > 1:
                co2_lines = [line for line in co2_lines 
                                  if line[char_index] == co2_digit]
        ox_rating, co2_rating = int(oxygen_lines[0], 2), int(co2_lines[0], 2)
        print(ox_rating * co2_rating)

Pour le coup, on pourrait refactoriser le tout pour n’avoir qu’une seule fonction most_recurrent_digit() qui aurait toujours 3 paramètres, mais dont la valeur par défaut de is_oxygen serait True pour ne pas avoir à s’en préoccuper pour la partie 1.

Les difficultés

Pour les deux problèmes d’aujourd’hui, le problème principal est que le format des données que l’on reçoit n’est pas idéal pour les opérations que l’on doit faire dessus.

En effet, on reçoit une liste de nombres binaires mais on nous demande plutôt de travailler sur chaque colonne. Pour le coup, les compréhensions de listes sont assez pratiques ici pour créer rapidement la structure de données utile pour résoudre ensuite rapidement le problème.

Pour le reste, reconnaître le complément à un permet de simplifier un peu les calculs en partie 1. Et ensuite, la partie 2 nécessite une manipulation assez précise des listes que l’on filtre au fur et à mesure. Pour ne pas m’embêter avec des modifications de listes intempestives (dues au fait que les listes sont mutables), je redéfinis à chaque fois oxygen_lines et co2_lines par compréhension.

comments powered by Disqus