RPN : Recherche de permutations

On trouve, dans certains ouvrages sur les calculatrices HP, des tables donnant les combinaisons de touches pour réordonner les éléments de la pile XYZT :

Par exemple ci-dessus, ligne n°9, si ABCD sont sur la pile XYZT, la combinaison x><y, CLX et ENTER permettra d’avoir 00AC sur la pile.

L’idée m’est venue de faire la même chose pour les calculatrices HP-48/49/50g qui ont d’autres commandes pour manipuler la pile.

A partir d’éléments (e1 au niveau 1, e2 au niveau 2 etc.), on voudrait les réordonner en utilisant les commandes de base du langage RPN : DUP, SWAP, DROP, OVER, ROT, DUP2, DROP2 et PICK3.

Exemples :

La pile contient uniquement 2 éléments e1 (niveau 1) et e2 (niveau 2)et on veut les inverser. Réponse : SWAP

La pile contient 3 éléments e1 : e2 : e3 et on veut inverser les éléments des niveaux 2 et 3 pour obtenir e1 : e3 : e2. Réponse : ROT SWAP

Je vous propose un petit programme Python qui fait une recherche brute de toutes les possibilités (en partant des combinaisons à 1 commandes, puis à 2 commandes etc. Avec répétitions, par exemple OVER OVER). Il recherche jusqu’à une combinaison de 7 commandes ce qui peut demander du temps !

Tester le programme en ligne

from itertools import *

keys = "DUP", "SWAP", "DROP", "OVER", "ROT", "DUP2", "DROP2", "PICK3"

def DUP(s): return [s[0]] + s if len(s)>0 else -1
def SWAP(s): return [s[1], s[0]] + s[2:] if len(s) > 1 else -1
def DROP(s): return s[1:] if len(s) > 0 else -1
def OVER(s): return [s[1]] + s if len(s) > 1 else -1
def ROT(s): return [s[2]] + s[:2] +s[3:] if len(s) > 2 else -1
def DUP2(s): return [s[0], s[1]] + s if len(s) > 1 else -1
def DROP2(s): return s[2:] if len(s) > 2 else -1
def PICK3(s): return [s[2]] + s if len(s) > 2 else -1

def find(target, n):
 rr = [1 + v for v in range(n)]
 tr = [int(v) for v in target]
 for n in range(1, 8):
  for op in combinations_with_replacement(keys, n):
   for p in permutations(op):
    r = rr
    for c in p:
     t = eval(c)(r)
     if t == -1: break
     r = t
    if r == tr:
     rs = ':'.join([str(v) for v in rr])
     return '{} -> {} = {}'.format(rs, ':'.join(list(target)), " / ".join(p))

 return "No solution"

# 5 levels on the stack (e1:e2:e3:e4:e5). Goal : e2:e2
>>> find("22", 5)
'1:2:3:4:5 -> 2:2 = ROT / DROP2 / SWAP / ROT / DROP2 / DUP'

# 3 levels on the stack (e1:e2:e3). Goal : e1:e2:e3:e1:e2:e3
>>> find("123123", 3)
'1:2:3 -> 1:2:3:1:2:3 = PICK3 / PICK3 / PICK3'

# 3 levels on the stack. Goal : e1:e1:e2:e2:e3:e3
>>> find("112233", 3)
'1:2:3 -> 1:1:2:2:3:3 = PICK3 / ROT / ROT / DUP2 / ROT'

# 4 levels on the stack. Goal : e2:e2:e4:e4
>>> find("2244", 4)
'1:2:3:4 -> 2:2:4:4 = ROT / DROP2 / DUP2 / ROT'

Version pour la HP-50g

Cette calculatrice a plus de commandes que les HP-48G pour manipuler la pile, j’ajoute DUPDUP, UNROT et 4 ROLL (comme ROT mais avec le niveau 4)

from itertools import *

# Version HP-50g

keys = "DUP", "DUPDUP","SWAP", "DROP", "OVER", "ROT", "UNROT", "DUP2", "DROP2", "PICK3", "_4_ROLL"

def DUP(s): return [s[0]] + s if len(s)>0 else -1
def DUPDUP(s): return [s[0], s[0]] + s if len(s)>0 else -1
def SWAP(s): return [s[1], s[0]] + s[2:] if len(s) > 1 else -1
def DROP(s): return s[1:] if len(s) > 0 else -1
def OVER(s): return [s[1]] + s if len(s) > 1 else -1
def ROT(s): return [s[2]] + s[:2] +s[3:] if len(s) > 2 else -1
def UNROT(s): return s[1:3] + [s[0]] +s[3:] if len(s) > 2 else -1
def DUP2(s): return [s[0], s[1]] + s if len(s) > 1 else -1
def DROP2(s): return s[2:] if len(s) > 2 else -1
def PICK3(s): return [s[2]] + s if len(s) > 2 else -1
def _4_ROLL(s): return [s[3]] + s[:3] +s[4:] if len(s) > 3 else -1

