Initiation à Python : que font ces programmes ?

Le but de ces exercices est de trouver le résultat final sans taper le programme, donc en effectuant les étapes à la main sur papier comme si vous étiez l’ordinateur.
Dans un second temps, des applications concrètes sont données, à vous de déterminer laquelle des fonctions mystère sera utile.

Exercice 1

def mystere(arr):
  s = 0
  m = arr[0]
  for v in arr:
    if v > m:
      m = v
      s += 1
  return s

>> mystere([2,3,7,0,6,3])
??

>> mystere([2])
??

Exercice 2

def mystere(arr, n):
  s = 0
  for v in arr:
    if v > n:
        s += 1
  return s

>> mystere([2,3,7,0,6,3], 3)
??

>> mystere([2,3,7,0,6,3], 10)
??

Exercice 3

def mystere(arr, n):
  s = None
  for v in arr:
    if v > n:
      if s is None or s > v:
        s = v
  return s

>> mystere([2,3,7,0,6,3], 3)
??

>> mystere([2,3,7,0,6,3], 10)
??

Exercice 4

def mystere(arr):
  s = 0
  for i, v in enumerate(arr):
    s += v * (-1) ** i 
  return s

>> mystere([2,3,7,0,6,3])
??

>> mystere(range(5))
??

Exercice 5

def mystere(arr):
  s = 0
  for i, v in enumerate(arr):
    if v == i:
     s += 1 
  return s

>> mystere([2,1,0,3,5,4])
??

>> mystere(range(5))
??

exercice 6

def mystere(arr):
  s = sorted(list(arr))
  for i, v in enumerate(arr):
    s[i] = arr.index(s[i])
  return s

>> mystere([2,5,3,1])
??

>> mystere([2])
??

exercice 7

def mystere(arr):
  for i, v in enumerate(arr):
    if i > 0 and v == arr[i - 1]: return True
  return False

>> mystere([2,5,3,1])
??

>> mystere([2,5,5,3])
??

>> mystere(range(10))
??

Exercice 8

def mystere(arr1, arr2):
  s = []
  for v in arr1:
    if v in arr2 and v not in s:
        s.append(v)
  return s
  
>> mystere([2,5,5,3], [4,4,2,5,7])
??

>> mystere([2],[4])
??

Exercice 9

def mystere(arr1, arr2):
  s = []
  for v in arr1 + arr2:
    if v not in s:
        s.append(v)
  return s

>> mystere([2,2,2,7],[4,4,5])
??

Pour aller plus loin : Ecrire une fonction analogue (l’ordre des éléments pouvant être différent), en utilisant set, union et list.

exercice 10

def mystere(arr):
  s = []
  for u in arr:
    n = 0
    for v in arr:
      if v > u:
        n += 1
    s.append(n)
  return s

>> mystere([8,2,5,1,7])
??

>> mystere([1])
??

>> mystere(range(5))
??

Applications concrètes

Pour chacun des exemples concrets ci-dessous, retrouvez quelle fonction mystère serait adaptée pour répondre à la question.

Société de transport “Okilo”

La société OKILO a plusieurs camions, chacun étant spécialisé dans le transport de colis de plus de X kilos. Par exemple le camion ci-dessous ne transporte que des colis de plus de 10 kilos, il va donc refuser 3 colis sur les 5 et ne garder que les 2 de 15 et 13 kg.

Ce camion n’accepte que les colis de plus de 10 kilos

Quelle fonction mystere permet, à partir d’une liste de poids et de la valeur minimale acceptée par le camion, d’obtenir le nombre de colis qui seront transportés ?

Mélange

Vous mettez 6 billes numérotées de 0 à 5 dans un sac. Au hasard vous les sortez une à une et les placez dans des boites numérotées également de 0 à 5. On se demande combien de billes ont un numéro correspondant à celui de leur boite ?

Seules les billes n°2 et 5 sont bien rangées, c’est-à-dire dans des boites correspondant à leurs numéros

Quelle fonction mystere permet, à partir d’une liste de numéros ([1, 0, 2, 4, 3, 5] pour l’exemple) de connaitre le nombre de billes qui sont bien rangées (2 pour l’exemple) ?

Nombre de personnes dans le bus

