Risolvi il puzzle di rotazione


14

Su alcuni vecchi telefoni Nokia, c'era una variante del quindici puzzle chiamato Rotation. In questa variante, invece di far scorrere una tessera alla volta, hai ruotato quattro tessere alla volta in una direzione.

In questo gioco, inizieresti con una tavola come questa:

4 9 2
3 5 7
8 1 6

E ruotando il blocco in basso a sinistra due volte in senso orario e il blocco in alto a sinistra una volta in senso orario, otterrai questo:

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

e la 1tessera si troverebbe nell'angolo in alto a sinistra dove dovrebbe essere. Alla fine, dopo qualche altra mossa, finiresti con:

1 2 3
4 5 6
7 8 9

che è la configurazione "originale".

Il tuo compito è costruire un programma che prenderà come input una griglia di numeri 3x3 da 1 a 9 (in qualsiasi formato tu scelga) e restituisca come output una sequenza di mosse che rappresentano le mosse che devi fare per riportare la scheda al suo originale configurazione (di nuovo, in qualsiasi formato tu scelga). Le mosse legali sono definite come lo spostamento del blocco [alto / basso] - [sinistra / destra] di 4 tessere [orario / antiorario].

Il tuo programma deve essere in grado di risolvere tutte le possibili griglie 3x3 (tutte le permutazioni sono risolvibili).

Il codice più breve per farlo vince.


...and return as output a sequence of moves representing the moves you must take to return the board back to its originalQuesto significa "torna a 1 2 3\n4 5 6\n7 8 9"? Non sono sicuro di come leggerlo.
undergroundmonorail

Sì, voglio dire indietro a 1 2 3 4 5 6 7 8 9.
Joe Z.

1
Penso che la seconda e la terza scheda del tuo esempio debbano scambiare 3 e 5.
Martin Ender,

@JoeZ. Suggerirei di modificarlo per dichiarare che la soluzione deve avere prestazioni limitate nel caso peggiore.
HostileFork dice di non fidarsi di SE

Risposte:


7

GolfScript, 39/83 byte

# 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`*

Velocità vs dimensioni

La versione ottimizzata per dimensioni sceglie casualmente le rotazioni in senso orario fino a raggiungere la permutazione desiderata. Questo è sufficiente, poiché una rotazione in senso antiorario equivale a tre rotazioni consecutive in senso orario dello stesso quadrato.

La versione ottimizzata per la velocità fa lo stesso, ad eccezione di quanto segue:

  1. Se il numero 1 si trova nell'angolo in alto a sinistra, non ruota più il quadrato in alto a sinistra.

  2. Se il numero 9 si trova nell'angolo in basso a destra, non ruota più il quadrato in basso a destra.

  3. I passaggi per scambiare le posizioni 7 e 8 sono hardcoded, quindi ci sono due posizioni che permettono al loop di rompersi.

Oltre a modificare l'algoritmo, la versione ottimizzata per la velocità ottiene la rotazione in modo semplice, mentre la versione ottimizzata per dimensioni utilizza l'ordinamento incorporato di GolfScript mediante mappatura. Inoltre codifica lo stato finale (per il confronto) invece di ordinare lo stato in ogni iterazione.

La versione ottimizzata per la velocità richiede meno iterazioni e ogni iterazione è molto più veloce da sola.

Punti di riferimenti

Ho usato il seguente codice per randomizzare le posizioni dei numeri ed eseguire esecuzioni di test, decommentando la riga corrispondente alla versione da testare:

