Petits exercices de programmation (niveau Lycée) en Python, JavaScript ou… APL

Page Twitter où j’ai mis quelques challenges que je vous propose de résoudre en Python, Javascript ou APL.


Python est le langage officiel enseigné dans les lycées français, donc pas le choix 😅
Lien pour taper en Python

JavaScript est un langage permettant de faire des interactions avec une page web. Indispensable si vous voulez devenir développeur web 🤓

APL n’est rien de tout ça ! Peu ou pas connu des informaticiens, critiqué pour son aspect ésotérique, il existe et résiste depuis plus de 50 ans (60 ans de façon théorique) ! 🏆
Ce langage développe une certaine façon de penser et vous donnera souvent des idées que vous pourrez ensuite réinterpréter en Python ou JavaScript. Son apprentissage n’est pas aisé mais sa beauté compense tous ses vilains défauts 🥰
Lien pour taper en APL

Les énoncés et corrigés sont aussi disponibles en version Notebook

Premier exercice (tiré de codewars.com)

Résumé en français : 2 paramètres entiers vous sont donnés, par exemple 1 et 9, le premier étant toujours inférieur au second (mais ils peuvent être négatifs). Vous devez trouver combien de nombres sont entre les 2 sachant qu’ils ne doivent pas contenir de ‘5’.

Python & Javascript

Testez cette version en ligne ici :

def dont_give_me_five(debut, fin):
 compteur = 0
 for v in range(debut, fin+1):
  if '5' not in str(v):
   compteur += 1
 return compteur

Cette version a le mérite d’être simple à comprendre. On initialise un compteur à zéro et on parcourt la liste complète des nombres entre debut et fin (avec un +1 pour inclure la valeur fin).
Pour savoir si 5 est ou n’est pas dans le nombre, on transforme le nombre v en chaine de caractères (str) et on teste si le caractère ‘5’ n’est pas dans cette chaine. C’est beaucoup plus facile que de le faire avec des formules mathématiques !
Si c’est vrai que ‘5’ n’est pas dans la chaine, le compteur augmente de 1.

Son équivalent en JavaScript que vous pouvez tester ici :

function dont_give_me_five(debut, fin) {
    let compteur = 0;
    for (let i = debut; i <= fin; i++)
        if (!i.toString().includes("5"))
            compteur++;
    return compteur;
}

En JavaScript, le “!” signifie “négation” et includes “contient”. On peut aussi utiliser une expression régulière qui testera (test) s’il y a un 5 et qui au passage convertira le paramètre (ici le nombre i) en chaine :

function dont_give_me_five(debut, fin) {
    let compteur = 0;
    for (let i = debut; i <= fin; i++)
        if (!/5/.test(i))
            compteur++;
    return compteur;
}

APL

Vous pourrez tester les commandes ci-dessous en allant sur le site https://tryapl.org/

Si vous n’avez jamais entendu parler d’APL, j’ai fait 3 petites vidéos d’introduction à son sujet, la première est ici :

APL permet de générer facilement la liste des nombres de 1 à N ≥ 0. Par exemple :

      ⍳6       ⍝ le symbole ⍳ se lit 'iota'
1 2 3 4 5 6

Pour que la liste démarre à 0 (ce qui est le cas des positions dans les tableaux en Python ou JavaScript), on peut l’imposer :

      ⎕IO ← 0
      ⍳6
0 1 2 3 4 5

Si maintenant on teste 17+4-1 et 17-1+4, on s’attend à obtenir la même chose… Et bien non !

      17+4-1
20
      17-1+4
12

La logique d’APL est de faire les calculs de la droite vers la gauche, donc pour la première écriture on aura -1 puis 4-1=3 puis +3=3 et enfin17+3=20.
Pour la seconde écriture, 4 puis 1+4=5 puis -5 puis 17-5=12.
Il ne faut donc pas confondre -1+4 et ¯1+4 (il y a un symbole spécial pour les nombres négatifs)

Pour avoir la liste des nombres entre 4 et 17 :

      4 + ⍳17+1-4
4 5 6 7 8 9 10 11 12 13 14 15 16 17
      4 {⍺ + ⍳⍵+1-⍺} 17
4 5 6 7 8 9 10 11 12 13 14 15 16 17

Le symbole ⍺ correspond au paramètre qui est à gauche et ⍵ à celui de droite.

Transformons chacun de ces nombres en chaines avec ⍕ et ¨ (pour chaque)

      ⍕¨4 {⍺ + ⍳⍵+1-⍺} 17             ⍝ Nombres transformés en chaines
┌─┬─┬─┬─┬─┬─┬──┬──┬──┬──┬──┬──┬──┬──┐
│4│5│6│7│8│9│10│11│12│13│14│15│16│17│
└─┴─┴─┴─┴─┴─┴──┴──┴──┴──┴──┴──┴──┴──┘
      ⍴∘⍕¨4 {⍺ + ⍳⍵+1-⍺} 17           ⍝ Longueurs des chaines
┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
│1│1│1│1│1│1│2│2│2│2│2│2│2│2│
└─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘

Cherchons maintenant si le caractère ‘5’ est dans ces chaines, pour cela on va composer ∊ (test d’appartenance) avec ⍕ :

      '5' ∊∘⍕¨ 4 + ⍳14
0 1 0 0 0 0 0 0 0 0 0 1 0 0

et en ajoutant la négation ~ :

     ~ '5' ∊∘⍕¨ 4 + ⍳14
1 0 1 1 1 1 1 1 1 1 1 0 1 1

Il ne reste plus qu’à compter le nombre de 1 en utilisant une réduction :

    +/ ~ '5' ∊∘⍕¨ 4 + ⍳14
12

Ce qui donne comme programme final que vous pouvez lancer directement ici :

      dont_give_me_five ← {+/ ~ '5' ∊∘⍕¨ ⍺ + ⍳⍵+1-⍺}

      
      4 dont_give_me_five 17
12
      ¯17 dont_give_me_five ¯4
12

Autres versions en python/javascript

Notre version APL propose de créer une liste de booléens suivant que ‘5’ est où non dans la chaine puis d’en faire la réduction par la somme. Or en Python :

> True + True        # 1 + 1
2
> True + False       # 1 + 0 
1

D’où cette seconde version :

def dont_give_me_five(debut, fin):
    return sum(['5' not in str(i) for i in range(debut, fin + 1)])

# qui peut même s'écrire sans les crochets :

def dont_give_me_five(debut, fin):
    return sum('5' not in str(i) for i in range(debut, fin + 1))

Pour terminer, voyons comment utiliser reduce en JavaScript afin d’ajouter +1 (ou + true) à chaque fois que le nombre convient. Pour cela il faut partir d’un tableau de taille fin – debut + 1, peut importe son contenu :

>> [...Array(17 - 4 + 1)]
(14) [undefined, undefined, ..., undefined]

reduce utilise un accumulateur ayant une valeur initiale (pour nous 0) et au fur et à mesure cet accumulateur va pouvoir changer. Ci-dessous 3 paramètres ont être ajoutés, l’accumulateur a, la valeur v et le rang r. Ainsi, en parcourant les éléments d’un tableau, on peut récupérer leurs valeurs et leurs rangs. Le 0 après la virgule est pour l’initialisation de l’accumulateur.

[...Array(17 - 4 + 1)].reduce((a,v,r) =>    , 0)

Vous devriez comprendre l’idée de cette version :

dontGiveMeFive = (debut,fin) => [...Array(fin - debut + 1)]
     .reduce(
       (a, _ ,r) => a + !/5/.test(r + debut)
     ,0)