Site WWW de Laurent Bloch
Slogan du site

ISSN 2271-3905
Cliquez ici si vous voulez visiter mon autre site, orienté vers des sujets moins techniques.

Pour recevoir (au plus une fois par semaine) les nouveautés de ce site, indiquez ici votre adresse électronique :

L’IA remplacera-t-elle les programmeurs ?
Article mis en ligne le 17 septembre 2025
dernière modification le 19 septembre 2025

par Laurent Bloch

Dans un article précédent je rendais compte d’un étude menée scientifiquement pour évaluer l’aide que pourrait apporter, ou pas, l’IA au travail de 16 développeurs expérimentés pour résoudre 246 problèmes de maintenance relatifs aux projets dont ils étaient familiers depuis des années : la conclusion était que l’IA leur faisait plutôt perdre du temps.

Si la consommation électrique induite par l’IA vous inquiète, vous pouvez lire cet article de Pierre Beyssac (ingénieur SupElec).

Ici je vais décrire de façon beaucoup plus empirique mon expérience avec l’IA (ChatGPT en l’occurrence) pour m’initier à un langage de programmation avec lequel je débute, TypeScript. TypeScript est une couche objet ajoutée à JavaScript par des ingénieurs de Microsoft (sous la direction d’Anders Hejlsberg) pour en rendre la programmation plus robuste et plus lisible (ce sont des euphémismes). Soyez charitables pour les faiblesses que j’exhibe ! En tout cas ChatGPT m’a bien aidé : c’est comme travailler sous les yeux d’un collègue plus expérimenté qui aperçoit tout de suite les erreurs grossières de débutant.

Première question

// Bonsoir ChatGPT,

// Voici, ci-dessous, un code TypeScript correct, constitué de deux classes et d’un certain nombre de méthodes. Peux-tu répartir les classes et les méthodes dans autant de NameSpaces, puis placer chaque NameSpace dans un fichier, tels que le compilateur tsc puisse assembler le code et le soumettre à la machine JavaScript ?

// Une classe pour le type nœud : N

class N<T> {
  // Valeur du nœud
  public data: T;
  // Référence du nœud suivant
  public next: N<T> | null = null;

  // Création d'un nœud avec la valeur donnée
  constructor(value: T) {
    this.data = value;
  }
}

// Une classe pour la liste :

export class ListeSimple<T> {
    // Nœud de tête :
  public head: N<T> | null = null;

    // Ajouter un nœud en tête :
    prepend(data: T): void {
        const newNode = new N(data); // crée un nouveau nœud, valeur = data
        newNode.next = this.head; // le pointeur next du nouveau nœud pointe sur l'actuel nœud de tête
        this.head = newNode; // Désormais le nœud de tête est le nouveau nœud
    }

    // Ajouter un nœud à la fin :
    append(data: T): void {
        const newNode = new N(data);

        if (!this.head) { // Si personne en tête : liste vide, le nouveau vient en tête
            this.head = newNode;
            return;
        }

        let current = this.head;

        while (current.next) { // Parcourir la liste jusqu'à trouver le dernier nœud,
            current = current.next; // reconnaissable à ce que son pointeur next vaut
        }                    // null

        current.next = newNode; // Et alors le nouveau vient en queue
    }

    // Retirer le nœud de tête :
    deleteHead(): void {
        if (this.head) {
            this.head = this.head.next; // Le second prend la place
        }
    }

    // Retirer le nœud de fin :
    deleteTail(): void {
        if (!this.head) return; // Si liste vide retour

        if (!this.head.next) {
            this.head = null; // Si un seul élément on l'oublie
            return;
        }

        let current = this.head;

        while (current.next && current.next.next) { // On parcourt
            current = current.next; // la liste jusqu'au dernier
        }
        current.next = null; // Quand on l'a trouvé on l'oublie
    }

    // Retirer le premier nœud d'une valeur donnée :
    delete(data: T): void {
        if (!this.head) return; // Si liste vide, adios

        if (this.head.data === data) { // Si le premier est le bon
            this.head = this.head.next; // on l'oublie
            return;
        }

        let current = this.head;

        while (current.next) { // Sinon on parcourt la liste
            if (current.next.data === data) { // jusqu'à trouver
                current.next = current.next.next; // si bon on supprime
                return;
            }
            current = current.next; // Tant que pas trouvé, on parcourt
        }
    }

