Löse das Rotationsrätsel

14

Auf einigen alten Nokia-Handys gab es eine Variante des fünfzehn Puzzles Namen Rotation. In dieser Variante haben Sie nicht jeweils eine Kachel verschoben, sondern jeweils vier Kacheln in eine Richtung gedreht.

In diesem Spiel würden Sie mit einem Brett wie diesem beginnen:

4 9 2
3 5 7
8 1 6

Und wenn Sie den unteren linken Block zweimal im Uhrzeigersinn und den oberen linken Block einmal im Uhrzeigersinn drehen, erhalten Sie Folgendes:

4 9 2
8 3 7
1 5 6

4 9 2
1 8 7
3 5 6

1 4 2
8 9 7
3 5 6

und das 1Plättchen wäre in der oberen linken Ecke, wo es sein soll. Nach ein paar weiteren Zügen würden Sie am Ende Folgendes haben:

1 2 3
4 5 6
7 8 9

Das ist die "ursprüngliche" Konfiguration.

Ihre Aufgabe ist es, ein Programm zu erstellen, das als Eingabe ein 3x3-Raster mit Zahlen von 1 bis 9 (in jedem von Ihnen gewählten Format) verwendet und als Ausgabe eine Folge von Zügen zurückgibt, die die Züge darstellt, die Sie ausführen müssen, um das Brett wieder in seinen ursprünglichen Zustand zu versetzen Konfiguration (wieder in jedem von Ihnen gewählten Format). Die erlaubten Züge sind definiert als der [obere / untere] - [linke / rechte] Block von 4 Kacheln [im / gegen den Uhrzeigersinn].

Ihr Programm muss in der Lage sein, alle möglichen 3x3-Gitter zu lösen (alle Permutationen sind lösbar).

Der kürzeste Code dafür gewinnt.

Joe Z.
quelle
...and return as output a sequence of moves representing the moves you must take to return the board back to its originalBedeutet das "zurück zu 1 2 3\n4 5 6\n7 8 9"? Ich bin mir nicht sicher, wie ich das lesen soll.
undergroundmonorail
Ja, ich meine zurück zu 1 2 3 4 5 6 7 8 9.
Joe Z.
1
Ich denke, die zweite und dritte Karte in Ihrem Beispiel sollten die 3 und 5 getauscht haben.
Martin Ender
@JoeZ. Ich würde vorschlagen, es zu ändern, um zu deklarieren, dass die Lösung eine eingeschränkte Worst-Case-Leistung aufweisen muss.
HostileFork sagt nicht SE

Antworten:

7

GolfScript, 39/83 Bytes

# Optimized for size:

{.4rand.p.2/+>`{?1420344440`=}+$..$>}do

# Optimized for speed:

6,(7++:t;~{.(1=.@7=9=+4\-rand+..2/+@.@>:s^[3s=0s=2s=4s=1s=]+s|.)9<\t>|}do.$>30764`*

Geschwindigkeit gegen Größe

Die größenoptimierte Version wählt nach dem Zufallsprinzip Rotationen im Uhrzeigersinn aus, bis die gewünschte Permutation erreicht ist. Dies ist ausreichend, da eine Drehung gegen den Uhrzeigersinn drei aufeinanderfolgenden Drehungen im Uhrzeigersinn desselben Quadrats entspricht.

Die geschwindigkeitsoptimierte Version macht dasselbe, mit Ausnahme der folgenden:

  1. Wenn sich die Zahl 1 in der oberen linken Ecke befindet, wird das obere linke Quadrat nicht mehr gedreht.

  2. Wenn sich die Zahl 9 in der unteren rechten Ecke befindet, wird das untere rechte Quadrat nicht mehr gedreht.

  3. Die Schritte zum Vertauschen der Positionen 7 und 8 sind fest codiert, sodass die Schleife an zwei Positionen unterbrochen werden kann.

Abgesehen von der Änderung des Algorithmus erreicht die geschwindigkeitsoptimierte Version die Rotation auf einfache Weise, während die größenoptimierte Version die in GolfScript integrierte Sortierung durch Mapping verwendet. Außerdem wird der Endzustand (zum Vergleich) hartcodiert, anstatt den Zustand in jeder Iteration zu sortieren.

Die geschwindigkeitsoptimierte Version erfordert weniger Iterationen und jede Iteration ist für sich viel schneller.

Benchmarks

Ich habe den folgenden Code verwendet, um die Positionen der Zahlen nach dem Zufallsprinzip zu ordnen und Testläufe durchzuführen, wobei die Zeile, die der zu testenden Version entspricht, aus dem Kommentar entfernt wurde:

[{[
    0:c;10,1>{;2 32?rand}$
    #{c):c;.4rand.2/+>`{?1420344440`=}+$..$>}do
    #6,(7++:t;{.(1=.@7=9=+4\-rand+..2/+@.@>:s^[3s=0s=2s=4s=1s=]+s|.)9<\t>|}do.$>30764`*
],c+}\~*]

