Rubik - classificando uma matriz (também conhecida como quebra-cabeça do toro)


16

A idéia para esse é simples: dada uma matriz de números inteiros, vamos classificá-la aplicando movimentos no estilo Rubik. Isso significa que você pode selecionar uma única linha ou coluna e girar seus elementos em qualquer direção:

[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)

Portanto, dada uma matriz de números inteiros de qualquer dimensão, classifique seus elementos aplicando apenas essas transformações no estilo Rubik. Uma matriz

[uma11uma12uma13uma14uma21uma22uma23uma24uma31uma32.uma33uma34]

será considerado classificado se seus elementos estiverem em conformidade com a seguinte restrição:

uma11uma12uma13uma14uma21uma22uma23uma24uma31uma32.uma33uma34

I / O

  • A entrada será uma matriz de números inteiros positivos sem valores repetidos.
  • Saída serão os movimentos necessários para classificá-la. Como esse não é um desafio de código de golfe e você não precisa se preocupar com o tamanho, o formato proposto para cada movimento é #[UDLR]onde #está o número da linha ou coluna a ser movida (indexada a 0) e [UDLR]é um caractere único nesse intervalo que especifica se o movimento é Para cima / Para baixo (para colunas) ou Esquerda / Direita (para linhas). Isso 1Usignifica "mover a 1ª coluna para cima", mas 1R"mover a 1ª linha para a direita". Movimentos serão separados por vírgulas, de modo que uma solução será expressa como este: 1R,1U,0L,2D.

Pontuação

Tentar classificar uma matriz dessa maneira pode ser caro, pois existem muitas combinações possíveis de movimentos e também existem muitas listas possíveis de movimentos que podem classificá-lo, portanto, o objetivo é escrever um código que classifique o N * N matrizes abaixo. A pontuação será o maior tamanho N que você pode resolver em um período razoável de tempo 1 sem erros (quanto maior o tamanho da matriz resolvida, melhor). Em caso de empate, o desempate será o número de movimentos no caminho encontrado (quanto menor o caminho, melhor).

Exemplo: se um usuário A encontrar uma solução para N = 5 e B encontrar uma solução para N = 6, B vencerá independentemente do comprimento dos dois caminhos. Se ambos encontrarem soluções para N = 6, mas a solução encontrada por A tiver 50 etapas e a solução de B tiver 60 etapas, A vencerá.

As explicações sobre como seu código funciona são altamente incentivadas e publique as soluções encontradas para que possamos testá-las . Você pode usar Pastebin ou ferramentas semelhantes se as soluções forem muito grandes. Além disso, será apreciada uma estimativa do tempo gasto pelo seu código para encontrar suas soluções.

Casos de teste

As seguintes matrizes ( link Pastebin para uma versão mais passível de cópia) foram criadas a partir de matrizes já classificadas, embaralhando-as com movimentos aleatórios de 10K no estilo Rubik:

[8561110131513]
[211012161762214851926132431]
[1138165940.21262211241439.28.32.1937.310301736.734]
[342140.223541.183331301243191139.2428.2344136.538.451417916132683476254]
[ 20 36 17 1 15 50 18 72 67 34 10 32 3 55 42 43 9 6 30 61 3928 41 54 27 23 5 70 48 13 25 12 46 58 63 52 37 8 45 33 14
[2036.1711550.187267341032.35542.4396306139.28.41.54272357048.13251246.5863.5237.84533146859.6556.73606422]
[8556.5275894441.682715879132.37.39.73676419997846.1642.2163.1004172131197309328.40.336.50.7025805896084549617294334233577618248.29438.66.]
[56.799061711221103155114428.485130618844338.66.113249620102756858880983510077132164108106011440.23472731068232.1202636.53936910454191111176217278873349.15581169511257118915142.6545]

Casos de Teste de Texto Simples:

[[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]]

Por favor, peça mais se você resolver todos eles. :-) E muito obrigado às pessoas que me ajudaram a refinar esse desafio enquanto estavam na sandbox .