    // Trouver un nœud d'une valeur donnée :
    find(data: T): N<T> | null {
        if (!this.head) return null;

        let current: N<T> | null = this.head;

        while (current) {
            if (current.data === data) return current;
            current = current.next;
        }

        return null; // Si pas trouvé
    }

  // Liste de toutes les valeurs
    traverse(): void {
        let current = this.head;
        while (current) {
            console.log(current.data);
            current = current.next;
        }
    }

    // Insérer un nœud à une position donnée :
//  insertAt(pos: number, data: T): void {}
}

const uneListePersonnages = new ListeSimple ;
uneListePersonnages.prepend("Tintin") ;
uneListePersonnages.prepend("Haddock") ;
uneListePersonnages.prepend("Milou") ;
console.log(uneListePersonnages.traverse()) ;

Réponse :

Parfait 👍 Ton code est bien structuré. Pour répondre à ta demande, je vais te montrer comment répartir les classes dans des namespaces, puis les mettre chacune dans un fichier séparé afin que tsc puisse assembler le tout correctement.

1. Organisation proposée

On va séparer en deux fichiers principaux :

N.ts → contient le namespace NodeSpace avec la classe N

ListeSimple.ts → contient le namespace ListeSpace avec la classe ListeSimple (qui dépend de N)

Enfin, on aura un fichier main.ts pour tester l’ensemble.

2. Fichier : N.ts

namespace NodeSpace {
  export class N<T> {
    // Valeur du nœud
    public data: T;
    // Référence du nœud suivant
    public next: N<T> | null = null;

    // Création d'un nœud avec la valeur donnée
    constructor(value: T) {
      this.data = value;
    }
  }
}

3. Fichier : ListeSimple.ts

/// <reference path="./N.ts" />

namespace ListeSpace {
  export class ListeSimple<T> {
    // Nœud de tête :
    public head: NodeSpace.N<T> | null = null;

    // Ajouter un nœud en tête :
    prepend(data: T): void {
      const newNode = new NodeSpace.N(data);
      newNode.next = this.head;
      this.head = newNode;
    }

    // Ajouter un nœud à la fin :
    append(data: T): void {
      const newNode = new NodeSpace.N(data);

      if (!this.head) {
        this.head = newNode;
        return;
      }

      let current = this.head;
      while (current.next) {
        current = current.next;
      }
      current.next = newNode;
    }

    // Retirer le nœud de tête :
    deleteHead(): void {
      if (this.head) {
        this.head = this.head.next;
      }
    }

    // Retirer le nœud de fin :
    deleteTail(): void {
      if (!this.head) return;

      if (!this.head.next) {
        this.head = null;
        return;
      }

      let current = this.head;
      while (current.next && current.next.next) {
        current = current.next;
      }
      current.next = null;
    }

    // Retirer le premier nœud d'une valeur donnée :
    delete(data: T): void {
      if (!this.head) return;

      if (this.head.data === data) {
        this.head = this.head.next;
        return;
      }

      let current = this.head;
      while (current.next) {
        if (current.next.data === data) {
          current.next = current.next.next;
          return;
        }
        current = current.next;
      }
    }

    // Trouver un nœud d'une valeur donnée :
    find(data: T): NodeSpace.N<T> | null {
      if (!this.head) return null;

      let current: NodeSpace.N<T> | null = this.head;
      while (current) {
        if (current.data === data) return current;
        current = current.next;
      }
      return null;
    }

    // Liste de toutes les valeurs
    traverse(): void {
      let current = this.head;
      while (current) {
        console.log(current.data);
        current = current.next;
      }
    }
  }
}

4. Fichier : main.ts

/// <reference path="./N.ts" />
/// <reference path="./ListeSimple.ts" />

const uneListePersonnages = new ListeSpace.ListeSimple<string>();

uneListePersonnages.prepend("Tintin");
uneListePersonnages.prepend("Haddock");
uneListePersonnages.prepend("Milou");

