Entwickler-Ecke

Algorithmen, Optimierung und Assembler - Zahlenrätsel "Des chiffres et des lettres"


Mathematiker - So 06.01.13 11:31
Titel: Zahlenrätsel "Des chiffres et des lettres"
Hallo,
ich habe wieder einmal ein Problem, dass ich zum einen sehr interessant finde, zum anderen mir aber auch schlaflose Nächte bereitet hat.
Bekannt wurde es unter dem Namen "Des chiffres et des lettres" aus einer Spielshow des französischen Fernsehens. Mittlerweile ist es auch in anderen Ländern populär geworden. In den USA gibt es z.B. T-Shirts mit der Aufschrift
chiffre

Die Aufgabe ist, aus einer Menge gegebener Zahlen durch schrittweise Addition, Subtraktion, Multiplikation und Division eine Zielzahl zu ermitteln. Dabei darf und muss jede gegebene Zahl und jedes Zwischenergebnis genau einmal verwendet werden!
Beispiel: Aus den Zahlen 1 bis 6 ist die 278 zu konstruieren
4 · 5 = 20
3 + 20 = 23
23 · 6 = 138
1 + 138 = 139
139 · 2 = 278
Als einzelne Gleichung ergibt sich damit ((3 + 4 · 5) · 6 + 1) · 2 = 278.
Im Allgemeinen existieren entweder mehrere Lösungen oder aber gar keine. Aus den Zahlen n = 4, 5, 6, 7, 8 kann z = 29 zum Beispiel nicht konstruiert werden.

Meine Umsetzung als Programm nutzt vier Routinen, die sich gegenseitig rekursiv aufrufen. Elegant ist das nicht und vor allem für mehrere Ausgangszahlen wird es ziemlich langsam.
Schön wäre es, wenn jemand von Euch eine bessere Idee hätte.

Im Programm werden 4 bis 8 Ausgangszahlen betrachtet. Die möglichen Operationen Addition, ... können ausgewählt werden.
Mit dem Schalter "Problem lösen" werden alle möglichen Lösungen gesucht. Über den Schalter "Folge berechnen" wird beginnend bei der eingegebenen Zielzahl jeweils 1 Lösung ermittelt, die Zielzahl erhöht, erneut gesucht usw. bis eine Zielzahl ohne Lösung gefunden wird.

Beste Grüße
Mathematiker

Rev 1: Algorithmus korrigiert und Folgenberechnung vorerst entfernt.
Rev 2: Ein weiterer Fehler ist korrigiert. Es wurden nicht alle gefundenen Lösungen ausgegeben. Jetzt müsste es korrekt sein.
Rev 3: Die Folgenberechnung ist wieder möglich.
Rev 4: Die berechneten Ergebnisse können in einer Textdatei gespeichert werden.
Rev 5: Die Tests und damit auch die Rechenzeit sind deutlich auf etwa 25% gekürzt.
Rev 6: Es werden nur noch Ergebnisse angezeigt, die nach dem Kommutativgesetz voneinander verschieden sind.
Rev 7: Weitere deutliche Beschleunigung der Berechnung durch Reduktion der Rekursionsaufrufe.
Rev 8: Mit einer 3.Berechnung können nun alle möglichen Zielzahlen in einem festgelegten Bereich ermittelt werden. Besonders schnell!
Rev 9: "Folge berechnen" ist entfernt, da die Routine "Ziele suchen" die gleiche Aufgabe wesentlich schneller absolviert. Weitere Änderungen verkürzen die Rechenzeiten noch einmal.
Rev 10: Weitere Änderungen, vor allem der Tests.
Rev 11: Fehlerhafte Version 10 korrigiert.
Rev 12: Beim Aufruf der rekursiven Methoden wird kein Feld mehr direkt übergeben, d.h. wieder etwas schneller.


Mr_Emre_D - So 06.01.13 20:36

Ich würds auch per Bruteforce lösen:

Die Zahlen permutieren und bei jedem Perumationsschritt alle Kombinationen für die Rechenopeartionen ermitteln - den Wert evaluieren und zwischenspeichern.
Letzendlich würde ich dann diejenigen ausgeben, die gleich "Zielzahl" sind.

Hmm... es ist aber ein interessantes Problem.
Gibts eig. schon (ordentlich) mathematische Ansätze zu diesem Problem?


Mathematiker - So 06.01.13 20:48

Hallo,
im Endeffekt läuft es bei mir ja auch mit Bruteforce. Und das dauert eben, gerade wenn es dann 7 oder 8 Ausgangszahlen sind.
Als Beispiel: Die Berechnung der Zerlegungen von 1 bis 18920 mit den Ausgangszahlen 2, 3, 5, 7, 11, 13, 17 und 19 benötigte auf meinem 2,6 GHz-PC über 70 Stunden.
In englischsprachigen Ländern wurde das Problem unter dem Namen "Letters and Numbers" (oder umgedreht) bekannt, zuerst in Australien, später in den USA.
user profile iconMr_Emre_D hat folgendes geschrieben Zum zitierten Posting springen:
Gibts eig. schon (ordentlich) mathematische Ansätze zu diesem Problem?

Trotz ausführlicher Netzsuche habe ich noch nichts gefunden. Leider.