1 Uma quantidade razoável de tempo: qualquer quantidade que não comprometa nossa paciência durante o teste de sua solução. Observe que o TIO executa o código apenas por 60 segundos; qualquer quantidade de tempo acima desse limite nos fará testar o código em nossas máquinas. Exemplo: meu algoritmo bastante ineficiente leva alguns milissegundos para resolver matrizes de ordem 3x3 e 4x4, mas eu o testei apenas com uma matriz 5x5 e demorou 317 segundos para resolvê-lo (em mais de 5 milhões de movimentos, muito engraçado se considerarmos que a matriz a resolver foi embaralhada apenas 10 mil vezes). Tentei reduzir o número de movimentos para menos de 10K, mas me rendi depois de 30 minutos executando o código.


1
Bom desafio! No entanto, tenho alguns pedidos / perguntas: 1) Você poderia fornecer os casos de teste em um formato mais amigável para copiar e colar? (como pastebin) 2) Você poderia fornecer uma definição mais precisa da ordem do prazo? 3) A matriz é garantida como quadrada? (Os casos de teste sugerem que sim, mas a definição não faz.)
Arnauld

@ Arnauld 1) Estou nisso. 2) Eu não queria estabelecer um limite de tempo e ninguém sugeriu nenhum limite enquanto o desafio estava na caixa de areia. Se você precisar de um, consideraria 30 minutos um limite razoável? 3) Sim, as matrizes de teste são as mostradas e todas serão quadradas se forem necessárias mais.
Charlie

Existe um algoritmo O (tamanho de entrada) (relativamente fácil de implementar) para esse desafio, portanto não é tão interessante quanto pode parecer à primeira vista.
user202729

@ user202729 Qual seria o tamanho da entrada O(input size)? Para uma matriz 5x5 seria O(25)? Isso parece ser extremamente rápido, então eu ficaria muito interessado em ver esse algoritmo ou implementação seu. EDIT: Você percebe que inserimos a matriz 'codificada' e produzimos os movimentos, certo? Não o contrário.
Kevin Cruijssen 26/09/18

4
Eu acho, é algo como este algoritmo
Kirill L.

Respostas:


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

Experimente online!

Uma tentativa rápida de implementar o algoritmo da solução de quebra-cabeça Torus a partir de um artigo publicado nos Algorithms 2012, 5, 18-29 que mencionei nos comentários.

Aceita a matriz de entrada na forma achatada, como uma linha de números delimitados por espaço.

Aqui também está um validador no Python 2 . São necessárias duas linhas como entrada: a matriz codificada original da mesma forma que o código principal e a sequência de movimentos proposta. A saída do validador é a matriz resultante da aplicação desses movimentos.

Explicação

Na primeira parte do algoritmo, ordenamos todas as linhas, exceto a última.

proc triangle[p,q][s,t][p,q][você,v]

Na Fig. 2, os autores apresentam 8 padrões possíveis e as seqüências correspondentes de movimentos, mas no meu código todos os casos foram realmente cobertos por apenas 5 padrões, de modo que não. 1, 5 e 6 não são usados.

Na segunda parte, a última linha, exceto os dois últimos elementos, é ordenada executando "rotações de três elementos" em uma linha ( proc line), que consiste em duas rotações de triângulo cada (consulte a Fig. 3 do artigo).

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

nnTW=Rproc swapTW

n=21R

Atualização: Adicionado novo proc rotationsque reverte a direção dos movimentos, se isso resultar em menos etapas.


Impressionante! Vou criar mais alguns casos de teste então. Enquanto isso, tenho certeza de que essa solução pode ser otimizada, pois existem pedaços como o 7L,7L,7L,7L,7D,7D,7D,7Dque pode ser reduzido e o 8R,8R,8R,8R,8R,8R,8Rque pode ser convertido 8L,8Lpara uma matriz 9x9.
Charlie

