Come rappresentare un cubo di Rubik in una struttura di dati


58

Se sto tentando di simulare un cubo di Rubik , come creereste una struttura di dati per memorizzare lo stato del cubo in memoria, con un numero X di tessere per lato?

Cose da considerare:

  • il cubo può essere di qualsiasi dimensione
  • è un cubo di Rubik, quindi gli strati possono essere ruotati

2
Compiti a casa? O problema del mondo reale ...
SDG

4
Potresti essere interessato al codice sorgente di quel risolutore cubo di Rubik .
mouviciel,

22
Sono abbastanza sicuro che il numero di lati di un cubo dovrebbe essere 6
Simon Bergot

3
Sarei curioso di vedere un modello del cubo di Rubik appiattito in modo da poter vedere tutti i lati contemporaneamente. Sono tentato di scriverlo ora. Potrebbe essere la forma di una T o persino infinitamente piastrellata (non ho pensato fino in fondo).
Lee Kowalkowski il

6
Sono tentato di citare Eric Evans, "I modelli non sono né giusti né sbagliati. Sono semplicemente più o meno utili" (la citazione probabilmente non è corretta al 100% in quanto è citata dalla memoria)
Pete,

Risposte:


37

Cosa c'è che non va in una semplice vecchia gamma di dimensioni [6X][X]? Non hai bisogno di conoscere mini-cubi interni , perché non li vedi; non fanno parte dello stato del cubo. Nascondi due metodi orribili dietro un'interfaccia gradevole e semplice da usare, l'unità prova a morte e voilà, il gioco è fatto!


31
anche un vero cubo di Rubik non ha mini cubetti interni
jk.

8
Funzionerà ma il tuo algoritmo sarà probabilmente eccessivamente complesso per accogliere una struttura di dati così semplice.
maple_shaft

6
As long as you know how the six surfaces are "threaded" together Questo è esattamente ciò che ti darà una struttura dati più solida. Penso che stiamo discutendo per la stessa cosa. Una matrice di lati, e una parte è una matrice di blocchi, tuttavia ci sono molte proprietà interessanti su lati e blocchi che aiutano a capire che "threading" Non mi piace quel termine perché può essere confuso con il multi-threading; )
maple_shaft

7
@maple_shaft "Questo è esattamente ciò che ti darà una struttura dati più solida." Non lo so: una struttura di dati con più "struttura" determinerebbe necessariamente un'ulteriore complessità accidentale relativa alla configurazione, alla manutenzione e all'accesso alle singole parti di quella struttura. È difficile dire cosa sarebbe più complesso: implementare brutti turni su un semplice array con alcuni casi angolari più una "corsa libera" sull'accesso alle singole celle o una struttura con spostamenti un po 'meno complessi e letture un po' più complesse.
dasblinkenlight,

16
Come qualcuno che ha effettivamente scritto programmi per manipolare un cubo di Rubik, ho adottato il semplice approccio di utilizzare sei matrici bidimensionali, una per faccia. È vero che devi implementare alcune operazioni fondamentali sul cubo che sono un po 'fastidiose, ma dopo sei libero di dimenticare la rappresentazione. Non è mai stato un problema per me. Mi chiedevo spesso come avrebbero funzionato altre rappresentazioni dal punto di vista della performance, ma non mi sono mai sentito gravato dal punto di vista della programmazione.
PeterAllenWebb,

39

Va notato che sono un appassionato di speed cuber, ma non ho mai provato a rappresentare programmaticamente un cubo di Rubik in un algoritmo o in una struttura di dati.

Probabilmente avrei creato strutture di dati separate per catturare gli aspetti unici di ciascun blocco in un cubo.

