Deuxième exercice Python / JavaScript / APL

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.

Partons d’un tri

Une première idée est de trier les 3 éléments (par ordre croissant ou décroissant, peu importe), de chercher quelle valeur est au milieu et enfin de récupérer le rang de cette valeur dans le tableau initial. Par exemple avec 2, 3 et 1, le tri donne 1, 2, 3 ce qui permet de récupérer la valeur centrale “2” et donc son rang 0 dans le tableau initial.

PythonJavaScriptAPL
sorted et sortsort
Comment trier en…

En Python, la fonction sorted tri par défaut un tableau numérique par ordre croissant :

>> sorted([21, 3, 1])
[1, 3, 21]

On peut également utiliser la méthode sort :

>> a = [21, 3, 1]
>> a.sort()
>> a
[1, 3, 21]

En JavaScript, la méthode sort fait un tri alphabétique, si bien que :

>> [21, 3, 1].sort()
[1, 21, 3]

En effet, dans un dictionnaire, le “mot” 21 est avant le “mot” 3… C’est pourquoi on utilise très souvent une fonction de comparaison. Voici celle pour obtenir un tri croissant :

>> [21, 3, 1].sort((a, b) => a - b)
[1, 3, 21]

Lorsque la différence a – b est négative, a sera placé avant b, si la différence est positive a sera placé après b sinon les 2 éléments restent inchangés.

En APL, autre vision du tri !

      ⍋ 21 3 1
3 2 1

La réponse 3 2 1 signifie que l’élément qui doit être en premier est à la 3e position (c’est le 1 du vecteur initial) ensuite le second est à la 2e position (le 3 du vecteur initial) et enfin le dernier élément est à la 1ere position (le 21).

Remarquez que l’on a la réponse à l’exercice, le 2e élément de ⍋ est la position recherchée. On visualise le 0 en 2e position :

      ⎕IO ← 0          ⍝ On impose que l'origine des indices soit 0
      ⍋ 2 3 1
2 0 1

Voici des versions Python / JavaScript et APL en utilisant les tris :

Python

def milieu(triplet):
    return triplet.index(sorted(triplet)[1])

>> milieu([2, 3, 1])
0
>> milieu([5, 10, 14])
1

Javascript

milieu = triplet => triplet.indexOf([...triplet].sort((a, b) => a - b)[1])

>> milieu([2, 3, 1])
0
>> milieu([5, 10, 14])
1

[…triplet] permet de faire une copie du tableau initial.

APL

      ⎕IO ← 0
      milieu ← {(⍋⍵)[1]}

      milieu 2 3 1
0
      milieu 5 10 14
1

Autre écriture possible, le symbole ⊃ (pick) utilisé seul (monadique) permet de récupérer le 1er élément d’un vecteur (ou matrice) et sous sa forme dyadique le n-ième terme :

      ⊃ 'bonjour'          ⍝ Ecriture monadique = 1er élément
b
      3 ⊃ 'bonjour'        ⍝ Ecriture dyadique
n
      ⊃ 4 6 8              ⍝ 1er élément
4
      3 ⊃ 4 6 8            ⍝ 3e élément
8

Ce qui permet d’obtenir cette version :

      ⎕IO ← 0
      milieu ← 2 ⊃ ⍋

      milieu 2 3 1
0

Si certain.e.s parmi vous aiment les choses concises, APL ne devrait pas vous déplaire de ce point de vue !

Et si on cherchait un invariant ?

Remarquons que si l’on trouve les positions de la plus grande valeur (max) et de la plus petite valeur (min) du triplet, la position restante sera celle que l’on cherche.

Mais l’ensemble des positions sera toujours { 0, 1, 2 }. Par exemple si le max est en 2e et le min en 1er, alors le milieu est en position 0. L’invariant intéressant ici est la somme des positions qui vaut toujours 0+1+2=3. Le rang que l’on cherche est donc :
rang_cherché = 3 – rang(max) – rang(min)

Python

def milieu(triplet):
    return 3 - triplet.index(max(triplet)) \
             - triplet.index(min(triplet))

javascript

milieu = triplet => 3 - triplet.indexOf(Math.max(...triplet))
                      - triplet.indexOf(Math.min(...triplet))

Observez la différence en JavaScript entre :

