Un challenge de programmation LISP

@gilecad nous propose un petit challenge sur CADxp, à vos claviers ! (répondez sur CADxp)

Un jeu d’enfant pour les IA (sans doute?) mais comme le but est de réfléchir par soi-même :smile: je publierais dans quelques temps lorsque les êtres humains auront répondu :wink: les différentes réponses données par les IA avec une analyse de chaque méthodologie si entre temps nous n’avons pas de réponse avec l’assistance des IA.

Evident pour @gilecad mais je le précise quand même ici, si vous demandez l’assistance d’une IA, dites le bien, bien sûr… :grin: sinon c’est pas du jeu ! :innocent:

Et bien sûr répondez sur CADxp, pas ici.

Comme promis, après que plusieurs participants au challenge aient donné des solutions, voici mes réflexions sur ce challenge:

  1. Le challenge initial, posté par (gile), consiste à extraire les nombres d’une chaîne de caractères en AutoLISP.

  2. Plusieurs participants proposent des solutions :

    • bonuscad utilise une approche basée sur le mapcar et la manipulation de listes.
    • didier propose une solution utilisant str2lst et des fonctions de manipulation de chaînes.
    • GEGEMATIC partage un code plus ancien et complexe.
    • (gile) présente deux solutions :
      a) Une version récursive avec deux fonctions auxiliaires (ping et pong).
      b) Une version concise utilisant les expressions régulières.
    • VDH-Bruno propose plusieurs approches, dont une utilisant la fonction read et une autre avec un accumulateur.
  3. Les participants discutent des avantages et inconvénients de chaque approche, notamment en termes de robustesse et de gestion des cas particuliers (comme les points, les signes négatifs, etc.).

  4. La discussion met en lumière la complexité du problème, en particulier pour gérer tous les cas possibles (fractions, unités accolées aux nombres, etc.).

  5. Les participants apprécient le défi intellectuel et l’opportunité d’apprendre de différentes approches.

  6. La solution utilisant les expressions régulières de (gile) est reconnue comme particulièrement élégante et concise.

  7. La discussion soulève des questions sur l’interprétation de certains cas limites (comme « 1/3 m3 ») et la difficulté de créer une solution universelle sans connaître le domaine d’application spécifique.

Comme vous le devinez, le résumé ci-dessus est généré par IA…

Alors, laissez-moi vous partager mes réflexions, issues de mon humble cerveau cette fois-ci :smiley:. Lorsque j’ai vu ce défi pour la première fois, j’ai immédiatement pensé – comme beaucoup d’entre nous – qu’une solution algorithmique passerait par une boucle classique. Je me suis aussi dit que ce genre de boucle pourrait poser problème, car elle examine chaque caractère individuellement, hors de son contexte. Or, c’est précisément là que réside le problème : cette méthode exclut les caractères voisins (avant ou après) du caractère étudié (le contexte), ce qui complique l’analyse.

L’autre solution, qui semble être la plus concise, c’est l’utilisation des expressions régulières, comme l’a montré Gilles. Cela fonctionne très bien dans l’exemple qu’il a donné, et probablement dans la plupart des cas et on peut difficilement faire plus concis. Cependant, je vois deux inconvénients à cette méthode. Le premier, c’est qu’elle nécessite de charger des bibliothèques externes, ce qui va au-delà du code extrêmement concis que Gilles nous présente, la concision n’est donc qu’apparente. Le second problème est de savoir si cette méthode fonctionne avec toutes les chaînes de caractères, et pas seulement avec l’exemple donné par Gilles. Et là, la réponse est non, comme on pouvait s’y attendre, comme Gilles certainement (je lis dans son esprit :innocent:) pouvait s’y attendre, et comme VDH-Bruno l’a souligné.

En somme, cette solution fonctionne la plupart du temps, mais ne couvre pas tous les cas. Bien entendu, je ne doute pas que Gilles pourrait adapter son expression régulière pour gérer plus de cas. Toutefois, comme l’a exprimé VDH-Bruno, il reste une question : jusqu’à quel point une telle expression peut-elle vraiment traiter tous les cas possibles ? Ce challenge, qui semblait simple au départ, peut rapidement devenir complexe. En programmation algorithmique, traduire cela en code AutoLISP peut devenir verbeux.

Quand j’ai vu l’exemple de VDH-Bruno, qui n’était pas résolu par les solutions proposées, je me suis dit qu’il y avait deux manières d’aborder ce problème. La première : utiliser un cerveau humain (pourquoi pas, soyons fous)?, capable d’extraire des valeurs numériques sans difficulté dans les exemples donnés, à condition que le problème soit présenté de façon claire et exhaustive. Et un cerveau humain n’a aucun mal à résoudre ce challenge :stuck_out_tongue:. La deuxième option ? Évidemment, faire appel à une IA (mon dada).