$.0='Min: '\+puts .-1='Max: '\+puts ..{+}*\,/'Avg: '\+puts .,2/='Med: '\+

Die Ausgabe zeigt die minimale und maximale Anzahl der Schritte an, die zur Bestellung der Zahlen erforderlich waren, den Durchschnitt und den Median aller Läufe sowie die verstrichene Zeit in Sekunden:

$ TIME='\n%e s' time golfscript rotation-test-size.gs <<< 100
Min: 4652
Max: 2187030
Avg: 346668
Med: 216888

21500.10 s
$
$ TIME='\n%e s' time golfscript rotation-test-speed.gs <<< 1000
Min: 26
Max: 23963
Avg: 3036
Med: 2150

202.62 s

Auf meinem Computer (Intel Core i7-3770) betrug die durchschnittliche Ausführungszeit der größenoptimierten Version 3,58 Minuten. Die mittlere Ausführungszeit der geschwindigkeitsoptimierten Version betrug 0,20 Sekunden. Damit ist die geschwindigkeitsoptimierte Version rund 1075-mal schneller.

Die drehzahloptimierte Version liefert 114-mal weniger Umdrehungen. Die Ausführung jeder Umdrehung ist 9,4-mal langsamer, was hauptsächlich darauf zurückzuführen ist, wie der Status aktualisiert wird.

I / O

Die Ausgabe besteht aus 3-Bit-Zahlen. Das MSB ist für Rotationen gegen den Uhrzeigersinn gesetzt, das mittlere Bit ist für untere Quadrate gesetzt und das LSB ist für rechte Quadrate gesetzt. Somit ist 0 (4) das obere linke Quadrat, 1 (5) das obere rechte, 2 (6) das untere linke und 3 (7) das untere rechte.

Die geschwindigkeitsoptimierte Version druckt alle Umdrehungen in einer Zeile. Die größenoptimierte Version druckt eine Umdrehung pro Zeile, gefolgt von der endgültigen Position der Zahlen.

Bei der geschwindigkeitsoptimierten Version muss die Eingabe ein Array ergeben, das bei der Auswertung die Zahlen von 1 bis 9 enthält. Für die größenoptimierte Version muss die Eingabe eine Zeichenfolge ohne letzte Zeile sein. es wird nicht ausgewertet.

Beispiel läuft:

$ echo -n '253169748' | golfscript rotation-size.gs
3
0
123456789
$ golfscript rotation-speed.gs <<< '[5 4 7 1 2 9 3 8 6]'
2210300121312212222212211121122211122221211111122211211222112230764

Größenoptimierter Code

{               #
  .             # Duplicate the state.
  4rand         # Push a randomly chosen integers between 0 and 3.
  .p            # Print that integer.
  .2/+          # Add 1 to it if it is grater than one. Possible results: 0, 1, 3, 4
  >`            # Slice the state at the above index.
  {             # Push a code block doing the following:
    ?           # Get the index of the element of the iteration in the sliced state.
    1420344440` # Push the string "14020344440".
    =           # Retrieve the element at the position of the computed index.
  }+            # Concatenate the code block with the sliced state.
  $             # Sort the state according to the above code block. See below.
  ..$>          # Push two copies of the state, sort the second and compare the arrays.
}do             # If the state is not sorted, repeat the loop.

Die Aktualisierung des Status erfolgt auf folgende Weise:

Drehung 2 ergibt die ganze Zahl 3 nach dem Addieren von 1. Wenn der Status "123456789" ist, ergibt das Schneiden des Status "456789".

Kurz vor dem Ausführen von "$" sind die obersten Elemente des Stapels:

[ 1 2 3 4 5 6 7 8 9 ] { [ 4 5 6 7 8 9 ] ? "1420344440" = }

"$" Führt den Block einmal für jedes Element des Arrays aus, das sortiert werden soll, nachdem das Element selbst gedrückt wurde.

Der Index von 1 in "[4 5 6 7 8 9]" ist -1 (nicht vorhanden), daher wird das letzte Element von "1420344440" verschoben. Dies ergibt 48, wobei der ASCII-Code dem Zeichen 0 entspricht. Für 2 und 3 wird auch 48 gedrückt.

