Troisième exercice Python / JavaScript / APL

Résumé en français : 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)

Les ensembles

Que ce soit en Python, JavaScript ou APL, on peut créer des ensembles.

Commençons par Python, en plus des listes [ ], tuples ( ) et dictionnaires { }, les ensembles sont des listes non ordonnées sans éléments répétés :

>> set({5, 5, 1, 5, 1})
{1, 5}

>> set('abracadabra')
{'d', 'a', 'b', 'c', 'r'}

En JavaScript, on définit un ensemble de façon assez semblable :

>> new Set([5, 5, 1, 5, 1])
{5, 1}

>> new Set('abracadabra')
{'a', 'b', 'r', 'c', 'd'}

Et en APL le symbole est celui de l’union comme en mathématique :

      ∪ 5 5 1 5 1
5 1
      ∪ 'abracadabra'
abrcd

L’idée principale : On aura un heterogramme si la taille de l’ensemble des lettres utilisées pour écrire le mot est égal à la taille du mot initial.

Python

def isogram(mot):
    return len(mot) == len(set(mot.lower()))

>> isogram('Dermatoglyphics')
True
>> isogram('aba')
False
>> isogram('moOse')
False

Remarquez que le mot a besoin d’être mis en minuscule (ou majuscule) uniquement pour créer l’ensemble des lettres, pas pour trouver sa taille initiale. len permet en Python de donner la taille d’une chaine, d’une liste ou d’un dictionnaire.

javascript

isogram = mot => mot.length == new Set(mot.toLowerCase()).size

length pour la taille d’une chaine ou d’un tableau, size pour les ensembles.

APL

isogram ← ⍴ = (⍴∪∘⎕C)

      isogram'Dermatoglyphics'
1
      isogram'aba'
0
      isogram'moOse'
0

⎕C permet de convertir une chaine en minuscule, on cherche ensuite avec ∪ l’ensemble des lettres utilisées et on calcule sa taille ⍴. Finalement, on teste si elle est égale à celle du mot initial.

Recherche d’au moins une lettre en double

Une autre idée peut être de rechercher s’il existe au moins une lettre en double, par exemple en utilisant 2 boucles, en voici une version générique écrite en JavaScript mais facilement traduisible en Python :

function isogram(mot) {
  mot = mot.toLowerCase();
  for (i = 0; i < mot.length - 1; i++) {
    for (j = i + 1; j < mot.length; j++) {
      if (mot[i] == mot[j]) return false;
    }
  }
  return true;
}

D’autres pistes…

Voici d’autres pistes pour aborder ce problème dans chacun des langages :

javascript

Ecrivons une expression régulière (Regex) qui va tester si une correspondance (match en anglais) existe entre un caractère (noté “.” en Regex) et ce même caractère un peu plus loin.

L’expression régulière est (exemple ici avec le mot couleur):

(.).*\1

le (.) permet de chercher un caractère et de le mémoriser dans un groupe (les parenthèses). Ensuite .* signifie 0 ou plusieurs caractères quelconques et le \1 rappelle le premier groupe, donc pour trouver un éventuel doublon. On ajoute le paramètre i pour que la recherche soit indépendante de la casse :

isogram = mot => !/(.).*\1/i.test(mot.toLowerCase())

>> isogram('Dermatoglyphics')
true

Python

Pour savoir si une lettre est en double, on peut compter (count) combien de fois elle apparait dans le mot. Si toutes (all) les lettres n’apparaissent qu’une seule fois, c’est un heterogramme.

>> 'abracadabra'.count('a')
5

def isogram(mot):
    mot = mot.lower()
    return all(mot.count(c) == 1 for c in mot)

Ou utiliser le module collections qui contient la classe Counter permettant de faire du dénombrement.

>> from collections import Counter
>> Counter('abracadabra')
Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1})
>> Counter('abracadabra').values()
dict_values([5, 2, 2, 1, 1])

On a alors l’alternative :

from collections import Counter

def isogram(mot):    
 return all(v == 1 for v in Counter(mot.lower()).values())

APL

Le symbole ⌸ (key) permet de créer des sortes de tableaux croisés dynamiques comme sur Excel. Par exemple :

      {⍺,⍵}⌸ 'abracadabra'
a 1  4 6 8 11
b 2  9       
r 3 10       
c 5          
d 7 

Pour chaque élément distinct ⍺ de la chaine (c’est-à-dire les lettres a, b, r, c et d), il affiche les positions trouvées ⍵. La lettre ‘a’ a été vue aux positions 1, 4, 6… On peut lui demander de n’afficher que le nombre de positions trouvées, avec ou sans préciser ⍺ :

      {⍺,≢⍵}⌸ 'abracadabra'
a 5
b 2
r 2
c 1
d 1

      {≢⍵}⌸ 'abracadabra'       ⍝ ou encore ⊢∘≢⌸ 'abracadabra'
5 2 2 1 1

Si c’est un heterogramme, toutes ces valeurs doivent être égales à 1 :

      {≢⍵}⌸ ⎕C 'Dermatoglyphics'
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

On va comparer tous les éléments avec 1 puis faire une réduction avec un ET. Ce qui signifie : Est-ce que le premier élément vaut 1 ET est-ce que le second terme vaut 1 etc. Pour cela 2 notations :

      ^/ 1 = ∪{≢⍵}⌸ ⎕C 'aba'      ⍝ Test 1 = ... puis réduction ^/
0

      1 ^.= ∪{≢⍵}⌸ ⎕C 'aba'       ⍝ Ecriture équivalente avec 1 ^.=
0
      1 ^.= ∪{≢⍵}⌸ ⎕C 'Dermatoglyphics'
1

Ce qui donne finalement :

      isogram ← 1 ^.= ∪{≢⍵}⌸ ⎕C

      isogram 'aba'
0
      isogram 'Dermatoglyphics'
1

⍝ ou encore :

      isogram ← 1 ^.= (∪(⊢∘≢⌸ ⎕C))

Pour finir, autre version qui signifie littéralement “mettre le mot en minuscule, compter le nombre de d’occurrences de chaque lettre (sans répétition), prendre la plus grande de ces valeurs et tester si c’est 1“), limpide diront certains 😅

isogram ← 1 = (⌈/ (⊢∘≢ ⌸ ⎕C))