Rubik-sorting a matrix (aka il puzzle del toro)


16

L'idea per questa è semplice: data una matrice di numeri interi, ordiniamola applicando i movimenti in stile Rubik. Ciò significa che puoi selezionare una singola riga o colonna e ruotare i suoi elementi in qualsiasi direzione:

[1, 3, 2, 4]  => [3, 2, 4, 1] (rotate left for rows/up for columns)
[1, 3, 2, 4]  => [4, 1, 3, 2] (rotate right for rows/down for columns)

Quindi, data una matrice di numeri interi di qualsiasi dimensione, ordina i suoi elementi applicando solo queste trasformazioni in stile Rubik. Una matrice

[un'11un'12un'13un'14un'21un'22un'23un'24un'31un'32un'33un'34]

saranno considerati ordinati se i suoi elementi sono conformi alla seguente limitazione:

un'11un'12un'13un'14un'21un'22un'23un'24un'31un'32un'33un'34

I / O

  • L'input sarà una matrice di numeri interi positivi senza valori ripetuti.
  • L'output saranno i movimenti necessari per ordinarlo. Poiché questa non è una sfida di golf del codice e non devi preoccuparti della sua lunghezza, il formato proposto per ogni movimento è #[UDLR]dove si #trova il numero della riga o della colonna da spostare (indicizzato 0) ed [UDLR]è un singolo carattere in quella intervallo che specifica se il movimento è Su / Giù (per le colonne) o Sinistra / Destra (per le righe). Quindi 1Usignificherebbe "spostare la prima colonna verso l'alto", ma 1Rsarebbe "spostare la prima riga verso destra". Movimenti saranno separate da virgole, quindi una soluzione sarà espressa come segue: 1R,1U,0L,2D.

punteggio

Cercare di ordinare una matrice in questo modo può essere costoso in quanto ci sono molte possibili combinazioni di mosse e ci sono anche molti elenchi possibili di mosse che possono ordinarla, quindi l'obiettivo è quello di scrivere un codice che ordina N N matrici di seguito. Il punteggio sarà la dimensione N massima che è possibile risolvere in un ragionevole lasso di tempo 1 senza errori (maggiore è la dimensione della matrice risolta, meglio è). In caso di pareggio, il pareggio sarà il numero di movimenti nel percorso trovato (più breve è il percorso, meglio è).

Esempio: se un utente A trova una soluzione per N = 5 e B trova una soluzione per N = 6, B vince indipendentemente dalla lunghezza di entrambi i percorsi. Se entrambi trovano soluzioni per N = 6 ma la soluzione trovata da A ha 50 passi e la soluzione B ha 60 passi, A vince.

Le spiegazioni su come funziona il tuo codice sono fortemente incoraggiate e ti preghiamo di pubblicare le soluzioni trovate in modo da poterle testare . Puoi usare Pastebin o strumenti simili se le soluzioni sono troppo grandi. Inoltre, sarà apprezzata una stima del tempo impiegato dal tuo codice per trovare le tue soluzioni.

Casi test

Le seguenti matrici ( collegamento di Pastebin per una versione più incollabile) sono state create a partire da matrici già ordinate mescolandole con movimenti casuali in stile Rubik 10K:

[8561110131513]
[211012161762214851926132431]
[11381659402126221124143928321937310301736734]
[34214022354118333130124319113924282344136538451417916132683476254]
[20361711550187267341032355424396306139284154272357048132512465863523784533146859655673606422]
[85565275894441682715879132373973676419997846164221631004172131197309328403365070258058960845496172943342335776182482943866]
[567990617112211031551144284851306188443386611324962010275685888098351007713216410810601144023472731068232120263653936910454191111176217278873349155811695112571189151426545]

Casi di testo in chiaro:

[[8, 5, 6], [11, 10, 1], [3, 15, 13]]

[[21, 10, 12, 16], [17, 6, 22, 14], [8, 5, 19, 26], [13, 24, 3, 1]]

[[1, 13, 8, 16, 5], [9, 40, 21, 26, 22], [11, 24, 14, 39, 28], [32, 19, 37, 3, 10], [30, 17, 36, 7, 34]]

[[34, 21, 40, 22, 35, 41], [18, 33, 31, 30, 12, 43], [19, 11, 39, 24, 28, 23], [44, 1, 36, 5, 38, 45], [14, 17, 9, 16, 13, 26], [8, 3, 47, 6, 25, 4]]