Beste Grüße
Mathematiker


Martok - So 06.01.13 22:04

Kurze Regelfrage... was ist mit Nicht-Ganzen und negativen Zwischenergebnissen? Und Zwischenergebnissen, die gleich Zahlen sind die schon im Pool sind (ist [2,3,6]->12 als 2*3,6+6,12 möglich)?
Und dann wäre da ja noch das Problem der vielfachen äquivalenten Lösungen. Da waren wir ja schonmal beim Gleichungen vereinfachen ;)

Ansonsten: würde das stimmen? Die Rechnungen sind jeweils von rechts nach links zu lesen, das war grad einfacher programmiert :roll: Ist ein dummes Bruteforce, also auch keine neue Idee.

Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
pool is   1,2,3,4
target   9
found  3--6,-3*2,1-4
found  3--6,2*-3,1-4
found  6--3,2*3,1-4
found  6--3,3*2,1-4
found  5+4,6-1,2*3
found  4+5,6-1,2*3
found  10-1,6+4,2*3
found  4--5,1-6,2*3
found  6--3,1-4,2*3
found  10-1,4+6,2*3
found  3+6,4-1,2*3
found  6+3,4-1,2*3
found  5+4,6-1,3*2
found  4+5,6-1,3*2
found  10-1,6+4,3*2
found  4--5,1-6,3*2
found  6--3,1-4,3*2
found  10-1,4+6,3*2
found  3+6,4-1,3*2
found  6+3,4-1,3*2
found  11-2,12-1,3*4
found  10-1,12-2,3*4
found  12-3,1+2,3*4
found  12-3,2+1,3*4
found  11-2,12-1,4*3
found  10-1,12-2,4*3
found  12-3,1+2,4*3
found  12-3,2+1,4*3
done


PS: mit Abbruch nach erstem Fund:

Quelltext
1:
2:
3:
4:
5:
pool is  1,2,3,4,5,6
target  278
found  139*2,138+1,23*6,20+3,4*5
done
took  78631  ms


Mathematiker - So 06.01.13 22:32

Hallo Martok,
user profile iconMartok hat folgendes geschrieben Zum zitierten Posting springen:
Kurze Regelfrage... was ist mit Nicht-Ganzen und negativen Zwischenergebnissen? Und Zwischenergebnissen, die gleich Zahlen sind die schon im Pool sind (ist [2,3,6]->12 als 2*3,6+6,12 möglich)?

Negative Zwischenergebnisse sind zugelassen, gebrochene aber nicht, d.h. die Division muss "aufgehen". Tritt ein Zwischenergebnis auf, dass evtl. einer der Zahlen entspricht, so wird trotzdem mit diesem einfach weitergerechnet.

user profile iconMartok hat folgendes geschrieben Zum zitierten Posting springen:
Und dann wäre da ja noch das Problem der vielfachen äquivalenten Lösungen. Da waren wir ja schonmal beim Gleichungen vereinfachen ;)

Äquivalente Lösungen habe ich im Moment als weitere gezählt. Das Problem mit der Vereinfachung verfolgt uns scheinbar immer wieder.
Ich hatte schon einmal die Idee aus dem Term einen UPN-Term zu machen. Vielleicht könnte man dann einfacher äquivalente ausfiltern. Allerdings schrecke ich davor noch zurück. Ich mag UPN einfach nicht.

Für Dein Beispel pool is 1,2,3,4 / target 9 hat Du (ohne Division) 28 Lösungen, ich nur 20, also sind mir wohl ein paar abhanden gekommen.
Für 1,2,3,4,5,6 und target 278 braucht mein Programm nur rund 1 Sekunde für das Testen aller Möglichkeiten.

Beste Grüße
Mathematiker

Nachtrag: Ich habe gerade gesehen, dass mir alle Lösungen fehlen, die mit einer Subtraktion mit einem negativen Ergebnis beginnen.
Nachtrag 2: Ich habe die Lösungen doch, nur in der Form (4-1)+(2*3). Einige bei Dir verschiedene, sind bei mir ausgeblendet.
Nachtrag 3: Mein Algorithmus hat einen entscheidenden Fehler. Es werden nicht alle Möglichkeiten getestet. Sehr ärgerlich.
Nachtrag 4: Ich habe (hoffe ich) den Fehler im Algorithmus entfernt. Vorerst gibt's auch keine Folgenberechnung mehr. Die machte den Quelltext unübersichtlich.


Marc. - Mo 07.01.13 13:20

user profile iconMathematiker hat folgendes geschrieben Zum zitierten Posting springen:
Negative Zwischenergebnisse sind zugelassen, gebrochene aber nicht, d.h. die Division muss "aufgehen".

Damit kann man die Frage in dem Bild in Deinem ersten Post ganz klar mit nein beantworten. Jedenfalls in dem von Dir erlaubten Zahlenraum.
(Ansonsten ginge 8/(3-5/2)=16)

Für einen Algorithmus müsste man wohl erst einmal die Existenz einer Lösung nachweisen bzw. voraussetzen können. Allein das ist wohl schon schwierig genug. :nixweiss:


Mathematiker - Mo 07.01.13 13:27