uneListePersonnages.traverse();

5. Compilation avec tsc

Exécute :

tsc —outFile dist/app.js main.ts

⚡ Ici on utilise —outFile car on travaille avec des namespaces (et pas avec des modules ES6 import/export). Le compilateur va assembler N.ts, ListeSimple.ts et main.ts dans un seul fichier dist/app.js exploitable en JavaScript.

👉 Veux-tu que je te propose aussi une version avec modules ES6 (export / import), plus moderne que les namespace + /// (qui sont un peu old school) ?

Oui.

Seconde réponse, meilleure :

ChatGPT a dit :

Parfait 👌 allons-y avec la version moderne en modules ES6.
Ici, pas de namespace ni de /// → on utilise simplement export / import.
1. Fichier N.ts

export class N<T> {
  // Valeur du nœud
  public data: T;
  // Référence du nœud suivant
  public next: N<T> | null = null;

  // Création d'un nœud avec la valeur donnée
  constructor(value: T) {
    this.data = value;
  }
}

2. Fichier ListeSimple.ts

import { N } from "./N";

export class ListeSimple<T> {
  // Nœud de tête :
  public head: N<T> | null = null;

  // Ajouter un nœud en tête :
  prepend(data: T): void {
    const newNode = new N(data);
    newNode.next = this.head;
    this.head = newNode;
  }

  // Ajouter un nœud à la fin :
  append(data: T): void {
    const newNode = new N(data);

    if (!this.head) {
      this.head = newNode;
      return;
    }

    let current = this.head;
    while (current.next) {
      current = current.next;
    }
    current.next = newNode;
  }

  // Retirer le nœud de tête :
  deleteHead(): void {
    if (this.head) {
      this.head = this.head.next;
    }
  }

  // Retirer le nœud de fin :
  deleteTail(): void {
    if (!this.head) return;

    if (!this.head.next) {
      this.head = null;
      return;
    }

    let current = this.head;
    while (current.next && current.next.next) {
      current = current.next;
    }
    current.next = null;
  }

  // Retirer le premier nœud d'une valeur donnée :
  delete(data: T): void {
    if (!this.head) return;

    if (this.head.data === data) {
      this.head = this.head.next;
      return;
    }

    let current = this.head;
    while (current.next) {
      if (current.next.data === data) {
        current.next = current.next.next;
        return;
      }
      current = current.next;
    }
  }

  // Trouver un nœud d'une valeur donnée :
  find(data: T): N<T> | null {
    if (!this.head) return null;

    let current: N<T> | null = this.head;
    while (current) {
      if (current.data === data) return current;
      current = current.next;
    }
    return null;
  }

  // Liste de toutes les valeurs
  traverse(): void {
    let current = this.head;
    while (current) {
      console.log(current.data);
      current = current.next;
    }
  }
}

3. Fichier main.ts

import { ListeSimple } from "./ListeSimple";

const uneListePersonnages = new ListeSimple<string>();

uneListePersonnages.prepend("Tintin");
uneListePersonnages.prepend("Haddock");
uneListePersonnages.prepend("Milou");

uneListePersonnages.traverse();

4. Compilation avec tsc

Crée un tsconfig.json (si tu n’en as pas déjà un) :

{
  "compilerOptions": {
    "target": "ES6",
    "module": "ES6",
    "moduleResolution": "node",
    "outDir": "dist",
    "rootDir": "./",
    "strict": true
  },
  "include": ["*.ts"]
}

Ensuite, compile :

tsc

Cela va générer dans dist/ :

N.js

ListeSimple.js

main.js

👉 Petite différence d’usage :

Avec namespaces, on regroupe tout en un seul fichier avec —outFile.

Avec modules ES6, chaque fichier génère son propre .js, et on les relie via import / export.

Suite de la question :

Bonjour ChatGPT, j’ai créé en TypeScript une classe de liste associative :

Bonjour ! Veux-tu me montrer le code de ta classe TypeScript de liste associative ? Comme ça je pourrai t’aider à l’améliorer, la corriger, ou l’adapter selon ton besoin.