Des personnes montent et descendent d’un bus. Par exemple la liste [3,2,8,0,6,3] signifie qu’au premier arrêt 3 personnes sont montées et 2 sont descendues. A l’arrêt suivant 8 personnes montent et aucune ne descend et enfin au 3e arrêt, 6 montent et 3 descendent. Sachant qu’il y avait 10 personnes dans le bus avant d’arriver au 1er arrêt, combien restera-t-il de personnes dans le bus après le 3e arrêt ?

Combien y aura-t-il de personnes dans le bus après le 3e arrêt ?

Quelle fonction mystere vous aidera à trouver le nombre final de passagers ?

Combien sont plus grands que moi ?

5 personnes montrent aux autres leurs tailles respectives sur un petit panneau. Ainsi, tout le monde voit les tailles de tout le monde. Chacun se demande “Combien sont plus grands que moi ?”

Chacun se demande combien d’individus dans le groupe sont plus grands qu’eux

La plus grande personne pensera donc à zéro et la plus petite à 4.

Quelle fonction mystere permet, à partir de la liste des tailles [1.6, 1.95, 1.56, 1.61, 1.52] d’obtenir la liste des nombres auxquels chacun pense ?

Ecrivains célèbres

On se demande s’il existe des écrivains dont le nom et le prénom s’écrivent avec des lettres différentes. Par exemple pour VICTOR HUGO, on voit qu’il y a la lettre O en commun, cet auteur ne correspond pas à ce que l’on recherche.

Quelle fonction mystere permet, à partir de 2 chaines de caractères (une pour le prénom, l’autre pour le nom) de nous dire quelles lettres sont communes ?

Le ‘O’ est commun à ‘VICTOR’ et ‘HUGO’

Arriverez-vous à trouver un auteur vérifiant ce que l’on cherche ?

secret défense

Voici la technique de cryptographie que vous voulez employer :
– La phrase à transmettre est MESSAGEIMPORTANT
– Comme il y a 16 lettres dans ce message, vous prenez les 16 premières lettres de l’alphabet que vous mélangez au hasard. Par exemple en tapant :

>> from random import sample
>> sample("ABCDEFGHIJKLMNOP",16)
['H', 'C', 'G', 'B', 'N', 'I', 'A', 'M', 'L', 'F', 'O', 'D', 'J', 'E', 'K', 'P']

– Maintenant vous regardez les rangs des lettres en partant de ‘A’ jusqu’à ‘P’ dans cet alphabet mélangé. Le ‘A’ est à la 6e position, le ‘B’ la position 3, le ‘C’ à la position 1 etc.
– On obtient la liste : [6, 3, 1, 11, 13, 9, 2, 0, 5, 12, 14, 8, 7, 4, 10, 15]
– Finalement, pour crypter votre message, vous allez prendre sa lettre n°6 (le E) puis la n°3 (le S), puis la n°1 (le E) etc. jusqu’à la 16e position.
– Vous obtenez le mot codé : ESERAPSMGTNMIAOT

Quelle fonction mystere permettra de faire fonctionner le programme ci-dessous ?

def coder(txt,cle):
  ordre = mystere(cle)
  s = ""
  for i in range(len(txt)):
    s += txt[ordre[i]]
  return s 

>> coder("MESSAGEIMPORTANT", "HCGBNIAMLFODJEKP")
ESERAPSMGTNMIAOT

Gratte-ciels

Des immeubles de différentes hauteurs sont côte à côte. On se demande combien vont être touchés par des rayons de lumières parallèles au sol et provenant de la gauche. Sur le visuel ci-dessous les hauteurs sont [2, 2, 3, 2, 6, 3, 6, 2] et l’on voit que seuls 3 seront touchés par les rayons (les autres sont cachés par au moins un plus grand à leur gauche).

Seuls 3 immeubles seront touchés

Quelle fonction mystere va vous aider à compter le nombre d’immeubles touchés par les rayons ?

parterre de fleurs

Afin d’obtenir le label “Villes et villages fleuris”, le maire décide de créer des parterres de fleurs en alternant fleurs et tulipes. Voici un exemple qui lui convient : 🌼🌷🌼🌷🌼🌷🌼🌷

