루빅스 정렬 매트릭스 (일명 토러스 퍼즐)


16

대한 아이디어 는 간단합니다. 정수 행렬이 주어지면 Rubik 스타일의 움직임을 적용하여 정렬하십시오. 즉, 단일 행 또는 열을 선택하고 해당 요소를 원하는 방향으로 회전 할 수 있습니다.

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

따라서 모든 차원의 정수 행렬이 주어지면 이러한 Rubik 스타일 변환 만 적용하여 요소를 정렬하십시오. 매트릭스

[a11a12a13a14a21a22a23a24a31a32a33a34]

요소가 다음 제한 사항을 준수하는 경우 정렬 된 것으로 간주됩니다.

a11a12a13a14a21a22a23a24a31a32a33a34

I / O

  • 입력 값은 반복되는 값이없는 양의 정수 행렬입니다.
  • 출력은 정렬에 필요한 움직임입니다. 이 코드 골프 도전이 아니므로이 길이에 대해 걱정할 필요가 없습니다로서, 모든 움직임에 대한 제안 형식은 #[UDLR]어디 #이동 (0 인덱스)에 행 또는 열 수이고 [UDLR]있다는 점에서 하나의 문자입니다 이동이 위 / 아래 (열) 또는 왼쪽 / 오른쪽 (행)인지 지정하는 범위입니다. 따라서 1U"1 번째 열을 위로 이동"을 의미하지만 1R"1 번째 열을 오른쪽으로 이동"을 의미합니다. 움직임은 쉼표로 구분되므로 솔루션은 다음과 같이 표현됩니다 1R,1U,0L,2D.

채점

행렬을 이런 식으로 정렬하려고하면 가능한 많은 이동 조합이있을 수 있으며, 정렬 할 수있는 많은 이동 목록도 있으므로 N *을 정렬하는 코드를 작성하는 것이 목표입니다. 아래의 N 행렬. 점수는 오류없이 합리적인 시간 1 에서 해결할 수있는 가장 큰 크기 N이됩니다 (행렬의 크기가 클수록 클수록 좋습니다). 동점의 경우 동점 차단기는 찾은 경로의 이동 횟수가됩니다 (경로가 짧을수록 좋습니다).

예 : 사용자 A가 N = 5에 대한 해를 찾고 B가 N = 6에 대한 해를 찾으면 B는 두 경로의 길이에 관계없이 승리합니다. 둘 다 N = 6에 대한 해를 구하지 만 A에서 찾은 해는 50 단계이고 B의 해는 60 단계이면 A가 이깁니다.

코드 작동 방식에 대한 설명을 적극 권장하며 찾은 솔루션을 게시하여 테스트 해보십시오 . 솔루션이 너무 큰 경우 Pastebin 또는 유사한 도구를 사용할 수 있습니다 . 또한 솔루션을 찾기 위해 코드에서 사용한 시간을 평가 해 주시면 감사하겠습니다.

테스트 사례

이미 정렬 된 행렬부터 10K 임의의 Rubik 스타일의 움직임으로 스크램블하여 다음 행렬 ( 보다 복사하여 붙여 넣기 가능한 버전의 Pastebin 링크 )을 만들었습니다.

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

일반 텍스트 테스트 사례 :

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

당신이 그들 모두를 해결하는 경우 더 요청하십시오. :-) 그리고 샌드 박스에있는 동안이 문제를 해결하는 데 도움을 준 사람들에게 감사합니다 .


1 합리적인 시간 : 솔루션을 테스트하는 동안 인내심을 손상시키지 않는 시간. TIO는 60 초 동안 만 코드를 실행합니다.이 제한을 초과하면 시스템에서 코드를 테스트하게됩니다. 예 : 다소 비효율적 인 알고리즘은 3x3 및 4x4 차수의 행렬을 해결하는 데 몇 밀리 초가 걸리지 만 5x5 행렬로 방금 테스트했으며 그것을 해결하는 데 317 초가 걸렸습니다 (5 백만 회 이상 이동하면 매우 재미 있습니다. 해결 매트릭스 스크램블 된 전용 ) 10,000 배. 이동 횟수를 10K 미만으로 줄이려고 시도했지만 30 분 동안 코드를 실행 한 후 항복했습니다.


1
좋은 도전! 그러나 몇 가지 요청 / 질문이 있습니다. 1) 테스트 사례를 복사 붙여 넣기가 더 쉬운 형식으로 제공 할 수 있습니까? (예 : 페이스트 빈) 2) 시간 제한 순서를보다 정확하게 정의 할 수 있습니까? 3) 행렬이 정사각형으로 보장됩니까? (테스트 사례는 제안하지만 정의는 그렇지 않습니다.)
Arnauld