def find(target, n):
 rr = [1 + v for v in range(n)]
 tr = [int(v) for v in target]
 for n in range(1, 8):
  for op in combinations_with_replacement(keys, n):
   for p in permutations(op):
    r = rr
    for c in p:
     t = eval(c)(r)
     if t == -1: break
     r = t
    if r == tr:
     rs = ':'.join([str(v) for v in rr])
     return '{} -> {} = {}'.format(rs, ':'.join(list(target)), " / ".join(p))

 return "No solution"

>>> find("231", 3)
'1:2:3 -> 2:3:1 = UNROT'

>>> find("4123", 4)
'1:2:3:4 -> 4:1:2:3 = _4_ROLL'

>>> find("135", 5)
'1:2:3:4:5 -> 1:3:5 = SWAP / _4_ROLL / DROP2'

>>> find("4321", 4)
'1:2:3:4 -> 4:3:2:1 = SWAP / ROT / _4_ROLL'

Version en PROLOG (en cours de réalisation)

% Règles pour les opérations sur la pile

% SWAP : Inverse les niveaux 1 et 2
swap([A,B|T], [B,A|T]).

% DUP : Duplique le niveau 1
dup([A|T], [A,A|T]).

% DROP : Supprime le niveau 1
drop([_|T], T).

% OVER : Récupère le niveau 2 et le met au niveau 1
over([A,B|T], [B,A,B|T]).

% ROT
rot([A,B,C|T], [C,A,B|T]).


% Règle pour arrêter la récursivité lorsque la pile est déjà dans l'état voulu
trouve([], Stack, Stack).

% Règles pour trouver la séquence d'opérations 

trouve([swap|Actions], Stack, FinalState) :-
    swap(Stack, NewStack),
    trouve(Actions, NewStack, FinalState).

trouve([dup|Actions], Stack, FinalState) :-
    dup(Stack, NewStack),
    trouve(Actions, NewStack, FinalState).

trouve([drop|Actions], Stack, FinalState) :-
    drop(Stack, NewStack),
    trouve(Actions, NewStack, FinalState).

trouve([over|Actions], Stack, FinalState) :-
    over(Stack, NewStack),
    trouve(Actions, NewStack, FinalState).

trouve([rot|Actions], Stack, FinalState) :-
    rot(Stack, NewStack),
    trouve(Actions, NewStack, FinalState).

Quelques tests

rot([1,4,3],X).
Réponse : X = [3, 1, 4]

swap([1,4,3],X).
Réponse : X = [4, 1, 3]

drop([1,4,3],X).
Réponse : X = [4, 3]

over([1,4,3],X).
Réponse : X = [4, 1, 4, 3]

trouve(X, [1,4,3], [4,1,3]).
X = [swap]

Version 2 proposée par chatGPT

trouve(Actions, Stack, FinalState) :-
    trouver_sequence(Actions, [], Stack, FinalState).

% Cas où l'état actuel est l'état voulu
trouver_sequence([], _, Stack, Stack).

% Cas où l'état actuel est différent de l'état voulu, continue à chercher
trouver_sequence([Action|Actions], History, CurrentState, FinalState) :-
    \+ member(CurrentState, History), % Vérifie si l'état actuel a déjà été visité pour éviter les boucles infinies
    apply_action(Action, CurrentState, NextState),
    trouver_sequence(Actions, [CurrentState|History], NextState, FinalState).

% Applique une action sur l'état actuel de la pile
apply_action(swap, [A,B|T], [B,A|T]).
apply_action(dup, [A|T], [A,A|T]).
apply_action(drop, [_|T], T).
apply_action(over, [A,B|T], [B,A,B|T]).
apply_action(rot, [A,B,C|T], [C,A,B|T]).

GolfScript

Programmes à tester ici : https://tio.run/#golfscript

Programmes de la vidéo

Somme des entiers

10 .)*2/
donne 55

Syracuse version 1

27 .2%{3*)}{2/}if
donne 82

16 3*).2%6\?/
donne 8

Maximum d’un tableau

[2 5 1 3] $-1=
donne 5

Rentre Avec Tes Pieds

['RENTRE' 'AVEC' 'TES' 'PIEDS'] {[0=]}%'.'*
donne "R.A.T.P"

Fractions continues et nombre d’or

1{-1?).p}10*;

Somme des chiffres d’un entier

{0 {\.}{.10/\10%@+}while;}:sdc;

{.{.10%\10/sdc}0if+}:sdc;

Syracuse : Temps de vol

{0{\.(}{.2%\.3*)\2/if\)}while;}:syr;

{0{)\.2%\.3*)\2/if.(@\}do\;}:syr;

{{(}{3*).2%6\?/}/,}:syr;

Exemples supplémentaires (Project Euler)

P1 / Multiples de 3 ou 5 : https://projecteuler.net/problem=1

1000,.{3%},{5%},-{+}*

1000, : Création de la liste 0 à 999
. : On duplique la liste
{3%}, : On filtre ceux qui ont un reste non nul en les divisant par 3
{5%}, : Et ceux qui ont un reste non nul en les divisant par 5
- : On garde les multiples de 3 ou 5
{+}* : Réduction par la somme

P2 / Nombres de Fibonacci pairs : https://projecteuler.net/problem=2

0 1{2000.*<}{.@+}/\;.{2%},-{+}*