>> Math.max(2,5,8,1,10,3)
10
>> Math.max([2,5,8,1,10,3])
NaN
>> Math.max(...[2,5,8,1,10,3])
10

APL

En APL, on trouve les sommes, max, min des éléments d’un vecteur (ou matrice) en utilisant une réduction :

      +/ 2 3 1        ⍝ Somme des termes
6
      ⌈/ 2 3 1        ⍝ maximum
3
      ⌊/ 2 3 1        ⍝ minimum
1

Et pour trouver le rang d’un élément dans un vecteur, on utilise ⍳ (iota) :

      ⎕IO ← 1
      5 7 3 1 ⍳ 7     ⍝ Position de 7 dans le vecteur 5 7 3 1 ?  
2

Toujours avec le principe de l’invariant, somme – max – min donne le nombre qui est au milieu, celui dont on doit chercher le rang. On obtient finalement ce programme que vous pouvez tester directement ici :

⎕IO ← 0
milieu ← ⊢ ⍳ +/ - ⌈/ + ⌊/

      milieu 2 3 1
0
      milieu 5 10 14
1

Le ⊢ permet de récupérer le paramètre qui est à droite (équivalent de {⍵}) et je rappelle qu’APL fait les calculs de la droite vers la gauche, donc le minimum ⌊/ puis max + min ⌈/+⌊/ la soustraction va correspondre à – max – min et on ajoute la somme.

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)

Concours MPO105

C.Ret a organisé un petit MPO (Misez p’tit Optimisez) dont vous trouverez l’énoncé ici.

Il fallait déjà remarquer que :

eq1

La difficulté n’était donc pas tant au niveau de la programmation mais plutôt de trouver une machine ancienne, avec peu de pas de programme, suffisamment rapide et de créer un programme court avec un minimum de variables…

Voici les explications concernant le programme que j’ai proposé pour la calculatrice Radio Shack EC-4001 (Clone américain du Sinclair Cambridge Programmable de 1977) :

Le programme ci-dessous calcule la racine carrée du nombre voulu (4, 64, 169…) moins un.

1 F 3 1 -

Etapes en vidéo pour entrer le programme avec l’émulateur.

Signification :

  • 1 → Touche racine carrée (en mode programmation, les touches correspondent aux fonctions (7 → sin, 8 → cos etc.)
  • F → Moins (on voit le F en bleu sur le clavier)
  • 3 → # pour préciser que l’on va entrer un nombre
  • 1 → Le chiffre 1 (il faut donc 2 pas pour entrer ‘1’ )
  • – → “=” (le “-” en bleu, à ne pas confondre avec la touche “-”  !)

Erreur de 1/1000 pour la valeur 26553409

5152

J’ai également proposé une autre solution en utilisant une calculatrice financière non programmable en remarquant que la formule pour les intérêts composés est :

eq2

Donc, en prenant n = 2 et PV = 1, on obtient :

CodeCogsEqn

qui est exactement la formule que l’on cherche à programmer. Voici ce que cela donne par exemple sur une HP 10bII une fois que l’on a initialisé la machine par C ALL (Effacer les mémoires), 2 n, 1 +/- PV. Remarquez que les résultats sont multipliés par 100 car la machine affiche la valeur finale en %. Ainsi 18 100 correspond par exemple à 181.

Recherche de calculatrices

Bonjour à tous,

Comme vous avez pu le constater je fais régulièrement des vidéos sur des calculatrices anciennes (généralement des programmables). Si vous en avez à me proposer (en don ou à me vendre), n’hésitez pas à me contacter à cette adresse : schraf @ univ-angers . fr

Un petit aperçu de ma collection de Texas Instrument :

Collection

ti2

Mes énigmes Facebook

J’ai posté plusieurs énigmes sur le groupe Facebook Casse-tête, énigmes et paradoxes, suite aux remarques des membres, j’ai modifié quelques énoncés et vous en propose ici une version en PDF imprimable.

Vous trouverez une partie des solutions expliquées en vidéo ici :

Mes 6 énigmes proposées sur un groupe Facebook (Partie 1)
6 nouvelles énigmes proposées sur un groupe Facebook (Partie 2)
Petites énigmes mathématiques à programmer (avec solutions)