@ Arnauld 1) 나는 그것에있어. 2) 시간 제한을 설정하고 싶지 않았으며 도전이 샌드 박스에있는 동안 아무도 제한을 제안하지 않았습니다. 필요한 경우 30 분을 합리적인 한도로 고려 하시겠습니까? 3) 예, 테스트 매트릭스는 표시된 것이므로 더 필요한 경우 모두 정사각형입니다.
찰리

이 과제에는 (상대적으로 구현하기 쉬운) O (입력 크기) 알고리즘이 있으므로 처음 보는 것처럼 흥미롭지 않습니다.
user202729

@ user202729 그때 입력 크기는 무엇입니까 O(input size)? 5x5 매트릭스의 경우 O(25)? 그것은 매우 빠르기 때문에 알고리즘이나 구현을 보는 데 매우 관심이 있습니다. 편집 : 우리는 '스크램블 된'행렬을 입력하고 움직임을 출력한다는 것을 알고 있습니까? 다른 방법은 아닙니다.
Kevin Cruijssen

4
내 생각, 같은 그것의 일 이 알고리즘
키릴 L.

답변:


8

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

온라인으로 사용해보십시오!

내가 코멘트에서 언급 한 알고리즘 2012, 5, 18-29에 게시 된 기사에서 Torus 퍼즐 솔루션 알고리즘을 구현하려는 빠른 시도 .

공백으로 구분 된 숫자의 줄로 평평한 형태의 입력 행렬을받습니다.

다음은 Python 2 의 유효성 검사기입니다 . 기본 코드와 동일한 형식의 원본 스크램블링 된 행렬과 제안 된 이동 순서의 두 줄을 입력으로 사용합니다. 유효성 검사기의 출력은 이러한 동작을 적용한 결과입니다.

설명

알고리즘의 첫 번째 부분에서 마지막 행을 제외한 모든 행을 정렬합니다.

우리는 일련의 "삼각형 회전 (triangle rotations)"( proc triangle) 을 수행함으로써이 작업을 수행합니다. 이동 순서는 세 개의 셀만 서로 다른 장소를 교환하고 나머지는 변경되지 않은 상태로 유지됩니다. 각각의 연속적인 "작업"셀을 좌표로 가져옵니다[,]그런 다음 셀을 찾으십시오. [에스,] 현재 가야할 번호가 포함 된 [,]세 번째 점을 선택하여 직각 삼각형을 완성합니다 [,V] 링크 된 물품의도 4에 도시 된 바와 같이, 일부 패턴에 따라.

그림 2에서 저자는 가능한 패턴 8 개와 그에 상응하는 동작 시퀀스를 제시하지만, 제 코드에서는 실제로 5 개의 패턴 만 다루어 졌으므로 아니 었습니다. 1, 5 및 6은 사용되지 않습니다.

두 번째 부분에서 두 개의 마지막 요소를 제외한 마지막 행 proc line은 두 개의 삼각형 회전으로 구성된 선 ( ) 에서 "세 개의 요소 회전"을 수행하여 정렬됩니다 (기사의 그림 3 참조).

우리는 현재 작업 셀을 선택합니다 [,] 목표 값을 포함하는 셀 [에스,] 중심점으로 [에스,+1]올바른 지점으로. 이 서쪽 운동은이 기사에서 내 문자열이 proc을 형성합니다. 만약 이미 마지막 열이므로 +1 존재하지 않는, 우리는 [에스,1] 두 번째 삼각형 회전은 패턴 7과 8 (원본의 7과 1 대신)에 의해 수행됩니다. 순서).

마지막으로 솔루션이 존재한다는 것을 보장하기 때문에 나머지 두 요소가 이미 있어야합니다. 만약 짝수이고 나머지 두 요소가 제자리에 있지 않으면 Lemma 1 (22 페이지)에 따라 일련의 요소로 교체 할 수 있습니다. 이동 한 다음 동쪽으로 한 번 이동 (=아르 자형). 제공된 예를 스왑 때문에 처음 두 항목은, 우리는 마지막 두, 우리의 교환 할 필요가 proc swap수행하는 역순으로 이동합니다.

가장자리의 경우 =2 마지막 행 요소가 파트 1 이후에 하나도없는 경우에는 이러한 복잡한 절차가 전혀 필요하지 않습니다. 1아르 자형 이동은 행렬을 완전히 정렬하기에 충분합니다.

업데이트 :proc rotations 단계가 적을 경우 이동 방향을 반대로하는 새로운 기능 이 추가되었습니다 .


감동적인! 그런 다음 몇 가지 테스트 사례를 더 만들어 보겠습니다. 한편,이 솔루션은 최적화 할 수 있다고 확신 7L,7L,7L,7L,7D,7D,7D,7D합니다. 감소 8R,8R,8R,8R,8R,8R,8R할 수있는 것보다 8L,8L9x9 매트릭스 로 변환 할 수있는 것 보다 청크 가 있습니다 .
Charlie

