Fonctions booléennes

Les opérations ensemblistes

Proposition. Étant donné un ensemble fini X à n éléments, et une énumération x1,,xn des éléments de x, la fonction :

>b1bn{xibi=1}>

définit une bijection de Bn dans P(X).

En utilisant l’isomorphisme de la proposition, qui établit une bijection entre les mots binaires de longueur n et les sous-ensembles d’un ensemble X à n éléments, les correspondances entre les opérations booléennes et les opérations ensemblistes sont:

  1. Négation ¬ : complémentaire d’un sous-ensemble SX, noté Sc. ¬SXS.

  2. Conjonction : l’intersection de deux sous-ensembles S et T. STST.

  3. Disjonction : l’union de deux sous-ensembles S et T. STST.

  4. Implication () : l’union du complémentaire de S avec T.
    STScT.

  5. Équivalence () : l’union des parties où S et T ont les mêmes éléments (soit tous deux présents, soit tous deux absents).
    ST(ST)(ScTc).

  6. Disjonction exclusive () : la différence symétrique entre S et T, qui regroupe les éléments présents dans un seul des deux ensembles, mais pas dans les deux simultanément.
    ST(ST)(TS).

Fonctions bouléennes

Nombre d’opérations booléennes n-aires et de fonctions BnBk

  1. Nombre d’opérations booléennes n-aires : Une opération booléenne n-aire est une fonction BnB, où B={0,1} est l’ensemble des valeurs booléennes. Le nombre total de telles fonctions est donné par le nombre d’assignations possibles de valeurs de vérité pour 2n entrées (les 2n combinaisons des n variables booléennes) vers {0,1}. Il y a donc 22n fonctions booléennes n-aires.
  2. Une fonction de Bn vers Bk associe à chaque entrée de Bn une sortie dans Bk. Ce qui équivaut à décrire k fonctions indépendantes BnB. Le nombre total de telles fonctions est alors (2k)2n=2k2n.

Écriture de toute opération booléenne en termes des constantes 0, 1, ¬, , et

Preuve : Montrons que toute opération booléenne peut être exprimée en utilisant ¬, , , et les constantes 0 et 1, grâce à la DNF.

On sait que toute fonction booléenne f est entièrement caractérisée par sa table de vérité, qui énumère toutes les combinaisons possibles des n variables booléennes et leur valeur de sortie.

  1. Pour chaque combinaison (b1,,bn)f(b1,,bn)=1, on construit un minterme :

    m=i=1n(bi ou ¬bi),

    où chaque bi est pris tel qu’il correspond à la valeur de la variable dans cette ligne de la table.

  2. La fonction f est exprimée comme une disjonction de ces mintermes :

    f(x1,,xn)=mMintermesm.

    Les constantes 0 et 1 représentent les cas triviaux où f=0 (fonction nulle, aucun minterme) ou f=1 (fonction toujours vraie).

Le 2p-aire en utilisant 2p1 fois le binaire

L’opération n-aire correspond à une conjonction logique sur n entrées. On suppose n=2p pour un entier p0. Une manière naturelle de construire cette opération est d’appliquer récursivement le binaire suivant une structure en arbre binaire :

Diviser les 2p entrées en paires successives, appliquant à chaque paire (ce qui nécessite 2p1 applications). Répéter ce processus sur les 2p1 résultats intermédiaires (ce qui prend 2p2 applications en plus), et ainsi de suite, jusqu’à ce qu’il reste une seule valeur.

Le nombre total d’applications nécessaires est donc la somme des termes d’une progression géométrique :

S=2p1+2p2++1=2p121=2p1.

Peut-on faire mieux ?

Si k<2p1, alors il reste plus d’une sortie à produire, car 2pk>1. Le résultat final (le n-aire) ne peut pas dépendre de toutes les 2p entrées, ce qui est contradictoire car par définition, l’opération n-aire doit renvoyer 1 si toutes les entrées valent 1, et 0 sinon. Pour assurer ça, il faut que chaque entrée initiale contribue, directement ou indirectement, au résultat final.

Codages binaires de longueur fixée

