Send More Money in PHP programmiert

Im Dezember 2012 ist mir das alte „Send More Money“-Problem mal wieder in die Finger geraten. DarĂŒber habe ich dann im Artikel 6502 Assembler Programmierung geschrieben.

Schon im Dezember habe ich ĂŒberlegt, wie das wohl in PHP auf einem Webserver laufen wĂŒrde. Da ich im Augenblick sowieso gerade mit PHP herumexperimentiere (siehe auch Orthografie Schieber – Deutsch in der 7. Klasse Gymnasium), lag es auf der Hand, nun auch das liegengebliebene Send More Money Thema erneut aufzugreifen.

Besonders intelligent ist das Programm nicht.

Im ersten Ansatz durchlief es einfach alle Möglichkeiten fĂŒr alle Buchstaben, die es gibt, also 10^8 SchleifendurchlĂ€ufe = 100 Mio. Das dauerte dann schon mal schlappe xx Minuten. (Anmerkung: konnte die Laufzeit noch nicht ermitteln … es dauert Stunden!)

Da der Buchstabe M ein Übertrag aus zwei Ziffern ist, kann er maximal die Ziffer 1 darstellen. 0 ginge auch noch, aber das ist prinzipiell bei Alfametiken nicht zugelassen. Die Zahlen dĂŒrfen keine fĂŒhrenden Nullen aufweisen. Auf jeden Fall konnte ich die Anzahl der SchleifendurchlĂ€ufe auf diese Weise von 100 Mio auf 20 Mio reduzieren, das entspricht einer VerkĂŒrzung der Laufzeit auf 1/5 der ursprĂŒnglichen Zeit. Gemessen habe ich: xx Minuten. (Anmerkung: konnte die Laufzeit noch nicht ermitteln … es dauert Stunden!)

Eine weitere Beschleunigung ergab sich aus der Tatsache, dass zumindest die letzte Stelle der Summe, also der Buchstabe Y, nicht jede beliebige Ziffer annehmen konnte, sondern immer eine Summe aus dem Buchstaben D und dem Buchstaben E darstellte. Somit musste Y nicht 10mal iteriert werden, sondern konnte schlicht und ergreifend fĂŒr gegebene D und E berechnet werden. Statt 10 Schleifen nur noch eine Berechnung.

Nachdem die Lösungen gefunden waren, musste ich das Skript nur noch etwas mit CSS aufhĂŒbschen. Und hier ist das Ergebnis:

noch grĂ¶ĂŸer wird der Screenshot mit einem Mausklick

noch grĂ¶ĂŸer wird der Screenshot mit einem Mausklick

Leider kann ich euch nicht auf das Skript selber weiterleiten, weil es nur lokal bei mir auf dem XAMPP-Server lĂ€uft, aber nicht auf meinem Webserver. Da wird es nach 10 CPU Sekunden abgeschossen. 🙁

Gerne wĂŒrde ich das Programm weiter beschleunigen. Vielleicht wĂŒrde eine Rekursion etwas bringen. Ansonsten muss sich das wohl ein Mathematiker vorknöpfen. Es wĂ€re mal interessant, auf welche Laufzeit man das Skript beschleunigen könnte.

Falls jemand schnellere EntwĂŒrfe hat, wĂŒrde ich mich ĂŒber eine Diskussion darĂŒber sehr freuen!

Update 31.07.2013:
Ich konnte den Algorithmus durch frĂŒher greifende AusschlĂŒsse (letztendlich Constraints und Backtracking) weiter beschleunigen und benötige jetzt nur noch 381.170 SchleifendurchgĂ€nge. Das Skript lĂ€uft nun weniger als 1 Sekunde und ist damit auch auf dem Webserver ablauffĂ€hig: PHP-Skript SMM starten.

Ich habe es auch mal mit einem evolutionÀren Algorithmus probiert, aber der braucht ebenfalls sehr lange. Dadurch, dass die Chromosomen per Zufall mutiert werden, sind die Laufzeiten aber völlig unterschiedlich. Einmal war das Skript mit gerade mal 188 Generationen in 0,002 Sekunden fertig. Mal schauen, ob ich die QualitÀt der Mutationen noch verbessern kann.

Update 02.08.2013:
Nun habe ich mir das Thema auch noch einmal in Perl vorgenommen. Bei PHP werden Ausgaben auf dem Client (normalerweise) erst dann angezeigt, wenn das Skript komplett fertig ist. Das hat den Nachteil, dass ich wÀhrend eines langlaufenden Skriptes keine hilfreichen Infos anzeigen kann. Mit Perl als Standalone-Programmiersprache kann ich grundsÀtzlich den Webserver und den Webbrowser weglassen und das Programm direkt in einer DOS-Box (=Kommandozeile cmd.exe) ablaufen lassen. Die Bildschirmausgaben erfolgen stets unmittelbar.

Obwohl sich PHP und Perl sehr Àhnlich sehen, ist die Syntax von Perl doch deutlich anspruchsvoller.

Folgende Programm-Optimierungen konnte ich erreichen:

1. Brute Force Methode