Hallo Marc.,
user profile iconMarc. hat folgendes geschrieben Zum zitierten Posting springen:
Damit kann man die Frage in dem Bild in Deinem ersten Post ganz klar mit nein beantworten.

Wow, ich habe bisher noch nicht geprüft, ob dass auf dem T-Shirt möglich ist. :autsch:
Du hast vollkommen recht. In der Original-Spielshow musste die Division aber aufgehen. Obwohl es mit meinen Kenntnissen der französischen Sprache nicht so gut aussieht, habe ich mir auf "TV 5" die Sendung ab und an mal angesehen.

Lässt man gebrochene Zwischenergebnisse zu, heißt das für das Programm, dass der Algorithmus deutlich schwerer wird.

In der Revision 2 habe ich einen blöden Fehler beseitigt. Es wurden nicht alle gefundenen Lösungen ausgegeben. Jetzt müsste es korrekt sein.

Beste Grüße
Mathematiker

Nachtrag: Das T-Shirt ist wohl für richtige Nerds. :mrgreen: Ein "normaler" US-Amerikaner wird die Aufgabenstellung gar nicht verstehen, geschweige denn eine Lösung finden.
Lässt man sogar Kommas zu, geht auch: 8 * (3 - (,5 * 2)) = 16.
Interessant ist Nerds-Gifts [http://www.nerdygeekygifts.com/Products/T-Shirts/can-you-make-16-using-four-digits-math-puzzle-t-shirt-46.php].

Nachtrag 2: In der Revision 3 ist die Folgenberechnung wieder möglich.


Tranx - Mo 07.01.13 16:35

Also, bei 6 Zahlen gibt es oft schon 3000 - mehr als 6000 Lösungen. Das ist schon gewaltig. Obwohl wahrscheinlich die rel. Lösungshäufigkeit nicht ansteigt. Aber mit Permutationen hab ich das nicht so.


Mathematiker - Mo 07.01.13 17:01

Hallo Tranx,
user profile iconTranx hat folgendes geschrieben Zum zitierten Posting springen:
Also, bei 6 Zahlen gibt es oft schon 3000 - mehr als 6000 Lösungen. Das ist schon gewaltig.

Das finde ich auch überraschend.
Interessanter finde ich aber die Berechnung von mindestens einer Lösung für aufsteigende Zielwerte (Folge berechnen). Ich habe gerade für die Ausgangszahlen 2, 3, 5, 7, 11, 13, 17 für alle Zahlen von 1 bis 1800 wenigstens eine Darstellung ermitteln lassen. Knapp 25 Minuten Rechenzeit sind ziemlich viel.

Beste Grüße
Mathematiker


Martok - Mo 07.01.13 17:24

Das ist natürlich der Vorteil von meiner Lösung. Da werden alle möglichen Ergebnisse berechnet, die sich mit dem Pool erreichen lassen, das wird dann nur nach dem target gesiebt. Kann man natürlich dann andersrum rangehen und von dieser Liste wiederum min/max nehmen, dann weißt du, dass du alle erzeugbaren Werte dazwischen kennst ;)


Mathematiker - Mo 07.01.13 18:46

Hallo Martok,
user profile iconMartok hat folgendes geschrieben Zum zitierten Posting springen:
Das ist natürlich der Vorteil von meiner Lösung. Da werden alle möglichen Ergebnisse berechnet, die sich mit dem Pool erreichen lassen, das wird dann nur nach dem target gesiebt.

Klingt sehr gut. Die Lösung würde mich sehr interessieren. :D

Beste Grüße
Mathematiker


Delphi-Laie - Mo 07.01.13 22:18

Wenn "Brute Force" und Permutierung Grundlagen der Lösung sind, ist dann ein möglichst schneller Permutationsalgorithmus vorteilhaft? M.E. ja!


Mathematiker - Mo 07.01.13 22:57

Hallo Delphi-Laie,
user profile iconDelphi-Laie hat folgendes geschrieben Zum zitierten Posting springen:
... ist dann ein möglichst schneller Permutationsalgorithmus vorteilhaft?

Du hast schon recht und wenn ich es richtig verstanden habe, nutzt Martok auch alle Permutationen der Zahlen, Operationen und Klammermöglichkeiten.

Dies sind bei n Zahlen und alle Operationen ziemlich viele Möglichkeiten. Nach meiner Abschätzung gleich n! (n-1)^4 C(n), wobei C(n) = (2n)! / ((n+1)! n!) die n.te Catalansche Zahl ist, welche die Anzahl der Klammermöglichkeiten eines Berechnungsterms mit n Zahlen angibt.
n! sind die Permutationen der n Zahlen und (n-1)^4 die Anzahl der Operationsmöglichkeiten zwischen den Zahlen.
Für 6 Zahlen sind das 59,4 Millionen, für 7 Zahlen schon 2,8 Milliarden und für 8 Zahlen 138 Milliarden Möglichkeiten; bei einer einzigen Suche!

Den größten Zeitbedarf hat in meinem Programm die Überprüfung, ob das gewünschte Resultat erzeugt wird. Und das braucht seine Zeit. Deshalb hoffe ich ja, dass jemand eine vielleicht bessere Idee hat.

Beste Grüße
Mathematiker


Horst_H - Di 08.01.13 06:13

