マトリックスのルービック分類(別名トーラスパズル)


16

こののアイデアは単純です。整数の行列が与えられた場合、ルービックスタイルの動きを適用して並べ替えます。つまり、単一の行または列を選択し、その要素を任意の方向に回転できます。

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

したがって、任意の次元の整数の行列が与えられた場合、これらのルービックスタイルの変換のみを適用して要素を並べ替えます。行列

[a11a12a13a14a21a22a23a24a31a32a33a34]

要素が次の制限に準拠している場合、ソート済みと見なされます。

a11a12a13a14a21a22a23a24a31a32a33a34

I / O

  • 入力は、値が繰り返されない正の整数の行列になります。
  • 出力は、ソートに必要な動きになります。これはコードのゴルフ挑戦ではなく、あなたがその長さを心配する必要はないので、すべての動きのための提案フォーマットがされて#[UDLR]いる#行または列の数が移動することです(0インデックス)と[UDLR]その中に単一の文字であります移動が上/下(列の場合)か左/右(行の場合)かを指定する範囲。つまり1U、「1番目の列を上に移動する」という意味ですが、「1R1番目の行を右に移動する」ことになります。動きはコンマで区切られるため、解決策は次のように表現されます1R,1U,0L,2D

得点

この方法でマトリックスを並べ替えようとすると、考えられる動きの組み合わせが多数あるため、コストがかかる可能性があります。また、並べ替え可能な動きのリストも多数あるため、目標はN *を並べ替えるコードを記述することです。以下のN個の行列。スコアは、エラーなしで妥当な時間1で解くことができる最大のサイズNになります(解く行列のサイズが大きいほど良い)。タイの場合、タイブレーカーは、見つかったパスの移動数になります(パスが短いほど良い)。

例:ユーザーAがN = 5のソリューションを見つけ、BがN = 6のソリューションを見つけた場合、Bは両方のパスの長さに関係なく勝ちます。両者がN = 6の解を見つけたが、Aが見つけた解に50ステップがあり、Bの解に60ステップがある場合、Aが勝ちます。

あなたのコードがどのように動作するかの説明は非常に奨励されています、そして、私たちがそれらテストできるように見つけられたソリューションを投稿してください。ソリューションが大きすぎる場合は、Pastebinまたは同様のツールを使用できます。また、コードがソリューションを見つけるのに費やした時間を見積もっていただければ幸いです。

テストケース

次のマトリックス(よりコピーアンドペースト可能なバージョンの場合はPastebinリンク)は、10Kのランダムなルービックスタイルの動きでスクランブルすることにより、既にソートされたマトリックスから作成されています。

[8561110131513]
[211012161762214851926132431]
[11381659402126221124143928321937310301736734]
[34214022354118333130124319113924282344136538451417916132683476254]
[ 20 36 17 1 15 50 18 72 67 34 10 32 3 55 42 43 9 6 30 61 3928
[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秒かかりました(500万回以上の動きで、非常に面白い解く行列は10K回しかスクランブルされていませんでした)。動きの数を10K未満に減らしようとしましたが、コードを実行して30分後に降伏しました。


1
ナイスチャレンジ!ただし、いくつかのリクエスト/質問があります:1)テストケースをよりコピーアンドペーストしやすい形式で提供してもらえますか?(pastebinなど)2)時間制限の順序をより正確に定義できますか?3)行列は正方形であることが保証されていますか?(テストケースはそのように示唆していますが、定義はそうではありません。)
アーナルド

@Arnauld 1)私はそれに乗っています。2)制限時間を設定したくなかったため、チャレンジがサンドボックスにある間は誰も制限を提案しませんでした。必要な場合は、30分が妥当な制限と見なされますか?3)はい、テストマトリックスは表示されているものであり、さらに必要な場合はすべて正方形になります。
チャーリー

このチャレンジには(比較的簡単に実装できる)O(入力サイズ)アルゴリズムが存在するため、最初に見るほど興味深いものではありません。
user202729

@ user202729そのときの入力サイズはどうなりますO(input size)か?5x5マトリックスの場合はO(25)どうでしょうか?それは非常に速いようですので、私はそのアルゴリズムやあなたの実装を非常に興味があります。編集:「スクランブルされた」マトリックスを入力し、動きを出力することを理解していますか?その逆ではありません。
ケビンクルイッセン

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

オンラインでお試しください!

私がコメントで言及したAlgorithms 2012、5、18-29に掲載された記事から、トーラスパズルソリューションアルゴリズムを簡単に実装しようとしました。

スペースで区切られた数字の行として、フラット化された形式の入力行列を受け入れます。

Python 2のバリデーターもここにあります。入力として2行が必要です。メインコードと同じ形式の元のスクランブル行列と、提案された一連の動きです。バリデータの出力は、これらの動きを適用した結果のマトリックスです。

説明

アルゴリズムの最初の部分では、最後の行を除くすべての行を順序付けます。

これを行うには、一連の「三角形の回転」(proc triangle)を実行します。3つのセルのみが場所を交換し、残りのすべてが変更されないままになる一連の移動。私たちは取る「作業」の各連続した座標を持つセル、その後、セルを見つける現在に行くべき数含まれていることを、および第三のポイントを選択することで、直角三角形を完了するにはリンクされた記事の図4に示されているようなパターンに従って。[p,q][s,t][p,q][u,v]

図2では、著者は8つの可能なパターンと対応する一連の動きを示していますが、私のコードでは、実際には5つのパターンのみでカバーされています。1、5、および6は使用されません。