Par contre celui-ci ne convient pas : 🌼🌷🌼🌷🌷🌼🌷🌼🌷 puisque 2 tulipes sont côte à côte.

Quelle fonction mystere va vous permettre de savoir si un parterre est correct ou non ?

>> correct("🌼🌷🌼🌷🌼🌷🌼🌷")
True

>> correct("🌼🌷🌼🌷🌷🌼🌷🌼🌷")
False

Rubik’s Cube 2x2x2

Voici quelques explications sur le script Python proposé pour la NUMWORKS

Numérotation des vignettes :

Ce qui permet de définir les différents mouvements possibles :

MVT = ((8,9,11,10),(4,0,17,13),(5,1,16,12)),((4,5,7,6),(3,10,13,23),(1,11,15,22)), \
      ((17,16,18,19),(9,0,20,14),(8,2,21,12)),((0,1,3,2),(8,4,22,18),(10,6,20,16)), \
      ((20,22,23,21),(2,6,15,19),(3,7,14,18)),((13,12,14,15),(7,11,17,21),(5,9,19,23))

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)
Etapes pour dessiner la vignette n°8

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 ?

RETOUR VERS LES ANNEES 70 ! ⏰

Tableaux de fils tendus en Python

Voici quelques exemples de codes en Python pour créer des tableaux de fils, ils sont basés sur des fonctions décrites dans cette vidéo :

Free String Art Boat Pattern

Le bateau

Vous trouverez ici les explications pour créer le tableau à l’ancienne avec de vrais fils et des clous.

Vous pouvez copier-coller le code sur ce site : https://trinket.io/turtle

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))

Free String Art Heart Circle Pattern

Un coeur

Vous trouverez ici les explications ici. Plus complexe à réaliser !

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)

Free String Art Circle 2 Pattern

Vous trouverez ici les explications ici. Assez facile…

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)

Tapisseries des années 70

Tapisserie typique des années 70

Voir le programme final en action | Version pour la calculatrice NUMWORKS

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

Distance entre 2 centres
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)

Fichier pour la TI-83 Premium CE EDITION PYTHON

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)
Longueurs d’une face
h-p-p-p-p-h avec p = h / 2
Les angles
30 -120 – 60 – 300 – 120 – -60
Enchevêtrements

Lien vers le script pour la calculatrice NUMWORKS

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)

Dessinons un célèbre chapeau 3D sur la NUMWORKS

Sur d’anciennes revues des années 80, on pouvait voir cette surface 3D ressemblant à un chapeau :

http://archive.6502.org/publications/micro/micro_36_may_1981.pdf (page 2)

Remarquez que le code du programme (écrit en BASIC) était donné sur la publicité ! Voici à nouveau ce chapeau dans une autre revue :

Sciences & Avenir n°36 (page 25) – Collection personnelle

Passons à sa traduction en Python pour la NUMWORKS :

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)
Résultat final

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 :

Horloges mathématiques

J’ai eu l’occasion de faire plusieurs vidéos de montres originales sur ma chaine Youtube, en particulier :

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.

Additions et soustractions

Téléchargez | Voir la démo en ligne

-21+18+18=15 et -8+10+4=6, il est 15 h 06 min

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 !

Addition et multiplication

Téléchargez | Voir la démo en ligne

7 × 9 – 45 = 18 et 3 + 7 × 5 = 38, il est 18 h 38 min

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.

Version points sur des dés

Téléchargez | Voir la démo en ligne

Il y a 17 points sur le dé du haut et 35 sur le dé du bas, il est 17 h 35 min

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

Version aiguilles : Heures – Minutes – Secondes

Téléchargez | Voir la démo en ligne | Version tirage aléatoire de l’heure

3 horloges séparées pour lire heures, minutes et secondes. Il est 7 h 28 min 48 s

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)+')');

Version cercle trigonométrique

Téléchargez | Voir la démo en ligne

0° = 15h et -18° = 3 minutes après 15 soit 15 h 18 min

La formule pour les minutes n’est qu’une fonction affine : 0 min est à 90° et 60 min à -270°. On obtient la formule :

ANGLEMIN = min => -6 * min + 90