Quelques Définitions

  1. An l’ensemble des mots de longueur fixe n sur l’alphabet A :

    An={u=a1a2an | aiA, i{1,2,,n}}.

    Par exemple si A={0,1} et n=2, alors A2={00,01,10,11}.

    An est utile pour représenter des messages ou codes de taille fixée, par exemple dans les codages à longueur fixe.

  2. A est l’ensemble des mots de longueur arbitraire (y compris nulle) sur l’alphabet A :

    A=n0An.

    Ce qui inclut:

    • Les mots de longueur 0, c’est-à-dire l’ensemble {ε}ε est le mot vide.
    • Les mots de longueur 1 ( simplement A).
    • Les mots de longueur n pour tout n2.

    Par exemple : si A={a,b}, alors A={ε,a,b,aa,ab,ba,bb,aaa,}.

    A est plus général et utilisé pour représenter des messages de longueur variable, comme dans les codages préfixes.

  3. Soit X un ensemble fini. Une convention de codage sur n bits des éléments de X est définie par:

    • une fonction de codage γ:XBn, et
    • une fonction partielle de décodage δ:BnX, telles que :
    δ(γ(x))=x pour tout xX

    Un code de xX selon δ est n’importe quelle préimage uBn telle que δ(u)=x. La fonction γ sélectionne un de ces codes.

    • δ o γ est l’identité sur X, deux éléments distincts de X ne peuvent avoir la même image par γ. Ce qui impose que γ est injective.

      • γ n’est pas nécessairement surjective. Certains mots binaires de Bn peuvent ne pas correspondre au codage d’un élément de X.
    • Tout élément de X possède au moins un code, donc δ est surjective.

      • δ n’est pas toujours totalement définie. Il peut exister des mots binaires dans Bn qui ne décodent aucun élément de X.
      • Un même élément de X peut être associé à plusieurs mots binaires par δ. La correspondance n’est donc pas forcément univoque pour la fonction de décodage.

À quelle condition un ensemble X peut-il admettre un codage sur n bits ?

Un ensemble X peut admettre un codage sur n bits si et seulement si le nombre d’éléments de X satisfait :

|X|2n.

En effet, un mot binaire de longueur n peut représenter 2n valeurs distinctes (toutes les combinaisons possibles des n bits). Si |X|>2n, il est impossible d’associer un mot binaire unique à chaque élément de X.

Dès que X est non vide, toute convention de codage peut être étendue pour que la fonction de décodage soit totale.

Une fonction de décodage δ est dite totale si elle est définie pour tous les mots binaires possibles de longueur n (tous les éléments de Bn)

Si X, on peut compléter toute fonction de décodage partielle δ comme suit : Pour tout mot uBn qui n’est pas déjà associé à un élément de X par δ, on choisit arbitrairement un élément xX et on pose δ(u)=x. Ainsi, δ devient définie sur tout Bn.

Cette redondance ne doit pas poser pas problème dans les situations où on ne demande pas d’unicité : par exemple, si l’on veut juste une correspondance valide pour décoder n’importe quel mot binaire vers un élément de X, l’unicité n’est pas nécessaire.

  • La fonction de codage γ:XBn reste injective (chaque élément de X a un code unique).

Par exemple l’objectif peut être de garantir un décodage, même arbitraire, dans tous les cas possibles (comme dans certains systèmes d’encodage où le “reste des cas” mènent à une valeur “par défaut”).

  • Le problème de duplication ne survient qu’au moment de définir une fonction de décodage totale δ:BnX, qui ajoute des correspondances pour les mots binaires inutilisés.

À quelle condition un ensemble X peut-il admettre un codage sur n bits, avec une fonction de codage surjective (ou, de manière équivalente, une fonction de décodage totale et injective) ?

Une condition nécessaire est suffisante :

(1)|X|=2n

En effet, (1) est nécessaire: Bn contient exactement 2n mots binaires distincts. Si γ:XBn est injective, alors |X||Bn|=2n, car un élément de X ne peut pas partager un code avec un autre. Si δ:BnX est injective et totale, chaque mot binaire de Bn doit correspondre à un élément unique de X. Donc, |X|2n. En combinant |X|2n et |X|2n, on déduit que |X|=2n.

(1) est suffisante aussi : supposons |X|=2n. Construisons une fonction γ:XBn injective : comme |X|=|Bn|, γ est bijective, une telle bijection existe (par définition de la bijection). Construisons δ:BnX comme l’inverse de γ (car γ est bijective).

  • δ est injective car chaque code est associé à un unique élément de X par γ1.
  • δ est totale car tout mot binaire dans Bn a une préimage dans X.

Un codage sur n bits avec une fonction de décodage injective et totale existe si et seulement si |X|=2n.

Addition dans Z/4Z

En identifiant B4 avec B2×B2, on peut voir toute fonction B4 B2 comme une opération binaire sur Z/22Z=Z/4Z, modulo codage (disons petit-boutiste).

Vérifions que la fonction

B4B2b1b0c1c0((b0c0)b1c1)(b0c0)