// La classe ListeAssoc

import { Nassoc } from "./Nassoc";

export class ListeAssoc<Tkey, Tvalue> {
    // Noeud de tête :
    public head: Nassoc<Tkey, Tvalue> | null = null;
    
    // Ajouter un noeud en tête :
    prepend(theKey: Tkey, theValue: Tvalue): void {
        const newNode = new Nassoc(theKey, theValue);
        newNode.next = this.head;
        this.head = newNode;
    }

    // Ajouter un noeud à la fin :
    append(theKey: Tkey, theValue: Tvalue): void {
      const newNode = new Nassoc(theKey, theValue);

      if (!this.head) {
        this.head = newNode;
        return;
      }

Suite de la question :

Vous avez dit (je vouvoie encore ChatGPT)
 :
En fait on n’a besoin que de prepend. Et je crée un tableau de listes pour créer une table de hash, ainsi :

// La classe HashTable

import { ListeAssoc } from "../Listes/ListeAssociative/ListeAssoc";

type Tuple<
  T,
  N extends number,
  R extends readonly T[] = [],
> = R['length'] extends N ? R : Tuple<T, N, readonly [T, ...R]>;

export class HashTable<ListeAssoc, n extends number> {
    public aTable: Tuple<ListeAssoc, n> | null = null;
    init(n) {
        const laListeVide = new ListeAssoc<string, number>();
        for (let pas = 0; pas < n; pas++){
            this[pas] = laListeVide;
        }
    }
}

Réponse :

ChatGPT a dit :

Merci pour le partage ! 🙂
Là tu as deux morceaux : une liste associative chaînée (ListeAssoc) et une table de hachage (HashTable). Je vois quelques points qui risquent de poser problème dans ton code actuel :

1. La déclaration de HashTable

export class HashTable

Ici tu appelles ton paramètre générique ListeAssoc, mais ce nom est déjà pris par ta classe importée ListeAssoc. En fait, tu veux plutôt paramétrer les types clé et valeur, pas redéclarer une classe.
👉 Je te propose de renommer les génériques en Tkey, Tvalue et éventuellement N pour la taille.

2. La construction de aTable
public aTable : Tuple | null = null ;

Ici, tu veux un tableau de listes associatives. Or Tuple suppose que tu fournis une liste de longueur fixe connue à la compilation. Si ton n est un nombre dynamique, ce sera plus simple de garder un Array.

3. Ton init

init(n) {
    const laListeVide = new ListeAssoc<string, number>();
    for (let pas = 0; pas < n; pas++){
        this[pas] = laListeVide;
    }
}

Problème : tu réutilises la même instance de ListeAssoc pour toutes les cases. Résultat : tout ton tableau pointe vers une seule liste partagée.

Il faut recréer une nouvelle ListeAssoc à chaque itération.

Version corrigée

import { ListeAssoc } from "../Listes/ListeAssociative/ListeAssoc";

export class HashTable<Tkey, Tvalue> {
    public aTable: Array<ListeAssoc<Tkey, Tvalue>>;

    constructor(private size: number) {
        this.aTable = [];
        for (let i = 0; i < size; i++) {
            this.aTable[i] = new ListeAssoc<Tkey, Tvalue>();
        }
    }

    // Exemple de fonction de hachage très simple (à améliorer selon ton besoin)
    private hash(key: string): number {
        let hash = 0;
        for (let i = 0; i < key.length; i++) {
            hash = (hash + key.charCodeAt(i)) % this.size;
        }
        return hash;
    }

    set(key: Tkey & string, value: Tvalue): void {
        const index = this.hash(key);
        this.aTable[index].prepend(key, value);
    }

    get(key: Tkey & string): Tvalue | undefined {
        const index = this.hash(key);
        let node = this.aTable[index].head;
        while (node) {
            if (node.key === key) return node.value;
            node = node.next;
        }
        return undefined;
    }
}

👉 Ici :

HashTable prend des types génériques clé et valeur (Tkey, Tvalue).

On construit un tableau de ListeAssoc indépendantes.

J’ai ajouté une fonction de hachage simple + des méthodes set et get pour illustrer.

Troisième question :

Re-bonjour,

Voilà, j’ai déclaré une classe HashTable selon tes conseils :

// La classe HashTable

import { ListeAssoc } from "../Listes/ListeAssociative/ListeAssoc";

export class HashTable<Tkey, Tvalue> {
    public aTable: Array<ListeAssoc<Tkey, Tvalue>>;

    constructor(private size: number) {
        this.aTable = [];
        for (let i = 0; i < size; i++) {
            this.aTable[i] = new ListeAssoc<Tkey, Tvalue>;
        }
    }

 // Exemple de fonction de hachage très simple
    private hash(key: string): number {
        let hash = 0;
        for (let i = 0; i < key.length; i++) {
            hash = (hash + key.charCodeAt(i)) % this.size;
        }
        return hash;
    }

    set(key: Tkey & string, value: Tvalue): void {
        const index = this.hash(key);
        this.aTable[index].prepend(key, value);
    }

    get(key: Tkey & string): Tvalue | undefined {
        const index = this.hash(key);
        let node = this.aTable[index].head;
        while (node) {
            if (node.key === key) return node.value;
            node = node.next;
        }
        return undefined;
    }
}

Je crée une instance :

const myHashTable = new HashTable(6) ;

J’inscris des personnages dans la table :

myHashTable.set("Tournesol", 18) ;
myHashTable.set("Dupont", 19) ;

Quand j’interroge j’ai la bonne réponse :

console.log("Dupont : ", myHashTable.get("Dupont")) ;

 > Dupont : 19

Mais si j’écris :

for (let pas = 0; pas < 6; pas++){
    let current = myHashTable[pas];
    while (current){
	console.log(pas, current.aTable);
	current = current.next;
    }
}

Je n’ai pas de réponse, et si j’écris :

for (let pas = 0; pas < 6; pas++){
    let current = myHashTable[pas];
    while (current){
	console.log(pas, current.aTable);
	current.ListeAssoc.traverse;
	current = current.next;
    }
}

on me dit :

       current.ListeAssoc.traverse;
                           ^

TypeError: Cannot read properties of undefined (reading 'traverse')
    at Object.<anonymous> (/home/lbloch/nextcloud/Travaux/Mes-Livres.own/AlgoTypeScript/Programmes/Structures/HashTables/main.js:31:28)
    at Module._compile (node:internal/modules/cjs/loader:1356:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1414:10)
    at Module.load (node:internal/modules/cjs/loader:1197:32)
    at Module._load (node:internal/modules/cjs/loader:1013:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:128:12)
    at node:internal/main/run_main_module:28:49

Node.js v18.19.1

Qu’est-ce qui ne va pas ?

Réponse :

Je vois tout de suite où ça coince 👍

Dans ta boucle tu fais :

for (let pas = 0; pas < 6; pas++){
    let current = myHashTable[pas];
    while (current){
        console.log(pas, current.aTable);
        current = current.next;
    }
}

👉 problème :

myHashTable est un objet de type HashTable, pas un tableau.

L’attribut qui contient tes listes est aTable, donc tu dois accéder à myHashTable.aTable[pas].

Ensuite, chaque case de aTable est une ListeAssoc, qui a un champ head pointant vers le premier Nassoc.

En gros, myHashTable[pas] n’existe pas → ça vaut undefined.
✅ Correction

Pour parcourir toutes les cases et tous les nœuds, fais comme ceci :

for (let pas = 0; pas < 6; pas++) {
    let current = myHashTable.aTable[pas].head;
    while (current) {
        console.log(pas, current.key, current.value);
        current = current.next;
    }
}

Autres points :

Dans ton constructeur, il manque des () à l’appel du constructeur ListeAssoc :

this.aTable[i] = new ListeAssoc() ;

(sinon tu construis une référence vers la classe, pas une instance).

current.ListeAssoc.traverse n’a aucun sens :

current est un Nassoc, il a seulement key, value et next.

Si tu veux faire une méthode traverse, elle doit être définie dans ListeAssoc (et tu l’appellerais avec myHashTable.aTable[pas].traverse()).


Dans la même rubrique