Hallo,

Warum nicht einen UPN- Parser.
Ein Stack mit den vorhandenen n Zahlen und einen mit den n-1 möglichen operatoren , die immer auf zwei Elemente wirken.
Den Zahlen Stack mit allen Permutationen der n Zahlen füttern und die Operatoren mit den Zahlen 1..4 symbolisch für +,-,*,/
Wenn man einen Operator ändert, hat man ja immer noch das Ergbebnis der Berechnung der vorherigen Zahlen auf dem Stack.
Zu früh am Tag, später mehr

Gruß Horst


Tranx - Di 08.01.13 14:24

Ein Problem ist noch zu bedenken:

Neben der Kombinatorik der Operatoren gibt es ja noch die der Klammern:

(5 * 4) + 6 ist nicht gleich 5 *(4 + 6). Die Operatoren jedoch sind in der gleichen Reihenfolge.


Mathematiker - Di 08.01.13 14:37

Hallo Tranx,
user profile iconTranx hat folgendes geschrieben Zum zitierten Posting springen:
Neben der Kombinatorik der Operatoren gibt es ja noch die der Klammern: ...

Aus diesem Grund werden die zu untersuchenden Möglichkeiten so schnell groß.
user profile iconTranx hat folgendes geschrieben Zum zitierten Posting springen:
(5 * 4) + 6 ist nicht gleich 5 *(4 + 6).

Bei UPN wird der erste Term zu 5 4 * 6 +, der zweite zu 5 4 6 + *, d.h. sie sind unterscheidbar.

Vor Jahren habe ich mir einmal die Mühe gemacht, eine Routine zu schreiben die AOS-Terme in UPN transformiert. Die Umkehrung habe ich leider nicht. Kennt jemand von Euch für die Transformation von UPN nach AOS eine Quelle? Hier in der EE bin ich noch nicht fündig geworden.

Beste Grüße
Mathematiker


Horst_H - Di 08.01.13 15:55

Hallo,

ich habe die Vorstellung das ich alle z.B 6 Zahlen auf den Stack schiebe und anschließend die 5 Operatoren.
Ich hoffe, alle Berechnungsmöglichkeiten durch die Permutation der Zahlen zu erfassen.
(5 * 4) + 6 = UPN 5 4 * 6 + = UPN 6 5 4 * +


Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
Zahl   OP_erator    Ber_echnung
 1
 
 2      +,-,*,/   Zahl1 OP1 Zahl2 
 
 3     +,-,*,/   (Zahl1 OP1 Zahl2) OP2 Zahl3
 
 4     ....

Bei 6 Zahlen und 4 Operatoren pro Berechnung habe ich folgende Kombinationen an Berechnungen
4( Berechung 1) +4*4( Berechung 2) +4*4*4 +4*4*4*4 +4*4*4*4*4 = 1364 bei einer Zahlenkombination.
Da 1+k+k²+k³+...k^n = ( k^(n+1)-1)/(k-1) ist ergibt sich hier auch so
(4^6-1)/(4-1) -1 = 1364
Jetzt gibt es 720 Permutationen der Zahl1..Zahl6 so sind es dann 982080 Berechungen an der Zahl.
Zitat:
Für 6 Zahlen sind das 59,4 Millionen

:?!?:

Welche Berechnung wird denn nicht erfasst?

Gruß Horst


Martok - Di 08.01.13 16:07

Die Sache ist sogar noch einfacher, da du Operatoren mehrfach verwenden darfst. Da brauchst du nur die Zahlen auf einen Stack schieben, und diesen in einer Art Tiefensuche für jede Operation einmal verwürfeln. Ich bin mir relativ sicher, dass mein Code dazu mindestens doppelt so viel tut wie er muss (das könnte auch quadratisch sein), aber dafür ist er dank JS-Array Comprehensions sehr einfach ;)


Mathematiker - Di 08.01.13 16:56

Hallo Horst_H,
user profile iconHorst_H hat folgendes geschrieben Zum zitierten Posting springen:
Jetzt gibt es 720 Permutationen der Zahl1..Zahl6 so sind es dann 982080 Berechungen an der Zahl.
Zitat:
Für 6 Zahlen sind das 59,4 Millionen
:?!?:
Welche Berechnung wird denn nicht erfasst?

Nach Deiner Idee werden, wenn ich es richtig verstehe, für vier Operanden alle Möglichkeiten

Quelltext
1:
  ((a • b) • c) • d                    