Die für 4, 5, 6, 7, 8 und 9 geschobenen ganzen Zahlen sind 49, 52, 50, 48, 51 und 52.

Nach dem Sortieren ist das erste Element des Zustands eines der Elemente, die 48 ergeben. Das letzte ist eines von denen, die 52 ergeben. Die eingebaute Sortierung ist im Allgemeinen instabil, aber ich habe empirisch überprüft, dass sie in diesem speziellen Fall stabil ist.

Das Ergebnis ist "[1 2 3 7 4 6 8 5 9]", was einer Drehung des unteren linken Quadrats im Uhrzeigersinn entspricht.

Geschwindigkeitsoptimierter Code

6,(7++:t;       # Save [ 1 2 3 4 5 7 ] in variable “t” and discard it.
~               # Interpret the input string.
{               #
  :s            # Duplicate the current state.
  (1=           # Unshift the first element and push 1 if it is equal to 1 and 0 otherwise.
  .@            # Duplicate the boolean and rotate the unshifted array on top of it.
  7=9=          # Push 1 if the eighth element of “s” is equal to 9 and 0 otherwise.
  +4\-          # Add the booleans and subtract their sum from 4.
  rand          # Push a randomly chosen integers between 0 and the result from above.
  +.            # Add this integer to the first boolean and duplicate it for the output.
  .2/+          # Add 1 to the result if it is grater than one. Possible results: 0, 1, 3, 4
  @.            # Rotate the state on top of the stack and duplicate it.
  @>:s          # Slice the state at the integer from above and save the result in “s”.
  ^             # Compute the symmetric difference of state and sliced state.
  [             # Apply a clockwise rotation to the sliced array:
    3s=         # The fourth element becomes the first.
    0s=         # The first element becomes the second.
    2s=         # The third element remains the same.
    4s=         # The fifth element becomes the fourth.
    1s=         # The second element becomes the fifth.
  ]             # Collect the results into an array.
  +             # Concatenate with array of elements preceding the slice.
  s|            # Perform set union to add the remaining elements of “s”.
  .             # Duplicate the updated state.
  )9<           # Pop the last element; push 0 if it is equal to 9 and 1 otherwise.
  \t            # Swap the popped state on top and push [ 1 2 3 4 5 7 ].
  >             # Push 0 if the state begins with [ 1 2 3 4 5 6 ] and 1 otherwise.
  |             # Take the logical OR of the booleans.
}do             # If the resulting boolean is 1, repeat the loop.
.$              # Duplicate the state and sort it.
>30764`*        # If the state was not sorted, 7 and 8 are swapped, so push "30764".

Beachten Sie, dass die Rotationen 3, 0, 7, 6 und 4 die Elemente in den Positionen 7 und 8 vertauschen, ohne die Positionen der verbleibenden sieben Elemente zu ändern.

Dennis
quelle
Optimiert für Geschwindigkeit? Es ist Golfscript ...
ɐɔıɐɔuʇǝɥʇs
1
@Synthetica: Trotzdem ist es die schnellste Lösung, die bisher veröffentlicht wurde.
Dennis
4

Python mit Numpy - 158

from numpy import*
A=input()
while any(A.flat>range(1,10)):i,j,k=random.randint(0,2,3);A[i:i+2,j:j+2]=rot90(A[i:i+2,j:j+2],1+2*k);print"tb"[i]+"lr"[j]+"wc"[k]

Die Eingabe muss das folgende Format haben:

array([[1,2,5],[4,3,6],[7,8,9]])

Jede Ausgabezeile ist eine Bewegung, die in Zeichenfolgen wie trwoder blcund wie folgt codiert ist :

  • t: oben
  • b: Unterseite
  • l: links
  • r: richtig
  • c: im Uhrzeigersinn
  • w: gegen den Uhrzeigersinn (Widdershins)

Dieses Programm führt zufällige Bewegungen durch, bis die Zielkonfiguration erreicht ist. Unter der Annahme, dass jeder Zug eine unabhängige Wahrscheinlichkeit von 1/9 hat! um die Zielkonfiguration zu treffen¹, wird die Anzahl der Umdrehungen vor einer Lösung exponentiell mit einem Mittelwert (dh der durchschnittlichen Anzahl von Zügen) von 9 verteilt! ≈ 3,6 · 10⁵. Dies entspricht einem kurzen Experiment (20 Läufe).

¹ 9! Dies ist die Gesamtzahl der Konfigurationen.

Wrzlprmft
quelle
2
Also versucht es im Wesentlichen zufällige Bewegungen, bis es eine Lösung erreicht?
Joe Z.
Funktioniert bei mir. Obwohl ich an der erwarteten Anzahl von Umdrehungen interessiert wäre, bevor eine Lösung gefunden werden könnte.
Joe Z.
@ JoeZ .: Siehe die Bearbeitung zu meinem Beitrag.
Wrzlprmft
Das ist großartig.
Kyle Kanos
4

C ++ - Lösung mit den wenigsten Zügen - Breite zuerst (1847 Zeichen)

Nach einigem Nachdenken denke ich, dass ich das viel effizienter und vernünftiger gemacht habe. Diese Lösung ist, obwohl sie diesen Golf sicherlich nicht gewinnt, die einzige, die versucht, die kürzeste Anzahl von Umdrehungen zu finden, die das Brett lösen. Bisher löst es jedes zufällige Brett, das ich darauf geworfen habe, in neun oder weniger Zügen. Es schneidet auch deutlich besser ab als mein letztes und geht hoffentlich auf Dennis 'Kommentare unten ein.

Gegenüber der vorherigen Lösung bestand die größte Änderung darin, die Schlüsselhistorie aus dem Board-Status (BS) in eine neue Klasse zu verschieben, in der die Historie in einer bestimmten Tiefe (DKH) gespeichert wird. Jedes Mal, wenn die Anwendung einen Zug ausführt, wird der Verlauf in dieser Tiefe und in allen Tiefen überprüft, um festzustellen, ob er jemals ausgewertet wurde. In diesem Fall wird er nicht erneut zur Warteschlange hinzugefügt. Dies scheint den Speicher in der Warteschlange erheblich zu reduzieren (indem der gesamte Verlauf aus dem Board-Status selbst entfernt wird) und reduziert daher so gut wie alle dummen Bereinigungen, die ich durchführen musste, um zu verhindern, dass der Code keinen Speicher mehr hat. Außerdem läuft es viel schneller, da viel weniger in die Warteschlange kopiert werden muss.

Nun ist es eine einfache erste Suche in den verschiedenen Boardzuständen. Außerdem möchte ich, wie sich herausstellt, den Schlüsselsatz (der derzeit als Satz von Zahlen in der Basis 9 gespeichert ist, von denen jede von BS :: key als Basis 9-Darstellung der Karte berechnet wird) in einen Bitsatz umwandeln mit 9! Bits scheinen unnötig zu sein; obwohl ich herausgefunden habe, wie man einen Schlüssel im "Fakultätszahlensystem" berechnet, der verwendet werden könnte, um das zu testende / umschaltende Bit im Bitset zu berechnen.

Die neueste Lösung ist also:

#include <iostream>
#include <list>
#include <set>
#include <vector>
using namespace std;
struct BS{
#define LPB(i) for(int*i=b;i-b<9;i++)
struct ROP{int t, d;};
typedef vector<ROP> SV;
typedef unsigned int KEY;
typedef set<KEY> KH;
BS(const int*d){const int*x=d;int*y=b;for(;x-d<9;x++,y++)*y=*x;}
BS(){LPB(i)*i=i-b+1;}
bool solved(){LPB(i)if(i-b+1!=*i)return 0;return 1;}
void rot(int t, int d){return rot((ROP){t,d});}
void rot(ROP r){rotb(r);s.push_back(r);}
bool undo(){if (s.empty())return false;ROP &u=s.back();u.d*=-1;rotb(u);s.pop_back();return true;}
SV &sol(){return s;}
KEY key(){KEY rv=0;LPB(i){rv*=9;rv+=*i-1;}return rv;}
int b[9];
SV s;
void rotb(ROP r){int c=r.t<2?r.t:r.t+1;int bi=(r.d>0?3:4)+c;const int*ri=r.d>0?(const int[]){0,1,4}:(const int[]){1,0,3};for(int i=0;i<3;i++)swap(b[bi],b[c+ri[i]]);}
};
ostream &operator<<(ostream &o, BS::ROP r){static const char *s[]={"tl","tr","bl","br"};o<<s[r.t]<<(r.d<0?"w":"c");return o;}
struct DKH{
~DKH(){for(HV::iterator i=h.begin();i<h.end();++i)if(*i!=NULL)delete *i;}
void add(int d,BS b){h.resize(d+1);if(h[d]==NULL)h[d]=new BS::KH();h[d]->insert(b.key());}
bool exists(BS &b){BS::KEY k=b.key();size_t d=min(b.sol().size(),h.size()-1);do if (h[d]->find(k)!=h[d]->end())return true;while(d--!=0);return false;}
typedef vector<BS::KH *> HV;HV h;
};
static bool solve(BS &b)
{
const BS::ROP v[8]={{0,-1},{0,1},{1,-1},{1,1},{2,-1},{2,1},{3,-1},{3,1}};
DKH h;h.add(0,b);
list<BS> q;q.push_back(b);
while (!q.empty())
{
BS qb=q.front();q.pop_front();
if (qb.solved()){b=qb;return true;}
int d=qb.sol().size()+1;
for (int m=0;m<8;++m){qb.rot(v[m]);if (!h.exists(qb)){h.add(d,qb);q.push_back(qb);}qb.undo();}
}
return false;
}
int main()
{
BS b((const int[]){4,9,2,3,5,7,8,1,6});
if (solve(b)){BS::SV s=b.sol();for(BS::SV::iterator i=s.begin();i!=s.end();++i)cout<<*i<<" ";cout<<endl;}
}
DreamWarrior
quelle
1
Ihr Code sieht aus wie C ++ anstelle von C.
user12205
@ace, in der Tat ist es korrigiert.
DreamWarrior
Falls jemand anderes Probleme damit hat, dies mit g ++ zu kompilieren, musste ich alle Instanzen von int[]to ändern const int[]und das Flag setzen -fpermissive.
Dennis
@ Tennis, Entschuldigung, ich habe es mit zwei verschiedenen g ++ - Compilern kompiliert und es schien mir auch nichts auszumachen. Aber ich kann sehen, wie eine neuere, strengere Version jammern würde. Vielen Dank.
DreamWarrior
Kompiliert jetzt gut und es ist viel schneller. Adressierung des Kommentars, den Sie aus der Frage gelöscht haben: Es gibt einige Permutationen, für die anscheinend 11 Schritte erforderlich sind. 978654321 ist einer von ihnen.
Dennis
1

CJam - 39

l{4mr_o_1>+_@m<_[Z0Y4X]\f=\5>+m>__$>}g;

Ein weiterer Zufallslöser :)
Er nimmt eine Zeichenfolge wie 492357816 und gibt eine (lange) Folge von Ziffern von 0 bis 3 aus, die jeweils eine Drehung eines Blocks im Uhrzeigersinn darstellen: 0 = links oben, 1 = rechts oben, 2 = unten -Links, 3 = rechts unten.

Kurze Erklärung:

4mrgeneriert eine Zufallszahl von 0 bis 3
_1>+erhöht die Zahl, wenn sie größer als 1 ist (so erhalten wir 0, 1, 3 oder 4 - die
m<Startindizes der 4 Blöcke), dreht die Zeichenfolge nach links (z. B. 492357816 ->) 923578164, nicht die Blockdrehung), um den Block in die erste Position zu drehen,
[Z0Y4X]\f=wird die Blockdrehung ausgeführt, die die ersten 5 Zeichen betrifft, z. B. 12345 -> 41352;
X = 1, Y = 2, Z = 3, also ist [Z0Y4X] tatsächlich [3 0 2 4 1], und dies sind die auf 0 basierenden Indizes der gedrehten Kacheln, die
5>den Rest der Zeichenfolge
m>kopieren und die (modifizierte) Zeichenfolge zurückdrehen Das Recht
__$>prüft, ob die Zeichenfolge sortiert ist (es ist die Stoppbedingung).

aditsu
quelle
1

Mathematica, 104 Zeichen

Wir können die Aufgabe in der Sprache der Permutationsgruppen interpretieren. Die vier Umdrehungen sind nur vier Permutationen, die die symmetrische Gruppe S 9 erzeugen , und die Aufgabe besteht nur darin, eine Permutation als Produkt der Generatoren zu schreiben. Mathematica verfügt dazu über eine integrierte Funktion.

i={1,2,5,4};GroupElementToWord[PermutationGroup[Cycles/@({i}+#&/@i-1)],Input[]~FindPermutation~Range@9]

Beispiel:

Eingang:

{4, 9, 2, 8, 3, 7, 1, 5, 6}

Ausgabe:

{-2, -3, -4, 2, 4, 1, 4, -1, -2, 3, 2, -4, 3, 4, -3, -3, -4, -4, -2, -2, -3, -2, 3, -1}
  • 1: oben links im Uhrzeigersinn
  • 2: rechts oben im uhrzeigersinn
  • 3: rechts unten im uhrzeigersinn
  • 4: links unten im uhrzeigersinn
  • -1: links oben gegen den Uhrzeigersinn
  • -2: oben rechts gegen den Uhrzeigersinn
  • -3: rechts unten gegen den Uhrzeigersinn
  • -4: links unten gegen den Uhrzeigersinn
Alephalpha
quelle