100x100 매트릭스로 알고리즘을 시도했으며 4 초 이내에 해결합니다. 나는이 문제가 선형 시간 솔루션을 갖기를 정말로 기대하지 않았다. 앞으로 더 나은 과제를 작성하려고 노력할 것입니다!
Charlie

어쩌면이 테스트 과제로 단일 고정 매트릭스 로이 도전을 제기하고이를 해결하기 위해 발견 된 경로의 크기로 선정 기준을 설정하는 것이 더 좋았을 것입니다. (n ^ 2) 솔루션. 이러한 답을이기는 기준으로 새로운 질문에이 답변을 포팅하는 것을 고려 하시겠습니까?
Charlie

@Charlie 여전히 현재 솔루션을 약간 수정하려고하지만 전체 경로 최적화 문제를 해결하는 방법을 모릅니다.
Kirill L.

5

Python 2 , TIO에서 100 초 미만의 크기 : 100 초

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

온라인으로 사용해보십시오! Link에는 전체 이동 출력이 포함 된 3 개의 작은 테스트 사례와 100x100의 자동 테스트가 포함되어 코드가 작동 함을 보여줍니다 (이동 출력이 TIO의 한계를 초과 함). 설명 : 코드가 배열에서 삽입 정렬을 수행하여 오름차순으로 배열을 작성하려고합니다. 마지막 행을 제외한 모든 행에는 여러 가지 경우가 있습니다.

  • 요소가 올바른 행에 있지만 열 0에 속합니다.이 경우 단순히 열 0에 도달 할 때까지 회전됩니다.
  • 요소가 올바른 위치에 있습니다. 이 경우 아무 일도 일어나지 않습니다. (요소가 열 0에 속하는 경우에도 마찬가지입니다.이 경우에는 0 회전이 발생합니다.)
  • 요소가 맨 위 행에 있지만 잘못된 열에 있습니다. 이 경우 요소가 올바른 열에 올 때까지 아래로 회전 한 다음 가로로 다시 회전합니다.
  • 요소가 올바른 행에 있지만 잘못된 열에 있습니다. 이 경우 행이 위로 회전 한 다음 행이 열로 회전 한 다음 아래로 회전 한 다음 행이 다시 회전합니다. (이것은 사실상 다음 경우의 회전입니다.)
  • 요소가 올바른 열에 있지만 잘못된 행에 있습니다. 이 경우 행이 오른쪽으로 회전되어 마지막 경우로 줄어 듭니다.
  • 요소가 잘못된 행과 열에 있습니다. 이 경우 올바른 열이 잘못된 행으로 회전되고 (맨 위 행으로 건너 뛴) 해당 행이 올바른 열로 회전 된 다음 열이 다시 회전됩니다.

상기 회전은 어느 방향으로도 단계 수를 최소화하며; 크기 2 제곱은 위의 설명에 관계없이 항상 왼쪽 및 위로 이동을 사용하여 해결됩니다.

하단 행이 완료되기 전에 전체 거리를 최소화하기 위해 회전하지만 하단 행의 패리티가 알고리즘의 마지막 부분에 의해 변경 될 수 없으므로 짝수를 유지하도록 회전됩니다. 최소 거리가 동일한 회전이 두 개 이상인 경우 이동 수가 가장 적은 회전이 선택됩니다.

맨 아래 행의 알고리즘은 요소를 세 개의 열로 교환하는 7 작업 시퀀스에 의존합니다. 순서는 나머지 행의 나머지 번호에 각각 적용되어 원하는 위치로 이동합니다. 가능한 경우 해당 위치의 요소가 원하는 위치로 이동하지만 직선 스왑이 필요한 경우 해당 요소는 가장 가까운 가용 열로 이동하여 다음 번에 수정 될 수 있습니다.


답변 주셔서 감사합니다, 닐! 그러나 이것은 코드 골프가 아닙니다. 코드 길이 대신 해결 한 NxN 행렬의 최대 크기 N과 해당 행렬을 해결하기위한 이동 목록의 길이를 표시해야합니다.
찰리

@Charlie 글쎄, 그것은 6입니다. 그것은 무차별 적이지만 면적에 따라 선형으로 확장되므로 큰 행렬을 수행 할 수 있어야합니다.
Neil

실제로 최악의 경우 면적이 2 차일 수 있습니다.
Neil

1
@Charlie TIO 링크는 이제 임의의 100x100 매트릭스를 해결합니다.
Neil

1
@Charlie 나는 이제 맨 아래 줄에 대한 주요 최적화를 생각해 냈지만, 이것이 내가이 답변에 할 마지막 변경이라고 생각합니다.
Neil
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.