On fait ensuite un test pour savoir si le nombre est plus petit que -180 (par exemple -246), si c’est le cas on affichera plutôt -246 + 360 = 114°

La stratégie est la même pour les heures, en faisant attention aux 2 cas : heures entre 3h et 9h puis entre 9h-12h-3h. La formule utilisée est :

ANGLEH = h => -30 * (h % 12) + 90   // le % correspond à 'modulo'

Système d’équations

Téléchargez | Voir la démo en ligne

Il faut résoudre le système pour trouver H=16 et M=37, il est donc 16 h 37 min

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)
'-'

Version binaire

Téléchargez | Voir la démo en ligne

01111 = 1 + 2 + 4 + 8 = 15 et 011110 = 2 + 4 + 8 + 16 = 30, il est 15 h 30 min

Le programme est encore plus simple puisqu’en JavaScript on transforme un nombre en binaire par :

>> (15).toString(2)
'1111'
>> (30).toString(2)
'11110'

Pour écrire les heures nous avons besoin d’au plus 5 bits et 6 bits pour les minutes.

>> BIN = (n, s) => ('0'.repeat(5) + n.toString(2)).slice(-s)

>> BIN(15,5)    // 15 écrit sur 5 bits
'01111'

>> BIN(30,6)    // 30 écrit sur 6 bits 
'011110'

Version binaire plus simple

Téléchargez | Voir la démo en ligne

Les colonnes correspondent aux dizaines et unités pour les heures, minutes et secondes
Une colonne se lit de bas en haut et les fenêtres valent 1, 2, 4 et 8
Dizaine pour les heures = 1, Unités pour les heures = 2 + 4 = 6
Dizaine pour les minutes = 1 + 2 = 3, Unités pour les minutes = 1 + 2 + 4 = 7
Dizaines pour les secondes = 4, Unités pour les secondes = 8
Il est 16 h 37 min 48 s

Une vraie horloge existe, elle se nomme “The City Clock” et vous la trouverez ici.

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

Version hexadécimale (pour les designers)

Téléchargez | Voir la démo en ligne

#0E = 14, #2A = 2*16+10=42, #14=1*16+4=20, il est 14 h 42 min 20 s

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 :

>> HEX = n => ('0' + n.toString(16)).slice(-2).toUpperCase()

>> HEX(14)
'0E'
>> HEX(42)
'2A'
>> HEX(20)
'14'

Inversement, pour décoder, il faut prendre l’unité (entre 0 et F) et ajouter 16 fois la dizaine.

Horloge de Fibonacci

Téléchargez | Voir la démo en ligne

7e nombre de Fibonacci = 13, 9e+6e+3e = 34+8+2 = 44, il est 13 h 44 min

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)

Horloge romaine

Téléchargez | Voir la démo en ligne

IX = 9, XLVIII = 48, XXIV=24, il est 9 h 48 min 24 s

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)

Nombre de minutes avant la prochaine heure entière, ici encore 33 minutes

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

Carte de France en Python 500 octets

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))

Programme final ici

Avec la tortue

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 :

Versions Notebook des exercices Python et JavaScript

Titre de l’exercicePythonJavaScript
1. Je ne veux pas de 5 !📙📗
2. Nombre du milieu📙📗
3. Heterogramme📙📗
4. Prendre tant que…📙📗
5. Les rats et le joueur de flûte📙📗
6.Compression d’un texte📙📗
7. Persistance d’un nombre📙📗
8. Notation polonaise inverse (RPN)📙📗
9. Lapins de Fibonacci📙📗
10. Paires de gants📙📗
11. Rotations d’une matrice carrée📙📗
12. Explosion d’un nombre📙📗
13. Une pizza gratuite !📙📗

Treizième exercice en Python, JavaScript et APL

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’achats minimum, 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 ?

Partons de cet exemple :

conso = {
'John Doe' : [22, 30, 11, 17, 15, 52, 27, 12],
'Jane Doe' : [5, 17, 30, 33, 40, 22, 26, 10, 11, 45]
}

Python

>> conso.keys()                        # Autre solution plus bas
dict_keys(['John Doe', 'Jane Doe'])

>> list(conso.keys())
['John Doe', 'Jane Doe']

JavaScript

>> Object.keys(conso)
['John Doe', 'Jane Doe']

