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