Suite à ma vidéo d’initiation à JavaScript concernant la recherche de mots (palindromes, anacycliques, mots croissants…), je vous propose ici les traductions en Python
Importer les dictionnaires
Enregistrez le fichierdicos.py dans un dossier et créez un fichier recherche.py dans ce même dossier avec pour contenu :
import dicos
print(len(dicos.dico5))
En exécutant le fichier vous devriez voir le nombre 7276 qui correspond au nombre de mots de 5 lettres.
Mots en sens inverse et palindromes
import dicos
def inverse(mot):
return mot[::-1]
def palindrome(dico):
return [mot for mot in dico if mot == inverse(mot)]
Par exemple tourner la face du haut (U = Up) revient à mettre la vignette 8 en 9, la vignette 9 en 11, la vignette 11 en 10 et la vignette 10 en 8, ce qui est est noté (8,9,11,10). Mais cela bougera également les vignettes (4,0,17,13) et (5,1,16,12). Le principe est le même pour les mouvements R (Right), L (Left), D (Down) et P (Arrière).
Remplissage des faces :
H = ((1,0,0),(0,0,-1),(-1,0,0))
FACES = ((0,0,0),H),((0,0,-1.1),H),((0,-1.1,0),H),((0,-1.1,-1.1),H)
Exemple avec la vignette n°8 : On part du point de coordonnées (0,0,0) puis on ajoute successivement les vecteurs qu’il y a dans H, on obtient (1,0,0) puis (1,0,-1) et (0,0,-1). Nous avons les 4 coins de la vignette.
Les fonctions face, pos2D et remplir permettent alors de remplir cette surface avec la couleur voulue (pos2D transforme les coordonnées 3D en coordonnées à l’écran)
Le ,2 que l’on voit sur les 2 lignes ci-dessous permet de remplir plus rapidement la zone. Essayez avec ,3 et ,1 pour voir la différence.
for col in range( ... ,2):
for lig in range( ... ,2):
La fonction permu transforme la position du cube (liste de 24 nombres) en une nouvelle liste suivant le choix du mouvement. La nouvelle position est mise dans la variable suiv, ce qui permet lors de la mise à jour de l’affichage de ne remplir que les vignettes qui ont changé de couleur :
def aff(suiv,pos,force=0):
for n, c in enumerate(suiv):
if pos[n] != c: face(n,RVB[c]) # Changement de couleur ?
Rotation du cube entier :
Pour faire tourner le cube avec les 4 flèches, on applique plusieurs mouvements, par exemple avec la flèche du haut on applique les 4 mouvements 1,2,2,2. Répéter 3 fois le mouvement n°2 revient à appliquer 2 à l’envers (ce que l’on note souvent avec ‘, par exemple R’)
Cube terminé ?
L’idée est de parcourir les vignettes n°0, 1… 23 et de compter combien de fois on a changé de couleurs. Si ce nombre correspond à 6, le cube est terminé :
def fin(pos):
c, nb = pos[0], 1
for v in pos:
if v != c:
c = v
nb += 1
return nb != 6
Programme principal :
while True:
fill_rect(0,0,320,222,(0,)*3) # Fond noir
pos, jouer = melange(), True # mélange du cube
while jouer:
suiv = choix(key(TOUCHES), pos) # Future position du cube
aff(suiv,pos) # Affichage de la position
pos = list(suiv) # C'est la nouvelle position
sleep(.2)
jouer = fin(pos) # Le cube est-il fini ?
import turtle
t = turtle.Turtle()
t.speed(0) ; t.hideturtle()
# Ecran avec fond noir
t.color(0,0,0)
t.begin_fill()
for (x,y) in ((-200,200),(200,200),(200,-200),(-200,-200),(-200,200)):
t.goto(x,y)
t.end_fill()
# Représentation des clous
def clou(A):
t.pensize(5) ; t.color((160,140,130))
t.penup(); t.goto(A); t.pendown(); t.goto(A)
# Fil entre le point "A" et le point "B" avec la couleur "c"
# et ajout des clous aux extrémités
def fil(A,B,c):
clou(A) ; t.pensize(1) ; t.pencolor(c) ;
t.goto(B) ; clou(B)
# Division en "n" points du segment entre "a" et "b"
def segment(a,b,n):
return [[(i*PTS[b][0]+(n-1-i)*PTS[a][0])/(n-1), \
(i*PTS[b][1]+(n-1-i)*PTS[a][1])/(n-1)] for i in range(n)]
# Remplissage en utilisant 2 segments S1 et S2
# Chaque point de S1 va vers un point de S2
# puis on revient de S2 vers S1
# sauf si c'est le dernier point
def remplir(S1, S2, c):
for i in range(len(S1)):
fil(S1[i], S2[i], c)
if i < len(S1) - 1 : fil(S2[i], S1[i+1], c)
# Les coordonnées ont été trouvées en utilisant Geogebra
# et l'image fournie en exemple sur le site
PTS = (-142,-100),(0,-110),(2,196),(-62,-122),(142,-116),(-136,-124),
(-122,-152),(142,-124),(126,-164),(-156,-186),(166,-196)
# segment(0,1,40) correspond au segment de (-142,-100) à (0,-110)
# avec 40 divisions
remplir(segment(0,1,40), segment(2,1,40), (180,40,30))
remplir(segment(3,4,40), segment(4,2,40), (240,30,50))
remplir(segment(5,6,20), segment(7,8,20), (120,150,160))
remplir(segment(6,9,10), segment(8,10,10), (0,100,240))
import turtle
from math import *
t = turtle.Turtle()
t.speed(0) ; t.hideturtle()
t.color(0,0,0)
t.begin_fill()
for (x,y) in ((-200,200),(200,200),(200,-200),(-200,-200),(-200,200)):
t.goto(x,y)
t.end_fill()
def clou(A):
t.pensize(5) ; t.color((160,140,130))
t.penup(); t.goto(A); t.pendown(); t.goto(A)
def fil(A,B,c):
clou(A) ; t.pensize(1) ; t.pencolor(c) ;
t.goto(B) ; clou(B)
# Cercle rayon R divisé en N points
# Position du n-ième clou
def Ce(R,N,n,D=1,d=0):
return [R*sin(2*pi*(n+d/D)/N), R*cos(2*pi*(n+d/D)/N)]
# Partie bleue
c = (175,220,240)
for i in range(14):
fil(Ce(180,80,-14+2*i),Ce(180,80,-13+2*i),c)
fil(Ce(180,80,-13+2*i),Ce(180,80,14+4*i),c)
if i != 13:
fil(Ce(180,80,14+4*i),Ce(180,80,15+4*i),c)
fil(Ce(180,80,15+4*i),Ce(180,80,16+4*i),c)
fil(Ce(180,80,16+4*i),Ce(180,80,-12+2*i),c)
# Partie rose
c = (250,160,150)
pts1 = (40,2),(54,2),(68,4),(14,2),(28,2)
pts2 = (28,2),(29,2),(15,2),(70,4),(55,2),(41,2),(42,2)
for i in range(7):
for j,(a,b) in enumerate(pts1[:-1]):
fil(Ce(180,80,a+b*i),Ce(180,80,pts1[j+1][0]+pts1[j+1][1]*i),c)
if i != 6:
for k,(u,v) in enumerate(pts2[:-1]):
fil(Ce(180,80,u+v*i),Ce(180,80,pts2[k+1][0]+pts2[k+1][1]*i),c)
import turtle
from math import *
t = turtle.Turtle()
t.speed(0) ; t.hideturtle()
t.color(0,0,0)
t.begin_fill()
for (x,y) in ((-200,200),(200,200),(200,-200),(-200,-200),(-200,200)):
t.goto(x,y)
t.end_fill()
def clou(A):
t.pensize(5) ; t.color((160,140,130))
t.penup(); t.goto(A); t.pendown(); t.goto(A)
def fil(A,B,c):
clou(A) ; t.pensize(1) ; t.pencolor(c) ;
t.goto(B) ; clou(B)
def Ce(R,N,n,D=1,d=0):
return [R*sin(2*pi*(n+d/D)/N), R*cos(2*pi*(n+d/D)/N)]
for (a,b,c) in ((14,63,(120,110,180)),(64,33,(240,90,40))):
for i in range(50):
for n in range(2): fil(Ce(180,80,a+i),Ce(180,80,b+i+n),c)
import turtle
t = turtle.Turtle()
t.speed(0) ; t.hideturtle()
t.color(115,80,45)
t.begin_fill()
t.goto(0,-400); t.circle(800) # fond marron
t.end_fill()
col, lig, r = 9, 9, 20 # 9*9 motifs de rayon 20
h,e = 4.3*r, .75*r # Calculs des espacements
def motif(x,y,a,r):
t.penup(); t.goto(x,y)
t.setheading(a) # Orientation pour obtenir une des 4 figures
# on parcourt les 4 couleurs
for c in ((240,210,7),(230,130,5),(190,90,14),(110,60,30)):
t.color(c)
t.begin_fill()
t.circle(r,steps=60) # 60 pour un tracé plus précis
r /= 1.45 # Tailles de cercles : jaune, orange, marron...
t.end_fill()
for c in range(col):
for l in range(lig):
a = 45+90*(0,1,3,2)[l%2+2*(c%2)] # angle départ
# Motif à la bonne place et avec la bonne orientation
motif(-160+h*(c//2)+e*(c%2),160-e*(l%2)-h*(l//2),a,r)
Répétition de cercles
import turtle
from random import *
from math import sqrt
t = turtle.Turtle()
t.speed(0) ; t.hideturtle()
t.pensize(1)
t.color((220,210,130))
r = 20 # taille des cercles
d = sqrt(3) / 2
nc, nl = 400 // r, 280 // r
for c in range(nc):
for l in range(nl):
t.penup()
t.goto(-160 + c * r * d, -140 + l * r - (c % 2) * r / 2)
t.pendown()
t.circle(r)
import turtle
from math import sqrt
t = turtle.Turtle()
t.speed(0) ; t.hideturtle()
h = 20 # taille du motif
p, d = h // 2, sqrt(3) / 2
nb = 1 + int(800 / 3 / h / d)
def face(x,y):
t.penup(); t.goto(x,y); t.pendown()
t.setheading(30)
# Chacune des 3 faces a une couleur
for c in ((60,)*3,(230,200,0),(230,165,20)):
t.color(c)
t.begin_fill()
# Dessin d'une face et remplissage
for (u,v) in ((h,120),(p,60),(p,300),(p,60),(p,120),(h,-60)):
t.forward(u)
t.right(v)
t.end_fill()
# On place les motifs
for c in range(nb):
for l in range(nb):
face(-200 + (h + p) * d * c, \
-200 + l * (h + p) - (c % 2) * (h + p) / 2)
from kandinsky import *
from math import sqrt
from random import randint
# nb d'anneaux (couleurs alternées) et largeur anneau
(nb, r) = (randint(3,40), randint(2,10))
p = nb * r
COUL = (70, 75, 75)
fill_rect(0, 0, 320, 222, (255, 210, 0))
def cercles(u,v,du,dv):
# Bord opposé (en diagonale) au point de départ (u,v)
(u2, v2) = (u + du * nb * r, v + dv * nb * r)
# Pour chaque pixel du carré p * p
for x in range(p):
for y in range(p):
# Recherche du n° de l'anneau
d = int(sqrt(x ** 2 + y ** 2) / r)
# Si c'est un nb impair et qu'il est inférieur au nb d'anneaux total
if d & 1 and d < nb:
# On le dessine (gris foncé)
set_pixel(u + du * x, v + dv * y, COUL)
# Distance point par rapport à l'autre extrémité
# et calcul du n° de l'anneau
d2 = int(sqrt((x - p) ** 2 + (y - p) ** 2) / r)
# On dessine pixel si en dehors des anneaux précédents
if d2 > nb - 2:
set_pixel(u2 - du * x, v2 - dv * y, COUL)
def motif(x,y):
# (a,b) = position départ du motif et (c,d) = directions du remplissage en x et y
# Faire varier (c,d) avec d'autres combinaisons de 1 et -1
for (a,b,c,d) in ((1,1,-1,-1),(2,0,-1,1),(0,2,1,-1),(1,1,1,1)):
cercles(x + a * p, y + b * p, c, d)
for c in range(1+ 160 // p):
for l in range(1 + 110 // p): motif(2 * c * p,2 * l * p)
Yann Le Du m’a fait découvrir via Twitter l’existence du langage K (première version en 1992 !), dont les caractéristiques sont proches de l’APL.
Cette page me servira de mémo pour noter mes découvertes et compréhensions de ce langage (appelé également Shakti). Ce n’est donc pas un cours mais juste des notes personnelles que je partage librement avec vous.
➡ Références que je vais utiliser : En ligne | PDF
📲 J’ai également une page spéciale consacrée à oK Mobile, une autre version de K.
Quelques challenges Twitter
J’avais proposé sur Twitter (menu Challenges Twitter en haut de ce blog) de résoudre quelques petits exercices en Python, JavaScript ou APL. C’est à cette occasion que Yann Le Du (YLD) a donné ses propres solutions en K. Essayons de décrypter ses réponses !
Isogrammes (Challenge n°3)
Un isogram (En français on parle d’heterogramme) est un mot qui ne contient aucune lettre répétée. Ecrire une fonction qui renvoie vrai ou faux suivant que le mot est un heterogramme, sans tenir compte de la casse (majuscule/minuscule)
L’opérateur ?, dans sa forme monadique, correspond à l’union (éléments pris de façon unique)
? "abracadabra"
"abcdr"
Sous sa forme dyadique, il permet de générer des nombres aléatoires, par exemple tirer 10 nombres aléatoires entre 0 et 2 :
10 ? 3
2 1 2 2 2 0 1 1 2 0
L’adverbe \: s’appelle “Converge scan” et /: “Converge over”. Les 2 vont répéter les calculs jusqu’à arriver à une convergence (dans le sens : le terme suivant est égal au terme précédent). On peut également imposer le nombre d’itérations :
{1+1%x}/:1 / u(n+1) = 1 + 1 / u(n) en partant de u(0) = 1
1.618034 / Affichage du résultat final (Nombre d'or)
(5;{1+1%x})/:1 / 5 itérations uniquement
1.625
{1+1%x}\:1 / Affichage des résultats intermédiaires
1
2.
1.5
1.666667
1.6
1.625 / On retrouve la 5e itération
1.615385
1.619048
1.617647
1.618182
...
1.618034
?\:"abracadabra"
abracadabra
abcdr
Par contre / (reduce) et scan (\) sont similaires à l’APL :
+/ 1 2 3 4 / Réduction par la somme
10
=/ 2 2 1 / 2 = 2 est Vrai puis Vrai = 1 est Vrai
1
=/ 2 3 0 / 2 = 3 est Faux puis Faux = 0 est Vrai
1
+\ 1 2 3 4 / Scan par la somme : 1, 1+2, 1+2+3, 1+2+3+4
1 3 6 10
L’adverbe ‘ signifie “pour chaque”, par exemple compter le nombre de lettres de chaque mot :
#' ("bonjour";"tout";"le";"monde")
7 4 2 5
Signification du code de YLD sur un exemple :
=/#'?\:_ "moOse" / moOse est-il un isogram ?
0 / Réponse = non
Etapes :
_ "moOse" / Mettre le mot en minuscules
"moose"
?\:_ "moOse" / Répéter "Union" jusqu'à valeur stable
moose / Il y aura donc le mot du départ
emos / et le mot sans doublon
#'?\:_ "moOse" / Chercher les tailles des 2 mots
5 4
=/#'?\:_ "moOse" / Ces tailles sont-elles identiques ?
0 / si oui c'est un isogram
Gimme (Challenge n°2)
Résumé en français : On vous donne 3 nombres différents dans un ordre quelconque. En sortie, donnez le rang du nombre qui est entre les 2 autres. Par exemple avec 2, 3, 1 c’est le chiffre 2 qui est entre 1 et 3, son rang dans 2, 3, 1 est 0.
Solution proposée par YLD :
gimme:*1_<
gimme 5 10 14
1
L’opérateur de tricroissant< fonctionne comme en APL :
< 14 5 10
1 2 0
/ Le plus petit nb est à la position 1, c'est le 5
/ Le second nb est à la position 2, c'est le 10
/ Le plus grand est à la position 0, c'est le 14
En version dyadique, _ permet d’enlever des éléments au début ou à la fin d’un tableau :
2_ 4 5 6 7 8 / On enlève les 2 premiers éléments
6 7 8
-2_ 4 5 6 7 8 / On enlève les 2 derniers éléments
4 5 6
En version monadique, * récupère le premier élément d’un tableau :
* 4 5 6 7
4
Signification du code de YLD sur un exemple :
*1_< 8 5 12
0
Etapes :
< 8 5 12 / Tri du tableau
1 0 2 / L'élément du milieu sera à la position 0
1_< 8 5 12 / Pour récupérer ce nb on supprime le 1er élément
0 2
*1_< 8 5 12 / Et on prend le premier élément du tableau
0
Positions des mots (Challenge n°6)
Résumé en français : Vous devez créer un programme qui à partir d’une phrase, met tous les mots distincts dans une liste et retourne une chaine donnant les positions des mots de la phrase initiale dans cette liste. On ne tiendra pas compte de la casse.
Solution proposée par YLD :
,/$'s?s:" "\_
compress:{,/$'s?s:" "\_x}
En version dyadique, \ permet de faire un scan, également de séparer une chaine suivant un caractère (split) mais aussi d’écrire un nombre dans une base quelconque !
2 +\ 4 5 6
6 11 17 / 2+4, 2+4+5, 2+4+5+6
","\ "bonjour,tout,le,monde" / split avec ','
bonjour
tout
le
monde
10\ 3574 / Décomposition de 3574 en base 10
3 5 7 4
2\ 35 / Décomposition de 35 en base 2
1 0 0 0 1 1
Nous avons vu ? en version monadique (Union ou nombres aléatoires), en version dyadique x?y permet de trouver l’index de y dans le tableau x :
5 7 8 6 ? 7 5 / Positions de 7 et de 5 dans le tableau 5 7 8 6
1 0 / 7 est à la position 1 et 5 à la position 0
s:"abcabc" / affectation de la chaine "abcabc" dans s
s?s
0 1 2 0 1 2
$ transforme, en version monadique, un nombre en chaine. L’opération inverse s’effectue via l’opérateur . :
$ 123 / Transformation d'un nombre en chaine
"123"
."123" / Transformation d'une chaine en nombre
123
."2+5" / Evaluation
7
Signification du code de YLD sur un exemple :
,/$'s?s:" "\_ "un deuX un Trois UN deux"
"010301"
Etapes :
_ "un deuX un Trois UN deux" / En minuscules
"un deux un trois un deux"
" "\_ "un deuX un Trois UN deux" / Split avec " "
un
deux
un
trois
un
deux
s:" "\_ "un deuX un Trois UN deux" / Affectation dans s
s?s:" "\_ "un deuX un Trois UN deux" / Positions des éléments
0 1 0 3 0 1
$'s?s:" "\_ "un deuX un Trois UN deux" / Conversion en chaine
0
1
0
3
0
1
,/ $'s?s:" "\_ "un deuX un Trois UN deux" / Concaténation
"010301"
Paires de gants (Challenge n°10)
Résumé en français : On vous donne une liste contenant des couleurs de moufles (donc pas de main gauche ou droite à distinguer). On vous demande le nombre de paires que vous pouvez constituer, c’est-à-dire avoir 2 moufles de la même couleur.
Solution proposée par YLD :
+/\:(0=)_div#'=
Pour grouper les indices d’un tableau à partir des valeurs, on utilise =.
= 8 5 8 8 5 3 / On groupe les indices à partir des valeurs
3|,5
5|1 4
8|0 2 3
= "abracadabra"
a|0 3 5 7 10
b|1 8
c|,4
d|,6
r|2 9
La fonction mathématique div permet d’effectuer une division entière :
3 div 17 / division entière de 17 par 3
5
div 17 / division entière par 2
8
Pour filtrer un tableau, on utilise # ou _ :
notes: 5 12 10 6 19 3 / Notes d'élèves
(10>)_ notes / on supprime les notes inférieures à 10
12 10 19
Tableau de symboles :
s:`red`blue`green
s@1 / élément à la position 1 (ou encore s[1])
`blue
Signification du code de YLD sur un exemple :
+/\:(0=)_div#'=`red`red`blue`red`blue`green
[blue:1;red:1]
2
Etapes :
=`red`red`blue`red`blue`green / Répartition des indices
blue |2 4
green|,5
red |0 1 3
#'=`red`red`blue`red`blue`green / Effectifs
blue |2
green|1
red |3
div#'=`red`red`blue`red`blue`green / Division par 2
blue |1
green|0
red |1
/ On supprime les restes qui sont nuls
/ "green" ne permet pas de faire au moins une paire
(0=)_div#'=`red`red`blue`red`blue`green
blue|1
red |1
/ On utilise ou non \: pour avoir le détail puis réduction par la somme
+/(0=)_div#'=`red`red`blue`red`blue`green
2
Explosion (challenge n°12)
Résumé en français : On vous donne une chaine de caractères composée de “chiffres” (‘0’ à ‘9’). Vous devez écrire une fonction qui renvoie une chaine où chaque chiffre est répété le nombre de fois correspondant à sa valeur. Par exemple avec la chaine “312”, on doit répéter 3 fois le “3”, 1 fois le “1” et 2 fois le “2”, ce qui donne la chaine “333122”.
Solution proposée par YLD :
s@&10\. s:
Nous reconnaissons l’affectation s:, la conversion . d’une chaine en numérique, la décomposition d’un nombre en base 10. Il reste à comprendre & et @.
& (where) permet de répliquer les indices d’un tableau autant de fois que les valeurs indiquées dans ce tableau. Exemple :
& 4 3 0 2 / Répéter 4 fois l'indice 0, 3 fois l'indice 1...
0 0 0 0 1 1 1 3 3
notes: 5 10 15 9 13 8 / Notes d'élèves
notes<10 / Positions des élèves n'ayant pas la moyenne
1 0 0 1 0 1
¬es<10 / Indices correspondants
0 3 5 / Les élèves n°0, 3 et 5 n'ont pas la moyenne
& 1 0 0 1 1 / Plus généralement, avec un tableau binaire
0 3 4 / on récupère les positions des "1"
x@y donne la valeur qui est à l’indice y du tableau x :
"sujet"@ 2 1 0 4 3 / Lettres aux positions 2,1,0,4 et 3
"juste"
Signification du code de YLD sur un exemple :
s@&10\. s:"102269"
"12222666666999999999"
s:"102269" / Mémorisation de la chaine dans "s"
. s:"102269" / Conversion en nombre
102269
10\. s:"102269" / Décomposition en base 10
1 0 2 2 6 9
&10\. s:"102269" / On réplique les indices suivant les valeurs
0 2 2 3 3 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5
/ Pour finir on récupère à partir de "s" le caractère à la position 0
/ puis 2 fois celui à la position 2, 2 fois celui à la position 3
/ 6 fois celui à la position 4 et 9 fois celui à la position 5
/ On obtient bien 1,22,22,666666,999999999
Misez p’tit Optimisez en version K
Sur le forum de Silicium, j’avais proposé différentes solutions de challenges (à l’origine pour des calculatrices de poche programmables) en version APL. Je vais en reprendre quelques uns et voir ce que cela peut donner en K.
MPO 9 : Somme des chiffres
Calculer la somme des chiffres d’un nombre. Par exemple : 352791 doit retourner 27
On a tout ce qu’il faut ! $ pour transformer un nombre en chaine, . pour faire l’inverse, la réduction +/ et ‘ qui signifie “pour chaque” :
MPO9: +/.'$
MPO9 352791
27
Tout aussi court, on peut utiliser la décomposition en base 10:
MPO9: +/10\
MPO9 352791
27
Si maintenant on veut continuer le processus jusqu’à obtenir un chiffre entre 0 et 9, par exemple :
352791 -> 27 -> 9
Il suffit d’utiliser /: ou \: pour créer la boucle :
(+/10\)\: 352791 / Affichage des résultats intermédiaires
352791 27 9
(+/10\)/: 352791 / Uniquement résultat final
9
MPO1 : Evaluation d’un polynôme
Il s’agit d’évaluer le polynôme P(x)=3x^3+4x^2+x+9 en une valeur donnée en paramètre.
Cela revient à convertir le tableau 3 4 1 9 en base x, d’où :
On vous donne une liste de hauteurs d’immeubles adjacents et on vous demande combien seront visibles si vous les regardez à partir de la gauche. Par exemple, si les hauteurs sont 2 3 5 2 1 6 4, en vert ci-dessous les 4 seuls immeubles qui seront visibles (les autres sont cachés par des bâtiments plus hauts)
On supposera dans un premier temps qu’il n’y a pas de zones vides entre les immeubles, c’est-à-dire que la liste ne contient pas de 0 (immeubles de hauteur nulle).
Dans un second temps, considérez le cas général.
Je reprends le corrigé que j’avais mis sur Silicium mais en version K :
On va déjà scanner les immeubles pour récupérer les hauteurs maximales atteintes :
|\ 2 3 5 2 1 6 4 / On cherche les max progressivement
2 3 5 5 5 6 6
Il faut maintenant récupérer les hauteurs distinctes :
?|\ 2 3 5 2 1 6 4
2 3 5 6
Et les compter :
#?|\ 2 3 5 2 1 6 4
4
Le programme final :
scan:#?|\
scan 2 3 5 2 1 6 4
4
Si la liste commence par un ou plusieurs 0, le calcul sera faux :
scan 0 2 1
2
Ceci parce que les maximums progressifs sont { 0 2 2 } dont la réunion comporte 2 termes {0 2}. Il faut donc filtrer la liste des maximums pour enlever les 0.
(0=)_ 0 0 2 3 5 5 / Signifie enlever ceux égaux à 0
2 3 5 5
from math import *
from kandinsky import fill_rect, set_pixel
(BL, WH) = ((0, 0, 0), (255, 180, 50)) # Noir et Orange
fill_rect(0, 0, 320, 222, BL) # Fond noir
(xp, xr) = (120, 1.5 * pi)
yp = 45
(xf, zf) = (xr / xp, xr / yp)
for zi in range(-yp, yp):
zt = zi * xp / yp
xl = int(.5 + sqrt(xp * xp - zt * zt))
for xi in range(-xl, xl + 1):
xt = sqrt(xi * xi + zt * zt) * xf
yy = (sin(xt) + .4 * sin(3 * xt)) * yp
y1 = int(min(222, max(1, yy - zi + 100)))
x1 = 10 + int(min(360, max(0, xi + zi + 150)))
set_pixel(x1, 210 - y1, WH)
fill_rect(x1, 210 - (y1 - 1), 1, y1, BL)
Equation de la surface
En regardant plus attentivement le code, on voit que les zi (noté y ci-dessous) et xi (noté x) permettent de calculer yy (noté z), l’équation de la surface est :
Aujourd’hui nous allons nous inspirer des horloges ALBERT qui ne vous donnent l’heure qu’après avoir effectué un calcul arithmétique (additions, soustractions, multiplications ou divisions suivant le niveau choisi)
Je vous propose différentes versions web inspirées par cette idée. Une fois les fichiers récupérés, vous n’aurez besoin d’aucune connexion Internet et vous pourrez mettre les fichiers sur une tablette ou un ordinateur portable.
Installation et modifications
Par défaut les horloges s’actualisent toutes les 60 secondes. Pour modifier cet intervalle, ouvrez le fichier .html avec le bloc-note puis rechercher la ligne contenant 60000 (60 millisecondes). Remplacez cette valeur par exemple par 20000 (pour 20 secondes) et enregistrez.
Concernant les tablettes, mettre le 2 fichiers (.html et .ttf) sur votre tablette (via Bluetooth, par mail ou autre), par exemple dans le dossier download ou documents. Pour ouvrir le fichier .html avec un navigateur installé sur votre tablette (Chrome ou FireFox ou Brave), lancez le navigateur puis tapez l’adresse (avec 3 “/”) :
file:///storage/emulated/0/download/horloge.html
ou
file:///storage/emulated/0/documents/horloge.html
Si les fichiers sont sur une carte SD, l’adresse sera du type :
file:///sdcard/download/horloge.html
Une fois que vous avez réussi, mettre la page en Favori. Si vous n’y arrivez pas, vous pouvez toujours utiliser les liens démo ci-dessous, mais il vous faudra alors une connexion Internet.
Pour créer la formule des heures, l’idée a été de choisir au hasard 2 nombres entre -24 et 24 (par exemple -21 et +18), de les additionner (-3) et de regarder si la différence entre l’heure actuelle (par exemple 15h) et cette somme est ou non entre -24 et 24. Ici 15 – (-3) = 18 convient. D’où la formule -21+18+18. Sinon, on tire à nouveau 2 entiers et on recommence, les ordinateurs étant rapides, on ne se rend pas compte s’il y a eu besoin de 1 ou 1000 essais !
Les 2 nombres à multiplier sont choisis entre 2 et 9, l’un des 2 pouvant être négatif. Le troisième nombre est calculé pour obtenir le bon résultat (la bonne heure puis même chose avec les minutes). Volontairement les 2 lignes n’ont pas le même ordre d’affichage : a×b+c pour les heures et a+b×c pour les minutes.
C’est en regardant un manuel de CP cycle 2 que j’ai eu l’idée de cette version. Le programme est assez simple, on compte les dizaines et les unités (un dé existe pour tous les chiffres entre 0 et 9). Lorsque l’on a un multiple de 10, on s’arrange pour n’afficher que les dés “10” (qui est en fait le caractère “=”). Il n’y a qu’avec “0” seul que l’on affiche le dé sans point.
AFF = n => n == 0 ? // n vaut 0 ?
0 : // si oui on renvoie 0
'='.repeat(0 | n / 10) // Nombre de dizaines
+ (n % 10 != 0 ? n % 10 : '') // et unités si pas 0
L’horloge provient de Wikipedia, elle est au format SVG (donc transformable sans perte de qualité). Modification du SVG avec Inkscape pour créer les 2 visuels (heures et minutes), il a suffit ensuite de donner les noms heures, minutes, secondes aux aiguilles et d’appliquer une rotation en JavaScript (en précisant dans Inkscape où est le centre de rotation) :
var hm = HM(new Date().toLocaleTimeString())
// h[0] contient l'heure, h[1] les minutes et h[2] les secondes
heures.setAttribute('transform','rotate('+(hm[0]*30)+')');
minutes.setAttribute('transform','rotate('+(hm[1]*6)+')');
secondes.setAttribute('transform','rotate('+(hm[2]*6)+')');
Pour ne pas avoir de nombres trop grands, les coefficients devant les heures sont choisis aléatoirement entre -4 et 4 (et non nuls), et entre -3 et 3 pour les minutes (non nuls).
Il faut aussi s’arranger pour ne pas afficher les coefficients 1 et -1 (par exemple 1H s’écrit H et -1M s’écrit -M), pour cela on peut écrire cette fonction :
UN = n => Math.abs(n) != 1 ? n : n == 1 ? '' : '-'
>> UN(5)
5
>> UN(1)
''
>> UN(-1)
'-'
Le dessin a été réalisé avec Inkscape, les fenêtres ont été nommées hd1, hd2…, hu1, hu2,… pour les dizaines et unités des heures, idem avec les md, mu et sd, su.
Exemple avec 16 h : On récupère la dizaine (1) et l’unité (6) que l’on transforme en binaire (1 et 110). On parcourt les 4 fenêtres de la colonne dizaine et on met en jaune s’il y a un “1” sinon marron. Donc avec “1” seule la fenêtre du bas sera allumée. Avec 6, on parcourt les 4 fenêtres de la colonne unité et on allume la 2e et 3e fenêtre.
BIN = n => [...'0'.repeat(3)+n.toString(2)].reverse().join('')
>> BIN(6)
'011000'
>> BIN(1)
'1000'
ETAT = (v, col) => { // Exemple v = 6 et col = 'hu'
b = BIN(v) // Transformation du chiffre en binaire
for (let i = 0; i < 4; i++) { // 4 fenêtres par colonne
c = document.getElementById(col+(i+1)) // La fenêtre
c.setAttribute('fill', b[i] == '0' ? '#490101' : 'yellow');
}
}
>> ETAT(6, 'hu') va allumer ou éteindre la colonne "heure unités" pour afficher 6
La notation utilisée est celle que l’on trouve pour le codage des couleurs (retouche d’images, HTML…), par exemple le rouge vif correspond à #FF0000. Le programme est quasi identique à la version binaire :
La suite de Fibonacci est : 0,1,1,2,3,5,8,13,21,34,55,89… où le terme suivant est la somme des 2 précédents.
Théorème de Zeckendorf : Tous les entiers peuvent s’écrire comme une somme de nombres de Fibonacci.
// Liste des nombres de Fibonacci qui seront utiles
>> F = [0,1,1,2,3,5,8,13,21,34,55]
// Décomposition de n en somme
>> FIBO = n => {
res = [ ] // Résultat final
while (n > 0) { // Tant que n n'est pas nul
// On cherche le plus grand nb de Fibonacci inférieur à n
a = F.filter(v => v <= n).slice(-1)[0]
// On l'ajoute au tableau
res.push(F.indexOf(a))
// n diminue du nombre trouvé
n -= a
}
return res // On retourne le résultat
}
>> FIBO(13)
[7] // 13 = F(7)
>> FIBO(44)
[9, 6, 3] // 44 = F(9)+F(6)+F(3)
>> FIBO(51)
(4) [9, 7, 4, 1] // 51 = F(9)+F(7)+F(4)+F(1)
On trouve facilement sur Internet un algorithme pour convertir un nombre en numérotation romaine :
var arabe = [1000,900,500,400,100,90,50,40,10,9,5,4,1]
var romain = 'M,CM,D,CD,C,XC,L,XL,X,IX,V,IV,I'.split(',')
var CONV = nb => {
if (nb == 0) return ' '
var s = ''
for (i in arabe) {
var q = 0 | nb / arabe[i]; // division entière
var nb = nb % arabe[i] // reste de la division
s += romain[i].repeat(q)
}
return s
}
>> CONV(9)
'IX'
>> CONV(48)
'XLVIII'
>> CONV(24)
'XXIV'
Exemple du déroulement de l’algorithme pour 47 (Div = Division):
Reste = 47
Div du reste par 1000 = 0, reste = 47, ajout 0 =
Div du reste par 900 = 0, reste = 47, ajout 0 =
Div du reste par 500 = 0, reste = 47, ajout 0 =
Div du reste par 400 = 0, reste = 47, ajout 0 =
Div du reste par 100 = 0, reste = 47, ajout 0 =
Div du reste par 90 = 0, reste = 47, ajout 0 =
Div du reste par 50 = 0, reste = 47, ajout 0 =
Div du reste par 40 = 1, reste = 7, ajout 40 = XL // 40*1
Div du reste par 10 = 0, reste = 7, ajout 0 = XL
Div du reste par 9 = 0, reste = 7, ajout 0 = XL
Div du reste par 5 = 1, reste = 2, ajout 5 = XLV // 5*1
Div du reste par 4 = 0, reste = 2, ajout 0 = XLV
Div du reste par 1 = 2, reste = 0, ajout 2 = XLVII // 1*2
Pour ne pas voir les secondes, utilisez ce code :
setInterval(function x() {
var hm = HM(new Date().toLocaleTimeString())
document.querySelector("#hr").innerHTML = CONV(hm[0])
document.querySelector("#mn").innerHTML = CONV(hm[1])
return x
}(), 60000);
et supprimez la ligne :
<div id='se'></div>
Horloge “IL RESTE”
Téléchargez (changement toutes les 30 secondes) | Voir la démo (changement toutes les 4 secondes)
C’est le même programme que addition et soustraction, il suffit de remplacer l’heure par IL RESTE et de trouver un calcul qui donnera le nombre 60 – minutes
Suite à un Tweet de NumWorks présentant le visuel d’une carte de France, j’ai lancé le challenge de réaliser une carte aussi réaliste que possible (ou faussement réaliste) en Python et en moins de 500 octets.
Retour dans les années 80
Dans les années 80 est sorti un petit ordinateur individuel, le ZX-81. Il n’avait que 1Ko de RAM (soit 1000 caractères) mais permettait de s’initier à la programmation et de créer quelques jeux. L’achat d’une extension 16Ko ou 32Ko était cependant assez rapidement nécessaire.
Voici un programme proposé dans le livre “Pilotez votre ZX 81” de Patrick Gueule :
La machine possédait des caractères semi-graphiques, comme par exemple ◧ ◨. L’astuce dans ce programme est de mémoriser le nombre d’espaces à afficher à partir de la gauche puis le(s) caractère(s) semi-graphique(s), à nouveau le nombre d’espaces ensuite le(s) caractère(s) et terminer par 0 pour passer à la ligne suivante. Ainsi le 7780 de la variable A$ indique qu’il faut afficher 7+7+8=22 espaces et revenir à la ligne (ligne blanche au-dessus de la carte). Ensuite 6+5=11 espaces puis 3 caractères semi-graphiques et retour à la ligne :
Remarquez que les caractères semi-graphiques ayant une dimension de 2×2, cela permet d’afficher 2 rangées de la carte à la fois.
Même idée en Python
Dessinons la carte de France en tapant des 1 dans certaines cellules d’Excel (pour cela importez une image de la France dans Mise en Page – Arrière Plan, sélectionnez toutes les cellules puis Accueil – Mise en forme conditionnelle – Règle de surlignage – Egal à 1 – Mettre une couleur de remplissage).
A côté de la carte tapez la formule =A1+2*B1+4*A2+8*B2. Cela permet de convertir les 4 cellules (jaune sur la carte) en un nombre entre 1 et 1+2+4+8=15.
On peut alors étendre cette formule horizontalement et verticalement pour recouvrir toute la carte.
Si on écrit le 9 de la première ligne en binaire :
> bin(9)
'0b1001'
Cela signifie que les cellules 1 et 4 contiennent un 1 et les autres un 0. Il s’agit du caractère ci-dessous :
On est alors dans la même configuration que sur le ZX-81, on doit compter le nombre d’espaces (les 0) puis un caractère graphique codé sur 4 cases.
Voici le début du codage :
Les lettres F et I de la première ligne sont simplement les 6e et 9e lettres de l’alphabet et permettent donc de coder les nombres 6 et 9.
Le caractère * a 42 comme code Ascii, notre référence étant le code 33, ce qui fait une différence de 9 soit 9 espaces.
> ord('*')
42
> chr(33)
'!'
Enfin, j’utiliserai le caractère ‘!’ pour les fins de ligne. Le contour de la carte peut donc être traduit par :
FR = '*FI!*E,ID!(...'
Pour le programme principal, il suffit, suivant le code Ascii, de décider si l’on doit revenir à la ligne (caractère ‘!’ de code 33), se décaler suivant l’axe des x d’un certain nombre d’espaces ou afficher un caractère semi-graphique :
x, y = 0, 0
for s in FR:
n = ord(s)
if n == 33: x, y = 0, y + 10 # Retour à la ligne
elif n < 55: x = 10 * (n - 33) # 10 pixels par "espace"
else:
car(n - 64) # Affichage du caractère semi-graphique
x += 10 # Et décalage à droite de 10 pixels
Pour la fonction car, soit on décompose le paramètre en binaire pour savoir quelles cases on doit remplir, soit on les récupère petit à petit. Par exemple, si le caractère semi-graphique est L :
> ord('L') # on récupère son code Ascii
76
> 76 - 64
12 # 12e lettre
> bin(12)
'0b1100' # 2 premières cases noires et suivantes blanches
> 12 >> 0 & 1 # On peut aussi récupérer 1-1-0-0 petit à petit
0
> 12 >> 1 & 1
0
> 12 >> 2 & 1
1
> 12 >> 3 & 1
1
Fonction car avec des carrés noirs de 5*5 pixels :
def car(x, y, n):
for i in range(4):
if n >> i & 1:
fill_rect(x + i % 2 * 5, y + i // 2 * 5, 5, 5, (0,0,0))
Une autre idée est de parcourir le contour de la carte avec la tortue. Pour rester dans les 500 octets imposés, il ne faudra utiliser que quelques points stratégiques.
Pour cela on peut ouvrir la carte de France dans Gimp et utiliser l’outil Chemin :
On note les coordonnées des points dans un tableur. L’idée est de partir d’un des points du contour puis de déplacer la tortue en utilisant uniquement les différences entre les coordonnées :
Par exemple, pour passer du point (64,58) au point (62, 61) on se décale du vecteur (-2,3). L’intérêt ? On utilisera moins de caractères pour mémoriser les coordonnées !
Voici le codage de la carte, sachant que le point initial est en (90,-60) qui correspond au nord de la Corse
F = '18!23!3!30...'
Déplacer la tortue du vecteur (1,8) puis (-2,3) puis (-3,-30) etc.
Ce qui donne cette version :
from turtle import *
F = '18!23!3!30!53!11!2!8!5!55!32!9!3!5305!40!6!3!8!1!3!33!D22!2!5!4!6!1!4!4!3!4!1!2!12!1!2!10!28!11260!2!720124!12!25!11!73098E5!29!66043!223!1B51'
def f(s = 1):
global i
if F[i] == '!': i, s = i + 1, -1
i += 1
return s * int(F[i - 1], 16)
x = y = i = 0
penup()
while i < len(F):
goto(90 + 3 * x, -60 - 3 * y)
pendown()
x += f()
y += f()
hideturtle()
Mais le résultat n’est pas très joli car trop rectiligne et la Corse est reliée à la métropole
On va ajouter de l’aléatoire en faisant varier la trajectoire entre 2 coordonnées. Pour cela on doit décomposer le segment (x1,y1) vers (x2,y2) en sous-segments :
for j in range(9): # Décomposition en 9 étapes...
goto(90 + g(u,x,j),-60 - g(v,y,j)) # ...entre (u,v) et (x,y)
def g(a, b, j): return 3 * (a + (b - a) * j / 9) # Interpolation
Et avec de l’aléatoire :
def g(a,b,j): return 3 * a + 2 * random() + (b - a) * j / 3
Concernant la séparation avec la Corse, l’astuce utilisée est qu’avec la NumWorks pensize(0) ne trace pas de trait. Ainsi en ajoutant :
pensize(not(18 < i < 23))
On aura un trait d’épaisseur 1 sauf si i est entre 18 et 23 qui correspond au trait reliant la Corse.
Enfin, on peut dessiner de petits cercles à chaque étapes plutôt qu’un trait simple, on obtient finalement ce script qui fait exactement 500 octets avec le résultat suivant :
Résumé en français : Une pizzeria récompense ses meilleurs clients en offrant une pizza gratuite s’ils ont fait au moins 5 achats d’un montant au moins égal à 20 EUR. Cependant, ce système est susceptible d’être modifié dans le futur. On vous demande de créer une fonction qui à partir du nombre d’achatsminimum, du montant minimum et d’un dictionnaire contenant les données sur vos clients, va renvoyer la liste de ceux qui auront une pizza gratuite.
# Système 1 : Pour avoir une pizza gratuite, il faut avoir au moins 5 achats d'un montant minimum de 20 EUR.
min_achats = 5
min_prix = 20
conso = {
'John Doe' : [22, 30, 11, 17, 15, 52, 27, 12], # Montants des achats
'Jane Doe' : [5, 17, 30, 33, 40, 22, 26, 10, 11, 45]
}
>> free(conso, min_achats, min_prix)
['Jane Doe'] # Elle seule aura une pizza gratuite
# Système 2 : Pour avoir une pizza gratuite, il faut avoir au moins 2 achats d'un montant minimum de 50 EUR.
min_achats = 2
min_prix = 50
conso = {
'Joey Bonzo' : [22, 67, 53, 29], # Montants des achats
'Jennifer Bonzo' : [51, 19]
}
>> free(conso, min_achats, min_prix)
['Joey Bonzo']
Version classique
Pour chaque consommateur, on va compter le nombre d’achats dont le montant est ≥ au montant minimum imposé. Si ce nombre est ≥ au minimum d’achats, la personne aura une pizza gratuite.
On peut imaginer une première boucle pour parcourir les consommateurs, une seconde pour parcourir les achats et enfin un test pour savoir si cette personne doit avoir une pizza.
Comment : – récupérer les différents consommateurs ? – récupérer leurs achats ?
On peut également parcourir à la fois les clés et les valeurs. Voici un exemple qui calcule le montant total des achats des consommateurs :
>> conso = {
'John Doe' : [22, 30, 11, 17, 15, 52, 27, 12],
'Jane Doe' : [5, 17, 30, 33, 40, 22, 26, 10, 11, 45]
}
>> for (p, achats) in conso.items():
print('Total pour {} : {}'.format(p, sum(achats)))
Total pour John Doe : 186
Total pour Jane Doe : 239
Version finaleà tester ici qui reprend notre première approche :
def free(conso, min_achats, min_prix):
gagnants = [ ] # Personnes qui auront une pizza gratuite
for p in conso.keys(): # On parcourt les consommateurs
achats = conso[p] # Récupération des achats
total = 0 # Nb d'achats ≥ montant min
for m in achats: # On parcourt les achats
if m >= min_prix : # Si montant ≥ montant min
total += 1 # On ajoute +1
if total >= min_achats: # Suffisamment d'achats ?
gagnants.append(p) # Il aura une pizza gratuite
return gagnants # Retour de la liste des gagnants
>> free(conso, min_achats, min_prix) # Avec l'exemple 1
['Jane Doe']
const free = (conso, min_achats, min_prix) => {
gagnants = [ ];
for (p of Object.keys(conso))
{
achats = conso[p];
total = 0 ;
for (m of achats)
{
if (m >= min_prix) total +=1
}
if (total >= min_achats) gagnants.push(p)
}
return gagnants}
>> free(conso, min_achats, min_prix) // Avec l'exemple 1
['Jane Doe']
Version plus moderne
Nous devons filtrer les consommateurs suivant un double critère : Nombre de pizzas achetés et ayant un prix ≥ montant minimum. Rappelons brièvement comment on peut filtrer en Python, JavaScript et APL, par exemple en cherchant quels étudiants ont des notes ≥ 10 :
Et pour compter le nombre d’étudiants reçus (c’est-à-dire avec une note ≥ 10 :
notes = [5, 12, 11, 9, 3, 17, 18, 6]
Python
>> len([v for v in notes if v >= 10]) # Taille du tableau
4
>> sum([v >= 10 for v in notes]) # Somme de True ou False
4
>> sum(v >= 10 for v in notes) # Parenthèses inutiles
4
JavaScript
>> notes.filter(v => v >= 10).length
4
>> notes.reduce((a, v) => a + (v >= 10), 0)
4
APL
notes ← 5 12 11 9 3 17 18 6
notes +.≥ 10 ⍝ Somme des éléments ≥ 10
4
On obtient ainsi ces 2 versions plus modernes pour JavaScript et Python :
javascript
const free = (conso, min_achats, min_prix) =>
Object.keys(conso) // Liste de consommateurs
.filter(p => // Pour chaque personne p
conso[p] // On filtre ses achats
.filter(m => m >= min_prix) // en gardant ceux ≥ min_prix
.length // On compte le nombre d'achats ok
>= min_achats // On garde la personne si ≥ min_achats
)
python
def free(conso, min_achats, min_prix):
return [ \
p for p in conso.keys() \ # on garde le consommateur si...
if sum(m >= min_prix for m in conso[p]) \ # Nb achats ≥ min_prix
>= min_achats \ # ...dépasse min_achats
]
APL
On veut sélectionner ( / ) les noms des consommateurs, pour cela on va utiliser un vecteur logique (booléen) :
1↑¨conso ⍝ Noms des consommateurs
┌──────────┬──────────┐
│┌────────┐│┌────────┐│
││John Doe│││Jane Doe││
│└────────┘│└────────┘│
└──────────┴──────────┘
1 0 / 1↑¨conso ⍝ je veux le 1er et pas le 2e nom
┌──────────┐
│┌────────┐│
││John Doe││
│└────────┘│
└──────────┘
Résumé en français : On vous donne une chaine de caractères composée de “chiffres” (‘0’ à ‘9’). Vous devez écrire une fonction qui renvoie une chaine où chaque chiffre est répété le nombre de fois correspondant à sa valeur. Par exemple avec la chaine “312”, on doit répéter 3 fois le “3”, 1 fois le “1” et 2 fois le “2”, ce qui donne la chaine “333122”.
Version classique
Première idée, utiliser 2 boucles. La première pour récupérer un à un les caractères de la chaine et la seconde pour dupliquer le bon nombre de fois chacun de ces caractères.
python
def explose(s):
sortie = '' # initialisation du résultat final
for c in s: # on parcourt la chaine
for n in range(int(c)): # on ajoute le bon nombre de fois...
sortie += c # ...le caractère
return sortie # retour du résultat
>> explose("312")
'333122'
>> explose("302")
'33322'
>> explose("102269")
'12222666666999999999'
Une seule boucle + répéter
En Python, JavaScript ou APL, il est simple de répéter un caractère :
On peut également répéter un caractère 0 fois, dans ce cas on obtient la chaine vide. D’où cette seconde version :
Python
def explose(s):
sortie = ''
for c in s:
sortie += c * int(c) # on répète le caractère
return sortie
JavaScript
const explose = s => {
sortie = '';
for (c of s) sortie += c.repeat(+c); // Voir dessous pour +c
return sortie
}
>> + '5' // Transformer une chaine en nombre
5
>> Number('5') // Même chose que Number
5
Autres écritures : join, map, reduce
Nous devons transformer (map) chaque caractère en sa répétition, ce qui donne un tableau de taille celle de la chaine initiale :
Python
>> [c * int(c) for c in "312"] # Transformer 3 "chiffres"
['333', '1', '22'] # Tableau à 3 éléments
JavaScript
>> [..."312"].map(c => c.repeat(+c)) // map = transformation
['333', '1', '22']
APL
3 1 2 ⍴¨ '312' ⍝ le ¨ signifie "pour chaque"
┌───┬─┬──┐
│333│1│22│
└───┴─┴──┘
Il suffit ensuite de joindre les différents éléments, d’où cette troisième version :
Python
def explose(s):
return ''.join(c * int(c) for c in s)
>> explose("44012")
'44444444122'
JavaScript v1
const explose = s => [...s].map(c => c.repeat(+c)).join('')
>> explose('55011')
'555555555511'
JavaScript est tolérant sur les mélanges de types :
>> 'a'.repeat('3') // utilisation de '3' au lieu de 3
'aaa'
>> 3 * '4' // multiplication d'un nombre par un caractère
12
JavaScript v2
>> const explose = s => [...s].map(c => c.repeat(c)).join``
On peut également utiliser reduce, c’est-à-dire partir d’une chaine vide et au fur et à mesure ajouter les caractères répétés, voici une version en JavaScript :
const explose = s => [...s].reduce((a, c) => a + c.repeat(c), '')
>> explose("44012")
'44444444122'
APL
Nous avons déjà vu comment transformer une chaine en vecteur :
s ← '312'
⍎¨s ⍝ On obtient un vecteur de 3 caractères
3 1 2
Remarquons que nous devons dupliquer les caractères et ensuite les concaténer :
C’est exactement ce que fait un produit internef.g, à savoir :
x1 x2 x3 f.g y1 y2 y3 signifie réduction f/ appliquée à (x1 g y1) (x2 g y2) (x3 g y3)
3 1 2 ,.⍴ '312' ⍝ On duplique puis concatène
┌──────┐
│333122│
└──────┘
{(⍎¨⍵) ,.⍴ ⍵} '312' ⍝ Créons notre fonction
┌──────┐
│333122│
└──────┘
(⍎¨,.⍴⊢) '31402' ⍝ Même version sans utiliser ⍵
┌──────────┐
│3331444422│
└──────────┘
⍝ Version finale en APL
explose ← ⍎¨ ,.⍴ ⊢
explose '314159'
┌───────────────────────┐
│33314444155555999999999│
└───────────────────────┘
explose ← ∊ ⍎¨ ,.⍴ ⊢
explose '314159'
33314444155555999999999
Expressions régulières
Une autre idée est de ce dire que chaque “chiffre” doit être remplacé par sa duplication. Voyons comment on effectue des remplacements en Python et JavaScript :
javascript
>> 'bonjour'.replace('o','*') // Un seul 'o' sera remplacé par '*'
'b*njour'
>> 'bonjour'.replace(/o/g,'*') // Tous les 'o' sont remplacés
'b*nj*ur' // 'g' pour global
>> "3a1b22".replace(/\d/g, '*') // Remplacer les chiffres (digits)
'*a*b**'
>> '4032'.replace(/./g, v => 9 - v) // '.' = caractère quelconque
'5967' // Les chiffres sont remplacés par 9 - valeur
// Mettre toutes les voyelles en majuscules
>> "okjaicompris".replace(/a|e|i|o|u/g, c => c.toUpperCase())
'OkjAIcOmprIs'
D’où cette version finale en JavaScript :
const explose = s => s.replace(/./g, v => v.repeat(v))
python
Python a la méthode replace pour des remplacements simples.
>> 'bonjour'.replace('o','*') # Tous les 'o' sont remplacés
'b*nj*ur'
D’où l’idée de remplacer chacun des caractères de ‘0’ à ‘9’ par leur duplication :
def explose(s):
for i in range(10):
s = s.replace(str(i), str(i) * i)
return s
>> explose('314159')
'33314444155555999999999'
Pour utiliser des expressions régulières (Regex), nous devons importer la bibliothèque re.
>> import re
>> re.sub(r'\d','*','3a1b22') # Remplacer les chiffres par '*'
'*a*b**'
On peut également effectuer des transformations, pour cela on : – recherche les éléments à modifier à l’aide d’une expression régulière – récupère la chaine correspondante (group ou [0]) – effectue la transformation (lambda x : …)
# Mettre toutes les voyelles en majuscules
>> re.sub(r'a|e|i|o|u', lambda x: x.group().upper(), 'okjaicompris')
'OkjAIcOmprIs'
# Ecriture équivalente en utilisant [0]
>> re.sub(r'a|e|i|o|u', lambda x: x[0].upper(), 'okjaicompris')
'OkjAIcOmprIs'
# Transformer chaque chiffre en 9 - chiffre :
>> re.sub(r'.',lambda x: str(9 - int(x[0])), '4032')
'5967'
Ce qui nous donne cette version finale en Python :