Die stellt den Ausgangs-Algortihmus dar und ist in keiner Weise optimiert. Es werden einfach sĂ€mtliche möglichen Werte (0 bis 9) fĂŒr alle Buchstaben ausprobiert. Jede dieser Möglichkeiten wird anschließend daraufhin untersucht, ob Doppelungen vorkommen. Der Buchstabe „s“ darf beispielsweise nicht durch dieselbe Ziffer ersetzt werden wie irgendeiner der anderen 7 Buchstaben. Danach wird ĂŒberprĂŒft, ob die Addition aufgeht. Es wird nicht berĂŒcksichtigt, dass „M“ eigentlich nur eine „1“ sein kann.

Insgesamt ergeben sich 24 Lösungen mit M=0 und 1 Lösung mit M=1.

Das Programm durchlÀuft 100 Mio Schleifen und benötigt auf meinem alten Notebook 19:50 Minuten (=100%).

2. BerĂŒcksichtigung von EinschrĂ€nkungen fĂŒr M

Danach habe ich berĂŒcksichtigt, dass M entweder 0 oder 1 ist, da es sich um einen Übertrag aus der Addtion der beiden Tausenderstellen handeln kann. Bei der Addtion zweier Zahlen ist ein Übertrag>1 nicht möglich.

Entsprechend reduzierte sich die Anzahl der SchleifendurchlÀufe von 10*10 Mio auf 2*10 Mio = 20 Millionen.

Das Programm benötigte mit 4:04 Minuten nur noch etwa 21% der ursprĂŒnglichen Laufzeit.

3. Berechnung von Y

FĂŒr den Buchstaben Y mĂŒssen keine beliebigen Werte ausprobiert werden, denn er lĂ€sst sich immer aus den bereits zugewiesenen Buchstaben D und E errechnen. Dabei muss nur beachtet werden, dass der Buchstabe keinen Wert>9 annehmen darf.

Auf diese Weise können alle Schleifen fĂŒr Y entfallen, also ein Faktor von 10, so dass 2 Mio Schleifen ĂŒbrig bleiben.

Das Skript benötigte nun noch 26,42 Sekunden (2,21% des Brute-Force Algorithmus).

4. EinfĂŒhrung von Backtracking

Nun habe ich die Schleifen so frĂŒh wie möglich beendet, indem ich je nach Stufe der Schleifenkaskade ĂŒberprĂŒft habe, ob ein Buchstabe sich von den bereits zugewiesenen der höheren Schleifenlevel unterschied. Dadurch entfielen natĂŒrlich jedes Mal eine ganze Reihe von inneren (tiefer liegenden) Schleifen.

Insgesamt reduzierte sich die Anzahl der Schleifen nun auf 40.320.

Und entsprechend spielte die Laufzeit mit 0,49 Sekunden kaum noch eine Rolle.

5. Zahlen beginnen nicht mit Null

Anschließend habe ich die Regel implementiert, dass Zahlen nicht mit einer Null beginnen dĂŒrfen, die Buchstaben S und M also nicht gleich Null sein dĂŒrfen. Das reduzierte die Anzahl der Schleifen auf nur noch 14.760 und die Laufzeit auf 0,19 Sekunden.

Aber auch die Anzahl der Lösungen verringerte sich auf 1, nÀmlich auf die einzig valide.

Schlussbemerkung

Weitere logische Überlegungen habe ich nicht mehr implementiert, denn dann kann ich das Puzzle auch gleich ohne Computer lösen.

Interessant finde ich, dass ich durch ganz einfache zusĂ€tzliche Verbesserungen des Ausgangs-Algorithmus wirklich dramatische Laufzeitverbesserungen erreichen konnte. Von ursprĂŒnglich 100.000.000 SchleifendurchgĂ€ngen ging es runter auf 14.760 DurchgĂ€nge. Und die Laufzeit verbesserte sich von etwa 20 Minuten auf einen kaum mehr messbaren Wert unterhalb 1 Sekunde.

Update 03.08.2013:
Ich habe mir auch noch einmal den evolutionĂ€ren (=genetischen) Algorithmus vorgeknĂŒpft und konnte ihn unter Perl verbessern. Eigentlich funktionierte der schon ganz gut, aber leider blieb er in einem lokalen Maximum hĂ€ngen. Die guten Gene altern jetzt und können ebenfalls mutieren, wenn sie ein bestimmtes Alter ĂŒberschritten haben. Dadurch erhalte ich zwar kurzfristig wieder schlechtere Lösungen, aber eben auch die Chance, dass das globale Maximum gefunden wird.

Nachdem das in Perl klappte, habe ich es auf PHP ĂŒbertragen. Starten des evolutionĂ€ren PHP-Skrips.

Üblicherweise ist es sehr schnell. NatĂŒrlich variiert die Laufzeit in AbhĂ€ngigkeit von den erzeugten Generationen sehr stark. Die Mutationen werden ja zufĂ€llig erzeugt. Es wird auch nur noch die einzig valide Lösung ausgegeben. Die anderen Lösungen mit M=0 könnt ihr euch mit dem ersten Skript ansehen: Normales PHP-Skript SMM starten.

Kategorien: Allgemein  Tags: , ,

Du kannst die Kommentare als RSS 2.0-Feed abonnieren. Du kannst selber einen Kommentar schreiben, oder von deiner eigenen Website einen Trackback setzen.

Dein Kommentar

Du musst dich erst anmelden.