Algo Parcours

Identification

Infoforall

3 - Parcours séquentiel d'un tableau


Nous allons aujourd'hui comment localiser la position d'un élément dans un tableau.

Cela s'appelle une recherche.

Documents de cours : open document ou pdf

1 - Importance de l'algorithmique

Quelques rappels :

(Rappel) A - Algorithme : une définition ?

Définition ALGORITHME : un algorithme est un ensemble fini (au sens "pas infini") d'instructions précises permettant de résoudre une classe de problèmes. L'algorithme doit être écrit de façon suffisamment clair pour lever toute ambiguïté, et être transposable facilement dans un langage de programmation.

Le mot classe est ici important : on doit pouvoir résoudre un type de problèmes et pas un cas unique. Si l'algorithme ne fonctionne que pour UN cas particulier, on ne parlera pas d'algorithme.

De façon plus précise, un algorithme est un ensemble fini d'instructions précises effectuant des calculs et/ou des modifications sur des entrées pour produire une sortie.

ENTREE(S) → Algorithme → SORTIE

Comme vous pouvez le voir, cela ressemble fort à ce qu'on pourrait définir comme une fonction. Ce n'est pas un hasard, nous le verrons en Terminale.

Gérard Berry parle des algorithmes comme étant des méthodes à appliquer sans réflechir, en suivant machinalement le déroulement et les ordres.

On sépare réflexion et exécution.

La réflexion est utilisée par l'informaticien ou l'informaticienne lors de l'élaboration de l'algorithme.

Lors du déroulement de l'algorithme, l'ordinateur exécute. Point.

En réalité, cette définition est floue. Elle comporte beaucoup de non-dits. Nous verrons une définition plus formelle en Terminale.

(Rappel) B - Algorithmique

L'algorithmique consiste à étudier des problèmes en vue de les résoudre à l'aide d'un algorithme le plus efficace possible.

On cherche donc :

  • à estimer la rapidité d'exécution de l'algorithme.
    • Cela revient à connaître le coût de l'algorithme.
  • à vérifier que la réponse éventuelle soit juste.
    • Cette vérification se nomme la preuve de correction.
  • à vérifier qu'il s'arrête bien sur n'importe quelle entrée fournie.
    • Cette vérification se nomme la preuve de terminaison.
(Rappel) C - Bilan des coûts

    Problème facile

    Facile en informatique : peut répondre en un temps raisonnable.

  • Le coût constant
  • Le coût logarithmique
  • Le coût linéaire (lié à n)
  • Le coût quadratique (lié à n2)
  • Problème difficile

    Difficle en informatique : temps de calcul déraisonnable.

  • Le coût exponentiel (lié à xn) : avec un tel coût, on estime que l'algorithme n'est pas exploitable à grande échelle. Il va mettre trop de temps pour répondre.
  • Le coût factoriel (lié à la fonction factorielle n!) : encore moins exploitable.
(Rappel) D - Unités courantes : k M G T

Sous-unité Valeur en décimalPourquoi ?
Un kilo (k) représente 103

1 000

Un Méga (M) représente 106

1 000 000

un million, soit 1000 x 1 000
Un Giga (G) représente 109

1 000 000 000

un milliard, soit 1000 x 1 000 000
Un Téra (T) représente 1012

1 000 000 000 000

mille milliards, soit 1000 x 1 000 000 000

On voit donc que chaque sous-unité représente 1000 fois plus que la précédente.

Passons à la nouveauté :

1.1 Algorithme implémenté sous forme de fonction

Implémentation

L'implémentation est le nom qu'on donne au passage de :

  • l'idée abstraite de l'algorithme (sur papier) à
  • son exécution concrète à l'aide d'un programme (en machine)

Or, vous connaissez déjà un objet informatique qui accepte des valeurs en entrée et qui renvoie une réponse en sortie : les fonctions.

   ⇒   algo MAXIMUM   ⇒ 
[5, 10, 50, 0, 20] 50
 
   ⇒   fonction maximum()   ⇒ 
[5, 10, 50, 0, 20] 50
 
Exemple

L'algorithme du MAXIMUM est ainsi déjà implémenté en Python à travers la fonction native max() :

>>> max([5, 10, 50, 0, 20]) 50
Conclusion

On implémentera les algorithmes sous forme de fonctions :

  • Les ENTREES : les paramètres de la fonction
  • SORTIE : le retour de la fonction

Pourquoi est-il important d'optimiser les algorithmes alors que les machines actuelles sont très puissantes ?

A cause de l'augmentation énorme du volume de données à traiter : un algorithme ne pourra être implémenté en machine que s'il répond en un temps acceptable.

Vous êtes chef de service. On vous propose de choisir entre

  1. Investir dans un équipement informatique Tip Top ou
  2. Investir dans le salaire d'un.e nouvel.le informaticien.ne Tip Top. Quelqu'un qui a pris NSI dès la 1er. Si si.

Alors ? Votre choix ?

Imaginons qu'on veuille identifier quelqu'un dans une base de données comportant n fiches.