Esistono 3 tipi distinti di blocchi su un cubo:

  1. Corner Block - Ha tre facce colorate e tre pezzi adiacenti con cui condividerà un lato in qualsiasi momento.

  2. Edge Block - Ha due facce di colore e ha 4 pezzi adiacenti con cui condividerà un lato in qualsiasi momento. In blocchi 3x3 ha sempre 2 pezzi centrali e 2 pezzi d'angolo.

  3. Blocco centrale - In un cubo 3x3 questo pezzo non è mobile, tuttavia può essere ruotato. Avrà sempre 4 blocchi di bordi adiacenti. Nei cubi più grandi ci sono più blocchi centrali che potrebbero essere condivisi con un altro blocco centrale o un bordo. I blocchi centrali non sono mai adiacenti a un blocco d'angolo.

Sapendo questo, un blocco può avere un elenco di riferimenti ad altri blocchi che tocca. Terrei un altro elenco di elenchi, che sarebbe un elenco di blocchi che rappresentano una singola faccia del cubo e un elenco che mantiene i riferimenti a ogni faccia del cubo.

Ogni faccia del cubo sarebbe rappresentata come una faccia unica.

Con queste strutture di dati sarebbe abbastanza facile scrivere un algoritmo che esegua una trasformazione di rotazione su ogni faccia, spostando i blocchi appropriati dentro e fuori dagli elenchi appropriati.

EDIT: Nota importante, questi elenchi devono essere ordinati ovviamente, ma ho dimenticato di menzionarlo. Ad esempio, se capovolgo il lato destro, il blocco del lato destro dell'angolo sinistro si sposta nell'angolo destro del lato destro e viene ruotato in senso orario.


concordare che ogni blocco dovrebbe avere proprietà uniche. ma la trasformazione non sarebbe noiosa perché devi aggiornare i riferimenti ai blocchi adiacenti e ai tuoi list of lists. forse è meglio avere solo un elenco non ordinato di blocchi su cui è possibile eseguire una query. e aggiorni semplicemente i riferimenti di blocco adiacenti quando esegui una trasformazione. Se si desidera un elenco di tutti i blocchi in una faccia, è possibile eseguire una query sull'elenco per tutti i blocchi adiacenti ai blocchi centrali, giusto?
Mel

@Mel È possibile farlo in entrambi i modi, ma dopo aver parlato con dasblinkenlight penso davvero che il suo approccio sarebbe meno complesso. Vorrei che la sua risposta avesse più voti della mia. Non sono così bravo con gli algoritmi e il più efficiente, mi piacciono molto i cubi di rubik e li raccolgo (oltre 40 tipi diversi da tutto il mondo).
maple_shaft

Sebbene la risposta di dasblinknenlight sia la soluzione più semplice, ti sto assegnando la generosità perché mi piace il fatto che la tua risposta includa una parte della logica che sarebbe necessaria per una tale struttura di dati e i diversi attributi di blocco
Rachel,

Questo modello di dati è più fedele alla realtà, ma rende alcune delle semplici operazioni che vorresti fare più difficili di quanto dovrebbero essere - Basta ottenere lo stato del cubo richiederebbe un viaggio ricorsivo attraverso elenchi di cubi che sono ingombranti.
Ziv,

@Ziv True, tuttavia la domanda era sulla struttura dei dati e non necessariamente sugli algoritmi.
maple_shaft

13

Quando penso a questo problema, penso a un cubo statico con i colori che si muovono attraverso di esso in schemi noti. Così....

Un oggetto Cube contiene 6 oggetti Side che rimangono fissi indicizzati 0-5. Ogni lato contiene 9 oggetti posizione che rimangono fissi indicizzati 0-8. Ogni posizione contiene un colore.

Per semplicità, gestisci ogni azione con incrementi di un quarto di giro. Esistono 3 assi di rotazione, ciascuno in 2 possibili direzioni per un totale di 6 possibili azioni sul cubo. Con queste informazioni, diventa un compito abbastanza semplice mappare le 6 possibili azioni sul cubo.