représente l’addition : δ2((b0c0)b1c1,b0c0)=δ2(b1b0)+δ2(c1c0)mod4 avec δ2 le décodage binaire vers un entier dans Z/4Z.

On a :

  • δ2(b1b0)=2b1+b0,
  • δ2(c1c0)=2c1+c0.

La somme est donc :

δ2(b1b0)+δ2(c1c0)=(2b1+b0)+(2c1+c0)=2(b1+c1)+(b0+c0).

La retenue entre b0 et c0 est donnée par b0c0. La somme des bits de poids faible modulo 2 est (gère la somme des unités):

b0c0.

La somme des bits de poids fort inclut la retenue b0c0 (gère la somme des bits “plus significatifs”, en tenant compte des retenues depuis les bits faibles.):

b1+c1+(b0c0)(mod2).

Ce qui correspond exactement à (b0c0)b1c1.

Donc la concaténation de ces deux résultats donne bien:

((b0c0)b1c1)(b0c0)

Donc, la fonction donnée calcule :

δ2(b1b0)+δ2(c1c0)(mod4).

Exemple:

Dans Z/4Z, chaque entier est représenté par 2 bits : 311.

Additionnons : $$ \begin{array}{r@{}r@{}r@{}r} & ^1 & ^1 & \ & & 1 & 1\

  • & & 1 & 1 \ \hline & 1 & 1 & 0 \ \end{array} $$

Ce qui donne 6 en base de 10. Avec la troncation pour correspondre a Z/4Z , où uniquement les deux derniers bits comptent, on devrait avoir une représentation binaire de 10, qui représente 2 en base de 10 et dans

Z/4Z

, donc on a bien 62mod4.

  • Les bits de poids faible sont les derniers bits (les plus à droite) :

    • Bit faible de 3: b0=1,

    En faisant l’addition modulo 2 (XOR, ) :

    b0c0=11=0.

    Or, comme 1+1=10 en binaire, une retenue est générée :

    b0c0=11=1.
  • Les bits de poids fort sont les premiers bits (les plus à gauche) :

    • Bit fort de 3: b1=1,

    Nous ajoutons les bits forts, en tenant compte de la retenue générée par les bits faibles :

    (b0c0)b1c1=111=1

    La concaténation des deux résultats donne :

    ((b0c0)b1c1)(b0c0)=10

Données séquentielles

Le monoïde des mots

3.1.8. Exercice (Généralités sur le monoïde des mots).

Non-commutativité du monoïde A pour |A|2

Un mot uA est une suite finie de symboles pris dans l’alphabet A. La concaténation de deux mots u,vA est définie par :

uv=u1u2umv1v2vn

u=u1u2um et v=v1v2vn. Considérons un alphabet A={a,b}. Prenons les mots u=a et v=b. Alors :

uv=ab et vu=ba

Comme abba, la concaténation n’est pas commutative. Donc, A est non-commutatif dès que A contient au moins deux éléments.

Morphisme de monoïdes de A vers N

La longueur d’un mot u=u1u2unA est donnée par |u|=n. Vérifions que la fonction f:AN, définie par f(u)=|u|, est un morphisme de monoïdes:

  1. Elément neutre : l’élément neutre de A est le mot vide ε. Par définition, |ε|=0, qui est l’élément neutre de (N,+).

  2. Opération : pour u,vA, on a |uv|=|u|+|v|. Donc, f(uv)= f(u)+f(v), ce qui prouve que f est un morphisme.

    Si A={a}, chaque mot uA est de la forme an pour n N. La longueur |u| est une bijection entre A et N. Dans ce cas, f est un isomorphisme. Si A contient plusieurs symboles, il existe des mots distincts de même longueur (par exemple, ab et ba pour A={a,b} et |ab|=|ba|=2 ). Donc, f n’est pas injective et donc pas un isomorphisme.

Dénombrabilité de A

  1. Si A est fini ou dénombrable, A=nNAn, où An est l’ensemble des mots de longueur n. Chaque An est une puissance cartésienne de A, et un ensemble dénombrable de mots de longueur finie est une union dénombrable d’ensembles dénombrables, donc dénombrable.
  2. Si A est non dénombrable, A contient les mots de longueur 1 ( A1=A ), qui forment déjà un ensemble non dénombrable. donc, A n’est pas dénombrable.

Donc, A est dénombrable si et seulement si A est dénombrable.

Programmes élémentaires

fonction gonfle(s:str,k:int)

def gonfle(s: str, k: int) -> str:
    #Retourne une chaîne où chaque caractère de "s" est répété "k" fois.
    return "".join(char * k for char in s)