Cas 1

  • INFORMATICIEN NORMAL : l'algorithme créé par l'informaticien a besoin de n3 opérations pour identifier la personne dans la base de données qui comporte n fiches.
  • ORDINATEUR TIP-TOP : l'ordinateur est capable de faire 200 Giga comparaisons par seconde.

On peut en déduire le temps mis par l'ordinateur pour identifier les personnes en fonction de la taille de la base de données :

t = (n3) / (200.109)

En Python :

t = n**3 / 200E9

01 ✎° Calculer les temps mis pour réaliser cette tâche si la base de données possède

  • 5000 fiches (n = 5.103),
  • 5 millions de fiches (n = 5.106),
  • 5 milliards de fiches (n = 5.109).

...CORRECTION...

Pour n = 5000 fiches :

  • t = (5E3)**3 / 200E9 = 0,625 s

Pour n = 5 millions de fiches :

  • t = (5E6)**3 / 200E9 = 625000000 s
  • t = (5E6)**3 / (200E9*3600*24*365) = 19.8 ans

Pour n = 5 milliards de fiches :

  • t = (5E9)**3 / 200E9 = 6,25.1017 s
  • t = (5E9)**3 / (200E9*3600*24*365) = 19 818 619 990 ans !

Cas 2

  • INFORMATICIEN TIP-TOP : l'algorithme créé par l'informaticien a besoin de n2 comparaisons pour identifier la personne dans la base de données qui comporte n fiches.
  • ORDINATEUR MEDIOCRE : l'ordinateur est capable de faire 30 Mega comparaisons par seconde...

On peut en déduire le temps mis par l'ordinateur pour identifier les personnes en fonction de la taille de la base de données :

t = (n2) / (30.106)

En Python :

t = n**2 / 30E6

02 ✎° Calculer les temps mis pour réaliser cette tâche si la base de données possède

  • 5000 fiches (5.103),
  • 5 millions de fiches (5.106),
  • 5 milliards de fiches (5.109).

...CORRECTION...

Pour 5000 fiches :

  • t = (5E3)**2 / 30E6 = 0,83 s

Pour 5 millions de fiches :

  • t = (5E6)**2 / 30E6 = 833333 s
  • t = (5E6)**2 / (30E6*3600*24) = 9.6 jours

Pour 5 milliards de fiches :

  • t = (5E9)**2 / 30E6 = 833333333333 s
  • t = (5E9)**2 / (30E6*3600*24*365) = 26 424 ans !

Un ordinateur ultra performant n'a en réalité aucun chance face à un ordinateur moins performant muni d'un programme plus efficace.

L'aspect logiciel et algorithmique est finalement plus fondamental que l'aspect matériel.

Comparaison visuelle des deux cas
Comparaison visuelle des deux cas

Voici un script Python permettant de générer les courbes ci-dessus.

...PYTHON...

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
# 1 - Importation des modules nécessaires import math import matplotlib.pyplot as plt # 2 - Constantes # 3 - Déclaration des fonctions # 4 - Programme principal # Création des tableaux de données valeurs_n = [ n for n in range(10000) ] # Abscisse temps_1 = [ n**3 /200E9 for n in valeurs_n ] # Ordonnée temps_2 = [ n**2 /30E6 for n in valeurs_n ] # Ordonnée xmax = max(valeurs_n ) ymax = max(temps_1 ) # Création des courbes plt.plot(valeurs_n, temps_1, label="Cas 1", color='orange') plt.plot(valeurs_n, temps_2, label="Cas 2", color="red") plt.xlabel("Nbr de fiches") plt.ylabel("Temps d'exécution (s)") plt.title("Comparaison des temps d'exécution") plt.legend(loc='upper center') plt.axis([0, xmax, 0, ymax]) plt.grid() plt.show() # Affichage à l'écran des courbes

Et ça date de quand l'algorithmique ? Des années 2000 ? Des années 1940 ?

1.2 Historique rapide

Certains pensent que l'un des algorithmes les plus anciens est l'algorithme d'Euclide. Cet algorithme permet de trouver le plus grand commun diviseur (PGCD) de deux entiers a et b.

Par exemple, pour a = 15 et b = 20, il s'agit de 5.

En effet,

  • 15 peut se décomposer en 3*5 et
  • 20 peut se décomposer en 4*5.
Euclide
Gravure d'Euclide (depuis https://fr.wikipedia.org/wiki/Euclide#/media/Fichier:Euklid-von-Alexandria_1.jpg) - Domaine Public

Un algorithme efficace permettant de trouver automatiquement ce PGCD date de -300 et se trouve dans les écrits du mathématicien Euclide d'Alexandrie. Il a été découvert de façon autonome en Inde et en Chine quelques siècles plus tard.

Vers 800, Muhammad Ibn Mūsā al-Khuwārizmī dit Al-Khwarizmi, mathématicien, né dans dans l'actuel Ouzbékistan et mort à Badgad, publie de nombreux textes. Les traductions permettront l'introduction de l'algèbre en Europe. Il a notamment répertorié et classifié de nombreux algorithmes (créés par d'autres). Il y montre leurs terminaisons (le fait qu'ils s'arrêtent bien à tous les coups) ou pas.