Quindi il colore verde nel lato 6, posizione 3, può spostarsi nella posizione 3 lato 1, o nella posizione 7 lato 2, tra l'altro, a seconda dell'azione intrapresa. Non l'ho esplorato abbastanza per trovare traduzioni matematiche, ma probabilmente emergeranno schemi che puoi trarre vantaggio dal codice.

Utilizzando la struttura dei dati, come posso sapere se un determinato cubo in un determinato stato è risolvibile? Ho avuto difficoltà con questa domanda e non ho ancora trovato la risposta.

Per fare ciò, non iniziare mai con uno stato cubo casuale. Invece, inizia con uno stato risolto ed esegui n azioni a livello di codice per portare il cubo in uno stato iniziale casuale. Dato che hai intrapreso solo azioni legali per arrivare allo stato attuale, il cubo deve essere risolvibile.


1
Classico consiglio "non vuoi iniziare da qui"!
Ergwun,

8

Ho trovato un sistema di coordinate xyz un modo semplice per indirizzare un cubo di Rubik e le matrici di rotazione un modo semplice e generico per implementare le rotazioni.

Ho creato una classe Piece contenente un vettore di posizione (x, y, z). Un pezzo può essere ruotato applicando una matrice di rotazione nella sua posizione (una moltiplicazione matrice-vettore). Il pezzo tiene anche traccia dei suoi colori in una tupla (cx, cy, cz), dando i colori rivolti lungo ciascun asse. Una piccola quantità di logica garantisce che questi colori vengano aggiornati in modo appropriato durante una rotazione: una rotazione di 90 gradi nel piano XY significa che scambiamo i valori di cxe cy.

Poiché tutta la logica di rotazione è incapsulata nella classe Pezzo, il cubo può memorizzare un elenco non ordinato di pezzi e le rotazioni possono essere eseguite in modo generico. Per eseguire una rotazione della faccia sinistra, selezionare tutti i pezzi con una coordinata x di -1 e applicare la matrice di rotazione appropriata a ciascun pezzo. Per eseguire una rotazione dell'intero cubo, applicare la stessa matrice di rotazione su ogni pezzo.

Questa implementazione è semplice e ha un paio di aspetti positivi:

  1. La posizione di un oggetto Pezzo cambierà, ma i suoi colori no. Questo significa che puoi chiedere il pezzo rosso-verde, aggrapparti all'oggetto, fare alcune rotazioni e controllare lo stesso oggetto per vedere dove è finito il pezzo rosso-verde.
  2. Ogni tipo di pezzo (bordo, centro, angolo) ha un modello di coordinate unico. Per un cubo 3x3, un pezzo d'angolo non ha zeri nel suo vettore di posizione ( (-1, 1, 1)), un bordo ha esattamente uno zero ( (1, 0, -1)) e un pezzo centrale ha due zero ( (-1, 0, 0)).
  3. Le matrici di rotazione che funzionano per un cubo 3x3 funzioneranno per un cubo NxN.

Svantaggi:

  1. La moltiplicazione matrice-vettore è più lenta rispetto allo scambio di valori negli array.
  2. Ricerche a tempo lineare per pezzi per posizione. Dovresti archiviare i pezzi in una struttura di dati esterna e aggiornarli durante le rotazioni per ricerche a tempo costante per posizione. Questo sconfigge parte dell'eleganza dell'uso delle matrici di rotazione e fa perdere la logica di rotazione nella tua classe Cube. Se avessi implementato qualsiasi tipo di algoritmo di risoluzione basato sulla ricerca, avrei usato un'altra implementazione.
  3. L'analisi dei modelli (durante la risoluzione) non è così piacevole come potrebbe essere. Un pezzo non ha conoscenza dei suoi pezzi adiacenti e l'analisi sarebbe lenta a causa dei problemi di prestazioni sopra indicati.

