Calculette pour crucinuméristes

Au lieu de traiter des opérandes comme une simple calculatrice, la Méta calculette traite des listes de nombres et permet de rechercher des nombres possédant certains critères.

 

 

 

Elle à été développée pour aider les crucinuméristes, vous savez, les personnes se cassant la tête pour remplir des grilles de nombres croisés, où l’on doit trouver par exemple, un carré de 9 chiffres donc la racine est un palindrome et dont le cinquième chiffre doit être un 2 ou un 4… (D’accord, il reste encore 47 possibilités, mais la méta calculette a permis de sélectionner les 47 candidats en question !).

Usage

 

Le principe est assez simple : pour entrer les valeurs il y a deux champs de saisie : nommés x et y.

Trois boutons permettent de créer une nouvelle liste à partir de ces deux valeurs :

 

Ensuite, viennent les traitements applicables sur les éléments de la liste :

 

Chaque traitement est paraphrasé. Par exemple pour la question posée plus haut, la boite de texte contenait : « carrés des nombres de 1 à 100000 qui sont des palindromes qui possèdent 9 chiffres dont le cinquième chiffre est dans 24 » à la fin du calcul. La phrase se construit à chaque opération.

 

Un bug de Smalltalk empêche de traiter des listes de plus d’environ 30 000 000 d’éléments : à cause du calcul d’un rectangle dans la méthode d’affichage de la liste. Avec 30 millions d’éléments il y a des petits problèmes d’affichage de la sélection, mais cela ne plante pas. Evidemment, les traitements peuvent prendre un certain temps avec un nombre pareil d’éléments. Donc je conseille d’y aller progressivement et de vérifier si votre matériel et votre système supportent ces charges. Je l’ai développé sous Mac OS 9 et complété sous Windows XP.

 

 

Une fois la liste construite et éventuellement transformée par des traitements, on peut sélectionner des éléments possédant certains critères :

Tous ces filtres permettent de restreindre la liste.

 

Une seconde liste, à droite, peut être remplie à partir de la copie de la première.

Ensuite, on peut exécuter les opérations ensemblistes d’union, d’intersection et de différence entre la première liste et la seconde.

 

Pour faciliter les recherches et la construction des couples de listes, la première liste peut être mémorisée, puis rappelée.

 

Question : quelle est la racine du plus grand carré de 9 chiffres ?

Réponse :

Entrez 1 dans x et 100000 dans y

Appuyez sur « Remplir de x à y »

è    « nombres de 1 à 100000 »

Appuyez sue « Au carré »

è    « carrés des nombres de 1 à 100000 »

Entrez 9 dans x

Appuyez sur « Possède x chiffres »

è    « carrés des nombres de 1 à 100000 qui possèdent 9 chiffres »

 Appuyez sur « Racine »

è    « racines des carrés des nombres de 1 à 100000 qui possèdent 9 chiffres qui sont des carrés »

Le premier élément de la liste est 10000, et le dernier 31622 : le nombre cherché.

Note : le « qui sont des carrés » de la fin de la paraphrase vient du fait que la calculatrice filtre d’abord les carrés avant de calculer les racines.

 

Programme

 

Pour les calculs des nombres premiers, nous avons implémenté une simple méthode de crible.

La classe Premier construit une liste des nombres premiers au fur et à mesure des besoins et garde cette liste en mémoire.

 

Smalltalk defineClass: #Premier

     superclass: #{Core.Object}

     indexedType: #none

     private: false

     instanceVariableNames: 'nombre suivant '

     classInstanceVariableNames: 'premiers limite '

     imports: ''

     category: 'Premiers'

 

 

Chaque instance de Premier connaît le nombre qu’elle représente et possède ou non un élément suivant de la même classe.

Les variables d’instance de métaclasse premiers et limite permettent de mémoriser la liste déjà calculée et la limite atteinte.

 

La méthode de classe suivante permet d’obtenir la liste des nombres premiers jusqu'à n:

jusqua: n

     | f |

     limite isNil ifTrue: [

                limite := 2.

                f := Premier new nombre: 2 ]

     ifFalse: [ f := premiers ].

     limite to: n do: [:x |

                f filtrer: x ].

     limite := n.

     premiers := f.

     ^ f asCollection

 

 

La méthode d’instance filtrer: reçoit successivement les nombres de 2 à la limite souhaité.

Si le nombre passé en argument est divisible par le nombre d’une des instances, il est ignoré. Par contre, si l’on arrive au bout de la liste sans avoir trouvé de diviseur, il est premier, on crée alors une nouvelle instance portant ce nombre et on la colle au bout de la liste :

 

filtrer: n

     n \\ nombre == 0 ifFalse: [

                suivant notNil ifTrue: [

                            suivant filtrer: n ]

                ifFalse: [ suivant :=

                            Premier new nombre: n ] ]

 