Je me suis donc amusé à copier-coller le challenge de Gilles et à le soumettre à deux IA : ChatGPT et Claude. Le résultat était intéressant, mais pas extraordinaire. Ces IA ont bien produit du code AutoLISP fonctionnel (pas de plantage à l’exécution), mais pas tout à fait adapté au défi. Cela dit, elles ont fourni des explications assez détaillées, utiles pour les débutants comme pour les programmeurs plus expérimentés. Au final, ces IA ne donnent pas toujours une réponse directe. Elles ne sont pas conçues pour écrire du code parfait, mais elles peuvent être d’une aide précieuse pour les développeurs, c’est une certitude. J’en parlais ici.

Alors, je me suis demandé : pourquoi résoudre ce défi uniquement par un programme AutoLISP ? Bien sûr, par respect pour la proposition de Gilles :wink:. Mais après avoir lu les commentaires de VDH-Bruno, je me suis dit : pourquoi ne pas poser la question à une IA, en la faisant raisonner comme un humain plutôt que simplement coder ? J’ai donc écrit un prompt et demandé à Claude de proposer une solution. Voilà le résultat. Gardez en tête que les IA ne répondent pas toujours la même chose à une même question. Ceci est une réponse parmi une infinité d’autres:

Ma question (mon prompt) à Claude:
Extrait toutes les valeurs numériques de cette chaîne, qu’elles soient entières ou réelles, positives ou négatives, écrite sous forme fractionnaire ou sous forme de notation scientifique et donne moi le résultat sous la forme d’une liste de nombres entre parenthèses: " Volume 1/3 m3 tous les 10m²"

Sa réponse:
Voici la liste des valeurs numériques extraites de la chaîne donnée :

(1/3, 10)

Cette liste contient :

  • 1/3 : une fraction représentant le volume
  • 10 : un nombre entier représentant la surface en mètres carrés

Notez que l’exposant 3 du mètre cube n’est pas repris. Cela ne parait pas illogique et il aurait fallu adapter la question pour le prendre en compte. Notez aussi qu’il a détecté un volume et une surface.

Bon, nous constatons que ce challenge est un jeu d’enfant pour une IA (comme il l’aurait été pour un être humain).

En conclusion:

Je ne vais pas, à l’occasion de ce challenge et de mon exploration des IA, relancer la discussion sur des questions comme : « Les IA sont-elles intelligentes ? » ou « Imitent-elles le cerveau humain ? ». Nous avons déjà abondamment débattu de ces sujets, aussi bien sur CADxp ainsi que sur d’autres forums et dans d’actualité.

La solution proposée par une IA, en l’occurrence, ne répond pas naturellement au défi posé par Gilles, et nous en convenons tous. Cependant, j’ai trouvé extrêmement intéressant d’élargir cette discussion sur les IA, et je demande humblement – mais avec une certaine insistance :yum: – à ceux d’entre vous, même les plus réticents à ces technologies, de prêter attention à cette réflexion.

Elle soulève deux questions passionnantes à mon avis : premièrement, personne ne sait exactement comment fonctionnent les IA, pas même leurs créateurs (si, si, je vous assure). C’est une caractéristique unique de cette technologie. Par conséquent, je ne peux naturellement pas vous expliquer quel algorithme a été utilisé pour que l’IA, ici Claude, propose une solution. Je ne peux donc pas non plus vous dire si la solution était exacte, ni comment la corriger pour qu’elle le soit. Je ne pourrais y parvenir que par essais et erreurs, ce qui nous éloigne de l’idée initiale du challenge, qui consiste à concevoir d’abord un algorithme mentalement, comme Gilles l’a fait, puis à le programmer en AutoLISP (ou dans un autre langage, d’ailleurs).

Nota bene: la solution en Python est aussi concise que celle de Gilles en AutoLISP.

Même si je ne fais pas partie de ceux qui ont contribué directement sur le forum à ce petit défi intéressant, j’ai profité de l’occasion pour voir ce que les IA auraient à dire ou à enseigner sur le sujet. Ce qui est fascinant. Je n’ai pas rapporté ici les solutions proposées par ChatGPT ou Claude en termes d’algorithmique pure et d’AutoLISP, mais si cela intéresse quelqu’un, je peux le faire.

Vous pouvez également le vérifier par vous-même, car ces IA ne se contentent pas de vous fournir du code ; elles expliquent en détail comment l’algorithme a été conçu. Cela dit, je ne l’ai pas fait ici parce que, tout simplement, le code proposé ne fonctionnait pas.