berechnet. Dabei fehlt aber u.a. die Variante (a • b)•(c • d).
Wie ich schon weiter oben geschrieben habe, beschreibt die Catalansche Zahl C(n) (http://de.wikipedia.org/wiki/Catalan-Zahl) die Anzahl der Klammermöglichkeiten/Paarbildungen. Für n = 4 Elemente werden es C(n-1) = C(3) = 5. Diese sind:

Quelltext
1:
2:
3:
4:
5:
  (a • b)•(c • d) 
  (a •(b • c)) • d
  a • ((b • c) • d) 
  ((a • b) • c) • d
  a • (b • (c • d))

Oben hatte ich den Fehler C(n) gemacht; es sind aber "nur" C(n-1).
Bei 4 Operationen kann jede auch mehrfach auftreten, d.h. die Operationen permutieren nicht nur, sondern bilden eine Variation mit Wiederholung von 4 (Operationen) zu n-1 Verknüpfungen, d.h. = 4^(n-1). Mein 2.Fehler oben.
Mit den Permutationen der n gegebenen Zahlen wird damit insgesamt
n! 4^(n-1) C(n-1) = n! 4^(n-1) (2(n-1))! / (n! (n-1)!) = 4^(n-1) (2n-2)! / (n-1)!
Für n = 6, 7, 8 werden es insgesamt 30,9 Millionen , 2,7 Milliarden und 283 Mlliarden Möglichkeiten. Für zunehmende n wachsen die Möglichkeiten also noch stärker an.
Bei meiner ersten Berechnung hatte ich leider 2 Fehler. :autsch: Entschuldigung. :oops:

Hallo Martok,
user profile iconMartok hat folgendes geschrieben Zum zitierten Posting springen:
Die Sache ist sogar noch einfacher, da du Operatoren mehrfach verwenden darfst. Da brauchst du nur die Zahlen auf einen Stack schieben, und diesen in einer Art Tiefensuche für jede Operation einmal verwürfeln.

Diese Idee klingt wirklich gut.

Nebenbei: Bei meinem eigenen Versuch rufe ich die oben berechnete Zahl die Methoden für Addition, Subtraktion, usw. rekursiv auf und übergebe ein Feld von Zahlen.
Dies erschien mir sehr aufwendig. Deshalb habe ich es mit genau einem zweidimensionalen Feld versucht, bei dem im 2.Index die Rekursionstiefe gespeichert wird. Leider dauert das noch länger.

Beste Grüße
Mathematiker

Nachtrag: Ich habe in meiner Lösung die Aufrufe der Routinen gezählt. Für 6 Zahlen waren es einmal 112 Millionen, ein zweites Mal 152 Millionen. Hoffentlich habe ich mich schon wieder verrechnet. Ob am Programm etwas nicht stimmt :shock: , muss ich testen. Die unterschiedlichen Werte können aber dadurch entstehen, dass die Aufrufe fehlen, die wegen nicht "aufgehenden" Divisionen entfallen.

Nachtrag 2: Die überzähligen Tests haben mich beschäftigt. Und siehe da, im Programm waren zwei unnötige Tests je Routine. Damit konnte ich die Anzahl der Tests und somit auch die Rechenzeit deutlich auf etwa 25% kürzen. :D


Delphi-Laie - Di 08.01.13 22:34

user profile iconMathematiker hat folgendes geschrieben Zum zitierten Posting springen:
Den größten Zeitbedarf hat in meinem Programm die Überprüfung, ob das gewünschte Resultat erzeugt wird. Und das braucht seine Zeit. Deshalb hoffe ich ja, dass jemand eine vielleicht bessere Idee hat.


Warum nicht einfach vom Probeergebnis das gewünschte subtrahieren (oder umgekehrt) und auf 0 vergleichen?


Tranx - Mi 09.01.13 00:55

Eine weitere Einsparung von Rechenzeit kommt m.E. nur noch durch logische Ausschlüsse zustande. Was meine ich damit?

nehmen wir die Zahlen 3 4 5 6 und das Ergebnis 7

Dann wird mit Sicherheit die Anzahl der * als Operatoren nicht allzu häufig sein, da ja schon 3*4 > 7 ist. Und wenn, dann nicht im Zusammenhang mit +, sondern nur mit / und - . Aber das umzusetzen, um Permutationen von vorneherein auszuschließen, sie also garnicht erst zu testen, ist sicher sehr aufwändig.

Denn 283 Millionen Möglichkeiten durchzuchecken, das dauert nun mal.


Horst_H - Mi 09.01.13 10:13

Hallo,

die Angabe von user profile iconMathematiker brachte mich auf die Idee es mal als UPN umzustellen
Zitat:
(a • b)•(c • d)
(a •(b • c)) • d
a • ((b • c) • d)
((a • b) • c) • d
a • (b • (c • d))


Als Ergebnis:

Quelltext
1:
2:
3:
4:
5:
6:
  (a op1 b) op2 (c op3 d)       a  b  op1 c  d  op3 op2 
  
  (a op1(b op2 c)) op3 d        d  a  b  c op2 op1 op3
  a op1 ((b op2 c) op3 d)       a  d  b  c op2 op3 op1
  ((a op1 b) op2 c) op3 d       d  c  a  b op1 op2 op3
  a op1 (b op2 (c op3  d))      a  b  c  d op3 op2 op1


Nur die Variante mit nicht überlappenden Klammern muss anders gerechnet werden, die folgenden werden alle mit einer Permutation von [a,b,c,d] erfasst.
Vielleicht muss man "nicht überlappenden Klammern" anders formulieren.
Aber sicher lässt sich für diese Fälle ein Algorithmus finden, in wieviele Gruppen zu 2,3 elemtentigen Klammern man einen Term aufspalten kann.

Gruß Horst


Delete - Mi 09.01.13 11:31

http://www.mathisfunforum.com/index.php


Mathematiker - Mi 09.01.13 14:44

Hallo,
trotz der vielen Hinweise auf UPN (Danke dafür :flehan: ) habe ich erst einmal meinen Algorithmus weiterentwickelt.

Will ich a • b • c • d berechnen, so gehe ich im Moment so vor, dass ich zuerst für alle Operationen a • b und b • a mit jedem möglichen Paar a,b aus den Ausgangszahlen das Ergebnis ermittle.
Mit dem Ergebnis e verändere ich die Aufgabe zu e • c • d und verfahre erneut so.

Nun sind a • b und b • a für die Addition und die Multiplikation die gleichen Aufgaben. Aus diesem Grund betrachte ich bei + und * nur noch a • b, für alle Paare a,b. Das reduziert den Aufwand noch einmal deutlich.
Da ich zusätzlich bei der Ausgabe einer Lösung Summanden und Faktoren der Größe nach sortiere, erhalte ich jetzt nur noch Lösungen, die nicht nach dem Kommutativgesetz identisch sind. Für das Assoziativgesetz habe ich noch keine Idee.
Insgesamt wird aber die Rechenzeit nochmals geringer und als Lösungen erhält man nur derartige, die wirklich entsprechend dem Kommutativgesetz voneinander verschieden sind.

Die Berechnung der Darstellungen von 1 bis 1800 mit den Ausgangszahlen 2, 3, 5, 7, 11, 13, 17 dauert jetzt nur 204 Sekunden und nicht mehr 25 Minuten.
Ich bin erst einmal zufrieden. Es geht aber bestimmt noch mehr.

user profile iconhathor hat folgendes geschrieben Zum zitierten Posting springen:
http://www.mathisfunforum.com/index.php

Verstehe ich leider nicht. Unter dieser Adresse sind sehr viele interessante Text zu finden. Welche meinst Du konkret?

Beste Grüße
Mathematiker


Mathematiker - Mi 09.01.13 20:30

Hallo,
ich habe jetzt einmal die maximale Anzahl der Tests bei meinem Algorithmus durchgerechnet.

Je Rekursionsstufe werden für n Zahlen genau i(i-1) Paare bei der Subtraktion und Division und i(i-1)/2 bei Addition und Multiplikation getestet; insgesamt also 3i (i-1).
Gesucht wird für n, n-1, ..., 2 Zahlen, d.h. die maximale Testzahl wird

Quelltext
1:
Produkt 3·i·(i-1) (für i = 2,...,n) = 3^(n-1) n!² / n                    

Für n = 6, 7, 8 Zahlen sind das 20,9 Millionen, 2,6 Milliarden bzw. 444 Milliarden. Dies sind etwa die Werte bei vollständigem Test aller Permutationen.
Bei einem Testlauf wird dies aber deutlich weniger sein, da jede nicht aufgehende Division die Berechnung beschleunigt. Im günstigsten Fall sind es bei n = 6, 7, 8 Zahlen "nur" 2,7 Millionen, 232 Millionen bzw. 26 Milliarden Tests.

Bisher habe ich im Programm unnötigerweise die rekursiven Rufe bis einschließlich n = 2 durchgeführt. Die Änderung (Revision 7) auf n > 2 ergibt die gleichen Ergebnisse bei Einsparung einer Unmenge bedeutungsloser Aufrufe der Prozeduren.
Im Ergebnis brauche ich jetzt für die Zerlegungen von 1 bis 1800 mit den Zahlen 2, 3, 5, 7, 11, 13, 17 nur noch 60 Sekunden. Das sind gerade einmal 4 % der am Anfang benötigten Zeit. :dance2:

Beste Grüße
Mathematiker

Wichtiger Nachtrag:
Das Programm (8.Revision) enthält jetzt eine 3.Berechnungsmethode, das "Ziele suchen".
Dazu habe ich Martoks hervorragende Idee umgesetzt.

Mit den Ausgangszahlen werden alle möglichen Ergebnisse ermittelt. Liegen sie im Bereich "von" - "bis", erscheinen sie in der Liste.
Für diesen Bereich "von" - "bis" habe ich als untere Grenze 0 festgelegt. Außerdem darf "bis" nur maximal 100000 größer sein als "von"; sonst bekommt die Listbox akute Probleme.

Für mein Standardproblem der 1800 Zerlegungen, s.o., brauche ich jetzt noch 3 Sekunden; 0,2 % der Anfangszeit. Dabei benötigt die Vorbereitung und direkte Ausgabe in die Listbox allein 2 Sekunden.
Aus den 70 Stunden für 1 bis 18920 mit den Ausgangszahlen 2, 3, 5, 7, 11, 13, 17 und 19 sind noch 475 s geworden.
So etwas finde ich toll. :dance2: :dance2:


Horst_H - Do 10.01.13 19:13

Hallo,

Wie wäre es statt in 475 Sekunden in 5 ;-).
OK ist in integer, aber das ist ja nicht die Bremse.
Ich habe mein Verfahren, bei dem einfach nur die nächste Zahl mit dem vorherigen Ergebnis verknüpft wird, leider noch nicht komplett richtig, denn bei einer Division als letzter Operation kommt 647 div 2 = 323 durch, obwohl der Rest 1 ist. :-(

Aber es ja meist zig Lösungen ( 18619 hat bei mir "nur" 14 Varianten )
Als Beispiel mal eine Ausgabe für 4711 als Zielzahl:

Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
Ergebnis 4711
Auf dem Ausgangsstack stehen
 '7'   5 '6'  19 '5'  11 '4'   7 '3'  17 '2'  13 '1'   3 '0'   2 
Operatoren: * + * - * - /
Dort steht umgeschrieben:
((((((5*19)+11)*7)-17)*13)-3)div 2
Auf dem Stack stehen dann die Zwischenergebnisse und ganz oben das Endergebnis.
 '7'   5 '6'  95 '5' 106 '4' 742 '3' 725 '2'9425 '1'9422 '0'4711

Permutationen 40320
Berechnungen 171.263.844 statt 40320 * 4^7 = 660.602.880 
00:00:05.075
 94.824 Cpu-Takte pro Berechnung


Ich schaue mal, ob ich nicht überlappende Klammern überhaupt einbauen kann.

Gruß Horst
P.S
Ich habe mal spasseshalber die Berechung abgebrochen wenn jedes Ergebnis von 0 bis 18720 mindestens einmal vorkam. Das war in 2,5 Sekunden der Fall, aber die Divisionen muss nochmals genauer untersuchen.

EDIT:
Programm siehe unten


Mathematiker - Do 10.01.13 20:07

Hallo Horst_H,
user profile iconHorst_H hat folgendes geschrieben Zum zitierten Posting springen:
Ich habe mal spasseshalber die Berechung abgebrochen wenn jedes Ergebnis von 0 bis 18720 mindestens einmal vorkam. Das war in 2,5 Sekunden der Fall, ...

Das klingt wirklich sehr gut und vor allem interessant.
Leider kann ich Deinen Quelltext nicht compilieren. Den Befehl PtrUint kennt mein Delphi 5 nicht.
Mein Hauptproblem ist aber, dass ich die vielen Pointer-Operationen noch nicht verstehe. Wird wohl wieder eine lange Nacht.

In der Revision 9 habe ich "Folge berechnen" entfernt, da die Routine "Ziele suchen" die gleiche Aufgabe wesentlich schneller absolviert. Weitere Änderungen verkürzen die Rechenzeiten noch einmal.
Statt den oben erwähnten 475 s sind es noch 325 s; allerdings nicht mit dem Ergebnis von Horst_H zu vergleichen. Da müsste ich noch viel optimieren. Aber wie? :nixweiss:

Beste Grüße
Mathematiker


Horst_H - Do 10.01.13 21:47

Hallo,

PtrUint wurde bei fpc eingeführt damit man bei 64 und 32 bit Anwendungen den gleichen Namen einmal Qword das andere Mal Dword nutzen kann.
EDIT, ich habe eine neue Version hochgeladen

Gruß Horst


Mathematiker - Fr 11.01.13 00:14

user profile iconHorst_H hat folgendes geschrieben Zum zitierten Posting springen:


Delphi-Quelltext
1:
2:
type
  ptruint = Dword;// 32-Bit

Danke für den Hinweis. Leider kann ich es immer noch nicht compilieren. Bei

Delphi-Quelltext
1:
 while (k >=Low(Perm) ) AND (pDat^[0]> pDat^[1])do                    

kommt die Meldung "Konstantenausdruck verletzt untere Grenze".
Ich habe versucht, den Text zu verstehen. Kein Chance. Das ist für mich geradezu kryptisch. Dennoch hast Du meine Hochachtung dafür, dass Du solche Programm schreiben kannst.

In der Revision 10 habe ich noch ein paar Änderungen eingefügt. Durch Umlagern der Tests wird es noch etwas schneller.

Beste Grüße
Mathematiker


Horst_H - Fr 11.01.13 00:51

Hallo,

ich hatte diese merkwürdige Handhabung wegen 64-Bit FPC für Linux als schnellste Variante gefunden.
Ich habe jetzt eine Variante die unter 32-Bit genauso schnell ist, ohne das der Compiler rummoppert :-)
Nun im Anhang Programmtext und EXE.
Ja es ist sehr kryptisch ;-)
Wieso dauert eine komplette Berechnung nur 90 CPU-Takte.
Ich benutze immer die vorhergehenden Berechnungen, deren Ergebnisse ja auf dem Stack noch stehen.
So muss nach jeder Berechnung nur die oberste Zahl korrigieren und den neuen Operator darauf anwenden.
tiefe
2,1,0
Zahlen Operatoren
3,2,1 * +
3,(3*2) 6,7
->Ergebnis in Tiefe 0 , dort die 1 aus dem UrStack( Permutation der Zahlen) wieder einsetzen und den nächsten Operator - auf 6 und 1
anwenden.
3, 6, 1 -
3, 6, (6-1) 5