[[20, 36, 17, 1, 15, 50, 18], [72, 67, 34, 10, 32, 3, 55], [42, 43, 9, 6, 30, 61, 39], [28, 41, 54, 27, 23, 5, 70], [48, 13, 25, 12, 46, 58, 63], [52, 37, 8, 45, 33, 14, 68], [59, 65, 56, 73, 60, 64, 22]]

[[85, 56, 52, 75, 89, 44, 41, 68], [27, 15, 87, 91, 32, 37, 39, 73], [6, 7, 64, 19, 99, 78, 46, 16], [42, 21, 63, 100, 4, 1, 72, 13], [11, 97, 30, 93, 28, 40, 3, 36], [50, 70, 25, 80, 58, 9, 60, 84], [54, 96, 17, 29, 43, 34, 23, 35], [77, 61, 82, 48, 2, 94, 38, 66]]

[[56, 79, 90, 61, 71, 122, 110, 31, 55], [11, 44, 28, 4, 85, 1, 30, 6, 18], [84, 43, 38, 66, 113, 24, 96, 20, 102], [75, 68, 5, 88, 80, 98, 35, 100, 77], [13, 21, 64, 108, 10, 60, 114, 40, 23], [47, 2, 73, 106, 82, 32, 120, 26, 36], [53, 93, 69, 104, 54, 19, 111, 117, 62], [17, 27, 8, 87, 33, 49, 15, 58, 116], [95, 112, 57, 118, 91, 51, 42, 65, 45]]

Chiedi di più se li risolvi tutti. :-) E molte grazie alle persone che mi hanno aiutato a perfezionare questa sfida mentre ero nella sandbox .


1 Un ragionevole lasso di tempo: qualsiasi periodo di tempo che non mina la nostra pazienza durante il test della soluzione. Nota che TIO esegue il codice solo per 60 secondi, qualsiasi periodo di tempo oltre tale limite ci farà testare il codice nelle nostre macchine. Esempio: il mio algoritmo piuttosto inefficiente impiega alcuni millisecondi per risolvere matrici di ordine 3x3 e 4x4, ma l'ho appena testato con una matrice 5x5 e ci sono voluti 317 secondi per risolverlo (in oltre 5 milioni di movimenti, molto divertente se lo consideriamo la matrice da risolvere è stata criptata solo 10K volte). Ho provato a ridurre il numero di movimenti a meno di 10K ma mi sono arreso dopo 30 minuti eseguendo il codice.


1
Bella sfida! Tuttavia, ho alcune richieste / domande: 1) Potresti fornire i casi di test in un formato più facile da copiare e incollare? (come pastebin) 2) Potresti fornire una definizione più precisa dell'ordine dei termini? 3) La matrice è garantita per essere quadrata? (I casi di test lo suggeriscono, ma la definizione no.)
Arnauld

@Arnauld 1) Ci sto lavorando. 2) Non volevo stabilire un limite di tempo e nessuno ha suggerito alcun limite mentre la sfida era nella sandbox. Se ne hai bisogno, considereresti 30 minuti un limite ragionevole? 3) Sì, le matrici di prova sono quelle mostrate e saranno tutte quadrate se ne occorrono di più.
Charlie,

Esiste un algoritmo O (dimensioni di input) (relativamente facile da implementare) per questa sfida, quindi non è così interessante come potrebbe sembrare all'inizio.
user202729,

@ user202729 Quale sarebbe la dimensione di input nel tuo O(input size)allora? Per una matrice 5x5 sarebbe O(25)? Sembra essere estremamente veloce, quindi sarei molto interessato a vedere quel tuo algoritmo o implementazione del tuo. EDIT: Ti rendi conto che inseriamo la matrice 'strapazzata' e produciamo i movimenti, giusto? Non il contrario.
Kevin Cruijssen,

4
Penso che sia qualcosa di simile a questo algoritmo
Kirill L.,

Risposte:


8

Nim

import algorithm, math, sequtils, strutils

let l = split(stdin.readLine())
var m = map(l, parseInt)
let n = int(sqrt(float(len(m))))
let o = sorted(m, system.cmp[int])

proc rotations(P, Q: int): tuple[D, L, R, U: string, P, Q: int]=
  result = (D: "D", L: "L", R: "R", U: "U", P: P, Q: Q)
  if P > n - P:
    result.D = "U"
    result.U = "D"
    result.P = n - P
  if Q > n - Q:
    result.L = "R"
    result.R = "L"
    result.Q = n - Q