La méthode de décomposition utilisée est bête et méchante :

 

decomposer: unNombre

     "Premier decomposer: 20000"

     | liste facteurs n unFacteur |

     liste := self jusqua: unNombre.

     facteurs := OrderedCollection new.

     n := unNombre.

     [ n > 1 ] whileTrue: [

                unFacteur := liste detect: [:f | n \\ f == 0 ].

                facteurs add: unFacteur.

                n := n / unFacteur ].

     ^ facteurs

On pourrait largement optimiser en développant une méthode d’instance conservant un pointeur dans la liste, celle-ci repart du début de la liste pour chaque facteur trouvé, ce qui est inutile. La méthode d’instance du même nom est encore pire : elle recalcule la liste de nombres premiers à chaque fois, sans utiliser celle qui est mémorisée dans la variable de classe.

 

La méthode « asCollection » récupère les nombres de la liste d’instance dans une OrderedCollection du système.

 

do: bloc

     bloc value: nombre.

     suivant notNil ifTrue: [ suivant do: bloc ]

 

asCollection

     | c |

     c := OrderedCollection new.

     self do: [:x | c add: x ].

     ^c

 

On aurait pu implémenter Premier comme une sous-classe de Collection, c’est pour cela que j’ai défini une méthode « do: ».

 

Pratiquement toutes les autres fonctions nécessaires à l’application sont déjà implémenté dans Smalltalk, la manipulation des listes, des ensembles, les calculs de carrés et racine carré etc… Seules peut être les fonctions permettant d’obtenir les listes des chiffres d’un nombre, savoir si le nombre est un palindrome, obtenir le produit des chiffres et la somme des chiffres sont à définir. En attendant mieux, je les ai placés en méthode de classe de la classe Premier, mais ce n’est pas un bon endroit. Remarquez que ces méthodes ne font appel qu’au nombre passé en argument, elles pourraient être des méthodes d’instance dans la classe Number, si on admet de modifier le comportement standard des nombres. Dans ce cas, le paramètre serait supprimé et seul le récepteur serait utilisé.

 

chiffresDe: n

     "self chiffresDe: 12345"

     ^ n printString asOrderedCollection  collect: [:c |

                c digitValue ]

 

estPalindrome: n

     | chiffres |

     chiffres := self chiffresDe: n.

     1 to: chiffres size // 2 do: [:k |

                (chiffres at: k) = (chiffres at: chiffres size + 1 - k)

                            ifFalse: [ ^false ]].

     ^true

 

produitDesChiffres: n

     | chiffres |

     chiffres := self chiffresDe: n.

     ^ chiffres inject: 1 into: [:x :y | x * y ]

 

sommeDesChiffres: n

     | chiffres |

     chiffres := self chiffresDe: n.

     ^ chiffres inject: 0 into: [:x :y | x + y ]

Dernière remarque : La méthode chiffresDe: ne fonctionne que pour les nombres positifs et entiers. Le message digitValue ramène -3 pour le signe $- !

Construction de l’application avec le générateur d’interface

 

Pour créer l’application

  1. Cliquez sur le bouton « Edit a New Canvas »
  2. Trois fenêtres s’ouvrent :

    L’outil de design d’interface (Graphical Usuer Interface Painter Tool),

    La palette,

    Et le Canevas (Modèle de la fenêtre de l’application).
  3.  Dans le menu local du canevas,  Utilisez la commande Install… pour que le système puisse créer la classe représentant votre application, et y définir la méthode de classe « windowSpec » qui mémorisera tous votre design.

    La classe n’est pas encore connue, ce n’est pas grave, donnez le nom de votre classe (terminé par Application en principe) et appuyez sur « OK ». Cela ouvre le « Class Finder »

    Entrez le nom de la catégorie correspondant à votre projet et validez.

    Choisissez le Package correspondant à votre projet et validez. (ou créez un nouveau Package : ). Un Package est un ensemble de définitions, classes, méthodes concernant un projet donné.
  4. Une fois le nom, la catégorie et le package sélectionnés, on revient sur le premier dialogue :

    et on valide. Le système crée alors la classe et défini une méthode de classe nommée « windowSpec » qui lui servira de ressource pour connaître les éléments que vous définirez.

 

  1. Voila, la classe de l’application est définie et peut dès lors être ouverte avec le message open envoyé à la classe. Dans un navigateur (System Browser) vous pouvez retrouver la définition. Ou encore avec la commande « Browse… » du menu local au canevas.

    Dans l’onglet Class, protocole « interface specs » le GUI Painter à défini la méthode de classe windowSpec.

 