Wie oben schon geschrieben, kann ich nicht alle Rechenmöglichkeiten damit ausführen.

Gruß Horst
EDIT:
Ich möchte eigentlich doch verstanden werden ;-) ....
Die Operatoren auf dem OpStack sind direkt die Adressen auf die entsprechende procedure ADD/SUB/MUL/DIVI

In test(i) wird die eigentliche Berechnung durchgeführt, wobei i die Positon des Operators ist, in anderen Worten die Stacktiefe.

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
procedure DoIt(p:tproc); inline;// ohne inline extreme 40 CPU-Takte länger, wer hätte das gedacht
begin
  p; 
// in assembler
//   call  *%eax
//  ret
end;

(* Alternative Version von MUL aber langsamer
procedure MUL;
//Ein Aufruf dauert etwa 6..7 CPU-Takte
var
  i: integer;
begin
  i := TOS-1;
  TOS:= i;
  Stack[i] := Stack[i]*Stack[i+1];
end;;*)


procedure MUL;
//Ein Aufruf dauert etwa 4 CPU-Takte in enger Schleife
var
  i: integer;
  p : tpStackArr;// ^array[0..1] of integer;
begin
  i := TOS;
  dec(i);
  TOS := i;
  p := @Stack[i];
  p^[0] := p^[1]*p^[0];