APL

conso ← ('John Doe' 22 30 11 17 15 52 27 12) 
        ('Jane Doe' 5 17 30 33 40 22 26 10 11 45)

      1↑¨conso             ⍝ Premier élément de chaque
┌──────────┬──────────┐
│┌────────┐│┌────────┐│
││John Doe│││Jane Doe││
│└────────┘│└────────┘│
└──────────┴──────────┘

Et pour récupérer les achats d’un consommateur :

Python & JavaScript

>> conso['John Doe']
[22, 30, 11, 17, 15, 52, 27, 12]


APL

      1↓¨ conso         ⍝ Achats des différents clients
┌───────────────────────┬────────────────────────────┐
│22 30 11 17 15 52 27 12│5 17 30 33 40 22 26 10 11 45│
└───────────────────────┴────────────────────────────┘

python

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']

javascript

Vous pouvez tester cette version ici :

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 :

notes = [5, 12, 11, 9, 3, 17, 18, 6]

Python

>> [v for v in notes if v >= 10]
[12, 11, 17, 18]

JavaScript

>> notes.filter(v => v >= 10)
[12, 11, 17, 18]

APL

      notes ← 5 12 11 9 3 17 18 6

      (notes ≥ 10) / notes      ⍝ Version 1
12 11 17 18

      (10 ≤ notes) ⊆ notes      ⍝ Version 2
┌─────┬─────┐
│12 11│17 18│
└─────┴─────┘

      10 (≤ ⊆ ⊢) notes          ⍝ Version "train"
┌─────┬─────┐
│12 11│17 18│
└─────┴─────┘

     ∊ (10 ≤ notes) ⊆ notes
12 11 17 18

      10 (∊ < ⊆ ⊢) notes
12 11 17 18

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││
│└────────┘│
└──────────┘

Créons le vecteur logique :

      conso ← ('John Doe' 22 30 11 17 15 52 27 12) 
              ('Jane Doe' 5 17 30 33 40 22 26 10 11 45)

      (1↓⊢)¨ conso            ⍝  Achats des consommateurs
┌───────────────────────┬────────────────────────────┐
│22 30 11 17 15 52 27 12│5 17 30 33 40 22 26 10 11 45│
└───────────────────────┴────────────────────────────┘
      (20 +.≤ 1↓⊢)¨ conso     ⍝  Nombre d'achats ≥ 20
4 6

      20 (⊣ +.≤ 1↓⊢)¨ conso     ⍝ Version plus générale
4 6

    5 ≤  20 (⊣ +.≤ 1↓⊢)¨ conso  ⍝ Ce nb d'achats est-il ≥ 5 ?
0 1

    (5 ≤  20 (⊣ +.≤ 1↓⊢)¨ conso) / 1↑¨conso   ⍝ Pizza gratuite pour
┌──────────┐
│┌────────┐│
││Jane Doe││
│└────────┘│
└──────────┘

Ce qui amène à cette version finale en APL à tester ici :

      free ← {(⍺[1] ≤ (⍺[2] +.≤ 1↓⊢)¨⍵) / 1↑¨⍵}

      5 20 free conso
┌──────────┐
│┌────────┐│
││Jane Doe││
│└────────┘│
└──────────┘
      2 50 free ('Joey Bonzo' 22 67 53 29) ('Jennifer Bonzo' 51 19)
┌────────────┐
│┌──────────┐│
││Joey Bonzo││
│└──────────┘│
└────────────┘

Douzième exercice en Python, JavaScript et APL

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 :

Python

>> 'a' * 5
'aaaaa'

JavaScript

>> 'a'.repeat(5)
'aaaaa'

APL

       5 ⍴ 'a'
aaaaa

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 :

      (3 ⍴ '3') (1 ⍴ '1') (2 ⍴ '2')      ⍝ On duplique les caractères  
┌───┬─┬──┐
│333│1│22│
└───┴─┴──┘
     ,/ (3 ⍴ '3') (1 ⍴ '1') (2 ⍴ '2')    ⍝ Concaténation
┌──────┐
│333122│
└──────┘

C’est exactement ce que fait un produit interne f.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 :

import re