[{[
    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: '\+

L'output mostra il numero minimo e massimo di passaggi necessari per ordinare i numeri, la media e la mediana di tutte le esecuzioni, nonché il tempo trascorso in secondi:

$ 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

Sulla mia macchina (Intel Core i7-3770), il tempo medio di esecuzione della versione con ottimizzazione delle dimensioni era di 3,58 minuti. Il tempo medio di esecuzione della versione ottimizzata per la velocità è stato di 0,20 secondi. Pertanto, la versione ottimizzata per la velocità è circa 1075 volte più veloce.

La versione ottimizzata per la velocità produce rotazioni 114 volte inferiori. L'esecuzione di ciascuna rotazione è 9,4 volte più lenta, principalmente a causa dell'aggiornamento dello stato.

I / O

L'output è composto da numeri a 3 bit. L'MSB è impostato per le rotazioni in senso antiorario, il bit centrale è impostato per i quadrati inferiori e l'LSB è impostato per i quadrati giusti. Pertanto, 0 (4) è il quadrato in alto a sinistra, 1 (5) quello in alto a destra, 2 (6) in basso a sinistra e 3 (7) in basso a destra.

La versione ottimizzata per la velocità stampa tutte le rotazioni su una sola riga. La versione ottimizzata per le dimensioni stampa una rotazione per riga, seguita dalla posizione finale dei numeri.

Per la versione ottimizzata per la velocità, l'input deve produrre un array contenente i numeri da 1 a 9 quando valutato. Per la versione con ottimizzazione delle dimensioni, l'input deve essere una stringa senza newline finale; non viene valutato.

Esempi di esecuzione:

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

Codice ottimizzato per dimensioni

{               #
  .             # 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.

L'aggiornamento dello stato si ottiene nel modo seguente:

La rotazione 2 produce l'intero 3 dopo aver aggiunto 1. Se lo stato è "123456789", se si taglia lo stato si ottiene "456789".

Prima di eseguire "$", gli elementi più in alto dello stack sono:

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

"$" Esegue il blocco una volta per ogni elemento dell'array da ordinare, dopo aver spinto l'elemento stesso.

L'indice di 1 in “[4 5 6 7 8 9]” è -1 (non presente), quindi viene spinto l'ultimo elemento di "1420344440". Questo produce 48, il codice ASCII corrispondente al carattere 0. Per 2 e 3, anche 48 viene spinto.

I numeri interi inseriti per 4, 5, 6, 7, 8 e 9 sono 49, 52, 50, 48, 51 e 52.

Dopo l'ordinamento, il primo elemento dello stato sarà uno degli elementi che generano 48; l'ultimo sarà uno di quelli che producono 52. Il tipo incorporato è instabile in generale, ma ho verificato empiricamente che è stabile in questo caso particolare.

Il risultato è “[1 2 3 7 4 6 8 5 9]”, che corrisponde a una rotazione in senso orario del quadrato in basso a sinistra.

Codice ottimizzato per la velocità

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".

Osservare che le rotazioni 3, 0, 7, 6 e 4 scambiano gli elementi nelle posizioni 7 e 8, senza alterare le posizioni dei rimanenti sette elementi.


Ottimizzato per la velocità? È Golfscript ...
ɐɔıʇǝɥʇuʎs

1
@Synthetica: Tuttavia, è la soluzione più veloce finora pubblicata.
Dennis,

4

Python con 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]

L'input deve essere del seguente formato:

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

Ogni riga di output è una mossa codificata in stringhe come trwo blce da leggere come segue:

  • t: superiore
  • b: parte inferiore
  • l: sinistra
  • r: giusto
  • c: senso orario
  • w: antiorario (widdershins)

Questo programma esegue mosse casuali fino al raggiungimento della configurazione di destinazione. Con il presupposto approssimativo che ogni mossa ha una probabilità indipendente di 1/9! per colpire la configurazione target¹, il numero di rotazioni prima che una soluzione sia distribuita esponenzialmente con una media (cioè il numero medio di mosse) di 9! ≈ 3,6 · 10⁵. Ciò è conforme a un breve esperimento (20 corse).

¹ 9! essendo il numero totale di configurazioni.


2
Quindi essenzialmente prova mosse casuali fino a quando non raggiunge una soluzione?
Joe Z.

Per me va bene. Anche se sarei interessato al numero previsto di rotazioni prima che potesse essere raggiunta una soluzione.
Joe Z.

@JoeZ .: Vedi la modifica al mio post.
Wrzlprmft,

È fantastico
Kyle Kanos,

4

Minimo numero di mosse in C ++ - larghezza prima (1847 caratteri)

Dopo un po 'più di pensiero, penso di averlo fatto in modo molto più efficiente e sensato. Questa soluzione, anche se non sta certamente vincendo questo golf, è finora l'unica che tenterà di trovare il minor numero di rotazioni che risolverà la tavola. Finora, risolve ogni tavola a caso che ho lanciato in nove o meno mosse. Funziona in modo significativamente migliore del mio ultimo e, si spera, affronta i commenti di Dennis di seguito.

Dalla soluzione precedente, il cambiamento più grande è stato quello di spostare la cronologia delle chiavi dallo stato del board (BS) in una nuova classe che memorizza la cronologia a una data profondità (DKH). Ogni volta che l'applicazione fa una mossa, controlla la cronologia a quella profondità e tutte le profondità prima di vedere se è mai stata valutata, in tal caso, non verrà aggiunta di nuovo alla coda. Ciò sembra ridurre in modo significativo l'archiviazione sulla coda (rimuovendo tutta questa cronologia dallo stato della scheda stessa) e quindi riduce praticamente tutta la stupida potatura che ho dovuto fare per impedire alla memoria di esaurire il codice. Inoltre, funziona molto più velocemente poiché c'è molto meno da copiare sulla coda.

Ora, è una semplice prima ricerca nei vari stati del consiglio. Inoltre, a quanto pare, voglio cambiare il set di chiavi (attualmente memorizzato come set di numeri in base-9, ognuno dei quali sono calcolati da BS :: key come rappresentazione base-9 della scheda) su un bitset avendo 9! i bit sembrano essere inutili; però ho scoperto come calcolare una chiave nel "sistema numerico fattoriale" che avrebbe potuto essere usato per calcolare il bit nel bitset per testare / attivare.

Quindi, la soluzione più recente è:

#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;}
}

1
Il tuo codice sembra C ++ anziché C.
user12205

@ace, infatti, è corretto.
DreamWarrior

Nel caso in cui qualcun altro ha problemi compilando questo con g ++, ho dovuto cambiare tutte le istanze di int[]per const int[]e impostare il flag -fpermissive.
Dennis,

@Dennis, mi dispiace, l'ho compilato con due distinti compilatori g ++ e nessuno dei due sembrava preoccuparsene. Ma vedo come si lamenterebbe una versione più nuova, più rigorosa. Grazie.
DreamWarrior,

Compilare bene ora ed è molto più veloce. Affrontare il commento che hai eliminato dalla domanda: ci sono alcune permutazioni che sembrano richiedere 11 passaggi. 978654321 è uno di questi.
Dennis,

1

CJam - 39

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

Un altro risolutore casuale :)
Prende una stringa come 492357816 e genera una (lunga) serie di cifre da 0 a 3, ognuna delle quali rappresenta una rotazione oraria di un blocco: 0 = in alto a sinistra, 1 = in alto a destra, 2 = in basso -sinistra, 3 = in basso a destra.

Breve spiegazione:

4mrgenera un numero casuale da 0 a 3
_1>+incrementa il numero se è maggiore di 1 (quindi finiamo con 0, 1, 3 o 4 - gli indici iniziali dei 4 blocchi)
m<ruota la stringa a sinistra (come 492357816 -> 923578164, non la rotazione del blocco) per portare il blocco in rotazione nella prima posizione,
[Z0Y4X]\f=fa la rotazione del blocco che interessa i primi 5 caratteri, come 12345 -> 41352;
X = 1, Y = 2, Z = 3, quindi [Z0Y4X] è in realtà [3 0 2 4 1] e quelli sono gli indici basati su 0 delle
5>copie delle tessere ruotate, il resto della stringa
m>ruota la stringa (modificata) su il diritto
__$>controlla se la stringa è ordinata (è la condizione di arresto)


1

Mathematica, 104 caratteri

Possiamo interpretare l'attività nella lingua dei gruppi di permutazione. Le quattro rotazioni sono solo quattro permutazioni che generano il gruppo simmetrico S 9 , e il compito è solo quello di scrivere una permutazione come prodotto dei generatori. Mathematica ha una funzione integrata per farlo.

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

Esempio:

Ingresso:

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

Produzione:

{-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: in alto a sinistra in senso orario
  • 2: in alto a destra in senso orario
  • 3: in basso a destra in senso orario
  • 4: in basso a sinistra in senso orario
  • -1: in alto a sinistra in senso antiorario
  • -2: in alto a destra in senso antiorario
  • -3: in basso a destra in senso antiorario
  • -4: in basso a sinistra in senso antiorario
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.