end;
....
procedure Test(i:integer);

begin
  TOS := i+1;
  // Alle Operatoren von i .. 0 aufrufen
  For I:= i downto 0 do 
    Doit(OpStack[i]);
  // Das Ergbenis der Bechnung  steht dann in Stack[0]
  i := Stack[0];
  //Und entsprechend eintragen und zaehlen
  IF ( i >= Low(Erg)) AND (i <= High(Erg)) then
    begin
    IF Erg[i] = 0  then
      begin
      inc( AnzahlLoesung);//write(AnzahlLoesung:8,#8#8#8#8#8#8#8#8);
      end;
    inc(Erg[i]);
    end;
end;


In TOS= Top Of Stack steht die Position der ersten Zahl, die verarbeitet werden soll, diese ist global.
Und in diesem Spezialfall hier, dass immer 2 Zahlen verarbeitet werden, gibt es n-1 Operatoren, deshalb ist TOS immer i+1.
In der Schleife

Delphi-Quelltext
1:
For I:= i downto 0 do Doit(OpStack[i]);                    

ruft das Programm nacheinander die Proceduren direkt auf.Das ist das schöne an stackbasierten Rechnungen, das alle Parameter dort sind und man keine übergeben muss.
Wenn beispielsweise auf OpStack[i] die Adresse der Prozedur MUL ist, dann springt DoIt sofort in die Prozedur MUL.