3
Ho scoperto che questo tipo di implementazione funziona meglio per rappresentare il cubo in un programma di grafica 3D. La moltiplicazione della matrice consente di animare le rotazioni del livello. Vedi questo repository github per un'implementazione di esempio di questo approccio. Sto considerando che per aggiungere un risolutore al mio cubo 3D, avrò bisogno di un algoritmo e di una struttura dati da una delle altre risposte.
Jonathan Wilson,

5

puoi usare un array semplice (ogni elemento con una mappatura da 1 a 1 su un quadrato su una faccia) e simulare ogni rotazione con una certa permutazione

puoi cavartela con solo 3 permutazioni essenziali: ruota una fetta con l'asse attraverso la faccia anteriore, ruota il cubo attorno all'asse verticale e ruota il cubo sull'asse orizzontale attraverso le facce sinistra e destra. tutte le altre mosse possono essere espresse da una concatenazione di questi tre.

il modo più semplice per sapere se un cubo è risolvibile è risolverlo (trova una serie di permutazioni che risolveranno il cubo), se finisci con 2 bordi che hanno un posto scambiato, un solo bordo capovolto, un singolo angolo capovolto o 2 angoli scambiati hai un cubo irrisolvibile


1
the most straightforward way of know whether a cube is solvable is to solve it. Bene, usando il modello che suggerisci immagino sia vero. Ma se usi un modello più vicino a @ maple_shaft's e segui le rotazioni, puoi verificare rapidamente se un cubo 3x3x3 è risolvibile verificando la somma dei capovolgimenti mod 2 è 0 e le rotazioni degli angoli mod 3 è 0. Quindi controlla la parità della permutazione contando i edge swap e gli corner swap (necessari per tornare alla risoluzione), la loro somma mod 2 deve essere 0 (parità totale pari). Questi sono i test necessari e sufficienti per dimostrare che il cubo è risolvibile.
Jimhark,

3

La prima condizione che sia risolvibile sarebbe che ogni pezzo fosse presente e che i colori su ogni pezzo potessero essere usati per assemblare un cubo "sovrano". Questa è una condizione relativamente banale la cui verità può essere determinata con una semplice lista di controllo. La combinazione di colori su un cubo "standard" è definita , ma anche se non hai a che fare con un cubo standard ce ne sono solo 6! possibili combinazioni di facce risolte.

Una volta che hai tutti i pezzi e i colori giusti, allora è una questione determinare se una determinata configurazione fisica è risolvibile. Non tutti lo sono. Il modo più ingenuo per verificarlo è eseguire un algoritmo di risoluzione dei cubi e vedere se termina con un cubo risolto. Non so se ci siano tecniche combinatorie fantasiose per determinare la solvibilità senza effettivamente cercare di risolvere il cubo.

Per quanto riguarda la struttura dei dati ... che quasi non importa. La parte difficile è ottenere le trasformazioni giuste ed essere in grado di rappresentare lo stato del cubo in un modo che ti consenta di lavorare ordinatamente con gli algoritmi disponibili in letteratura. Come indicato da acero, ci sono tre tipi di pezzi. La letteratura sulla risoluzione del cubo di rubik si riferisce sempre a pezzi del loro tipo. Le trasformazioni sono anche rappresentate in modi comuni (consultare la notazione di Singmaster ). Inoltre, tutte le soluzioni che ho visto si riferiscono sempre a un pezzo come punto di riferimento (di solito mettendo il pezzo centrale bianco in basso).


1
Per il punto 2, invece di iniziare con un cubo casuale e verificare se è risolvibile. Vorrei iniziare con un cubo risolto ed eseguire n azioni casuali sul cubo per portarlo in uno stato casuale.
Matthew Vines,

Sì, assolutamente, questo è il modo più semplice per generare una configurazione che è fisicamente possibile risolvere. Iniziare con una configurazione arbitraria e determinare se è risolvibile è sicuramente un problema separato ma correlato.
Angelo