print(gonfle("ab", 3))  # Résultat : "aaabbb"
  1. Join est utilisée pour construire une nouvelle chaine en concaténant les répétitions de chaque caractère.
  2. char k génère k copies pour chaque caractère.

On peut aussi faire à la main:

def gonfle(s,k):
    retour = ""
    for c in s:
        for i in range(k):
            retour = retour+c
    return retour

ou bien comme k*c retourne la concaténation de k copie du charactère c :

def gonfle(s,k):
    retour = ""
    for c in s:
        retour = retour + k*c
    return retour

fonction palindrome(u)

def palindrome(u) -> bool:
    return u == u[::-1]
print(palindrome("radar"))  # Résultat : True
print(palindrome("python"))  # Résultat : False
  1. L’expression u[::1] retourne le mot u renversé.
  2. La comparaison u==u[::1] vérifie si le mot est identique à son inverse.

On peut faire aussi à la main:

def palindrome(u):
    l = len(u)
    for i in range(1//2):
        If u[i] != u[l-i-1]:
        	return False
	return True

ou alors:

def palindrome(u):
    return all(u[i] == u[-i-1] for i in range(len(u)))

ou bien en utilisant la fonction reversed (la conversion en tuple des deux côtés de l’égalité est nécessaire pour obtenir des séquences du même type) :

def palindrome(u):
    return tuple(reversed(u)) == tuple(u)

mais c’est moins efficace parce qu’on fait deux fois plus de tests (on parcourt toute la liste) ; ou bien avec un usage astucieux des tranches. :

def palindrome(u):
    l = len(u)
    return u[:1//2] == u[:-1-1//2:-1]

Codes de longueurs variables

Codages préfixes

Dans la théorie du codage et la représentation des données, les codages préfixes garantissent que les séquences de symboles encodés peuvent être décodées de manière unique. La propriété préfixe assure qu’aucun mot de code n’est le préfixe d’un autre, ce qui permet un décodage sans ambiguïté des codes concaténés. Cette propriété est utilisée dans des applications comme la compression de données (par exemple, le codage de Huffman) ou les encodages de caractères (comme UTF-8).

Soit γ:XA une fonction de codage, où X est un ensemble fini et A l’ensemble des mots finis sur un alphabet A. On dit que γ est un codage préfixe si, pour tous x,yX, γ(x) est un préfixe de γ(y) si et seulement si x=y.

Vérification de l’injectivité de γ :

Soient x1,x2,.,xnX et désignons u=x1x2.xnX.

Définissons γ le morphisme : γ(x1x2xn)=γ(x1)γ(x2)γ(xn).

Si γ est un codage préfixe sur X, aucun code γ(x) n’est un préfixe strict d’un autre code γ(y), sauf si x=y.

  • Si u=ε alors γ(u)=γ(ε)=γ(ε)=εA. Si γ(v)=γ(u)=ε, alors v=u=ε également.

  • Supposons que deux mots u=x1x2xk et v=y1y2ym de X sont tels que γ(u)=γ(v). Donc :

    γ(x1)γ(x2)γ(xk)=γ(y1)γ(y2)γ(ym).

    Comme γ est un codage préfixe, aucun code γ(x) ne peut être un préfixe strict d’un autre code γ(y). donc ça garantit que le début de γ(u), c’est-à-dire γ(x1), correspond nécessairement au début de γ(v), soit γ(y1) et vice versa. Par conséquent, on a :

    γ(x1)=γ(y1).

    Puisque γ est injectif, ça implique que:

    x1=y1.

    En supprimant la première lettre, on obtient deux nouveaux mots :

    u=x2x3xk,v=y2y3ym.

    Comme γ(x1)=γ(y1), il reste :

    γ(u)=γ(v).

    Et ainsi de suite, par récurrence, on obtient u=v.

Codage de taille fixe est toujours un codage préfixe

Un codage de taille fixe γ:XAn associe à chaque élément de X un mot de longueur constante n. Si deux codes distincts γ(x) et γ(y) ont toujours la même longueur, aucun ne peut être le préfixe de l’autre. Par conséquent, tout codage de taille fixe est nécessairement un codage préfixe.

Fonction prefixe(u, v) :

Voici une implémentation en Python d’une fonction qui retourne True si le mot u est préfixe de v et False sinon.

def prefixe(u, v):
    return v.startswith(u)

Avec les deux versions qui suivent on accepte que des mots de types différents soient préfixe l’un de l’autre ou non prefixe("a", ["a", "b"]) == True mais pas avec les autres versions (parce que pour Python une chaîne n’est jamais égale à une liste).

def prefixe(u,v):
    l = len(u)
    If l>len(v):
        return False
    for i in range(1):
        If u[1] != v[1]:
            return False
    return True
def prefixe(u,v):
    return len(u) <= len(v) and all(u[i] == v[i] for i in range(len(u)))


def prefixe(u,v):
    return len(u) <= len(v) and u == v[:len(u)]

UTF-8 est un codage préfixe

Chaque caractère unicode est codé par une séquence de 1 à 4 octets, où la structure du premier octet détermine la longueur totale de la séquence :

  • 1 octet : 0xxxxxxx (ASCII, où x représente des bits).
  • 2 octets : 110xxxxx 10xxxxxx.
  • 3 octets : 1110xxxx 10xxxxxx 10xxxxxx.
  • 4 octets : 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx.

Tous les octets non initiaux commencent par 10, ce qui les distingue des octets initiaux. Aucune séquence UTF-8 complète ne peut être un préfixe strict d’une autre séquence :

Les séquences à 1 octet (0xxxxxxx) ne peuvent jamais être le début d’une séquence à plusieurs octets, car les séquences à plusieurs octets commencent par 110, 1110, ou 11110.

Les octets internes (10xxxxxx) ne peuvent pas être interprétés comme des séquences valides par eux-mêmes.

Par exemple, le code UTF-8 pour un caractère ASCII est 0xxxxxxx. Ce code ne peut être le début d’une séquence UTF-8 valide à plusieurs octets, car un tel code doit commencer par 110, 1110, ou 11110. D’où, UTF-8 est un codage préfixe.

Aucun suffixe strict n’est un préfixe

Un suffixe strict d’un code UTF-8 est une partie terminale d’un code, ne contenant pas son premier octet. Les suffixes stricts contiennent uniquement des octets non initiaux, qui ont la forme 10xxxxxx.

Les octets non initiaux (10xxxxxx) ne peuvent pas être des octets initiaux (ils ne commencent ni par 0, ni par 110, 1110, ou 11110). Ils ne peuvent donc pas former un préfixe valide d’un autre code UTF-8, car un préfixe doit inclure le premier octet d’une séquence UTF-8.

Représenter les nombres

Théorème (Existence et unicité de l’écriture en base B ).

Si on fixe B>1, alors tout entier n[[Bk]]

admet une unique écriture sous la forme

>n=i=0k1ciBi>

avec $0 \leq c_i

Démonstration:

  • Existence:

    Cas de base (k=0) : Si k=0, alors n=0, et l’écriture est immédiate. Étape de récurrence : Supposons que le théorème soit vrai pour k1. Pour k>0, considérons comment n peut être décomposé :

    n=(i=0k2ci+1Bi)×B+c0

    Ici :

    • c0 est le reste de la division euclidienne de n par B (le chiffre des unités).
    • La partie restante, i=0k2ci+1Bi, est le quotient n÷B écrit en base B avec k1 termes.

Par récurrence, l’existence et l’unicité de l’écriture du quotient garantissent celles de n.

  • Unicité :

    Les coefficients ci sont déterminés par le processus de division euclidienne:

    • c0 est le reste obtenu en divisant n par B.
    • Les coefficients suivants proviennent des divisions successives du quotient par B.

Écriture en base 𝐵 en Python

Exemple d’une fonction representation(n,B)​ qui renvoie la liste des ci, pour 0i< |n|B , et sa fonction réciproque valeur( c, B) qui calcule la somme étant donnée la liste c des coefficients et la base B .

def representation(n, B, k=None):
    """
    n (int): Le nombre entier à convertir.
    B (int): La base dans laquelle effectuer la conversion.
    en option : k (int): Longueur de l'écriture si spécifiée.
    """
    if n == 0:
        res = [0]
    else:
        res = []
        while n > 0:
            res.append(n % B)
            n //= B
        res.reverse()

    if k is None:
        return res
    elif k >= len(res):
        #Retourne: list[int]: Liste des chiffres de n en base B.
        return [0] * (k - len(res)) + res
    else:
        #Si k est fourni et est inférieur à la taille minimale.
        raise ValueError("La longueur k est inférieure à la taille minimale |n|_B.")

def valeur(c, B):
    # Retourne: int: La valeur entière.
    return sum(d * (B ** i) for i, d in enumerate(reversed(c)))

def conversion(c, B1, B2):
    """
    Convertit une écriture d'une base B1 à une base B2.
    c (list[int]): Liste des chiffres en base B1.
    """
    n = valeur(c, B1)
    #Retourne:list[int]: Liste des chiffres en base B2.
    return representation(n, B2)