Ich habe die Idee, zufällige mathematische Ausdrücke zu generieren und auszuwerten. Also habe ich beschlossen, es auszuprobieren und einen Algorithmus auszuarbeiten, bevor ich ihn zum Testen codiere.
Beispiel:
Hier sind einige Beispielausdrücke, die ich zufällig generieren möchte:
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]
Die einfachen und mittleren sind ziemlich direkt. Zufällige int
s durch zufällige Operatoren getrennt, hier nichts verrücktes. Aber ich habe einige Probleme mit etwas Sie beginnen , die eine der schaffen könnte hart und härter Beispiele. Ich bin mir nicht mal sicher, ob ein einziger Algorithmus mir die letzten beiden geben könnte.
Was ich überlege:
Ich kann nicht sagen, dass ich diese Ideen ausprobiert habe , weil ich nicht wirklich viel Zeit damit verschwenden wollte, in eine Richtung zu gehen, in der es überhaupt keine Chance gab, zu arbeiten. Trotzdem habe ich mir ein paar Lösungen überlegt:
- Bäume benutzen
- Reguläre Ausdrücke verwenden
- Verwenden einer verrückten "for-type" -Schleife (sicherlich die schlechteste)
Was ich suche:
Ich würde gerne wissen, welcher Weg Ihrer Meinung nach der beste ist, zwischen den von mir in Betracht gezogenen Lösungen und Ihren eigenen Ideen.
Wenn Sie einen guten Einstieg sehen, würde ich mich über einen Hinweis in die richtige Richtung freuen, z. B. mit dem Beginn des Algorithmus oder dessen allgemeiner Struktur.
Beachten Sie auch, dass ich diese Ausdrücke auswerten muss. Dies kann entweder nach der Generierung des Ausdrucks oder während seiner Erstellung erfolgen. Wenn Sie das in Ihrer Antwort berücksichtigen, ist das großartig.
Ich bin nicht auf der Suche nach sprachbezogenen Inhalten, aber für die Aufzeichnung denke ich darüber nach, sie in Objective-C zu implementieren, da dies die Sprache ist, mit der ich in letzter Zeit am meisten arbeite.
In diesen Beispielen war der :
Operator nicht enthalten , da ich nur int
s manipulieren möchte , und dieser Operator fügt viele Überprüfungen hinzu. Wenn Ihre Antwort eine Lösung für diese Frage liefert, ist das großartig.
Wenn meine Frage einer Klärung bedarf, bitte in den Kommentaren nachfragen. Danke für Ihre Hilfe.
quelle
Antworten:
Hier ist eine theoretische Interpretation Ihres Problems.
Sie möchten Wörter (algebraische Ausdrücke) aus einer bestimmten Sprache (der unendlichen Menge aller syntaktisch korrekten algebraischen Ausdrücke) zufällig generieren . Hier ist eine formale Beschreibung einer vereinfachten algebraischen Grammatik, die nur Addition und Multiplikation unterstützt:
Hierbei
E
handelt es sich um einen Ausdruck (dh ein Wort Ihrer Sprache) undI
um ein Endsymbol (dh es wird nicht weiter erweitert), das eine Ganzzahl darstellt. Die obige Definition fürE
hat drei Produktionsregeln . Basierend auf dieser Definition können wir eine gültige Arithmetik wie folgt zufällig erstellen:E
als einzelnes Symbol des Ausgangsworts.I
durch zufällige ganze Zahlen.Hier ist ein Beispiel für die Anwendung dieser Algorithmen:
Ich nehme an, Sie würden einen Ausdruck mit einer Schnittstelle darstellen,
Expression
die von Klassen implementiert wirdIntExpression
,AddExpression
undMultiplyExpression
. Die beiden letzteren hätten dann einleftExpression
undrightExpression
. AlleExpression
Unterklassen müssen eineevaluate
Methode implementieren , die die durch diese Objekte definierte Baumstruktur rekursiv bearbeitet und das zusammengesetzte Muster effektiv implementiert .Es ist zu beachten, dass für die obige Grammatik und den obigen Algorithmus die Wahrscheinlichkeit, einen Ausdruck
E
in ein Endsymbol zu erweitern,I
nur istp = 1/3
, während die Wahrscheinlichkeit, einen Ausdruck in zwei weitere Ausdrücke zu erweitern, gleich ist1-p = 2/3
. Daher ist die erwartete Anzahl von Ganzzahlen in einer Formel, die durch den obigen Algorithmus erzeugt wird, tatsächlich unendlich. Die erwartete Länge eines Ausdrucks hängt von der Wiederholungsrelation abwo
l(n)
bezeichnet die erwartete Länge des arithmetischen Ausdrucks nachn
Anwendungen der Produktionsregeln. Ich schlage daher vor, dass Siep
der Regel eine ziemlich hohe Wahrscheinlichkeit zuweisen,E -> I
sodass Sie einen ziemlich kleinen Ausdruck mit hoher Wahrscheinlichkeit erhalten.EDIT : Wenn Sie befürchten, dass die obige Grammatik zu viele Klammern erzeugt, lesen Sie die Antwort von Sebastian Negraszus , dessen Grammatik dieses Problem auf elegante Weise umgeht.
quelle
m
wir nach den Iterationen von 2 bis 4, dass Sie die rekursiven Produktionsregeln ignorieren. Dies führt zu einem Ausdruck der erwarteten Größel(m)
. Beachten Sie jedoch, dass dies (theoretisch) nicht erforderlich ist, da die Wahrscheinlichkeit, einen unendlichen Ausdruck zu generieren, Null ist, obwohl die erwartete Größe unendlich ist. Ihr Ansatz ist jedoch günstig, da in der Praxis das Gedächtnis nicht nur endlich, sondern auch klein ist :)Zunächst einmal würde ich den Ausdruck in der Postfix-Notation generieren. Sie können ihn nach dem Generieren Ihres zufälligen Ausdrucks problemlos in ein Infix konvertieren oder auswerten. Wenn Sie dies jedoch in der Postfix-Notation tun, müssen Sie sich keine Gedanken über Klammern oder Vorrang machen.
Außerdem würde ich die Anzahl der Begriffe, die dem nächsten Operator in Ihrem Ausdruck zur Verfügung stehen (vorausgesetzt, Sie möchten vermeiden, fehlerhafte Ausdrücke zu generieren), dh in etwa wie folgt zusammenfassen:
Dies ist offensichtlich Pseudocode, wird also nicht getestet / enthält möglicherweise Fehler, und Sie würden wahrscheinlich keine Zeichenfolge verwenden, sondern einen Stapel eines diskriminierten union-ähnlichen Typs
quelle
2+4*6-3+7
wird in einen Postfix -Stapel konvertiert+ 7 - 3 + 2 * 4 6
(der oberste Teil des Stapels befindet sich ganz rechts). Sie drücken 4 und 6 ab und wenden den Operator an*
, dann drücken Sie 24 wieder auf. Sie platzieren dann 24 und 2 und wenden den Operator + an, dann drücken Sie 26 wieder auf. Sie fahren auf diese Weise fort und finden die richtige Antwort. Beachten Sie, dass dies* 4 6
die ersten Begriffe auf dem Stapel sind. Das heißt, es wird zuerst ausgeführt, da Sie bereits die Priorität festgelegt haben, ohne dass Klammern erforderlich sind.Die Antwort von blubb ist ein guter Anfang, aber seine formale Grammatik schafft zu viele Klammern.
Hier ist meine Meinung dazu:
E
ist ein Ausdruck,I
eine Ganzzahl undM
ein Ausdruck, der ein Argument für eine Multiplikationsoperation ist.quelle
Die Klammern im "harten" Ausdruck geben die Reihenfolge der Bewertung an. Anstatt zu versuchen, das angezeigte Formular direkt zu generieren, erstellen Sie einfach eine Liste von Operatoren in zufälliger Reihenfolge und leiten die Anzeigeform des Ausdrucks daraus ab.
Zahlen:
1 3 3 9 7 2
Betreiber:
+ * / + *
Ergebnis:
((1 + 3) * 3 / 9 + 7) * 2
Das Ableiten der Anzeigeform ist ein relativ einfacher rekursiver Algorithmus.
Update: Hier ist ein Algorithmus in Perl, um das Anzeigeformular zu generieren. Da
+
und*
distributive sind, randomisiert es die Reihenfolge der Bedingungen für die Betreiber. Dies verhindert, dass sich die Klammern auf einer Seite aufbauen.quelle
(A + B) * (C + D)
wird nie in erzeugten Ausdrücken dargestellt, und es gibt auch eine Menge von verschachtelten Pars.Nehmen wir zum Erweitern des Baumansatzes an, dass jeder Knoten entweder ein Blatt oder ein binärer Ausdruck ist:
Beachten Sie, dass ein Blatt hier nur eine zufällig generierte Ganzzahl ist.
Jetzt können wir zufällig einen Baum erzeugen. Wenn Sie die Wahrscheinlichkeit bestimmen, dass jeder Knoten ein Blatt ist, können Sie die erwartete Tiefe steuern. Möglicherweise möchten Sie jedoch auch eine absolute maximale Tiefe:
Die einfachste Regel für das Drucken des Baums besteht darin,
()
jeden Nicht-Blatt-Ausdruck zu umbrechen und sich nicht um die Priorität des Operators zu sorgen.Zum Beispiel, wenn ich Ihren letzten Beispielausdruck in Klammern setze:
Sie können den Baum ablesen, der ihn erzeugen würde:
quelle
Ich würde Bäume benutzen. Sie können Ihnen eine gute Kontrolle über die Erzeugung der Ausdrücke geben. Sie können z. B. die Tiefe pro Zweig und Breite jeder Ebene separat begrenzen. Die baumbasierte Generierung gibt bereits während der Generierung die Antwort. Dies ist hilfreich, wenn Sie sicherstellen möchten, dass auch das Ergebnis (und die Unterergebnisse) schwer genug und / oder nicht zu schwer zu lösen sind. Insbesondere wenn Sie irgendwann einen Divisionsoperator hinzufügen, können Sie Ausdrücke generieren, die ganze Zahlen ergeben.
quelle
Hier ist eine etwas andere Einstellung zu Blubbs hervorragender Antwort:
Was Sie hier erstellen möchten, ist im Wesentlichen ein Parser, der umgekehrt arbeitet. Was Ihrem Problem und einem Parser gemeinsam ist, ist eine kontextfreie Grammatik , diese in Backus-Naur-Form :
Parser beginnen mit einem Stream von Terminals (wörtliche Token wie
5
oder*
) und versuchen, sie zu Nicht-Terminals zusammenzusetzen (Dinge, die aus Terminals und anderen Nicht-Terminals wienumber
oder bestehenop
). Ihr Problem beginnt mit Nicht-Endgeräten und funktioniert in umgekehrter Reihenfolge. Wählen Sie bei Auftreten eines Problems nach dem Zufallsprinzip zwischen den Symbolen "oder" (Pipe) und wiederholen Sie den Vorgang rekursiv, bis Sie ein Terminal erreichen.Einige andere Antworten haben darauf hingewiesen, dass dies ein Baumproblem ist, und zwar für eine bestimmte enge Klasse von Fällen, in denen es keine Nichtterminale gibt, die sich direkt oder indirekt über ein anderes Nichtterminale beziehen. Da Grammatiken dies zulassen, handelt es sich bei diesem Problem in Wirklichkeit um einen gerichteten Graphen . (Indirekte Verweise durch andere Nichtterminale zählen auch dazu.)
Es gab ein Programm namens Spew, das Ende der 1980er Jahre im Usenet veröffentlicht wurde und ursprünglich dazu gedacht war, zufällige Boulevard-Schlagzeilen zu generieren. Es war auch ein großartiges Mittel, um mit diesen "umgekehrten Grammatiken" zu experimentieren. Es funktioniert durch Lesen einer Vorlage, die die Produktion eines zufälligen Datenstroms von Terminals steuert. Abgesehen von seinem Vergnügungswert (Schlagzeilen, Country-Songs, aussprechbares Englisch-Kauderwelsch) habe ich zahlreiche Vorlagen geschrieben, mit denen sich Testdaten generieren lassen, die von Klartext über XML bis hin zu syntaktisch korrektem, aber nicht kompilierbarem C reichen. Trotzdem ich 26 Jahre alt bin und in K & R C geschrieben und mit einem hässlichen Vorlagenformat, kompiliert es ganz gut und funktioniert wie beworben. Ich habe eine Vorlage erstellt, die Ihr Problem löst, und sie im Pastebin veröffentlicht da das Hinzufügen von so viel Text hier nicht angemessen erscheint.
quelle