2
Si ipotizza che potrebbero esserci "tecniche fantasiose" per determinare se un cubo può essere risolto; in effetti ci sono. Se si smonta un cubo ma si mantengono gli adesivi e quindi si rimonta il cubo, non si ottiene necessariamente un cubo che è risolvibile; in effetti, le probabilità sono 1-12 contro il fatto che hai un cubo risolvibile. Puoi determinare se sei in uno stato risolvibile attraverso un'analisi di parità dei bordi e degli angoli; in realtà non devi provare a risolvere il cubo.
Eric Lippert,

1
Ecco una breve panoramica dei tre tipi di proprietà di associazione dei cubi che devono essere conservati affinché il cubo sia risolvibile. ryanheise.com/cube/cube_laws.html .
Eric Lippert,

1
Ho pubblicato quella domanda nel sito dello stackexchange della partita e ho ottenuto risposte davvero piacevoli: math.stackexchange.com/questions/127577/…
Mel

3

Dato che hai già ricevuto ottime risposte, vorrei aggiungere solo un dettaglio.

Indipendentemente dalla tua rappresentazione concreta, nota che gli obiettivi sono uno strumento molto fine per "ingrandire" le varie parti di un cubo. Ad esempio, guarda la funzione cycleLeftin questo codice Haskell . È una funzione generica che consente ciclicamente qualsiasi elenco di lunghezza 4. Il codice per eseguire la mossa L è simile al seguente:

moveL :: Aut (RubiksCube a)
moveL =
    cong cube $ cong leftCols cycleLeft
              . cong leftSide rotateSideCW

cycleLeftFunziona così secondo il punto di vista di leftCols . Allo stesso modo, rotateSideCWche è una funzione generica che affianca una versione ruotata di essa, opera sulla vista data da leftSide. Le altre mosse possono essere implementate in modi simili.

L'obiettivo di quella libreria Haskell è quello di creare belle immagini. Penso che sia riuscito: La libreria diagrams-rubiks-cube in azione


2

Sembra che tu stia facendo due domande separate.

  1. Come rappresentare un cubo con X numero di lati?