2番目の部分では、2つの最後の要素を除く最後の行が、proc lineそれぞれ2つの三角形の回転で構成される行()で「3つの要素の回転」を実行して順序付けられます(記事の図3を参照)。

現在の作業セルを左ポイントとして、ターゲット値を含むセルを中心ポイントとして、を右ポイントとして選択します。この西行きの動きは、この記事ではと名付けられて、このための私の文字列形成です。場合そのように最後の列は、すでにが存在しない、我々が取る第三の点としては、それに応じてアクションを変更する2つの三角形の回転は、パターン7,8(の代わりにすることによって行われます元のシーケンスの7および1 )。[p,q][s,t][s,t+1]TWtt+1[s,t1]TW

最後に、が奇数の場合、解決策が存在することが保証されるため、残りの2つの要素は既に配置されている必要があります。場合偶数であり、残りの2つの要素は、次に補助定理1(22ページ)によれば、所定の位置にない、それらは一連の交換することができる 1つのシフト東続いて移動する、()。提供された例スワップ最初の2つのエントリ、と我々は最後の二つを交換する必要があるので、私たちの行いは逆順に移動します。nnTW=RT Wproc swapTW

のエッジケースでは、これらすべての複雑な手順はまったく必要ありません-パート1の後に最後の行要素が配置されていない場合、 1回動かすだけで行列を完全に順序付けることができます。n=21R

更新:proc rotationsステップが少なくなる場合に移動の方向を逆にする新しい機能が追加されました。


印象的!その後、さらにいくつかのテストケースを作成します。一方、このソリューションは最適化できると確信しています。これは、9x9マトリックス用に変換し7L,7L,7L,7L,7D,7D,7D,7Dたり縮小したりできるチャンク8R,8R,8R,8R,8R,8R,8Rがある8L,8Lためです。
チャーリー

100x100マトリックスでアルゴリズムを試しましたが、4秒未満で解決します。私は、この問題に線形時間の解決策があるとは本当に思っていませんでした。将来的にはより良いチャレンジを書いてみます!
チャーリー

おそらく、単一の固定マトリックスを唯一のテストケースとしてこの課題を提起し、それを解決するために見つかったパスのサイズだけに勝つ基準を設定する方が良いでしょう。 (n ^ 2)ソリューション。この答えをそのような勝利基準で新しい質問に移植することを検討しますか?
チャーリー

@Charlie私はまだ現在のソリューションを少し洗練しようとするでしょうが、私は本当に全体的なパスの最適化問題に取り組むためにどのようにないアイデアを持っていない...
キリルL.

5

Python 2、TIOで30秒未満でサイズ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

オンラインでお試しください!リンクには、フルムーブ出力を伴う3つの小さなテストケースに加えて、コードが機能することを示す100x100のサイレントテストが含まれます(ムーブ出力はTIOの制限を超えます)。説明:コードは、配列に対して挿入ソートを実行しようとし、昇順で配列を作成します。最後の行を除くすべての行について、いくつかのケースがあります。

  • 要素は正しい行にありますが、列0に属します。この場合、列0に達するまで単純に回転します。
  • 要素は正しい場所にあります。この場合、何も起こりません。(これは、要素が列0に属している場合にも当てはまります。その場合、0回転しか発生しません。)
  • 要素は一番上の行にありますが、間違った列にあります。その場合、要素は下に回転し、要素が正しい列に収まるまで水平に回転してから、再び上に回転します。
  • 要素は正しい行にありますが、間違った列にあります。その場合、上に回転し、次に行がその列に回転し、次に下に回転し、次に行が元に戻ります。(事実上、これは次のケースのローテーションです。)
  • 要素は正しい列にありますが、間違った行にあります。その場合、行は右に回転し、最後のケースに縮小されます。
  • 要素が間違った行と列にあります。この場合、正しい列は間違った行に回転し(最上行をスキップ)、その行は正しい列に回転し、その後、列は元に戻ります。

上記の回転は、ステップ数が最小になる方向に実行されます。サイズ2の正方形は、上記の説明に関係なく、常に左と上への移動を使用して解決されます。

一番下の行が完成する前に、位置のずれを最小化するために回転しますが、アルゴリズムの最後の部分では変更できないため、一番下の行のパリティが均等になるようにします。同じ最小距離で複数の回転がある場合、移動数が最小の回転が選択されます。

一番下の行のアルゴリズムは、3列の要素を交換する7回の操作シーケンスに依存しています。シーケンスは、一番下の行の残りの番号のそれぞれに順番に適用され、順番にそれらを目的の場所に移動します。可能であれば、その場所の要素は目的の場所に移動されますが、ストレートスワップが必要な場合、要素は最も近い利用可能な列に移動されるだけで、できれば次回の修正が可能です。


答えてくれてありがとう、ニール!ただし、これはコードゴルフではありません。コードの長さの代わりに、解いたNxNマトリックスの最大サイズNと、そのマトリックスを解くための動きのリストの長さを示す必要があります。
チャーリー

@チャーリーまあ、それは6ですが、大きなマトリックスに貼り付けるのが面倒だからです。それはブルートフォースですが、面積に比例してスケーリングするため、大きなマトリックスに対応できるはずです。
ニール

実際には、最悪の場合は面積の2次的な場合があります。
ニール

1
@Charlie TIOリンクは、ランダムな100x100マトリックスを解決するようになりました。
ニール

1
@チャーリー私は今、一番下の行の主要な最適化を考え出しましたが、それが私がこの答えに加える最後の変更だと思います。
ニール
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.