proc triangle(r: int): string=
  let p = r div n
  let q = r mod n
  let i = find(m, o[r])
  let s = i div n
  let t = i mod n
  var u = s
  var v = q
  if s == p and t == q:
    return ""
  var patt = 0
  if p == s: 
    u = s + 1
    patt = 4
  elif q == t:
    if q == n - 1:
      v = t - 1
      patt = 8
    else:
      u = p
      v = t + 1
      patt = 3
  elif t > q:
    patt = 2
  else:
    patt = 7
  var Q = abs(max([q, t, v]) - min([q, t, v]))
  var P = abs(max([p, s, u]) - min([p, s, u]))
  let x = p*n + q
  let y = s*n + t
  let z = u*n + v
  let w = m[x]
  m[x] = m[y]
  m[y] = m[z]
  m[z] = w
  let R = rotations(P, Q)

  result = case patt:
    of 2:
      repeat("$#$#," % [$v, R.D], R.P) & 
        repeat("$#$#," % [$u, R.L], R.Q) &
        repeat("$#$#," % [$v, R.U], R.P) & 
        repeat("$#$#," % [$u, R.R], R.Q)
    of 3:
      repeat("$#$#," % [$q, R.U], R.P) & 
        repeat("$#$#," % [$p, R.L], R.Q) &
        repeat("$#$#," % [$q, R.D], R.P) & 
        repeat("$#$#," % [$p, R.R], R.Q)
    of 4:
      repeat("$#$#," % [$p, R.L], R.Q) & 
        repeat("$#$#," % [$q, R.U], R.P) &
        repeat("$#$#," % [$p, R.R], R.Q) & 
        repeat("$#$#," % [$q, R.D], R.P)
    of 7:
      repeat("$#$#," % [$v, R.D], R.P) & 
        repeat("$#$#," % [$u, R.R], R.Q) &
        repeat("$#$#," % [$v, R.U], R.P) & 
        repeat("$#$#," % [$u, R.L], R.Q)
    of 8:
      repeat("$#$#," % [$s, R.R], R.Q) & 
        repeat("$#$#," % [$t, R.D], R.P) &
        repeat("$#$#," % [$s, R.L], R.Q) & 
        repeat("$#$#," % [$t, R.U], R.P)
    else: ""

proc Tw(p, t, P, Q: int): string =
  let S = P + Q
  result = "$#D,$#$#U,$#$#D,$#$#U," % [
    $t, if P > n - P: repeat("$#L," % $p, n - P) else: repeat("$#R," % $p, P),
    $t, if S > n - S: repeat("$#R," % $p, n - S) else: repeat("$#L," % $p, S), 
    $t, if Q > n - Q: repeat("$#L," % $p, n - Q) else: repeat("$#R," % $p, Q), 
    $t]

proc line(r: int): string=
  let p = n - 1
  let q = r mod n
  let i = find(m, o[r])
  var t = i mod n
  if t == q: 
    return ""
  let j = t == n - 1
  var P = t - q
  let x = p*n + q
  let y = x + P
  let z = y + (if j: -1 else: 1)
  let w = m[x]
  m[x] = m[y]
  m[y] = m[z]
  m[z] = w
  if j:
    let R = rotations(1, P)
    result = "$#D,$#$#U,$#$#R,$#D,$#L,$#U," % [
      $t, repeat("$#$#," % [$p, R.R], R.Q), 
      $t, repeat("$#$#," % [$p, R.L], R.Q), 
      $p, $t, $p, $t]
  else:
    result = Tw(p, t, P, 1)  
  
proc swap: string=
  result = ""
  if m[^1] != o[^1]:
    m = o
    for i in 0..(n div 2-1):
      result &= Tw(n - 1, n - 2*i - 1, 1, 1)
    result &= "$#R," % $(n - 1)
  
var moves = ""
for r in 0..(n^2 - n - 1):
  moves &= triangle(r)
if n == 2 and m[^1] != o[^1]:
  m = o
  moves &= "1R"
else:
  for r in (n^2 - n)..(n^2 - 3):
    moves &= line(r)
  if n mod 2 == 0:
    moves &= swap()
  if len(moves) > 0:
    moves = moves[0..^2]
  
echo moves

Provalo online!

Un rapido tentativo di implementare l'algoritmo della soluzione di puzzle Torus da un articolo pubblicato in Algorithms 2012, 5, 18-29 che ho citato nei commenti.

Accetta la matrice di input in forma appiattita, come una linea di numeri delimitati da spazi.