def explose(s):
  return re.sub(r'.',lambda v: v[0] * int(v[0]), s)

Onzième exercice en Python, JavaScript et APL

Résumé en français : On vous donne une matrice carrée et un nombre n, vous devez renvoyer cette matrice dont les tous les termes ont été tournés (n rotations) dans le sens anti-horaire. Exemple avec une matrice 3 x 3 :

      Matrice initiale
1 2 3
4 5 6
7 8 9
      n = 1 rotation anti-horaire
3 6 9
2 5 8
1 4 7
      n = 2 rotations anti-horaire
9 8 7
6 5 4
3 2 1
      n = 3 rotations anti-horaire
7 4 1
8 5 2
9 6 3

Python : Facile avec une bibliothèque

La méthode rot90 de la bibliothèque Python numpy permet d’effectuer des rotations de tableaux :

>> import numpy as np

>> np.rot90([[1, 2], [3, 4]], 1)         # 1 rotation anti-horaire
array([[2, 4],
       [1, 3]])

>> np.rot90([[1, 2, 3], [4, 5, 6]], 2)   # 2 rotations, matrice 2 x 3
array([[6, 5, 4],
       [3, 2, 1]])

Remarquons que 2 rotations n1 et n2 sont équivalentes si la différence n1 – n2 est un multiple de 4. Par exemple, effectuer 1 rotation anti-horaire est équivalent à faire 5 rotations anti-horaires.

De façon plus générale, n rotations anti-horaires sont équivalentes à n % 4 rotations anti-horaires. D’où cette version finale en Python :

import numpy as np

def rotation(matrice, n):
    return np.rot90(matrice, n % 4).tolist()

>> rotation([[1, 2], [4, 5]], 2)
[[5, 4], [2, 1]]

JavaScript & APL : Effort moyen avec renversements et transposée

Renversements et transposée

Le diagramme ci-dessous montre que l’on peut obtenir une rotation anti-horaire à l’aide de (transposée) et ⊖ (renversement vertical) ou de ⍉ et ⌽ (renversement horizontal) :

APL

APL dispose de plusieurs primitives pour effectuer des renversements et transposée :

      m ← 2 2 ⍴ ⍳4         ⍝ Matrice 2 x 2
      m
1 2
3 4
      ⌽ m                   ⍝ renversement horizontal
2 1
4 3
      ⊖ m                   ⍝ renversement vertical
3 4
1 2
      ⍉ m                   ⍝ transposée
1 3
2 4

Vérifions :

      m ← 3 3 ⍴ ⍳9
      m
1 2 3
4 5 6
7 8 9
      (⊖⍉) m
3 6 9
2 5 8
1 4 7
      (⍉⌽) m
3 6 9
2 5 8
1 4 7

Nous avons vu dans l’exercice n°9 que ⍣ permet de répéter une opération plusieurs fois, par exemple pour 4 rotations anti-horaires (ce qui redonnera bien sûr la matrice initiale) :

      ((⊖⍉) ⍣ 4) m
1 2 3
4 5 6
7 8 9

D’où cette version finale en APL :

      rotation ← { (⍉⌽) ⍣ (4|⍺) ⊢ ⍵ }

      3 rotation 3 3 ⍴ ⍳9
7 4 1
8 5 2
9 6 3
      1 rotation 4 4 ⍴ ⍳16
4 8 12 16
3 7 11 15
2 6 10 14
1 5  9 13

Javascript

La bibliothèque JavaScript Ramda permet également de faire des renversements et de transposer des tableaux. On peut donc comme en APL remplacer la rotation anti-horaire par la combinaison transpose et reverse. Exemple que vous pouvez tester ici :

R.transpose([[1, 2], [3, 4]]).reverse()
[[2, 4], [1, 3]]

Version finale récursive à tester ici :

var rotation = (matrice, n) => {
  n %= 4                                     // reste division par 4
  if (n == 0) return matrice                 // fin récursivité
  matrice = R.transpose(matrice).reverse()   // rotation anti-horaire   
  return rotation(matrice, n - 1)            // appel récursif
}

>> rotation([[1, 2], [3, 4]], 1)
[[2, 4], [1, 3]]

JavaScript & Python : Version sans bibliothèque

