J'ai cette idée qui court dans ma tête, pour générer et évaluer des expressions mathématiques aléatoires. J'ai donc décidé de lui donner un coup de feu et d'élaborer un algorithme, avant de le coder pour le tester.
Exemple:
Voici quelques exemples d'expressions que je souhaite générer de manière aléatoire:
4 + 2 [easy]
3 * 6 - 7 + 2 [medium]
6 * 2 + (5 - 3) * 3 - 8 [hard]
(3 + 4) + 7 * 2 - 1 - 9 [hard]
5 - 2 + 4 * (8 - (5 + 1)) + 9 [harder]
(8 - 1 + 3) * 6 - ((3 + 7) * 2) [harder]
Les plus faciles et les moyens sont assez simples. Les aléatoires int
sont séparés par des opérateurs aléatoires, rien de fou ici. Mais j'ai du mal à démarrer avec quelque chose qui pourrait créer l'un des exemples les plus difficiles et les plus difficiles . Je ne suis même pas sûr qu'un seul algorithme puisse me donner les deux derniers.
Ce que j'envisage:
Je ne peux pas dire que j'ai essayé ces idées, car je ne voulais pas vraiment perdre beaucoup de temps à aller dans une direction qui n'avait aucune chance de travailler en premier lieu. Mais quand même, j'ai pensé à quelques solutions:
- Utiliser des arbres
- Utilisation d'expressions régulières
- Utiliser une folle boucle "for-type" (sûrement la pire)
Ce que je cherche:
Je voudrais savoir quelle est, selon vous, la meilleure solution, entre les solutions que j'ai envisagées et vos propres idées.
Si vous voyez une bonne façon de commencer, j'apprécierais une avance dans la bonne direction, par exemple avec le début de l'algorithme, ou une structure générale de celui-ci.
Notez également que je devrai évaluer ces expressions. Cela peut être fait soit après la génération de l'expression, soit lors de sa création. Si vous prenez cela en considération dans votre réponse, c'est parfait.
Je ne recherche rien de lié à la langue, mais pour mémoire, je pense à l'implémenter dans Objective-C, car c'est la langue avec laquelle je travaille le plus récemment.
Ces exemples n'incluaient pas l' :
opérateur, car je veux seulement manipuler int
s, et cet opérateur ajoute de nombreuses vérifications. Si votre réponse donne une solution pour gérer celle-ci, c'est parfait.
Si ma question nécessite des éclaircissements, veuillez la poser dans les commentaires. Merci de votre aide.
la source
Réponses:
Voici une interprétation théorique de votre problème.
Vous cherchez à générer de manière aléatoire des mots (expression algébrique) à partir d'une langue donnée (l'ensemble infini de toutes les expressions algébriques syntaxiquement correctes). Voici une description formelle d'une grammaire algébrique simplifiée prenant uniquement en charge l'addition et la multiplication:
Ici,
E
est une expression (c'est-à-dire un mot de votre langue) etI
est un symbole terminal (c'est-à-dire qu'il n'est plus développé) représentant un entier. La définition ci-dessus pourE
a trois règles de production . Sur la base de cette définition, nous pouvons construire au hasard une arithmétique valide comme suit:E
comme symbole unique du mot de sortie.I
par des nombres entiers aléatoires.Voici un exemple de l'application de ces algorithmes:
Je suppose que vous choisiriez de représenter une expression avec une interface
Expression
implémentée par les classesIntExpression
,AddExpression
etMultiplyExpression
. Les deux derniers auraient alors unleftExpression
etrightExpression
. Toutes lesExpression
sous-classes sont nécessaires pour implémenter uneevaluate
méthode qui fonctionne récursivement sur la structure arborescente définie par ces objets et implémente efficacement le modèle composite .Notez que pour la grammaire et l'algorithme ci-dessus, la probabilité de développer une expression
E
dans un symbole terminalI
est seulementp = 1/3
, tandis que la probabilité de développer une expression dans deux autres expressions est1-p = 2/3
. Par conséquent, le nombre attendu d'entiers dans une formule produite par l'algorithme ci-dessus est en fait infini. La longueur attendue d'une expression est soumise à la relation de récurrenceoù
l(n)
dénote la longueur attendue de l'expression arithmétique aprèsn
application des règles de production. Je suggère donc que vous affectiez une probabilité assez élevéep
à la règle deE -> I
sorte que vous vous retrouvez avec une expression assez petite avec une probabilité élevée.EDIT : Si vous craignez que la grammaire ci-dessus produise trop de parenthèses, regardez la réponse de Sebastian Negraszus , dont la grammaire évite ce problème très élégamment.
la source
m
itérations de 2 à 4, vous «ignorez» les règles de production récursives. Cela conduira à une expression de la taille attenduel(m)
. Notez cependant que cela n'est (théoriquement) pas nécessaire, car la probabilité de générer une expression infinie est nulle, même si la taille attendue est infinie. Cependant, votre approche est favorable car dans la pratique, la mémoire n'est pas seulement finie, mais aussi petite :)tout d'abord, je générerais l'expression en notation postfixe , vous pouvez facilement convertir en infixe ou évaluer après avoir généré votre expression aléatoire, mais le faire en postfix signifie que vous n'avez pas à vous soucier des parenthèses ou de la priorité.
Je garderais également un total cumulé du nombre de termes disponibles pour l'opérateur suivant dans votre expression (en supposant que vous vouliez éviter de générer des expressions mal formées), c'est-à-dire quelque chose comme ceci:
évidemment c'est du pseudo code donc il n'est pas testé / peut contenir des erreurs et vous n'utiliseriez probablement pas une chaîne mais une pile d'union comme le type
la source
2+4*6-3+7
est convertie en pile post-correction+ 7 - 3 + 2 * 4 6
(le haut de la pile étant le plus à droite). Vous poussez 4 et 6 et appliquez l'opérateur*
, puis vous repoussez 24. Vous sautez ensuite 24 et 2 et appliquez l'opérateur +, puis vous repoussez 26. Vous continuez de cette manière et vous constaterez que vous obtiendrez la bonne réponse. Notez que ce* 4 6
sont les premiers termes de la pile. Cela signifie qu'elle est effectuée en premier parce que vous avez déjà déterminé la priorité sans nécessiter de parenthèses.La réponse de blubb est un bon début, mais sa grammaire formelle crée trop de paranthèses.
Voici mon point de vue:
E
est une expression,I
un entier etM
est une expression qui est un argument pour une opération de multiplication.la source
Les parenthèses dans l'expression "dure" représentent l'ordre d'évaluation. Plutôt que d'essayer de générer directement le formulaire affiché, il vous suffit de dresser une liste d'opérateurs dans un ordre aléatoire et d'en dériver la forme d'affichage.
Nombres:
1 3 3 9 7 2
Les opérateurs:
+ * / + *
Résultat:
((1 + 3) * 3 / 9 + 7) * 2
Dériver la forme d'affichage est un algorithme récursif relativement simple.
Mise à jour: voici un algorithme en Perl pour générer le formulaire d'affichage. Parce que
+
et*
sont distributifs, il randomise l'ordre des termes pour ces opérateurs. Cela aide à empêcher les parenthèses de s'accumuler d'un côté.la source
(A + B) * (C + D)
sont jamais présentées dans les expressions générées, et il y a aussi beaucoup de parens imbriqués.Pour développer l'approche arborescente, disons que chaque nœud est soit une feuille soit une expression binaire:
Notez qu'une feuille n'est ici qu'un entier généré de manière aléatoire.
Maintenant, nous pouvons générer au hasard un arbre. Décider de la probabilité que chaque nœud soit une feuille nous permet de contrôler la profondeur attendue, bien que vous souhaitiez également une profondeur maximale absolue:
Ensuite, la règle la plus simple pour imprimer l'arborescence est d'enrouler
()
autour de chaque expression non-feuille et d'éviter de se soucier de la priorité de l'opérateur.Par exemple, si je mets entre parenthèses votre dernier exemple d'expression:
vous pouvez lire l'arbre qui le générerait:
la source
J'utiliserais des arbres. Ils peuvent vous donner un grand contrôle sur la génération des expressions. Par exemple, vous pouvez limiter la profondeur par branche et la largeur de chaque niveau séparément. La génération basée sur un arbre donne également la réponse déjà pendant la génération, ce qui est utile si vous voulez vous assurer que le résultat (et les sous-résultats) sont également assez difficiles et / ou pas trop difficiles à résoudre. Surtout si vous ajoutez un opérateur de division à un moment donné, vous pouvez générer des expressions évaluées en nombres entiers.
la source
Voici une interprétation légèrement différente de l'excellente réponse de Blubb:
Ce que vous essayez de construire ici est essentiellement un analyseur qui fonctionne à l'envers. Ce que votre problème et un analyseur ont en commun, c'est une grammaire sans contexte , celle-ci sous la forme Backus-Naur :
Les analyseurs commencent par un flux de terminaux (des jetons littéraux comme
5
ou*
) et essaient de les assembler en terminaux non (éléments composés de terminaux et d'autres terminaux non, tels quenumber
ouop
). Votre problème commence par des non terminaux et fonctionne à l'envers, en choisissant n'importe quoi entre les symboles "ou" (pipe) au hasard quand on en rencontre et en répétant récursivement le processus jusqu'à atteindre un terminal.Quelques autres réponses ont suggéré qu'il s'agit d'un problème d'arbre, qui l'est pour une certaine classe étroite de cas où il n'y a pas de terminaux non terminaux qui se référencent directement ou indirectement via un autre terminal non terminal. Puisque les grammaires le permettent, ce problème est vraiment un graphe orienté . (Les références indirectes par le biais d'un autre non terminal comptent également pour cela.)
Il y avait un programme appelé Spew publié sur Usenet à la fin des années 1980 qui a été initialement conçu pour générer des titres de tabloïdes aléatoires et s'avère également être un excellent véhicule pour expérimenter ces "grammaires inverses". Il fonctionne en lisant un modèle qui dirige la production d'un flux aléatoire de terminaux. Au-delà de sa valeur d'amusement (titres, chansons country, charabia anglais prononçable), j'ai écrit de nombreux modèles qui sont utiles pour générer des données de test allant du texte brut au XML au C. syntaxiquement correct mais non compilable malgré son âge de 26 ans. et écrit en K&R C et ayant un format de modèle laid, il compile très bien et fonctionne comme annoncé. J'ai préparé un modèle qui résout votre problème et l'ai publié sur pastebin car ajouter autant de texte ici ne semble pas approprié.
la source