Anche qui c'è un validatore in Python 2 . Ci vogliono due righe come input: la matrice codificata originale nella stessa forma del codice principale e la sequenza di mosse proposta. L'output del validatore è la matrice risultante dall'applicazione di queste mosse.

Spiegazione

Nella prima parte dell'algoritmo, ordiniamo tutte le righe tranne l'ultima.

proc triangle[p,q][S,t][p,q][u,v]

In Fig. 2, gli autori presentano 8 possibili schemi e le corrispondenti sequenze di mosse, ma nel mio codice tutti i casi sono stati effettivamente coperti da soli 5 schemi, quindi no. 1, 5 e 6 non vengono utilizzati.

Nella seconda parte, l'ultima riga, tranne i due ultimi elementi, viene ordinata eseguendo "tre elementi di rotazione" su una linea ( proc line), che consistono in due rotazioni di triangolo ciascuna (vedere la Figura 3 dell'articolo).

[p,q][S,t][S,t+1]TWtt+1[S,t-1]TW

nnTW=Rproc swapTW

n=2 non abbiamo affatto bisogno di tutte queste procedure complesse - se gli elementi dell'ultima riga non sono presenti dopo la parte 1, un singolo 1R lo spostamento è sufficiente per rendere la matrice completamente ordinata.

Aggiornamento: aggiunto nuovo proc rotationsche inverte la direzione delle mosse se ciò comporterebbe meno passi.


Degno di nota! Creerò altri casi di test allora. Nel frattempo, sono sicuro che questa soluzione può essere ottimizzata, poiché ci sono blocchi come 7L,7L,7L,7L,7D,7D,7D,7Dquelli che possono essere ridotti e in cui 8R,8R,8R,8R,8R,8R,8Rpossono essere convertiti 8L,8Lper una matrice 9x9.
Charlie,

Ho provato il tuo algoritmo con una matrice 100x100 e lo risolve in meno di 4 secondi. Non mi aspettavo davvero che questo problema avesse una soluzione a tempo lineare. Proverò a scrivere sfide migliori in futuro!
Charlie,

Forse sarebbe stato meglio porre questa sfida con una singola matrice fissa come unico caso di prova e impostare il criterio vincente come la dimensione del percorso trovato per risolverlo, se avessi saputo prima che questo problema aveva una O (n ^ 2) soluzione. Considereresti di portare questa risposta a una nuova domanda con tale criterio vincente?
Charlie,

@Charlie Mentre cercherò ancora di affinare un po 'la soluzione attuale, non ho davvero idea di come affrontare il problema generale di ottimizzazione del percorso ...
Kirill L.

5

Python 2 , dimensione 100 in <30 secondi su TIO

import random
def f(a):
 d = len(a)
 r = []
 def V(j, b = -1):
  b %= d
  if d - b < b:
   for k in range(d - b):
    if r and r[-1] == "U%d" % j:r.pop()
    else:r.append("D%d" % j)
    b = a[-1][j]
    for i in range(len(a) - 1):
     a[-1 - i][j] = a[-2 - i][j]
    a[0][j] = b
  else:
   for k in range(b):
    if r and r[-1] == "D%d" % j:r.pop()
    else:r.append("U%d" % j)
    b = a[0][j]
    for i in range(len(a) - 1):
     a[i][j] = a[i + 1][j]
    a[-1][j] = b
 def H(i, b = -1):
  b %= d
  if d - b < b:
   for k in range(d - b):
    if r and r[-1] == "L%d" % i:r.pop()
    else:r.append("R%d" % i)
    a[i] = a[i][-1:] + a[i][:-1]
  else:
   for k in range(b):
    if r and r[-1] == "R%d" % i:r.pop()
    else:r.append("L%d" % i)
    a[i] = a[i][1:] + a[i][:1]
 b = sorted(sum(a, []))
 for i in range(d - 1):
  for j in range(d):
   c = b.pop(0)
   e = sum(a, []).index(c)
   if e / d == i:
    if j == 0:H(i, e - j)
    elif j < e % d:
     if i:
      V(e % d, 1)
      H(i, j - e)
      V(e % d)
      H(i, e - j)
     else:
      V(e)
      H(1, e - j)
      V(j, 1)
   else:
    if j == e % d:
     H(e / d)
     e += 1
     if e % d == 0:e -= d
    if i:
     V(j, i - e / d)
    H(e / d, e - j)
    V(j, e / d - i)
 c = [b.index(e) for e in a[-1]]
 c = [sum(c[(i + j) % d] < c[(i + k) % d] for j in range(d) for k in range(j)) % 2 and d * d or sum(abs(c[(i + j) % d] - j) for j in range(d)) for i in range(d)]
 e = min(~c[::-1].index(min(c)), c.index(min(c)), key = abs)
 H(d - 1, e)
 for j in range(d - 2):
  e = a[-1].index(b[j])
  if e > j:
   c = b.index(a[-1][j])
   if c == e:
    if e - j == 1:c = j + 2
    else:c = j + 1
   V(e)
   H(d - 1, j - e)
   V(e, 1)
   H(d - 1, c - j)
   V(e)
   H(d - 1, e - c)
   V(e, 1)
 return r