Al-Khwarizmi
Timbre Al-Khwarizmi - Domaine Public

Son nom est bien entendu à l'origine d'algorithme.

Depuis, bien des gens ont travaillé et travaillent toujours pour trouver, créer ou améliorer les algorithmes.

L'un des grands noms de l'algorithmique est Donald Knuth, informaticien et mathématicien américain de renom. Il est un des pionniers de l'algorithmique et a fait de nombreuses contributions dans plusieurs branches de l'informatique théorique (fondements logiques et mathématiques de l'informatique). A tel point, qu'on le nomme parfois le père de l'analyse algorithmique.

https://fr.wikipedia.org/wiki/Donald_Knuth#/media/Fichier:KnuthAtOpenContentAlliance.jpg
Donald Knuth - CC BY-SA 2.5

Pour en savoir plus sur son oeuvre 

The Art of Computer Programming

L'algorithmique est donc un domaine d'études très riche en recherches passées et futures. Si vous aimez les maths, l'informatique et les casses-têtes, c'est un domaine fait pour vous !

Si vous voulez vous amuser, n'hésitez pas aller consulter l'excellent site de France-IOI.

2 - Recherche d'un élément dans un tableau

Nous allons maintenant tenter de réaliser un algorithme qui signale si un élément existe dans un tableau (et renvoie son indice) ou renvoie -1 sinon.

Cette méthode se nomme méthode par balayage ou recherche linéaire.

En anglais, on parle de linear search.

Vous allez comprendre pourquoi en utilisant l'animation suivante : créer un tableau avec l'un des boutons aléatoires, choisir une valeur à chercher et appuyer sur le bouton lançant l'animation.

Valeur cherchée :

Indice i 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19
Case t[i] 60 89 15 56 82 1 86 97 24 86 75 6 14 69 98 31 37 89 91 56
2.1 Principe de la recherche linéaire

Principe général

BUT : trouver la position de la première occurrence de l'élément x recherché dans un tableau t.

PRINCIPE (en 4 étapes) :

  1. On commence par la case d'indice 0
  2. TANT QUE la case est valide ET ne contient pas le contenu cherché, on passe à la case suivante
  3. SI la valeur finale de l'indice est valide : la réponse est bien l'indice. SINON, la réponse est -1 car on a atteint la fin du tableau sans trouver.
  4. On RENVOIE la réponse
Exemples

Prenons t = [13, 18, 89, 1024, 45, -12, 18]

Indice i 0 1 2 3 4 5 6
Case t[i] 13 18 89 1024 45 -12 18

Voici trois exemples de réponses attendues :

Recherche de 89 (présent une fois) :

t = [13, 18, 89, 1024, 45, -12, 18]  ⇒   RECHERCHER   ⇒ 
et 2
x = 89

Recherche de 18 (présent deux fois) :

t = [13, 18, 89, 1024, 45, -12, 18]  ⇒   RECHERCHER   ⇒ 
et 1
x = 18

Recherche de 100 (non présent) :

t = [13, 18, 89, 1024, 45, -12, 18]  ⇒   RECHERCHER   ⇒ 
et -1
x = 100
2.2 Algorithme de la recherche linéaire

Description des entrées/sortie
  • NOM : RECHERCHE_LINEAIRE
  • ENTREES :
    • t : un tableau contenant n éléments.
    • x : l'élément qu'on recherche dans le tableau
    • (optionnelle selon les langages d'implémentation) longueur, le nombre d'éléments dans le tableau t
  • SORTIE : un entier valant
    • soit l'indice où se trouve la première (et peut-être) seule occurrence de l'élément cherché.
    • une réponse voulant dire que l'élément x n'est présent dans le tableau. VIDE ou -1
Description de l'algorithme

Ci-dessous, on fait le choix de répondre -1 en cas de valeur non trouvée.

    i0

    TANT QUE i < longueur ET que t[i] est différent de x

      ii + 1

    Fin TANT QUE

    SI i < longueur

      reponsei

    SINON

      reponse-1

    Fin Si

    Renvoyer reponse

03° Utiliser les instructions suivantes pour revoir comment fonctionne un tableau :

>>> t = [5, 10, 7] >>> len(t) ??? >>> t[0] ??? >>> t[1] ??? >>> t[2] ??? >>> t[3] ???

...CORRECTION...

>>> t = [5, 10, 7] >>> len(t) 3 >>> t[0] 5 >>> t[1] 10 >>> t[2] 7 >>> t[3] ElémentError: list index out of range
FONDAMENTAL POUR COMPRENDRE LA SUITE

On rappelle une évidence super-importante :

  • i veut dire : l'indice, le numéro de la case
  • t[i] veut dire : le contenu de la case i du tableau t

04° Quel est le seul cas où un ET logique a ET b répond VRAI ?

...CORRECTION...

Un ET logique est VRAI si et seulement si toutes les conditions sont évaluées à VRAI.

Il faut donc que a soit VRAI et que b soit VRAI.

Cela veut dire que dès qu'une condition est fausse, le ET est évalué à FAUX.

05° Voici la ligne du TANT QUE :

TANT QUE i < longueur ET que t[i] est différent de x

Question 1 : trouver à quelle expression du TANT QUE correspondent respectivement les deux phrases suivantes :

  • "Cette case existe"
  • ET
  • "La case ne contient pas la valeur qu'on recherche".

Question 2 : s'agit-il des conditions de poursuite de la boucle TANT QUE ou des conditions d'arrêt ?

...CORRECTION...

  • "Cette case existe" équivalent à i < longueur
  • ET
  • "La case ne contient pas la valeur qu'on recherche" équivalent à t[i] est différent de x.

L'ensemble des deux conditions forme la condition de poursuite de la boucle puisqu'il faut que les deux sous-conditions soient à VRAI pour continuer.

06 ✎° Deux questions.

Question 1 : Ecrire le déroulé de l'algorithme s’il travaille sur le tableau ci-dessous en recherchant 1024. Vous devriez trouver qu'il renvoie 3.

t[13, 18, 89, 1024, 45, -12, 18]

Voici le début d'une rédaction possible (avec en gris des indications supplémentaires qu'il est inutile de marquer sur votre feuille de cours) :

i vaut 0.

longueur vaut 7 car le tableau a 7 cases numérotées de 0 à 6.

x vaut 1024 car c'est ce qu'on cherche.

Début du TQ ? Oui car 0 < 7 et t[0] est différent de 1024. Donc i ← 1

Poursuite du TQ ? Oui car 1 < 7 et t[1] est différent de 1024. Donc ...

Question 2 : Quelle est la condition évaluée à FAUX qui a provoqué l'arrêt du TANT QUE :

  • la valeur de la variable indice i devenue trop grande ou
  • le contenu qui correspond à la valeur voulue ?

...CORRECTION...

t[13, 18, 89, 1024, 45, -12, 18]

i vaut 0.

longueur vaut 7.

x vaut 1024.

Début du TQ ? Oui car 0 < 7 et t[0] est différent de 1024. Donc i ← 1

Poursuite du TQ ? Oui car 1 < 7 et t[1] est différent de 1024. Donc i ← 2

Poursuite du TQ ? Oui car 2 < 7 et t[2] est différent de 1024. Donc i ← 3

Poursuite du TQ ? Non car la condition t[3] différent de 1024 est évaluée à FAUX !

Arrêt de la boucle TANT QUE.

Puisque i < 7, on renvoie sa valeur, à savoir 3.

Ici, on s'arrête donc puisqu'on a trouvé la valeur 1024 voulue dans le tableau.

07° Appliquer l'algorithme à la main avec une recherche sur 1025. Vous devriez trouver qu'il renvoie -1.

Quelle est la condition évaluée à FAUX qui a provoqué l'arrêt du TANT QUE :

  • la valeur de la variable indice i devenue trop grande ou
  • le contenu qui correspond à la valeur voulue ?

...CORRECTION...

t[13, 18, 89, 1024, 45, -12, 18]

i vaut 0.

longueur vaut 7.

x vaut 1025.

Début du TQ ? Oui car 0 < 7 et t[0] est différent de 1025. Donc i ← 1

Poursuite du TQ ? Oui car 1 < 7 et t[1] est différent de 1025. Donc i ← 2

Poursuite du TQ ? Oui car 2 < 7 et t[2] est différent de 1025. Donc i ← 3

Poursuite du TQ ? Oui car 3 < 7 et t[3] est différent de 1025. Donc i ← 4

Poursuite du TQ ? Oui car 4 < 7 et t[4] est différent de 1025. Donc i ← 5

Poursuite du TQ ? Oui car 5 < 7 et que t[5] est différent de 1025. Donc i ← 6

Poursuite du TQ ? Oui car 6 < 7 et que t[6] est différent de 1025. Donc i ← 7

Poursuite du TQ ? Non car la condition 7 < 7 est évaluée à FAUX !

Arrêt de la boucle TANT QUE.

Puisque i < 7 est FAUX, on renvoie -1.

Ici, on s'arrête donc puisqu'on a atteint une valeur impossible pour l'indice.

08° 18 apparaît deux fois dans le tableau. La valeur renvoyée par cet algorithme sur une recherche de la première occurrence sur 18 donne :

  • A : 0
  • B : 1
  • C : 6
  • D : (1,6)

Rappel du contenu

Indice i 0 1 2 3 4 5 6
Case t[i] 13 18 89 1024 45 -12 18

...CORRECTION...

On lit le contenu progressivement de façon séquentielle.

En lisant l'algorithme, on voit qu'on renvoie la première occurrence trouvée, soit 1.

3 - Implémentation Python

09° Compléter la fonction Python rechercher() pour qu'elle soit une implémentation correcte de notre algorithme de recherche.

Le prototype de la fonction (remarquez bien que cela ressemble pas mal à notre schéma ENTREES/SORTIE)

1
def rechercher(t:list, x:'Element') -> int:

La fonction à compléter (avec une documentation 'complète' plutôt que la version 'rapide') :

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
def rechercher(t, x): """Renvoie l'indice de la première occurrence de x dans t, -1 si non présent :: param t(list) :: un tableau contenant le même type d'éléments que le paramètre x :: param x(au choix) :: l'élément qu'on cherche à trouver dans le tableau :: return (int) :: l'indice de la case ou -1 :: exemples :: >>> test = [10,20,30,40,50] >>> rechercher(test, 20) 1 >>> rechercher(test, 60) -1 """ pass tab_test = [13, 18, 89, 1024, 45, -12, 18] a = rechercher(tab_test, 18) b = rechercher(tab_test, 1024) c = rechercher(tab_test, 1025)

Comme vous pouvez le voir, sous la fonction à réaliser, on a rajouté des instructions pour créer un tableau et mémoriser quelques appels dans des variables. Vous pourrez visualiser le résultat pour voir si votre fonction répond correctement.

...CORRECTION...

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
def rechercher(t, x): """Renvoie l'indice de la première occurrence de x dans t, -1 si non présent :: param t(list) :: un tableau contenant le même type d'éléments que le paramètre x :: param x(au choix) :: l'élément qu'on cherche à trouver dans le tableau :: return (int) :: l'indice de la case ou -1 :: exemples :: >>> test = [10,20,30,40,50] >>> rechercher(test, 20) 1 >>> rechercher(test, 60) -1 """ i = 0 longueur = len(t) while i < longueur and t[i] != x: i = i + 1 if i < longueur: reponse = i else: reponse = -1 return reponse tab_test = [13, 18, 89, 1024, 45, -12, 18] a = rechercher(tab_test, 18) b = rechercher(tab_test, 1024) c = rechercher(tab_test, 1025)

10 ✎° Rajouter un commentaire derrière chaque instruction pour traduire en français le rôle de cette ligne.

Si vous êtes en classe, demander le document-réponse de cette question.

...CORRECTION...

1 2 3 4 5 6 7 8 9 10 11
def rechercher(t:list, x:int) -> int: """Renvoie l'indice de la première occurrence de x dans t, -1 si non présent""" i = 0 # On se place sur la première case longueur = len(t) # On mémorise le nb de cases while i < longueur and t[i] != x: # Tant qu'il reste des cases et que la case actuelle n'est pas la bonne i = i + 1 # On passe à la case suivante if i < longueur: # Si i obtenu est une valeur possible pour ce tableau reponse = i else: # Sinon, c'est qu'on est sorti du tableau reponse = -1 return reponse # Renvoie la réponse
RAPPEL : phase d'initialisation du TANT QUE

Attention à l'une des erreurs typiques lors de l'utilisation d'une boucle non bornée TANT QUE : le fait d'oublier de donner une valeur par défaut permettant de rentrer dans la boucle à la variable qui gère la boucle.

11° Réaliser une modification de rechercher() en utilisant plutôt une boucle for associée à un return : dès qu'on connaît la réponse, on répond. On ne renvoie -1 qu'après avoir effectué toute la boucle : si on arrive à cette ligne, c'est qu'on a pas rencontré l'élément recherché avant.

Le prototype de la fonction reste le même :

1
def rechercher(t:list, x:'Element') -> int:

...CORRECTION...

1 2 3 4 5 6 7 8 9 10 11 12
def rechercher(t, x): """Renvoie l'indice de la première occurrence de x dans t, -1 si non présent for i in range( len(t) ): if t[i] == x: return i return -1 tab_test = [13, 18, 89, 1024, 45, -12, 18] a = rechercher(tab_test, 18) b = rechercher(tab_test, 1024) c = rechercher(tab_test, 1025)

12° Au niveau algorithmique, quelle est la version la plus rigoureuse : celle avec le while ou celle avec for+return ?

...CORRECTION...

Sur le principe de la recherche, on doit interrompre la recherche dès qu'on trouve le bon élément. Il s'agit donc d'une boucle non bornée.

Donc, le while est le plus adapté.

13° Au niveau programmation, quelle est la version la plus lisible : celle avec le while ou celle avec for+return ?

...CORRECTION...

Cette fois, la version avec le for est plus lisible : pas besoin de noter l'initialisation, l'incrémentation et de tester s'il reste encore une case ou non.

Le problème des boucles non bornées ? On peut créer sans le vouloir une boucle infinie ! Dans tout programme sérieux, on vérifiera donc la terminaison de la boucle, c'est à dire le fait qu'on finisse nécessairement par sortir de la boucle au bout d'un moment.

Pour les plus rapides°  : fournir une fonction de recherche un peu différent : cette fois, on veut renvoyer l'indice de la dernière occurrence dans le tableau.

Recherche de 18 (présent deux fois) :

t = [13, 18, 89, 1024, 45, -12, 18]  ⇒   RECHERCHER   ⇒ 
et 6
x = 18

Il faudra vous demander s'il faut utiliser une boucle bornée ou non bornée, comment parvenir à stocker les réponses successives...

Il a plusieurs façons de faire cela.

4 - Exemples de recherche

La compréhension totale de cette partie va nécessiter de faire le complément Python sur la construction par compréhension (la partie 5 ci-dessous)

14° Utiliser le programme ci-dessous. Il génére un tableau t de n nombres compris entre 10 et 20, génère un nombre x au hasard puis utilise la fonction rechercher(). On affiche le résultat obtenu.

Lancer 5 fois le programme et vérifier qu'il donne la bonne réponse à chaque fois.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
import random def rechercher(t:list, x:int) -> int: """Renvoie l'indice de la première occurrence de x dans t, -1 si non présent""" i = 0 # On se place sur la première case longueur = len(t) # On mémorise le nb de cases while i < longueur and t[i] != x: # Tant qu'il reste des cases et que la case actuelle n'est pas la bonne i = i + 1 # On passe à la case suivante if i < longueur: # Si i obtenu est une valeur possible pour ce tableau reponse = i else: # Sinon, c'est qu'on est sorti du tableau reponse = -1 return reponse # Renvoie la réponse n = random.randint(5, 10) # nombre de cases entre 5 et 10 t = [random.randint(1, 20) for x in range(n)] # le tableau de cases aléatoires x = random.randint(0, 30) # génération du nombre qu'on cherche indice = rechercher(t, x) # récupération de l'indice print(t) print(f"On cherche {x} : réponse {indice}")

Malheureusement, tester (même 1 million de fois) ne permet pas d'être certain que la réponse est bonne.

15° Même si nous savons que vérifier ne veut pas dire démontrer, ce programme va vous permettre de lancer en boucle autant de tests que vous le désirez. Si l'un d'entre eux est faux, le programme doit stopper immédiatement.

Comment aller vous procéder :

  • Si c'est -1, on vérifie que l'élément n'appartient pas au tableau
  • Si on récupère un indice valide, il faudra tester deux choses :
    • l'élément se trouve bien à cette position i et
    • aucun indice plus petit ne contient cet élément.

Tâches à accomplir :

  • Placer ce nouveau programme en mémoire
  • Lire le prototype de la fonction pour comprendre ce que doit faire cette fonction
  • Compléter la fonction ("code à trou")
  • Tester que votre fonction semble bien fonctionner
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
import random def rechercher(t:list, x:int) -> int: """Renvoie l'indice de la première occurrence de x dans t, -1 si non présent""" i = 0 # On se place sur la première case longueur = len(t) # On mémorise le nb de cases while i < longueur and t[i] != x: # Tant qu'il reste des cases et que la case actuelle n'est pas la bonne i = i + 1 # On passe à la case suivante if i < longueur: # Si i obtenu est une valeur possible pour ce tableau reponse = i else: # Sinon, c'est qu'on est sorti du tableau reponse = -1 return reponse # Renvoie la réponse def tester(): for k in range(1000): n = random.randint(3, 10) t = [random.randint(10, 20) for x in range(n)] x = random.randint(8, 22) indice = rechercher(t, x) print(f"Test {k+1}") if indice == ... and ... in ...: # Si on a -1 alors que x est présent print("ECHEC DU TEST") print(t) print(f"On cherche {x} : réponse {indice}") return ... else: for i in range(0, indice): # Pour chaque indice j inférieur à indice if t[...] == ...: # Si on trouve x dans la case j return ... print("OK AVEC LES 1000 TESTS AUTOMATIQUES") return ... # si on arrive ici, c'est que tous les tests sont bons tester()

...CORRECTION...

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
import random def rechercher(t:list, x:int) -> int: """Renvoie l'indice de la première occurrence de x dans t, -1 si non présent""" i = 0 # On se place sur la première case longueur = len(t) # On mémorise le nb de cases while i < longueur and t[i] != x: # Tant qu'il reste des cases et que la case actuelle n'est pas la bonne i = i + 1 # On passe à la case suivante if i < longueur: # Si i obtenu est une valeur possible pour ce tableau reponse = i else: # Sinon, c'est qu'on est sorti du tableau reponse = -1 return reponse # Renvoie la réponse def tester(): for k in range(1000): n = random.randint(3, 10) t = [random.randint(10, 20) for x in range(n)] x = random.randint(8, 22) indice = rechercher(t, x) print(f"Test {k+1}") if indice == -1 and x in t: # Si on a -1 alors que x est présent print("ECHEC DU TEST") print(t) print(f"On cherche {x} : réponse {indice}") return False else: for i in range(0, indice): # Pour chaque indice j inférieur à indice if t[i] == x: # Si on trouve x dans la case j return False print("OK AVEC LES 1000 TESTS AUTOMATIQUES") return True # si on arrive ici, c'est que tous les tests sont bons tester()

5 - [Complément Python] tableau par compréhension

Nous allons utiliser une nouvelle façon de créer des tableaux.

Jusqu'à présent, vous ne savez créer des tableaux qu'en extension : on fournit directement dans les crochets les valeurs voulues. Pour créer un tableau contenant les entiers de 1 à 3000, ca risque d'être long à taper...

5.1 Création par compréhension à partir d'un autre tableau

A - Principe

La déclaration d'un tableau par compréhension consiste à créer un tableau à l'aide d'une boucle for. Créons un nouveau tableau nt.

nt = [None for element in base]

Comme doit-on comprendre cela ? Suivez bien ce qui suit.

  1. On déclare un nouveau tableau nt :
  2. nt = [...]

  3. Cette déclaration contient une boucle, il s'agit d'une déclaration par compréhension :
  4. nt = [... for element in base]

    On dit que pour chaque element lu dans le tableau base, il faut faire quelque chose.

  5. On indique de rajouter à notre nouveau tableau une nouvelle case contenant None pour chaque case de l'autre tableau.
  6. nt = [None for element in base]

    En tapant ceci, voici ce que peut comprendre l'interpréteur Python : "Crée un nouveau tableau initialement vide nommé nt. Pour chaque case du tableau base, crée une case contenant None dans nt.

B - Exemple 1
>>> base = [1, 2, 5] >>> nt = [None for valeur in base] >>> nt [None, None, None]

On obtient bien un nouveau tableau ayant autant de cases mais rempli de None.

Exemple 2

On peut mettre autre chose que None dans chaque case : on peut indiquer un calcul expliquant ce que doit être le contenu de la nouvelle case.

dd = [v * 2 for v in b]

L'interpréteur Python comprend qu'on veut créer un nouveau tableau dd où chaque case correspond au double de ce qui est contenu dans la case correspondante du tableau b.

>>> b = [1, 2, 5] >>> dd = [v*2 for v in b] >>> dd [2, 4, 10]

16° Créer (par compréhension) un tableau qui contient des valeurs 100 fois plus grandes que le tableau suivant :

>>> t1 = [1, 2, 3, 5, 7, 11]

...CORRECTION...

>>> t1 = [1, 2, 3, 5, 7, 11] >>> dd = [v*100 for v in t1] >>> dd [100, 200, 300, 500, 700, 1100]

17° Créer, par compréhension, un tableau longueurs dont les cases contiennent le nombre de lettres des prénoms contenus dans le tableau prenoms :

>>> prenoms = ["Lisa", "Scott", "Matthias", "Antoine", "Ethan", "Lucas", "Manon", "Alexandre", "Alexandre", "Kenzo", "Thomas", "Lilou", "Aurélien", "Charles", "Manon", "Francia", "Imrane", "Sarah", "Yassin", "Sofian", "Noé", "Lenny", "Matt", "Ryiad", "Yanis", "Jason", "Damien", "Antonin", "Samy", "Laurine", "Rayan", "Eliot", "Victor", "Theo", "Julien", "Benjamin"]

Vous devriez pouvoir visualiser ceci dans la console après exécution de votre création par compréhension :

>>> longueurs [4, 5, 8, 7, 5, 5, 5, 9, 9, 5, 6, 5, 8, 7, 5, 7, 6, 5, 6, 6, 3, 5, 4, 5, 5, 5, 6, 7, 4, 7, 5, 5, 6, 4, 6, 8]

...CORRECTION...

>>> prenoms = ["Lisa", "Scott", "Matthias", "Antoine", "Ethan", "Lucas", "Manon", "Alexandre", "Alexandre", "Kenzo", "Thomas", "Lilou", "Aurélien", "Charles", "Manon", "Francia", "Imrane", "Sarah", "Yassin", "Sofian", "Noé", "Lenny", "Matt", "Ryiad", "Yanis", "Jason", "Damien", "Antonin", "Samy", "Laurine", "Rayan", "Eliot", "Victor", "Theo", "Julien", "Benjamin"] >>> longueurs = [len(p) for p in prenoms] >>> longueurs [4, 5, 8, 7, 5, 5, 5, 9, 9, 5, 6, 5, 8, 7, 5, 7, 6, 5, 6, 6, 3, 5, 4, 5, 5, 5, 6, 7, 4, 7, 5, 5, 6, 4, 6, 8]
5.2 Création par compréhension sans tableau initial

A - Principe

On peut créer des tableaux contenant un nombre précis d'élements en utilisant range().

B - Exemple 1
>>> t = [0 for i in range(10)] >>> t [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

On place 0 dans une case pour chaque des valeurs i (qui vaut 0 puis 1 puis 2 ... jusqu'à 9). Donc 10 cases.

C - Exemple 2
>>> t = [i*10 for i in range(10)] >>> t [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]

La variable de boucle i varie de 0 à 9, et on place 10 fois plus dans la case correspondante du nouveau tableau.

D - Exemple 3
>>> import random >>> tableau = [random.randint(1, 100) for i in range(10)] >>> tableau [42, 41, 96, 35, 58, 94, 16, 21, 84, 51]

On crée 10 cases dans lesquelles on place un nombre aléatoire entre 1 et 100.

18° Créer par compréhension un tableau de 100 éléments contenant des nombres aléatoires compris entre 20 et 50.

...CORRECTION...

>>> import random >>> tableau = [random.randint(2, 50) for x in range(100)]

19° Créer par compréhension un tableau de 500 éléments contenant 0.

...CORRECTION...

>>> tableau = [0 for _ in range(500)]

On peut même faire mieux : on peut inclure une instructions conditionnelles ou même un appel de fonction dans la construction.

5.3 Création par compréhension avec condition

A - Principe

On peut rajouter une expression booléenne à la toute fin de la déclaration.

B - avec condition d'ajout

On crée un tableau de 50 notes comprises entre 0 et 20. On crée un second tableau à partir du premier : on ne garde que les notes supérieures ou égales à 10.

>>> import random >>> t = [random.randint(0,20) for i in range(50)] >>> t [12, 8, 1, 3, 3, 17, 8, 2, 2, 10, 15, 3, 16, 13, 5, 18, 12, 15, 19, 10, 9, 14, 0, 15, 11, 6, 11, 1, 10, 6, 17, 3, 17, 10, 9, 8, 1, 9, 7, 12, 15, 10, 15, 15, 9, 8, 18, 8, 9, 6] >>> t2 = [note for note in t if note >= 10] >>> t2 [12, 17, 10, 15, 16, 13, 18, 12, 15, 19, 10, 14, 15, 11, 11, 10, 17, 17, 10, 12, 15, 10, 15, 15, 18]
C - avec une fonction

La fonction détermine la valeur à placer lors de la création par compréhension. Imaginons qu'on dispose de cette fonction en mémoire :

1 2 3 4 5
def pas_plus(valeur, seuil): """Fonction qui écrête : elle renvoie la valeur ou le seuil si la valeur est supérieure au seuil""" if valeur > seuil: return seuil return valeur
>>> import random >>> tableau = [pas_plus(random.randint(0,20), 15) for valeur in range(10)] >>> tableau [14, 15, 7, 14, 11, 1, 13, 12, 15, 1]

On obtient bien un tableau de 10 éléments sans qu'aucun élément ne soit plus grand que 15.

20° Créer un tableau p6 dont les cases contiennent les prénoms de 6 caractères ou plus contenus dans le tableau prenoms :

>>> prenoms = ["Lisa", "Scott", "Matthias", "Antoine", "Ethan", "Lucas", "Manon", "Alexandre", "Alexandre", "Kenzo", "Thomas", "Lilou", "Aurélien", "Charles", "Manon", "Francia", "Imrane", "Sarah", "Yassin", "Sofian", "Noé", "Lenny", "Matt", "Ryiad", "Yanis", "Jason", "Damien", "Antonin", "Samy", "Laurine", "Rayan", "Eliot", "Victor", "Theo", "Julien", "Benjamin"]

Vous devriez pouvoir visualiser ceci dans la console après exécution de votre création par compréhension :

>>> p6 ['Matthias', 'Antoine', 'Alexandre', 'Alexandre', 'Thomas', 'Aurélien', 'Charles', 'Francia', 'Imrane', 'Yassin', 'Sofian', 'Damien', 'Antonin', 'Laurine', 'Victor', 'Julien', 'Benjamin']

...CORRECTION...

>>> prenoms = ["Lisa", "Scott", "Matthias", "Antoine", "Ethan", "Lucas", "Manon", "Alexandre", "Alexandre", "Kenzo", "Thomas", "Lilou", "Aurélien", "Charles", "Manon", "Francia", "Imrane", "Sarah", "Yassin", "Sofian", "Noé", "Lenny", "Matt", "Ryiad", "Yanis", "Jason", "Damien", "Antonin", "Samy", "Laurine", "Rayan", "Eliot", "Victor", "Theo", "Julien", "Benjamin"] >>> p6 = [p for p in prenoms if len(p) >= 6] >>> p6 ['Matthias', 'Antoine', 'Alexandre', 'Alexandre', 'Thomas', 'Aurélien', 'Charles', 'Francia', 'Imrane', 'Yassin', 'Sofian', 'Damien', 'Antonin', 'Laurine', 'Victor', 'Julien', 'Benjamin']

6 - FAQ

Chercher, c'est courant... Python ne sait pas faire ça tout seul ?

Si, il existe des fonctions ou méthodes de Python qui permettent de le faire. Mais, comment fonctionnent-elles ?

En NSI, nous cherchons à comprendre et construire, pas simplement utiliser.

Maintenant, vous savez comment cela fonctionne, on peut vous montrer comment le faire en quelques lignes.

>>> t = [40, 70, 30, 20, 30] >>> t.index(40) 0 >>> t.index(30) 2 >>> t.index(80) Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: 80 is not in list

7 -

Dans la prochaine activité, nous allons analyser cet algorithme et voir ce que les mathématiques nous permettent d'en dire.

Activité publiée le 02 02 2020
Dernière modification : 12 12 2021
Auteur : ows. h.