Berechnete ich immer alles komplett neu, müsste ich, wie bei Testlauf als ersten Aufruf,die Zahlen vom UrStack auf den Stack schreiben ( cop )und dann test(MaxStack-1) aufrufen.
Wenn sich aber nur der Operator der Ebene/Tiefe 0 geändert hat brauche ich nur Zahl 0 neu beschreiben da in Ebene1 noch das Ergebnis der Berechnung bis dort hin steht.

Mit OpRek bestimme ich den aktuellen Operator in der jeweiligen Tiefe.
Ich erhöghe also oprek[0].Wenn die Zahl größer als die Anzahl der Operatoren ist, setzte ich diesen zurück auf 0 und erhöhe bei Tiefe = 0+1.
Dabei werden parallel die richtigen Zahlen auf den Stack geschrieben und der OperandenStack mit den Adressen der Operatoren beschrieben.

Das simuliert eben MAXSTACK-1 verschachtelte For-Schleifen.
Was vielleicht befremdlich wirkt, ist die Namensgebung Stack, denn ich benutze diesen außer beim rechnen selbst nicht immer so. Ich schreibe die Zahlen direkt hinein ohne Push und Pop, das hält mich nur auf ;-)

Ich werde mal testen, wieviele Lösungen ich nicht finde.
18619 gibt es bei mir nur 14 mal aus 2,3,5,7,11,13,17,19


Horst_H - Fr 11.01.13 15:17

Hallo,

ich habe mal einen Test gemacht, wieviele Lösungen user profile iconMathematiker bei seinen Standardzahlen für 18619 findet, wo ich nur 14 Ergebnisse habe.
Seins findet über 200 bei 3,2 Milliarden Rechnungen in 342 Sekunden als abgebrochen habe.
ZiffernUndZahlen

Meine jetztige Überlegung ist nun zusätzlich zu meinem simplen Ansatz, nach Klammern aufzuteilen in 2er Gruppen in denen innnerhalb nur '+','-' gerechnet wird und ausserhalb der Klammer nur '*','/'.
Und dann die Klammern peu a peu zu verbreitern damit ich auch mal mit Catalan Freude bekomme.
Bei acht Zahlen

Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
2-2-2-2
3-1-2-2 
1-3-2-2 braucht es wegen der '/'
2-1-3-2
2-3-1-2
2-2-3-1
2-2-1-3
4-2-2
2-4-2
2-2-4
4-1-3
1-4-3
1-3-4
...

Oder gibt es da merkwürdige Hindernisse?

Gruß Horst


Mathematiker - Fr 11.01.13 17:51

Hallo Horst_H,
vielen Dank für den veränderten Quelltext und die Exe.
Ich werde versuchen, Schritt für Schritt, Dein Programm zu verstehen.

Beste Grüße
Mathematiker

Nachtrag, 12.1.:
Ich habe das Programm so verändert, dass beim Aufruf der rekursiven Methoden das Feld nicht mehr kopiert wird. Dadurch wird es erneut etwas schneller, nur leider nicht so viel, wie ich gehofft hatte.