En conclusion, je pense fermement que les programmeurs, quel que soit le langage qu’ils utilisent, ne peuvent pas ignorer totalement les IA. Même si je comprends qu’on puisse être réticent à leur égard, c’est un fait incontournable. Les IA sont et seront utilisées par les programmeurs, par ceux qui vous le diront honnêtement, comme par… les autres. Et, selon moi, il est rarement sage d’ignorer un fait sociétal, technique ou scientifique, comme l’apparition des IA grand public il y a environ deux ans (bien que ces technologies soient beaucoup plus anciennes). Cela reviendrait à se priver d’une réflexion sur l’évolution de la technologie, surtout dans un domaine aussi fascinant que le lien entre IA et fonctionnement du cerveau humain.

Je suis curieux d’entendre vos réflexions, ici ou sur CADxp. :eyes:

En réponse à VDH-Bruno, je change légèrement le prompt pour répondre stricto sensu au challenge :yum::

Bon je reprends ce challenge de Gile pour la raison suivante: l’IA chinoise DeepSeek dont on parle beaucoup ces jours-ci (comment pas vous? :yum:) est supposée faire des merveilles en problèmes complexes, alors je lui ai soumis le challenge de Gile et voici sa réponse:

DeepSeek fait d’abord une analyse en anglais que voici ci-dessous, puis donne le résultat en français:

l'analyse en anglais

Okay, the user wants an AutoLISP program that extracts numbers from a string. Let’s think about how to approach this.

First, I need to parse the input string character by character. Numbers can be integers or decimals, and they might have a negative sign. So the program should check each character to see if it’s part of a number.

Hmm, maybe iterate through each character in the string. When a digit, a ‹ . ›, or a ‹ - › (if it’s the start of a number) is found, start collecting characters until a non-numeric character is encountered. That way, each number is captured as a substring.

Wait, but in AutoLISP, strings are handled with functions like substr and strcat. Also, I need to keep track of whether I’m currently building a number. So variables like current-number and a flag indicating if we’re in the middle of a number would help.

Let’s outline the steps:

  1. Initialize variables: a list to hold the numbers, a temporary string to build each number, and a flag.

  2. Loop through each character in the input string.

  3. For each character, check if it’s part of a number. Allowed characters are digits, ‹ . ›, and ‹ - ›. But ‹ - › should only be allowed at the beginning of a number.

  4. If the current character is part of a number, add it to the temporary string. If not, check if the temporary string has a valid number. If yes, convert it to a real or integer and add to the list. Then reset the temporary string.

  5. Also, handle the case where the number is at the end of the string, so after the loop ends, check if there’s a remaining temporary string to process.

But wait, how to handle multiple dots or invalid positions of ‹ - ›? For example, « 12.3.4 » or « –5 » should be invalid. So during the collection, we need to validate the temporary string. Maybe after collecting a potential number, check if it’s a valid number before adding to the list.

Alternatively, use the AutoLISP function (read) which can parse a string into a number if possible. So, when we collect a substring that could be a number, try to read it. If it’s a number, add it to the list. Otherwise, skip it.

That might simplify things. So, collect sequences of characters that could form a number (digits, ‹ . ›, ‹ - ›) and then attempt to convert them using (read). If (numberp) returns T for the result, then it’s a valid number.

Let’s structure the code with this in mind.

Initialize variables: result list, current string, and a flag.

Loop through each character:

  • For each char, check if it’s a digit, ‹ . ›, or ‹ - ›.

  • If it’s a ‹ - ›, make sure it’s either the start of the current string or preceded by a non-number character. Wait, maybe that’s complicated. Alternatively, collect all ‹ - › as part of the current string, but when converting, if it’s not valid, it will be ignored.

But maybe the initial approach is to collect any sequence that starts with a ‹ - › (if it’s not part of a previous number), followed by digits and dots. Hmm, perhaps not. Let’s proceed with the initial idea of collecting any characters that are digits, ‹ . ›, or ‹ - ›, and then check validity via (read).

So, in code:

(defun extraireNombres (chaine / i char courant liste nombre)

(setq i 1)

(setq courant «  »)

(setq liste '())

(while (<= i (strlen chaine))

(setq char (substr chaine i 1))

(if (or (<= « 0 » char « 9 »)

(member char '(« . » « - »))

)

(setq courant (strcat courant char))

(progn

(if (/= courant «  »)

(progn

(setq nombre (read courant))

(if (numberp nombre)

(setq liste (cons nombre liste))

)

(setq courant «  »)

)

)

)

)

(setq i (1+ i))

)

; Check if there’s remaining courant after loop

(if (/= courant «  »)

(progn

(setq nombre (read courant))

(if (numberp nombre)

(setq liste (cons nombre liste))

)

)

)

(reverse liste)

)

Wait, but this code may have issues. For example, if the string has something like « a-123 », the ‹ - › is not at the start. But in the code above, when the code encounters the ‹ - ›, it would add it to ‹ courant ›. However, when processing the ‹ a ›, which is not a number part, it would process ‹ courant › which is « - », then read that as a symbol, not a number. So (numberp '-) is nil, so it’s not added. Then ‹ 123 › is collected and added.

Another example: « 12.3.4 » would collect « 12.3.4 », but when read, that’s not a number. So it’s ignored.

But maybe that’s acceptable. Let’s test with the example given.

Input string: « Longueur = 25.4mm Largeur = 12.7mm Quantité = -42 »

Breaking down the string:

Characters like ‹ L ›, ‹ o ›, etc. are non-number parts. When the code hits the ‹ 2 › in ‹ 25.4 ›, it starts collecting. Then ‹ . › and ‹ 4 › are added. When it hits ‹ m ›, which is not part of number, it processes ‹ 25.4 › as a number. Then ‹ mm › are skipped. Similarly for ‹ 12.7 › and ‹ -42 ›.

So in code, when ‹ 25.4mm › is parsed, the 'm’s trigger the processing of ‹ 25.4 › which is a valid number. Similarly for the others.

Wait, but in the code above, when a non-numeric character is found, the code processes the current ‹ courant › string. However, the code checks if the current character is part of a number. If not, it processes ‹ courant › and resets it.

But in the example, when the code is on the ‹ m › in ‹ 25.4mm ›, the ‹ m › is not a number character, so the code would take ‹ courant › (which is « 25.4 ») and process it. Then ‹ courant › is reset. Then the next ‹ m › is processed, but ‹ courant › is empty, so nothing is done. So that works.

But what about when the number is followed by another number? Like « 123abc456 ». The ‹ a › is non-numeric, so ‹ 123 › is processed. Then ‹ abc › is skipped, and ‹ 456 › is collected when the digits are found. So the code would return (123 456).

But what about a ‹ - › in the middle of a number? Like « 12-34 ». The ‹ - › is a non-digit, so when encountered, ‹ 12 › is processed, then ‹ - › is considered part of the next number. But ‹ 12-34 › would split into 12 and -34, which is correct.

Wait, in the code above, when the ‹ - › is encountered, since it’s part of the allowed characters, it would be added to ‹ courant ›. But if the previous ‹ courant › was empty, then starting with ‹ - ›, which is allowed. So « -34 » would be read as -34.

But in the case of « 12-34 », after processing ‹ 1 › and ‹ 2 ›, the ‹ - › is encountered. Since the code is in the loop, when the ‹ - › is checked: it is part of allowed characters (member '(« - »)), so added to ‹ courant ›. Wait, but ‹ courant › is « 12 » so far. So adding ‹ - › would make it « 12- », which when read would be (read « 12- ») which is invalid, so (numberp '12-) is nil, so not added. Then the next characters ‹ 3 › and ‹ 4 › would form « 34 », which is 34. So the result would be (34), but the input was « 12-34 ». Hmm, this would not capture the ‹ 12 › because the ‹ - › is considered part of the same ‹ courant › string. Wait, let’s think:

Loop steps for « 12-34 »:

i=1: char « 1 » → allowed, courant becomes « 1 »

i=2: char « 2 » → allowed, courant becomes « 12 »

i=3: char « - » → allowed, courant becomes « 12- »

i=4: char « 3 » → allowed, courant becomes « 12-3 »

i=5: char « 4 » → allowed, courant becomes « 12-34 »

Then, when the loop ends, it processes the remaining courant « 12-34 ». (read « 12-34 ») would be interpreted as 12-34, which is a symbol, not a number. So (numberp …) is nil. So nothing is added. So the code would miss the numbers 12 and 34 here. That’s a problem.

So the current approach collects « 12-34 » as a single string, which is not a valid number. So the code would fail to extract 12 and 34 from « 12-34 ».

This indicates a flaw. The code groups consecutive characters that are digits, ‹ . ›, or ‹ - ›, but that can lead to invalid numbers if ‹ - › is in the middle.

So the approach needs to ensure that ‹ - › is only allowed at the beginning of a number.

To handle this correctly, we need to split the numbers correctly. A number can start with ‹ - ›, followed by digits and optional ‹ . › and more digits. But if a ‹ - › appears elsewhere, it’s not part of the number.

So the code should check whether the current character is allowed in the current context of building a number.

Alternative approach: Use a state machine. The states are:

  • Not in a number.

  • In a number (could be integer or real, possibly negative).

When in « Not in a number » state:

  • If the current character is ‹ - ›, then start a new number.

  • If it’s a digit or ‹ . ›, start a new number (but ‹ . › alone is not valid, need to check later).

When in « In a number » state:

  • Allow digits, ‹ . › (only once), and if the ‹ - › is already part of the number (i.e., only at the start).

But this complicates the code. How to implement this in AutoLISP?

Maybe track whether we are in a number, and the current position’s allowed characters.

Alternatively, during the collection, validate the characters as they are added. For example, if the current character is ‹ - ›, and the current ‹ courant › is not empty, then it’s invalid. So split the current ‹ courant › and start a new number with ‹ - ›.

But this requires more complex logic.

Alternatively, split the string into tokens separated by non-numeric characters, and then check each token to see if it’s a valid number.

But how to split the string into tokens? AutoLISP doesn’t have a built-in split function, so we have to implement it.

Another idea: use regular expressions. But AutoLISP doesn’t support regex. So manual parsing is necessary.

Hmm. Let’s think of another approach. Maybe use (vl-string-search) or other string functions to find the numbers.

Alternatively, iterate through each character, and when a ‹ - › is found, check if it’s followed by a digit or ‹ . › to consider it as the start of a number. For digits and ‹ . ›, check if they are part of a number.

But this is getting complicated. Maybe a better approach is to collect sequences that start with a ‹ - › (optional) followed by digits and optional ‹ . › and more digits.

So during iteration:

  • When we encounter a ‹ - › followed by a digit or ‹ . ›, start collecting a number.

  • Or, encounter a digit or ‹ . ›, start collecting a number.

Wait, but ‹ . › without a digit before or after is invalid. For example, « .5 » is valid as 0.5, and « 5. » is valid as 5.0 in some contexts. But AutoLISP may read « .5 » as 0.5?

Testing in AutoLISP: (read « .5 ») returns 0.5. (read « 5. ») returns 5.0. So (numberp (read « .5 »)) returns T, same for « 5. ».

So the code should allow numbers like « -.5 », « -5. », etc.

So the logic should allow:

  • The number starts with ‹ - › (optional).

  • Followed by at least one digit or ‹ . › (but need to ensure that the combination is valid).

But during collection, how to ensure that? Perhaps it’s easier to collect all sequences that start with ‹ - › (if followed by digit or ‹ . ›) or digit or ‹ . ›, and then check if (read) returns a number.

But the problem with the previous code was that it grouped characters like « 12-34 » into a single token which is invalid. So perhaps the code should split on ‹ - › that are not at the start of a number.

Wait, but in the example, « -42 » is valid. So how to differentiate between a ‹ - › that is part of a number and one that isn’t.

Alternative approach:

Whenever a ‹ - › is encountered, check if it is followed by a digit or ‹ . › to determine if it’s part of a number. But how to implement that in AutoLISP.

Maybe when a ‹ - › is found, look ahead to the next character. If the next character is a digit or ‹ . ›, then start a new number. Otherwise, ignore the ‹ - ›.

But this requires checking the next character, which complicates the loop.

Alternatively, when a ‹ - › is found and the current ‹ courant › is empty, then start a new number. If ‹ courant › is not empty, then the previous number is completed, and then start a new number with ‹ - ›.

Wait, perhaps that’s the key. For example, in « 12-34 », the ‹ - › is encountered when ‹ courant › is « 12 ». Since ‹ - › is allowed but the ‹ courant › is not empty, this indicates that the current number is « 12 », and the ‹ - › is the start of a new number. So process « 12 » as a number, then start a new ‹ courant › with ‹ - ›.

But how to detect that in the code.

Modify the code:

When the current character is ‹ - ›, check if ‹ courant › is empty. If yes, add to ‹ courant ›. If no, then process the current ‹ courant › (if valid), then start a new ‹ courant › with ‹ - ›.

But wait, in the case of « 12-34 », when the code encounters ‹ - ›, ‹ courant › is « 12 ». The character is ‹ - ›, which is allowed, but since ‹ courant › is not empty, we need to check if ‹ - › is allowed here. Since ‹ - › in the middle of a number is invalid, we should split here.

So, in code:

When processing each character:

If the character is ‹ - ›, and ‹ courant › is not empty, then process the current ‹ courant › and start a new ‹ courant › with ‹ - ›.

Similarly, if the character is ‹ . › and ‹ courant › already contains a ‹ . ›, process the current ‹ courant › and start new.

But this adds more conditions. Let’s try to implement this.

So, in the loop:

For each character:

Check if the current character is part of a number. If so, check if it’s allowed in the current ‹ courant ›.

For example:

If current char is ‹ - ›, and ‹ courant › is empty, add to ‹ courant ›.

If ‹ courant › is not empty, then this ‹ - › cannot be part of the current number, so process ‹ courant › and start a new ‹ courant › with ‹ - ›.

If current char is ‹ . ›, check if ‹ courant › already contains a ‹ . ›. If yes, process ‹ courant › and start new with ‹ . ›.

But this can get complicated. Alternatively, whenever a character is not allowed in the current ‹ courant ›, split and process.

Alternatively, perhaps the original code is sufficient, and the problem with « 12-34 » is an edge case that the user may not have considered. But according to the example, the code should extract « -42 » which starts with ‹ - ›, so the code needs to handle that.

Wait, but in the original code, when « 12-34 » is processed, ‹ courant › becomes « 12-34 », which when read gives a symbol, not a number, so it’s ignored. But the correct numbers would be 12 and 34, but they are not captured.

So the original code would not handle such cases correctly.

But the user’s example doesn’t include such cases. However, the problem statement says to extract all numbers in the string. So the code needs to handle any valid number in the string, regardless of their position.

So how to adjust the code to handle this.

Alternative approach: Use a regular expression-like scanning, where numbers are identified by the pattern: optional ‹ - › followed by digits and optional ‹ . › and more digits, or ‹ . › followed by digits.

But since AutoLISP doesn’t support regex, we have to implement this manually.

So, let’s try to structure the code to capture numbers correctly:

Initialize variables: i (current index), courant (current number string), liste (result list).

Loop through each character in the string:

For each character at position i:

If the character is a digit, add to courant.

Else if the character is ‹ . ›:

If courant is empty, start with ‹ . › (assuming « .5 » is allowed).

If courant already has a ‹ . ›, then split: process courant as a number, then start new with ‹ . ›.

Else, add ‹ . › to courant.

Else if the character is ‹ - ›:

If courant is empty, add ‹ - › to courant.

Else, split: process courant, then start new with ‹ - ›.

Else:

If courant is not empty, process it and reset.

After loop ends, process any remaining courant.

But how to implement this in code.

Implementing this logic would require checking the current character and the state of ‹ courant ›.

Let’s try to code this:

(defun extraireNombres (chaine / i char courant liste temp)

(setq i 1)

(setq courant «  »)

(setq liste '())

(while (<= i (strlen chaine))

(setq char (substr chaine i 1))

(cond

((or (= char « 0 ») (= char « 1 ») (= char « 2 ») (= char « 3 ») (= char « 4 »)

(= char « 5 ») (= char « 6 ») (= char « 7 ») (= char « 8 ») (= char « 9 »))

(setq courant (strcat courant char))

)

((= char « . »)

(if (and (/= courant «  ») (not (wcmatch courant « . »)))

(setq courant (strcat courant char))

(progn

(if (/= courant «  »)

(progn

(setq temp (read courant))

(if (numberp temp) (setq liste (cons temp liste)))

)

)

(setq courant char)

)

)

)

((= char « - »)

(if (= courant «  »)

(setq courant char)

(progn

(setq temp (read courant))

(if (numberp temp) (setq liste (cons temp liste)))

(setq courant char)

)

)

)

(t

(if (/= courant «  »)

(progn

(setq temp (read courant))

(if (numberp temp) (setq liste (cons temp liste)))

(setq courant «  »)

)

)

)

)

(setq i (1+ i))

)

; Process remaining courant

(if (/= courant «  »)

(progn

(setq temp (read courant))

(if (numberp temp) (setq liste (cons temp liste)))

)

)

(reverse liste)

)

Let’s test this code with the example:

Input: « Longueur = 25.4mm Largeur = 12.7mm Quantité = -42 »

Processing each character:

When ‹ 2 › is encountered, courant becomes « 2 », then ‹ 5 › → « 25 », ‹ . › → « 25. » (since courant doesn’t have ‹ . › yet), ‹ 4 › → « 25.4 ». Then ‹ m › is not a number character, so process « 25.4 » → 25.4 added to list.

Similarly for « 12.7 ».

When ‹ -42 › is encountered:

The ‹ - › is processed. Since courant is empty, it’s added. Then ‹ 4 › and ‹ 2 › are added. At end, courant is « -42 » → read as -42, added.

So the list becomes (25.4 12.7 -42), which is correct.

Now, testing with « 12-34 »:

Processing ‹ 1 › → « 1 », ‹ 2 › → « 12 ».

Next ‹ - ›, which is not empty. So process « 12 » (12 added), then set courant to « - ».

Then ‹ 3 › → « -3 », ‹ 4 › → « -34 ». At end, process « -34 » → -34 added. So the result is (12 -34). But the numbers are 12 and -34. However, in the original input « 12-34 », the user might expect 12 and 34, but according to the code, it’s 12 and -34. But that’s correct if the string is « 12-34 », which could be interpreted as 12 followed by -34. However, in reality, « 12-34 » is « 12-34 », which may not contain any numbers if ‹ - › is a separator. But according to the code’s logic, it’s extracting 12 and -34. Whether that’s desired depends on the input. For example, if the string is « 12-34 », it’s ambiguous. But according to the problem statement, the code should extract all numbers present. In this case, « -34 » is a valid number, so it’s extracted.

Another test case: « abc-123.45def6.7 » → should return (-123.45 6.7).

Another case: « 12.3.4 » → the first ‹ . › is allowed, but when the second ‹ . › comes, since courant is « 12.3 » and already has a ‹ . ›, the code would split here. Process « 12.3 », then start new courant with ‹ . ›, then ‹ 4 › is added. So « 12.3 » is a number, then « .4 » is processed as 0.4. So the list would be (12.3 0.4).

But « 12.3.4 » as a string would be split into 12.3 and 0.4. But in reality, it’s not a valid number. But according to the code’s logic, it’s allowed. However, when (read « 12.3.4 ») is called, it would return 12.3.4 which is a symbol, not a number. Wait, no: in AutoLISP, (read « 12.3.4 ») would result in an error or a symbol? Testing in AutoCAD®: (read « 12.3.4 ») returns 12.3, because the read function stops at the second ‹ . ›? Or does it parse until the first invalid character?

Wait, let me test this. In AutoLISP, the (read) function parses the string until it encounters a delimiter. For example:

(read « 12.3.4 ») → parses « 12.3 » as a real number, then the remaining « .4 » is left. But in AutoLISP, the (read) function processes the entire string and returns the first atom. So (read « 12.3.4 ») would return 12.3, because it reads up to the second ‹ . › which is invalid. Wait, no. Let me check in AutoCAD®:

Command: (read « 12.3.4 »)

12.3

But (numberp (read « 12.3.4 »)) → T, because 12.3 is a number, and the « .4 » is left unread. Wait, no. Actually, (read) processes the entire string as much as possible to form a single atom. So « 12.3.4 » would be read as the symbol '12.3.4 if it’s not a valid number. But in AutoLISP, numbers can’t have two decimal points. So (read « 12.3.4 ») would actually read « 12.3 » as a real number, and leave the « .4 » part. Wait, but (read) parses from left to right, and stops at the first character that cannot be part of the number. So « 12.3.4 » would be parsed as 12.3, then the next character is ‹ . ›, which is not part of a number, so the result is 12.3. But (read « 12.3.4 ») would return 12.3, and the rest is ignored. So in the code above, when the string « 12.3.4 » is processed, courant becomes « 12.3.4 », which when (read) is applied, returns 12.3 (a number), and the rest « .4 » is ignored. Wait, no. Wait, (read) reads the entire string « 12.3.4 » and tries to parse it as a number. Since it’s not a valid number, (read) returns it as a symbol. For example:

Command: (read « 123a »)

123A

(type (read « 123a »)) → SYM

(numberp (read « 123a »)) → nil

So in the code, when courant is « 12.3.4 », (read courant) would return a symbol, so (numberp) is nil, and it’s not added to the list.

Ah, so in the code above, when processing « 12.3.4 », the code would split when the second ‹ . › is encountered. Let’s see:

The code’s handling of ‹ . ›:

When the current character is ‹ . ›, and courant is not empty and does not contain a ‹ . › yet, then add to courant. Else, process courant and start new with ‹ . ›.

Wait, in the code’s cond clause for ‹ . ›:

((= char « . »)

(if (and (/= courant «  ») (not (wcmatch courant « . »)))

(setq courant (strcat courant char))

(progn

(if (/= courant «  »)

(progn

(setq temp (read courant))

(if (numberp temp) (setq liste (cons temp liste)))

)

)

(setq courant char)

)

)

)

So, when courant is « 12.3 » and the next character is ‹ . ›, then (wcmatch courant « . ») is T, so the else clause is executed. Process courant (« 12.3 »), add to list, then set courant to « . ».

Then the next character is ‹ 4 ›, which is a digit. Add to courant → « .4 ». At end, courant is « .4 », which is read as 0.4. So the list would be (12.3 0.4).

So the code would handle « 12.3.4 » as (12.3 0.4), which is incorrect because « 12.3.4 » is not a valid number. But according to the code’s logic, it’s splitting into « 12.3 » and « .4 ».

But the original string « 12.3.4 » doesn’t have any non-number characters between the numbers. So the code is splitting on the second ‹ . ›, treating it as a separator. But in reality, the entire « 12.3.4 » is one invalid number. So the code is making an assumption here, which may not be correct. However, according to the problem statement, the routine should extract all numbers in the string. So if the string contains « 12.3.4 », the code would extract 12.3 and 0.4, but these are valid numbers. Whether this is desired depends on the requirements. In this case, perhaps the code is acceptable as per the user’s example.

Another test case: « –42 ». The first ‹ - › starts courant. The next ‹ - ›, since courant is « - », which is not empty. So process courant « - » → (read « - ») is -, a symbol. Not added. Then start new courant with « - », then ‹ 4 ›, ‹ 2 › → « -42 », which is a valid number. So the code returns (-42).

But the string « –42 » would be parsed as -42. But according to the code’s logic, the first ‹ - › is processed, but not added as a number. The second ‹ - › starts a new number, « -42 ». So the result is (-42).

This is correct.

Another case: « -abc123 ». The ‹ - › is added to courant, but then ‹ a › is encountered. Process courant « - » → not a number. Reset. Then ‹ abc › is ignored. Then ‹ 123 › is processed. So result is (123). But the ‹ - › was not part of a number, so it’s ignored. That’s correct.

Overall, the code seems to handle various cases, though there might be edge cases where numbers are split incorrectly. But given the user’s example and the problem statement, the code should work.

So the final code would be as above, with comments in French if needed.

Et le résultat:

Voici un programme AutoLISP qui extrait les nombres d’une chaîne de caractères, y compris les nombres négatifs et décimaux :

(defun extraireNombres (chaine / i char courant liste temp)
  (setq i 1)
  (setq courant "")
  (setq liste '())
  
  (while (<= i (strlen chaine))
    (setq char (substr chaine i 1))
    (cond
      ((wcmatch char "[0-9]")
       (setq courant (strcat courant char))
      )
      ((= char ".")
       (cond
         ((and (/= courant "") (not (wcmatch courant "*.*")))
          (setq courant (strcat courant char))
         )
         ((= courant "")
          (setq courant char)
         )
         (t
          (if (numberp (setq temp (read courant)))
            (setq liste (cons temp liste))
          )
          (setq courant char)
         )
       )
      )
      ((= char "-")
       (if (= courant "")
         (setq courant char)
         (progn
           (if (numberp (setq temp (read courant)))
             (setq liste (cons temp liste))
           )
           (setq courant char)
         )
       )
      )
      (t
       (if (/= courant "")
         (progn
           (if (numberp (setq temp (read courant)))
             (setq liste (cons temp liste))
           )
           (setq courant "")
         )
       )
      )
    )
    (setq i (1+ i))
  )
  
  (if (/= courant "")
    (if (numberp (setq temp (read courant)))
      (setq liste (cons temp liste))
    )
  )
  
  (reverse liste)
)

Explication du fonctionnement :

  1. On parcourt chaque caractère de la chaîne
  2. On détecte les chiffres, points décimaux et signes négatifs
  3. On gère les cas particuliers :
    • Les nombres négatifs (avec ‹ - › en début de nombre)
    • Les nombres décimaux (avec un seul point)
    • Les séparations entre nombres
  4. On utilise la fonction read pour convertir les segments en nombres
  5. On vérifie la validité numérique avec numberp

Exemple d’utilisation :

(extraireNombres "Longueur = 25.4mm Largeur = 12.7mm Quantité = -42")

Retourne :

(25.4 12.7 -42)

Ce programme gère correctement :

  • Les nombres décimaux
  • Les nombres négatifs
  • Les formats mixtes avec unités
  • Les nombres adjacents à d’autres caractères

Et alors, ça donne quoi ?

Ben j’ai fait juste un test et ça a l’air de marcher. A vos tests !

Et signalons aussi que des versions personnalisées de ChatGPT se débrouillent bien comme celui-ci:

Voici une implémentation en AutoLISP pour extraire les nombres d’une chaîne de caractères :

Code AutoLISP :

(defun extraireNombres (chaine / i len char num liste temp)
  (setq i 0
        len (strlen chaine)
        temp ""
        liste '()
  )
  
  (while (< i len)
    (setq char (substr chaine (1+ i) 1))
    
    ;; Vérifie si le caractère fait partie d'un nombre valide
    (if (wcmatch char "0,1,2,3,4,5,6,7,8,9,.,-")
        (setq temp (strcat temp char))  ;; Ajoute le caractère à la chaîne temporaire
        (progn
          ;; Si on rencontre un séparateur, on ajoute le nombre à la liste
          (if (and (> (strlen temp) 0) (not (wcmatch temp "-")))
              (setq liste (cons (distof temp) liste)))
          (setq temp "")
        )
    )
    
    (setq i (1+ i))
  )

  ;; Vérifie si un nombre est resté en attente à la fin de la chaîne
  (if (> (strlen temp) 0)
      (setq liste (cons (distof temp) liste))
  )

  (reverse liste)  ;; Retourne la liste dans l'ordre d'apparition
)

Explication du code :

  1. Initialisation :

    • i est l’index parcourant la chaîne.
    • len est la longueur de la chaîne.
    • temp stocke temporairement les chiffres d’un nombre.
    • liste contiendra les nombres extraits.
  2. Boucle while :

    • On parcourt la chaîne caractère par caractère.
    • Si le caractère fait partie d’un nombre (0-9, . ou -), il est ajouté à temp.
    • Si un séparateur est trouvé, on ajoute temp à la liste (si ce n’est pas juste un - isolé).
  3. Conversion des nombres :

    • distof convertit la chaîne en valeur numérique.
    • On utilise reverse à la fin pour conserver l’ordre d’apparition.

Exemple d’utilisation :

(extraireNombres "Longueur = 25.4mm Largeur = 12.7mm Quantité = -42")

Résultat :

(25.4 12.7 -42)

Remarque :

  • Cette fonction gère les nombres négatifs et les nombres décimaux.
  • Elle ignore les caractères non numériques en tant que séparateurs.
  • Elle est compatible avec AutoCAD® AutoLISP.