Eu tentei seu algoritmo com uma matriz 100x100 e resolve em menos de 4 segundos. Eu realmente não esperava que esse problema tivesse uma solução de tempo linear. Vou tentar escrever melhores desafios no futuro!
Charlie

Talvez fosse melhor colocar esse desafio com uma única matriz fixa como o único caso de teste e definir o critério de vencimento como o tamanho do caminho encontrado para resolvê-lo, se eu soubesse antes que esse problema tinha um O (n ^ 2) solução. Você consideraria portar esta resposta para uma nova pergunta com esse critério de vitória?
Charlie

@Charlie Embora eu ainda tente refinar um pouco a solução atual, não faço ideia de como lidar com o problema geral de otimização de caminho ...
Kirill L.

5

Python 2 , tamanho 100 em <30 segundos no 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

Experimente online! O link inclui três pequenos casos de teste com saída de movimentação completa, além de um teste silencioso de 100x100 para mostrar que o código funciona (a saída de movimentação excederia os limites do TIO). Explicação: O código tenta executar uma classificação de inserção na matriz, construindo-a em ordem crescente à medida que avança. Para todas as linhas, exceto a última linha, há vários casos:

  • O elemento está na linha correta, mas pertence à coluna 0. Nesse caso, é simplesmente girado até atingir a coluna 0.
  • O elemento está no local correto. Nesse caso, nada acontece. (Isso também é verdade se o elemento pertencer à coluna 0, é só que 0 rotações acontecem nesse caso.)
  • O elemento está na linha superior, mas na coluna errada. Nesse caso, ele é girado para baixo e horizontalmente até que o elemento esteja na coluna correta e depois girado para cima novamente.
  • O elemento está na linha correta, mas na coluna errada. Nesse caso, é girada para cima, depois a linha é girada para sua coluna, depois é girada para baixo e a linha é girada de volta. (Efetivamente, essa é uma rotação do próximo caso.)
  • O elemento está na coluna correta, mas na linha errada. Nesse caso, a linha é girada para a direita, para reduzi-la ao último caso.
  • O elemento está na linha errada e na coluna errada. Nesse caso, a coluna correta é rotacionada para a linha errada (ignorada para a linha superior), essa linha é rotacionada para a coluna correta e a coluna é rotacionada de volta.

As rotações acima são realizadas em qualquer direção, minimizando o número de etapas; um quadrado tamanho 2 é sempre resolvido usando movimentos para a esquerda e para cima, independentemente da descrição acima.

Antes de a linha inferior ser concluída, ela é rotacionada para minimizar a distância total fora do lugar, mas também para garantir que a paridade da linha inferior seja uniforme, pois não pode ser alterada pela parte final do algoritmo. Se houver mais de uma rotação com a mesma distância mínima, é escolhida a rotação com o menor número de movimentos.

O algoritmo para a linha inferior baseia-se em uma sequência de 7 operações que troca os elementos em três colunas. A sequência é aplicada a cada um dos números restantes da linha inferior para, por sua vez, trazê-los para o local desejado; se possível, o elemento nesse local é movido para o local desejado, mas se for necessária uma troca direta, o elemento é simplesmente movido para a coluna disponível mais próxima, esperançosamente permitindo que seja corrigido da próxima vez.


Muito obrigado pela sua resposta, Neil! Mas lembre-se, este não é um código de golfe. Em vez do comprimento do código, você deve indicar o maior tamanho N da matriz NxN que resolveu e o comprimento da lista de movimentos para resolver essa matriz.
Charlie

@ Charlie Bem, isso é 6, mas só porque eu tenho preguiça de colar em uma matriz maior. Embora seja uma força bruta, ele escala linearmente com a área; portanto, deve ser capaz de grandes matrizes.
Neil

Na verdade, o pior caso pode ser quadrático com a área.
Neil

1
O link @Charlie TIO agora resolve uma matriz 100x100 aleatória.
Neil

1
@ Charlie Agora, eu venho com uma grande otimização para a linha inferior, mas acho que essa é a última alteração que vou fazer nesta resposta.
Neil
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.