Pour se fixer les idées, supposons que l’on ait une matrice 3 x 3. Observez que faire une rotation anti-horaire, c’est remplacer la 1ere ligne par la dernière colonne, la 2e ligne par la 2e colonne et la 3e ligne par la 1ere colonne :

matrice initiale

1 2 3
4 5 6
7 8 9

rotation anti-horaire

3 6 9       // 1ere ligne = 3e colonne de la matrice initiale
2 5 8       // 2e ligne = 2e colonne de la matrice initiale
1 4 7       // 3e ligne = 3e colonne de la matrice initiale

Dit autrement, faire une rotation anti-horaire, c’est prendre les colonnes de la droite vers la gauche et les écrire en lignes.

Ce que l’on peut traduire en Python ou JavaScript par :

anti_horaire = matrice => {
    res = [ ];                           // Matrice finale
    taille = matrice.length;             // Taille matrice
    for (l = 0; l < taille; l++) {       // Création de chaque ligne
        ligne = [ ];                     // Initialisation 
        for (i = 0; i < taille; i++) {   // Chaque élément de la ligne 
            ligne.push(matrice[i][taille - l - 1])
        };
        res.push(ligne)                  // Ajout de la ligne
    };
    return res;                          // On retourne le résultat
}

>> anti_horaire([[1,2,3], [4,5,6], [7,8,9]])
[3, 6, 9]
[2, 5, 8]
[1, 4, 7]

javascript

Comme il s’agit de transformer chacune des lignes, on peut également utiliser map. Voici ci-dessous la même version du programme précédent. Le matrice[0] permet de récupérer la première ligne de la matrice et donc un tableau qui a la largeur de cette matrice, son contenu ne nous intéresse pas puisqu’il va être calculé, d’où le _. Pour chaque numéro de ligne (variable l), on remplace les éléments par la colonne correspondante en partant de la droite (x.length – l – 1):

anti_horaire = matrice => 
    matrice[0].map((_,l) => matrice.map(x => x[x.length - l - 1]))

Version finale (sans bibliothèque) en JavaScript :

const rotation = (matrice, n) => {
    for (i = 0; i < n % 4; i++)
     matrice = matrice[0].map((_,l) => 
                matrice.map(x => x[x.length - l - 1]))
    return matrice
}

>> rotation([[1,2,3], [4,5,6], [7,8,9]], 1)
[3, 6, 9]
[2, 5, 8]
[1, 4, 7]

>> rotation([[1,2,3], [4,5,6], [7,8,9]], 2)
[9, 8, 7]
[6, 5, 4]
[3, 2, 1]

Python

Comme nous l’avons vu, faire une rotation anti-horaire, c’est faire une transposée (remplacer la 1ere ligne par la 1ere colonne, la 2e ligne par la 2e colonne etc.) puis inverser l’ordre des lignes :

Voici comment inverser les lignes en Python :

>> [[1, 2, 3], [4, 5, 6], [7, 8, 9]] [::-1]
[[7, 8, 9], [4, 5, 6], [1, 2, 3]]

Ce qui donne ce programme final :

def rotation(matrice, n):
  for _ in range(n % 4):
    matrice = [[r[i] for r in matrice] for i in range(len(matrice))][::-1]
  return matrice

Ou cette autre version avancée qui utilise la notion de dépaquetage avec zip et * :

def rotation(matrice, n):
  for _ in range(n % 4):
    matrice = [list(col) for col in list(zip(*matrice))][::-1]
  return matrice

Quelques exemples pour mieux comprendre zip et * :

>> [* [[1, 2], [3, 4]]]           # Exemple n°1 de dépaquetage
[[1, 2], [3, 4]]

>> [* {1, 2, 3}]                  # Exemple n°2 de dépaquetage
[1, 2, 3]

>> [* 'abcd']                     # Exemple n°3 de dépaquetage
['a', 'b', 'c', 'd']

>> list(zip([1,2], [3,4]))
[(1, 3), (2, 4)]

>> list(zip([[1, 2], [3, 4]]))    # ne fonctionne pas
[([1, 2],), ([3, 4],)]

>> list(zip(* [[1, 2], [3, 4]]))  # Il faut déjà dépaqueter
[(1, 3), (2, 4)]