Provalo online! Link include tre piccoli casi di test con output di spostamento completo, oltre a un test silenzioso di 100x100 per mostrare che il codice funziona (l'output di spostamento supererebbe i limiti di TIO). Spiegazione: Il codice tenta di eseguire un ordinamento di inserzione sull'array, costruendolo in ordine crescente man mano che procede. Per tutte le righe ad eccezione dell'ultima riga, esistono diversi casi:

  • L'elemento si trova nella riga corretta, ma appartiene alla colonna 0. In questo caso, viene semplicemente ruotato fino a raggiungere la colonna 0.
  • L'elemento è nella posizione corretta. In questo caso, non succede nulla. (Questo vale anche se l'elemento appartiene alla colonna 0, è solo che in quel caso si verificano 0 rotazioni.)
  • L'elemento si trova nella riga superiore ma nella colonna sbagliata. In tal caso, viene ruotato verso il basso, quindi in orizzontale fino a quando l'elemento si trova nella colonna corretta, quindi nuovamente ruotato verso l'alto.
  • L'elemento è nella riga corretta ma nella colonna sbagliata. In tal caso, viene ruotato verso l'alto, quindi la riga viene ruotata sulla sua colonna, quindi viene ruotata verso il basso, quindi la riga viene ruotata indietro. (In effetti questa è una rotazione del caso successivo.)
  • L'elemento si trova nella colonna corretta ma nella riga sbagliata. In tal caso, la riga viene ruotata a destra, per ridurla all'ultimo caso.
  • L'elemento è nella riga sbagliata e nella colonna sbagliata. In questo caso, la colonna corretta viene ruotata sulla riga sbagliata (ignorata per la riga superiore), quella riga viene quindi ruotata sulla colonna corretta e la colonna viene quindi ruotata indietro.

Le rotazioni sopra riportate vengono eseguite in qualunque direzione minimizza il numero di passi; un quadrato di dimensione 2 viene sempre risolto usando le mosse sinistra e su, indipendentemente dalla descrizione sopra.

Prima che la riga inferiore sia completata, viene ruotata per ridurre al minimo la distanza totale fuori posto, ma anche per garantire che la parità della riga inferiore sia uniforme, poiché non può essere modificata dalla parte finale dell'algoritmo. Se esiste più di una rotazione con la stessa distanza minima, viene scelta la rotazione con il minor numero di mosse.

L'algoritmo per la riga inferiore si basa su una sequenza di 7 operazioni che scambia gli elementi in tre colonne. La sequenza viene applicata a ciascuno dei numeri rimanenti della riga inferiore in ordine a sua volta per portarli nella posizione desiderata; se possibile, l'elemento in quella posizione viene spostato nella posizione desiderata, ma se è necessario uno scambio diretto l'elemento viene semplicemente spostato nella colonna disponibile più vicina, sperando che possa essere riparato la prossima volta.


Grazie mille per la tua risposta, Neil! Ma ricorda, questo non è un codice golf. Invece della lunghezza del codice, dovresti indicare la dimensione N massima della matrice NxN che hai risolto e la lunghezza dell'elenco di movimenti per risolvere quella matrice.
Charlie,

@Charlie Bene, sono 6, ma solo perché sono stato troppo pigro per incollare in una matrice più grande. Sebbene sia una forza bruta, si ridimensiona linearmente con l'area, quindi dovrebbe essere capace di matrici di grandi dimensioni.
Neil,

In realtà, il caso peggiore potrebbe essere quadratico con area.
Neil,

1
Il link @Charlie TIO ora risolve una matrice casuale 100x100.
Neil,

1
@Charlie Ora ho trovato un'importante ottimizzazione per la riga in basso, ma penso che sia l'ultima modifica che sto per apportare a questa risposta.
Neil,
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.