Lors des modifications ultérieures, ajouts de contrôles, modifications des propriétés des éléments etc.… il faudra toujours valider les changements en utilisant la commande « Install… ». Mais cette fois, comme la classe sera déjà existante, un simple « ok » suffira pour que la méthode « windowSpec » soit mise à jour.

La fermeture du canevas ferme en même temps les fenêtres « Palette » et « Painter Tool ».

Pour rappeler le constructeur d’interface graphique, faites :

Tools.UIPainter new openOnClass: Metacalculette andSelector: #windowSpec

 

Cette expression (ou une équivalente) est en commentaire au début de la méthode windowSpec de la classe de l’application.

 

Les trois fenêtres s’ouvrent à nouveau (ici, ce sont celles de mon propre projet) :

 

L’outil de design d’interface, affichant les contrôles (à gauche) et leurs propriétés (à droite).

 

La palette, permettant de déposer de nouveaux contrôles et de les aligner.

 

Le canevas, modèle graphique de la future fenêtre de l’application, où l’on place et sélectionne les contrôles.

Définir une liste :

Placez un contrôle List dans le canevas,  dans l’onglet « Basic » des propriétés, définissez un aspect (par exemple #liste),

 

Avec le menu local utilisez la commande « Define… » et faites « OK ».

 

Le constructeur d’interface crée une variable d’instance nommée « liste » dans l’application et ajoute la méthode suivante (du même nom) :

liste

     "This method was generated by UIDefiner.  Any edits made here

     may be lost whenever methods are automatically defined.  The

     initialization provided below may have been preempted by an

     initialize method."

 

     ^liste isNil

                ifTrue:

                            [liste := SelectionInList new]

                ifFalse:

                            [liste]

En fait cette méthode d’accès s’assure qu’un modèle capable de porter une liste est affecté à la variable d’instance.

Pour utiliser cette liste, la remplir, la modifier, on passera toujours par cette méthode d’accès.

Exemple : le bouton « Au carré » effectue l’action suivante :

carre

     self liste list: (self liste list collect: [:n | n * n ]).

     self paraphrase value: ('carrés des ', self paraphrase value).

 

Pour lire la liste de valeur, on envoie le message list au modèle, pour affecter une nouvelle liste on envoie le message list:.

Définir un bouton d’action :

Placez un bouton en sélectionnant le bouton d’action dans la palette et en le déposant dans le canevas. Dans les propriétés de base, entrez le texte du bouton (Label) et une « action » qui sera le message envoyé à l’application lors de l’utilisation du bouton.

 

La commande « Define… » définira une méthode par défaut dans l’application. A vous ensuite d’écrire le code de l’action (voir par exemple ci-dessus). Attention de ne pas redéfinir une action déjà définie, sinon, vous écraseriez votre ancienne définition.

Définir un champ de saisie numérique :

Le principe est le même : on dépose le contrôle dans le canevas avec la palette. Puis on défini les propriétés de base :

L’aspect sera un symbole pour définir une variable d’instance et une méthode du même nom donnant accès à un modèle portant une valeur. Ici, un ValueHolder.

Les propriétés Type et Format permettent de spécifier qu’il s’agit d’un nombre entier dans notre cas.

Enfin, la commande Define… permet de générer la variable et le code correspondant.

x

     "This method was generated by UIDefiner.  Any edits made here

     may be lost whenever methods are automatically defined.  The

     initialization provided below may have been preempted by an

     initialize method."

 

     ^x isNil

                ifTrue:

                            [x := 0 asValue]

                ifFalse:

                            [x]

 

Pour obtenir dans le code de l’application la valeur saisie par l’utilisateur, on incantera « self x value » et pour modifier si besoin la valeur par programme, le message value: devra être envoyé au modèle. Par exemple self x value: 1

Définir un éditeur de texte :

Les opérations sont tout à fait similaires.

Placer le contrôle, spécifier les propriétés (dont en particulier l’aspect), définir le modèle avec la commande « Define… ».

Pour accéder au texte de l’éditeur, utiliser les messages value et value:.

Définir les positions des contrôles :

Lorsque la fenêtre de mon application est agrandie, je voudrai que la liste soit agrandie en même temps.

 

Pour cela, j’utilise les positions relatives à la place des positions absolues dans l’onglet « Position » des propriétés du contrôle.

L, T, R et B correspondent à Left, Top, Right et Bottom.

La colonne « Proportion » permet de donner les position relative à la fenêtre : 0,0, en haut à gauche et 1,1 en bas à droite.

Enfin, la colonne Offset peut être combinée à la colonne proportion pour ajouter un décalage absolu. L’unité est toujours le pixel.

Dans mon cas (ci-dessus), la liste commence à 10 pixels des bords Haut, Gauche et Bas, et fait toujours 138 pixels de large.