0 1 : On place 0 et 1 sur la pile
{2000.*<}{}/ : Tant que l'on ne dépasse pas 4 millions, ajout ds tableau
.@+ : Nombre Fibonacci suivant (a b -> a b b -> b b a -> b b+a)
\; : On garde uniquement le tableau
.{2%},- : On duplique le tableau et on ne garde que les nombres pairs
{+}* : Réduction par la somme

P6 / Sum square difference : https://projecteuler.net/problem=6

101,(;.{+}*2?\{2?}%{+}*-

101, : [0 ... 100]
( : supprime le 1er élément et le met à la fin [1 ... 100] 0
;. : supprime le 0 et duplique le tableau
{+}*2? : réduction par la somme de l'autre tableau puis carré
\{2?}%{+}* : SWAP, met chaque élément au carré puis réduction par la somme
- : Différence des 2 valeurs

Recherches personnelles

Palindrome

{.,1>{(\)@={pal}0if}1if\;}:pal;

"ABBA" pal
donne 1
"ABBCA" pal
donne 0

Python & Cryptographie partie 5 : Autoclave

def grille(a, b):
    return chr(65 + (ord(a) + ord(b)) % 26)

def invGrille(a, b):
    return chr(65 + (ord(a) - ord(b)) % 26)

def chiffreAutoclaveV1(phrase:str, cle:str):
    phrase = phrase.upper()
    chiffre = ''
    for c in phrase:
        cle = grille(c, cle)
        chiffre += cle
    return chiffre

def dechiffreAutoclaveV1(chiffre:str, cle:str):
    clair = ''
    for c in chiffre:
        clair += invGrille(c, cle)
        cle = c
    return clair    
  
phrase = 'OPERATIONURGENTE'
cle = 'K'
chiffre = chiffreAutoclaveV1(phrase, cle)
print('Chiffrée :', chiffre)
print('Déchiffrée :', dechiffreAutoclaveV1(chiffre, cle))


def chiffreAutoclaveV2(phrase:str, mot:str):
    phrase = phrase.upper()
    cle = mot.upper() + phrase
    chiffre = ''
    for i, c in enumerate(phrase): chiffre += grille(c, cle[i])
    return chiffre

def dechiffreAutoclaveV2(chiffre:str, mot:str):
    clair = ''
    cle = mot
    for i, c in enumerate(chiffre):
        lettre = invGrille(c, cle[i])
        clair += lettre
        cle += lettre
    return clair

phrase = 'MESSAGEIMPORTANTPOURNESTORLAGIRAFEAUNLONGCOU'
mot = 'POIRIER'
chiffre = chiffreAutoclaveV2(phrase, mot)
print('Chiffrée :', chiffre)
print('Déchiffrée :', dechiffreAutoclaveV2(chiffre, mot))

Python & Cryptographie partie 4 : Chiffre de Pollux

from random import choice

# Initialisation des variables
codes = ".-+-...+-.-.+-..+.+..-.+--.+....+..+.---+-.-+.-..+--+-.+---+.--.+--.-+.-.+...+-+..-+...-+.--+-..-+-.--+--..".split("+")
chiffres = [chr(v) for v in range(48, 58)]
alphabet = [chr(v) for v in range(65,91)]
alphanum = chiffres + alphabet
morse = dict(zip(alphabet, codes))

def dicoChiffrement():
 dico = {'.':[], '-':[], '+':[]}
 cles = '.-+'
 possibilites = list(alphanum)  # Copie de alphanum
 for i in range(36):
  valeur = choice(possibilites)
  possibilites.remove(valeur)
  dico[cles[i % 3]].append(valeur)
 return dico

def chiffrePollux(phrase:str):
 phrase = phrase.upper()
 dico = dicoChiffrement()     # Création du dictionnaire
 chiffre = ''
 for c in phrase:
  if 'A' <= c <= 'Z':
   codeMorse = morse[c] + '+'
   codageLettre = ''
   for v in codeMorse:
    codageLettre += choice(dico[v])
   chiffre += codageLettre
 return chiffre, dico    
  
def dechiffrePollux(chiffre:str, dico):
 # Traduction chiffré vers Morse
 enMorse = chiffre.upper()
 for k, v in dico.items():
  for c in v: enMorse = enMorse.replace(c, k)
 # Traduction Morse vers message clair
 clair = ''
 cleMorse = list(morse.keys())
 valMorse = list(morse.values())
 for m in enMorse.split('+'):
   if m != '':
    clair += cleMorse[valMorse.index(m)]
 return clair 

def enColonne(chiffre):
 for i in range(0, len(chiffre), 5):
  if i % 4 == 0 and i > 0: print()
  print(chiffre[i:i + 5], end='\t')
  
message = 'rendezvouscesoiravingtheuresplacevictorhugo' 
chiffre, dico = chiffrePollux(message)
print('Texte chiffré :')
enColonne(chiffre)
print('\nDictionnaire utilisé :')
print(dico)
print('Déchiffrement :')
print(dechiffrePollux(chiffre, dico))

Python & Cryptographie niveau lycée : Utilisation d’une grille

Codes de la vidéo

from random import randint

def grilleVide():
 grille = []
 for i in range(6):
  ligne = []
  for j in range(6): ligne.append('⬛')
  grille.append(ligne)
 return grille

def rotate(m, n):
 for _ in range(n): m = list(zip(*m))[::-1]
 return m

def afficher(g):
 for l in range(6): print(''.join(g[l]))

def creerGrille():
    grille = grilleVide()
    m = [[6 * l + c for c in range(6)] for l in range(6)]
    for c in range(3):
     for l in range(3):
      m = rotate(m, randint(0, 3))
      pos = m[l][c]
      x, y = pos % 6, pos // 6      
      grille[y][x] = '⬜'
    return grille
    
def completeEtoiles(phrase):
 return phrase + "*" * (9 - len(phrase) % 9)

def listeTrous(g):
 trous = []
 for l in range(6):
  for c in range(6):
   if g[l][c] == '⬜': trous.append(6 * l + c)
 return trous  

def chiffrer(phrase, grille):
 phrase = completeEtoiles(phrase)
 chiffre = grilleVide()
 trous = listeTrous(grille)
 for i, v in enumerate(phrase):
  pos = i % 9 
  if pos == 0 and i > 0:
   grille = rotate(grille, 1)
   trous = listeTrous(grille)
  x, y = trous[pos] % 6, trous[pos] // 6
  if v == "*": v = chr(randint(65, 90))
  chiffre[y][x] = v
 return chiffre 

def dechiffrer(crypte, grille):
 clair = ''
 trous = listeTrous(grille)
 for i in range(36):
  pos = i % 9 
  if pos == 0 and i > 0:
   grille = rotate(grille, 1)
   trous = listeTrous(grille)
  x, y = trous[pos] % 6, trous[pos] // 6
  clair += crypte[y][x]
 return clair
 
grille = creerGrille()        
afficher(grille)
phrase='RENDEZVOUSAQUINZEHEURESPLACELECLERC'
crypte = chiffrer(phrase, grille)
afficher(crypte)
clair = dechiffrer(crypte, grille)
print(clair)
  

Python & Cryptographie niveau lycée : Chiffrement par substitution

# Substitution simple

def subSimple(phrase:str, decalage:int):
    phrase = phrase.upper()
    chiffrement = ''
    for c in phrase:
        if 'A' <= c <= 'Z':
            position = (ord(c) - 65 + decalage) % 26
            chiffrement += chr(65 + position)
        else:
            chiffrement += c
    return chiffrement

# Substitution double (avec clé numérique)

def subDouble(phrase:str, decalage):
    phrase = phrase.upper()
    chiffrement = ''
    longueur = len(decalage)
    for i,c in enumerate(phrase):
        if 'A' <= c <= 'Z':
            position = (ord(c) - 65 + decalage[i % longueur]) % 26
            chiffrement += chr(65 + position)
        else:
            chiffrement += c
    return chiffrement            

# Substitution avec mot-clé

def fabriqueAlphabet(cle:str):
    lettres = list(cle.upper()) + [chr(v) for v in range(65, 91)]
    alphabet = ''
    for c in lettres:
        if c not in alphabet: alphabet += c
    tailleCle = len(set(cle))
    alphabetFinal = ''
    for col in range(tailleCle):
        suiv = col
        while suiv < 26:
         alphabetFinal += alphabet[suiv]
         suiv += tailleCle       
    return alphabetFinal

def subCleSimple(phrase:str,cle:str):
    alphabet = fabriqueAlphabet(cle)
    phrase = phrase.upper()
    chiffrement = ''
    for c in phrase:
        if 'A' <= c <= 'Z':
            chiffrement += alphabet[ord(c) - 65]
        else:
            chiffrement += c
    return chiffrement 

Python & Cryptographie niveau lycée : Déchiffrement des substitutions

def decryptSimple(phrase:str):
    freq = [0] * 26
    for c in phrase:
     if 'A' <= c <= 'Z':
         freq[ord(c) - 65] += 1
    decal = 4 - freq.index(max(freq))
    return subSimple(phrase, decal)

def decryptDouble(phrase:str, taille:int):
    clair = ''
    res = []
    for i in range(taille):
     res.append(decryptSimple(phrase[i::taille]))
    return ''.join(''.join(v) for v in zip(*res))

def inverseAlphabet(chiffre:str, clair:str):
    alphabetDecode = ''
    for i in range(65, 91):
      c = chr(i)
      if c in chiffre: code = clair[chiffre.index(c)]
      else: code = '?'
      alphabetDecode += code     
    return alphabetDecode 

def decryptEnPartie(phrase:str,alphabet:str):
    alphabet = alphabet.upper()
    phrase = phrase.upper()
    chiffrement = ''
    for c in phrase:
        if 'A' <= c <= 'Z':
            chiffrement += chr(65 + alphabet.index(c))
        else:
            chiffrement += c
    return chiffrement 

Exemples de textes :

clair = 'CHOISISSEZUNTRAVAILQUEVOUSAIMEZETVOUSNAUREZPASATRAVAILLERUNSEULJOURDEVOTREVIE'
# Clé numérique [3, 2, 1, 0]
crypte = 'FJPIVKTSHBVNWTBVDKMQXGWOXUBIPGAEWXPUVPBUUGAPDUBTUCWALNMEUWOSHWMJRWSDHXPTUGWIH'

# Extrait du livre "Le petit Prince"

princeClair = 'LESGRANDESPERSONNESMONTCONSEILLEDELAISSERDECOTELESDESSINSDESERPENTSBOASOUVERTSOUFERMESETDEMINTERESSERPLUTOTALAGEOGRAPHIEALHISTOIREAUCALCULETALAGRAMMAIRECESTAINSIQUEJAIABANDONNEALAGEDESIXANSUNEMAGNIFIQUECARRIEREDEPEINTREJAVAISETEDECOURAGEPARLINSUCCESDEMONDESSINNUMERO1ETDEMONDESSINNUMEROLESGRANDESPERSONNESNECOMPRENNENTJAMAISRIENTOUTESSEULESETCESTFATIGANTPOURLESENFANTSDETOUJOURSETTOUJOURSLEURDONNERDESEXPLICATIONSJAIDONCDUCHOISIRUNAUTREMETIERETJAIAPPRISAPILOTERDESAVIONSJAIVOLEUNPEUPARTOUTDANSLEMONDEETLAGEOGRAPHIECESTEXACTMABEAUCOUPSERVIJESAVAISRECONNAITREDUPREMIERCOUPDŒILLACHINEDELARIZONACESTTRESUTILESILONESTEGAREPENDANTLANUITJAIAINSIEUAUCOURSDEMAVIEDESTASDECONTACTSAVECDESTASDEGENSSERIEUXJAIBEAUCOUPVECUCHEZLESGRANDESPERSONNESJELESAIVUESDETRESPRESCANAPASTROPAMELIOREMONOPINIONQUANDJENRENCONTRAISUNEQUIMEPARAISSAITUNPEULUCIDEJEFAISAISLEXPERIENCESURELLEDEMONDESSINNUMERO1QUEJAITOUJOURSCONSERVEJEVOULAISSAVOIRSIELLEETAITVRAIMENTCOMPREHENSIVEMAISTOUJOURSELLEMEREPONDAITCESTUNCHAPEAUALORSJENELUIPARLAISNIDESERPENTSBOASNIDEFORETSVIERGESNIDETOILESJEMEMETTAISASAPORTEEJELUIPARLAISDEBRIDGEDEGOLFDEPOLITIQUEETDECRAVATESETLAGRANDEPERSONNEETAITBIENCONTENTEDECONNAITREUNHOMMEAUSSIRAISONNABLE'

princeChiffre = 'OGTGUCODHUQEUUPNQGTMRPUCRPTELNMEGGMALUTEUFFCRVFLHUEEVUJNVFFSHTQEQVTBRCTOXXFRWUPUIGSMHUFTGGNIQVFRHUTEURMUWQUAOCHERISASJJEDNIIVVPIUGBUFCMCXNFTDNBGUCNMDKSEFGTTDKOSLSVEMCJAECODRPOEDNBGHFFSLZBNVWOEPCHNLHJQXGDAUTJEUGEESGJNWTFJDXBIVGUEGGDOXTBGHRBROKOSXEDEVFFMRPEEVUJNQWNEUQ1EWFFMRPEEVUJNQWNEUQMEVISAQFFSSGSSRPOEVPFCROQRHPOEQVKAPCJSUKFNWQVTHUTEXNFSHVDEVVGAWKHAQVQOXTMEVGOFDPUSGGUOXLPUUUFTWQVJRWSSOGVRGQONHTEEVGYPOKDAWKPNVLBIGQOCGWDHRKTIUWOAXVSEPGUIHTFTMCJASRSIVCQIOQUEUFFSDXJOQUKALXPLHWOPHWQAUVPUWFBNVNFMRPEEHVMAJGPGUCQHLGDEVVFXDEUMDDFAXEPUSUFRYKKEVCWALUSEFQONDKURHFVPUGNIHTDOXREŒLNMAFJJNHFFLDTJZRPBCHUUTUGTUWKMEVKMOQGTTHIBRHRFNGCOTOCOULVKALCJNVKFUDWDOXTTDHOBVLGEEVVBSGGDOQVBCWUBVHEEEVVBSGGHEQUTEUKFUALBIEGBUFQVPYGDUFJFZOGTGUCODHUQEUUPNQGTJHNFSDKWUHUEEWTFSSTFSFCOASCTTUQQAPGMIRTFMRPPPLPJOQSVAQFKEQTFNFQOTUCJSXPFQXKNESCSALUTALVVNSGVLXEJDHLFFDKTALUMEARFRLGOCHUVRHNMEGGNOQFFSVKONXOFRR1RUHLBIWQVJRWSSFQOSHTWEMGWOXNBIVUBVRKSSLGMLHGUALVWRDKNEQVDOPRSEKGOSLXFMDKTTRWKOXTTEONFMHTFPRPEALVDEVVVNFJBPHCVAOQSSMGOEOWJPDTMALUOIGGTEURFNWUCODUOIGGGOUGUSYKFRJGTNLFFTRKMEVLFMHOFTWCJSDUBPRTUEHLFLXKQAUNBIVFFBUKEGHFFGRNGDHRPLLVJQXGFTGGDRDXBTHUFTOCHRDPEESGSSRPOEHVBIWDJEQEPNWGOTHFFCRPOALVSEXPIOPOFAXUTIUCJSRPOAENF'

Coccinelle et chaine de Markov

Voici l’énoncé d’un problème que j’ai posté sur le groupe HP Calculator Fan Club

Des propositions de simulations pour quelques calculatrices HP :

Le calcul théorique est très simple :

La réponse à trouver était donc : 5 mouvements

Version en Python

from random import random

def simul(n:int):
    tot = 0    # Total de tous les mouvements
    for _ in range(n):    # n simulations
        r = 1       # Dernier mvt de 2 vers 1
        # Aller-retour de 2 vers 3 ou 4 avec probabilité 2/3
        while int(3 * random()) != 0: r += 2
        # On ajoute le nb de mvt au total
        tot += r
    # Moyenne
    return tot / n

>>> simul(1000)
4.752
>>> simul(10000)
5.0992
>>> simul(100000)
4.98308

Binary plot

David, un collègue enseignant, a posté un tweet sur l’utilisation du mode de représentation Truth sur les anciennes calculatrices HP 48G. Voici son premier résultat, le tapis de Sierpinski :

Avec cette équation très courte :

Quelques explications : R→B permet de convertir un nombre en binaire. Pour chaque abscisse X (entre 0 et 63) et chaque Y (entre 0 et 63), on regarde s’ils ont au moins un bit en commun dans leurs écritures binaires respectives. Par exemple si X = 12 = 1100b et Y = 6 = 110b ont un bit en commun à la 3e position, on affiche dans ce cas un pixel noir à l’écran.

A partir de là j’ai trouvé la page Binary Plot du site Wollfram avec quelques visuels que j’ai voulu reproduire en Python.

Les puissances 3, 2 et 1 des entiers de 1 à 160
from kandinsky import *

for y in range(30):
 n = 2 ** y
 for x in range(160):
    for i in range(3): 
     if x ** (i + 1) & n > 0:
       fill_rect(2 * x, 214 - 63 * i - 4 * y, 2, 4, (0, 0, 0))

En Python il est très simple de faire des opérations bit à bit. Pour le “ET” on utilise &. Par exemple :

1100b AND 110b donne 100b = 4

Pour le “OU” le symbole est |. Par exemple 12 | 6 = 14 car 1100b | 110b = 1110b

Et le “OU EXCLUSIF” par ^. Par exemple 12 ^ 6 = 10 car 1100b ^110b = 1010b

Passons à la représentation des coefficients binomiaux :

Les scripts (bibliothèque PIL et NUMWORKS) sont ici.

En bas à gauche les coefficients (écrits en binaires) qui apparaissent dans les développements de (a+b)^0, (a+b)^1, (a+b)^3 etc.

Quelques formes amusantes apparaissent !

L’image à droite a été générée par l’IA DALL E

On peut également représenter la suite de Fibonacci :

Les 700 premiers termes de la suite

Les scripts sont ici.

Enfin, l’idée m’est venue de représenter la conjecture de Syracuse (on part d’un entier, s’il est pair on le divise par 2 sinon on le multiplie par 3 et on ajoute 1, la conjecture prétend que l’on arrivera à 1 au bout d’un certain temps). Avec N = 27 comme départ on arrive à 1 au bout de 111 itérations (appelé temps de vol) et le maximum atteint est 9232.

Représentons les termes de la suite sous forme binaire :

Suite de Syracuse en partant de N = 27

Il est alors assez facile de lire la valeur exacte de chacune des colonnes, par exemple du maximum. Il suffit de repérer les numéros de lignes (En bas = 0). Sur le visuel on lit les lignes 4, 10 et 13. Le nombre correspondant est donc 2^4 + 2^10 + 2^13 = 9232.

Autres exemples

Script NUMWORKS pour les “spirales”

tan(x * y / 3) > sin(y / 2)
« X Y * 4 /. TAN Y 2 / SIN > » (En mode radians)
Version HD avec bibliothèque PIL
cos(x / (y + 1) * 10) > sin( y / 5)
« X Y 1 + / 12 * COS Y 2 / SIN > » (En mode radians)
Version HD
sin(x * x / (y + 1) * 2) >= sin( y / (x + 1) * 10)
Version HD
sin(x / 8) % 1 > sin(y / 8) % 1
sqrt(x) % 1 >= sqrt(y) % 1
Tapis de Sierpinski
from kandinsky import *

def tapis(x, y):
  while x > 0 and y > 0:
    if x % 3 == 1 and y % 3 == 1: return 0
    x //= 3
    y //= 3
  return 1  

for y in range(222):
  for x in range(320):
    if tapis(x, y): set_pixel(x, 221 - y, (0,) * 3)
Version HD : 729 * 729 pixels (729 = 3^6)
@ Adaptation d'un script de David Cobac pour HP-48

« 3 PICK 3 MOD » 'MOD3 STO
« 3 / IP SWAP » 'IP3 STO
{ (0 0) (130 63) X 0 (0 0) TRUTH Y } 'PPAR STO

« X Y 
WHILE DUP2 *
 MOD3 MOD3 *
 1 ≠ *
REPEAT
 IP3 IP3
END
* NOT » 'EQ STO

« ERASE DRAW {} PVIEW » 'TAP STO

Lancez TAP


Version 2 :

« X Y 
WHILE DUP2 DUP2
 3 MOD SWAP 3 MOD
 * 1 ≠ * *
REPEAT
 3 / IP SWAP 3 / IP
END
* NOT »
Résultat sur HP 50g après environ… 50 minutes !

Graphiques en secteurs et humour

Il y a une dizaine d’années certains internautes se sont amusés à créer des graphiques en secteurs originaux, voyons comment les reproduire en Python avec la TI-83

Le coin de ma chambre

2 murs et le sol
import ti_plotlib as plt
from ti_draw import *
from math import *

data = {-14: (143, 133, 133), 90: (158, 143, 133), 214: (93, 42, 40)}

def dec(l,n,u): return [v[n] + u for v in l]

def secteurs(data):
  plt.cls()
  o = (0, 0)
  xy = [o]
  mini = min(data.keys())
  for i in range(mini, 361 + mini):
    xy.append((cos(radians(i)) * 100, -sin(radians(i)) * 100))
    if i in data.keys():
      if i != mini: fill_poly(dec(xy, 0, 160), dec(xy, 1, 105))
      set_color(*data[i])
      xy = [o] + xy[-1:]
  fill_poly(dec(xy, 0, 160), dec(xy, 1, 105))

secteurs(data)
show_draw()

Les données sont représentées sous la forme angle : couleur. Pour l’exemple ci-dessus, on part de l’angle -14° avec la couleur (143, 133, 133), on trace le secteur jusqu’à l’angle 90° puis on change de couleur, etc.

L’idée du programme est de tracer un polygone en partant du centre de l’écran (160, 105), la variable i parcourt les entiers allant de l’angle le plus petit (-14° dans l’exemple) jusqu’à faire un tour complet (360 – 14 = 346°). On mémorise dans la variable xy les coordonnées des points sur le bord du cercle de 1° en 1° et dès qu’il y a un changement de couleur on trace le polygone xy.

La pyramide

Même programme que précédemment mais en changeant data.

Une pyramide ?
data = {-42: (65, 100, 145), 222: (210, 163, 83), 298: (88, 57, 36)}
Version Maître Gims
data = {-42: (65, 100, 145), -1: (220,220,220), 0:(65, 100, 145), \
        179: (220,220,220), 180:(65, 100, 145), 202: (220,220,220), \
        204:(65, 100, 145), 222: (210, 163, 83), 298: (88, 57, 36)}

pacman

PACMAN
data = {-32: (255, 255, 255), 32: (216, 160, 24)}

Dégradé

data = {}

for i in range(20): data[18 * i] = (12 * i,) * 3

Art génératif – Modes de fusion

Lien vers Basthon P5 : https://console.basthon.fr/

Mode écran avec p5

from p5 import *
from random import *

def setup():
 createCanvas(600, 600)
 background(20,20,20)
 fill(10, 25, 10)
 blendMode(SCREEN)
 for x in range(30):
   for n in range(1 + x):
    rect(20 * x + randint(0,10), randint(-100,600), 80, 80) 

def draw():
 noLoop()

run()

Mode multiplier – NUMWORKS

from kandinsky import *
from random import randint

def rvb01(c): return tuple(v / 255 for v in c)
def rvb255(c): return tuple(255 * v for v in c)
def zip01(c1,c2): return zip(rvb01(c1), rvb01(c2))
def multiply(c1,c2): return rvb255(a * b for (a, b) in zip01(c1,c2))

def rect(x,y,w,h,c,mode):
 for i in range(w):
  for j in range(h):
   rvb = mode(c, get_pixel(x + i, y + j))
   set_pixel(x + i, y + j, rvb)

for x in range(0,320,8):
 for y in range(0,220,3):      
  rect(x + randint(0, 7), y + randint(0, 6), \
       randint(1, 320 - x), randint(1, 9), (250, 100, 250), multiply)

Mode différences – NUMWORKS et P5

# Version - NUMWORKS

from kandinsky import *
from random import *

def rvb01(c): return tuple(v / 255 for v in c)
def rvb255(c): return tuple(int(255 * v) for v in c)
def zip01(c1,c2): return zip(rvb01(c1),rvb01(c2))

def diff(c1,c2):
 return rvb255(abs(a - b) for (a, b) in zip01(c1,c2))

def rect(x,y,w,h,c,mode):
 for i in range(w):
  for j in range(h):
    rvb = mode(c, get_pixel(x + i, y + j))
    set_pixel(x + i, y + j, rvb)

fill_rect(0,0,320,222,(250, 100, 250))
for _ in range(500):  
  t = randint(20,40)   
  rect(randint(-10, 315), randint(-10, 220), t, t, (250, 100, 250), diff)

# Version P5 - Python

from p5 import *
from random import *

c = (250, 100, 250)

def setup():
 createCanvas(900, 600)

 background(c)
 blendMode(DIFFERENCE)
 fill(c)
 for _ in range(1000):
    t = randint(20,80)
    rect(randint(-20,900), randint(-20,600), t, t) 

def draw():
 noLoop()

run()

Mode addition – NUMWORKS

from kandinsky import *
from random import *
from math import cos

def rvb01(c): return tuple(v / 255 for v in c)
def rvb255(c): return tuple(int(255 * v) for v in c)
def zip01(c1,c2): return zip(rvb01(c1),rvb01(c2))

def add(c1,c2):
 return rvb255(min(1,a+b) for (a, b) in zip01(c1,c2))

def rect(x,y,w,h,c,mode):
 for i in range(w):
  for j in range(h):
    rvb = mode(c, get_pixel(x + i, y + j))
    set_pixel(x + i, y + j, rvb)

fill_rect(0,0,320,222,(40,40,40))
for i in range(50):
 rect(randint(-20,300), randint(-20,200), 60, 60,\
      (randint(0,255), randint(0,255), randint(0,255)), add)

TISSU écossais – p5

Cet exemple a été supprimé au montage de la vidéo:

from p5 import *

def setup():
 createCanvas(770, 770)
 noStroke()
 background((40,40,40))
 blendMode(SCREEN)
 fill(20, 40, 20)
 for i in range(10):
    for j in range(10):
      rect(60 * i, 60 * j, 50 + 20 * i, 50 + 20 * j) 

def draw():
    noLoop()

run()

MODE addition – p5 et NUMWORKS

from p5 import *
from random import *

def setup():
 createCanvas(800, 400)
 noStroke()
 background((50,50,50))
 blendMode(ADD)
 fill(20, 140, 20)
 
 x, y = 200, 0
 for i in range(100):
    textSize(1 + i)
    x -= 2
    y += randint(-5,11)
    fill(20, 140, 20)
    if random()<.2: fill(255, 0, 0)
    text('PROGRAMMATION', x, y)

def draw():
    noLoop()

run()
from kandinsky import *
from random import *

BL, WH = (0, 0, 0), (255,) * 3

def rvb01(c): return tuple(v / 255 for v in c)
def rvb255(c): return tuple(int(255 * v) for v in c)
def zip01(c1,c2): return zip(rvb01(c1),rvb01(c2))

def screen(c1,c2):
  return rvb255(1 - (1 - a) * (1 - b) for (a, b) in zip01(c1,c2))

def rect(x,y,w,h,c,mode):  
 for i in range(w):
  for j in range(h):
   rvb = mode(c, get_pixel(x + i, y + j))
   set_pixel(x + i, y + j, rvb)

def dot(x, y, c, fg, t):
  draw_string(c, 0, 0, fg, (0,0,0))
  for v in range(18):
    for u in range(9):
      rect(x + u * t, y + v * t, t, t, get_pixel(u, v), screen)  

def aff(txt, x, y, t):
  coul = (255, 0, 0)if random()<.3 else (20, 140, 20)    
  for i, c in enumerate(txt):
    dot(x + i * t * 9, y, c, coul, t)

fill_rect(0,0,320,222,(50,50,50))
x, y = 150, -30
for i in range(80):
 x -= 2
 y += randint(1,4)
 aff("PROGRAMMATION", x, y, i//20)
fill_rect(0,0,20,20,(50,50,50))

Dégradés – NUMWORKS

from kandinsky import *

def rvb01(c): return tuple(v / 255 for v in c)
def rvb255(c): return tuple(int(255 * v) for v in c)
def zip01(c1,c2): return zip(rvb01(c1),rvb01(c2))

def alpha(c1, t, c2):
 return rvb255(a * t + b * (1 - t) for (a, b) in zip01(c1,c2))

def rect(x,y,w,h,c,d):
 (dx,dy) = d   
 for i in range(w):
  for j in range(h):
   t = 1   
   if dx == 1: t = 1 - i / w
   elif dx == -1: t = i / w 
   if dy == 1: t = 1 - j / h
   elif dy == -1: t = j / h
   rvb = alpha(c, t, get_pixel(x + i, y + j))
   set_pixel(x + i, y + j, rvb)

rect(0, 0, 200, 200, (255, 0, 0), (1,0))
rect(0, 0, 200, 200, (0, 255, 0), (0,1))
rect(0, 0, 200, 200, (0, 0, 255), (-1,0))

Effet alpha – NUMWORKS

from kandinsky import *
from random import randint, choice

coul = (255,0,255), (255,255,0), (255,127,0), (255,0,127)

def rvb01(c): return tuple(v / 255 for v in c)
def rvb255(c): return tuple(int(255 * v) for v in c)
def zip01(c1,c2): return zip(rvb01(c1),rvb01(c2))

def alpha(c1, t, c2):
 return rvb255(a * t + b * (1 - t) for (a, b) in zip01(c1,c2))

def rect(x,y,w,h,c,t):  
 for i in range(w):
  for j in range(h):
   if i == 0 or j == 0 or i == w - 1 or j == h - 1: 
    rvb = (255,255,255)
   else: 
    rvb = alpha(c, t, get_pixel(x + i, y + j))
   set_pixel(x + i, y + j, rvb)

def effet(t):
 for _ in range(150):
  x, y = randint(-10,300), randint(-10,200)
  w, h = randint(10,80), randint(10,80)
  rect(x,y,w,h,choice(coul),t)

effet(0.15)