Se hai intenzione di simulare un cubo di Rubic nel mondo reale, tutti i cubi di Rubik hanno 6 lati. Penso che tu voglia dire "numero X di tessere per dimensione per lato". Ogni lato del cubo di Rubic originale è 3x3. Altre dimensioni includono 4x4 (Professor's Cube), 5x5 e 6x6.

Vorrei rappresentare i dati con 6 lati, usando la notazione di risoluzione del cubo "standard":

  • FRONTE: la faccia di fronte al risolutore
  • INDIETRO
  • GIUSTO
  • SINISTRA
  • SU
  • GIÙ

Ogni lato è un array 2-D di X per X.


Puoi comprare un cubo 17x17 ! Presenta alcuni compromessi meccanici, ma è isomorfo rispetto alla realtà.
RBerteig,

1

Mi piace l'idea di @maple_shaft di rappresentare diversi pezzi (mini-cubi) in modo diverso: i pezzi centrale, bordo e angolo portano rispettivamente 1, 2 o 3 colori.

Rappresenterei le relazioni tra loro come un grafico (bidirezionale), con bordi che collegano pezzi adiacenti. Ogni pezzo avrebbe una serie di fessure per i bordi (connessioni): 4 fessure in pezzi centrali, 4 fessure in pezzi di bordo, 3 fessure in pezzi d'angolo. In alternativa, i pezzi centrali possono avere 4 connessioni ai pezzi di bordo e 4 per i pezzi d'angolo separatamente, e / o pezzi di bordo possono avere 2 collegamento ai pezzi centrali e 2 ai pezzi d'angolo separatamente.

Questi array sono ordinati in modo tale che l'iterazione sui bordi del grafico rappresenti sempre la stessa rotazione, modulo la rotazione del cubo. Cioè, ad esempio per un pezzo centrale, se si ruota il cubo in modo che la sua faccia sia in alto, l'ordine delle connessioni è sempre in senso orario. Allo stesso modo per bordi e pezzi d'angolo. Questa proprietà è valida dopo le rotazioni della faccia (o almeno così mi sembra ora).

  • Trovare pezzi appartenenti a un bordo è banale.
  • Trovare pezzi appartenenti a una faccia è banale.
  • Trovare facce che si trovano in una determinata direzione rispetto a una determinata faccia, o una faccia opposta, sta attraversando 2 o 3 collegamenti ben definiti.
  • Per ruotare una faccia, aggiorna le connessioni di tutti i pezzi collegati al pezzo centrale della faccia.

Rilevamento di condizioni chiaramente irrisolvibili (bordi scambiati / capovolti, angolo scambiato) se si spera anche facile, perché trovare pezzi di un tipo particolare e il loro orientamento è semplice.


1

Che ne dici di nodi e puntatori?

Supponendo che ci siano sempre 6 facce e che 1 nodo rappresenti 1 quadrato su 1 faccia:

r , g , b
r , g , b
r , g , b
|   |   |
r , g , b - r , g , b
r , g , b - r , g , b
r , g , b - r , g , b

Un nodo ha un puntatore a ciascun nodo accanto ad esso. Una rotazione del cerchio migra solo il puntatore (Numero di nodi / Numero di facce) -1 nodi sopra, in questo caso 2. Poiché tutte le rotazioni sono rotazioni di cerchi, è sufficiente creare una rotatefunzione. È ricorsivo, sposta ogni nodo di uno spazio e controlla se li ha spostati abbastanza, poiché avrà raccolto il numero di nodi e ci sono sempre quattro facce. In caso contrario, incrementa il numero di volte in cui il valore è stato spostato e la chiamata ruota di nuovo.

Non dimenticare che è doppiamente collegato, quindi aggiorna anche i nodi appena appuntiti. Ci sarà sempre Altezza * Larghezza numero di nodi spostati, con un puntatore aggiornato per nodo, quindi ci dovrebbe essere Altezza * Larghezza * 2 numero di puntatori aggiornati.

Dal momento che tutti i nodi puntano l'uno verso l'altro, basta andare in circolo aggiornando ciascun nodo man mano che ci si arriva.

Questo dovrebbe funzionare per qualsiasi cubo di dimensioni, senza casi limite o logica complessa. È solo una camminata / aggiornamento del puntatore.


-1

Dall'esperienza personale usando un set per tenere traccia di ogni parte rotazionale del cubo funziona bene. Ogni sottocubo è in tre set senza alcuna dimensione del cubo di rubik. Quindi, per trovare un cubo secondario in un punto in cui sul cubo di Rubik prendi semplicemente l'intersezione dei tre set (il risultato è un cubo secondario). Per fare una mossa, rimuovi i cubetti secondari effettuati dagli insiemi coinvolti nella mossa e poi rimettili negli insiemi che li prendono come risultato della mossa.

Il cubo 4 per 4 avrà 12 set. 6 set per le 6 facce e 6 set per le sei fasce che vanno attorno al cubo. Le facce hanno ciascuna 16 cubetti secondari e le bande hanno 12 cubetti ciascuno. Ci sono un totale di 56 cubetti secondari. Ogni cubo secondario contiene informazioni sul colore e sulla direzione dei colori. Il cubo di rubik stesso è un array 4 per 4 per 4 con ogni elemento con informazioni costituite dai 3 set che definiscono il cubo secondario in quella posizione.

A differenza delle altre 11 risposte, questa struttura di dati richiede l'utilizzo dell'intersezione di insiemi per definire la posizione di ciascun sottoblocco nel cubo. Questo risparmia il lavoro di dover aggiornare i blocchi secondari vicini quando viene effettuata una modifica.


questo non sembra offrire nulla di sostanziale rispetto ai punti formulati e spiegati nelle precedenti 11 risposte
moscerino del
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.