回転パズルを解く


14

一部の古いノキアの携帯電話では、回転と呼ばれる15のパズルのバリエーションがありました。このバリエーションでは、一度に1つのタイルをスライドさせるのではなく、一度に4つのタイルを一方向に回転させました。

このゲームでは、次のようなボードから始めます。

4 9 2
3 5 7
8 1 6

そして、左下のブロックを時計回りに2回、左上のブロックを時計回りに1回回転させると、次のようになります。

4 9 2
8 3 7
1 5 6

4 9 2
1 8 7
3 5 6

1 4 2
8 9 7
3 5 6

そして1タイルは、することになっての左上隅にあるであろう。最終的に、さらにいくつかの動きの後、あなたは次のようになります:

1 2 3
4 5 6
7 8 9

これが「元の」構成です。

あなたの仕事は、入力として1から9までの3x3のグリッド(選択した任意の形式)を受け取り、ボードを元に戻すために必要な動きを表す一連の動きを出力として返すプログラムを構築することです構成(ここでも、選択した任意の形式)。法的移動は、4タイルの[上/下]-[左/右]ブロックを[時計回り/反時計回り]移動することとして定義されます。

プログラムは、考えられるすべての3x3グリッドを解決できる必要があります(すべての順列は解決可能です)。

これを行う最短のコードが優先されます。


...and return as output a sequence of moves representing the moves you must take to return the board back to its originalこれは「戻る1 2 3\n4 5 6\n7 8 9」という意味ですか?私はそれをどう読むかわかりません。
地下

はい、私は1 2 3 4 5 6 7 8 9に戻って平均
ジョーZ.

1
あなたの例の2番目と3番目のボードは3と5を交換する必要があると思います。
マーティンエンダー

@JoeZ。ソリューションを変更して、最悪の場合のパフォーマンスを制限する必要があることを宣言することをお勧めします。
HostileForkは、SEを信用してはいけないと言っています

回答:


7

GolfScript、39/83バイト

# Optimized for size:

{.4rand.p.2/+>`{?1420344440`=}+$..$>}do

# Optimized for speed:

6,(7++:t;~{.(1=.@7=9=+4\-rand+..2/+@.@>:s^[3s=0s=2s=4s=1s=]+s|.)9<\t>|}do.$>30764`*

速度とサイズ

サイズが最適化されたバージョンでは、目的の順列が達成されるまで、時計回りの回転がランダムに選択されます。反時計回りの回転は、同じ正方形の時計回りの3つの連続した回転に相当するため、これで十分です。

速度が最適化されたバージョンは、次の点を除いて同じことを行います。

  1. 番号1が左上隅にある場合、左上の正方形はもう回転しません。

  2. 数字の9が右下隅にある場合、右下の正方形はもう回転しません。

  3. 位置7と8を交換する手順はハードコードされているため、ループを中断できる位置は2つあります。

アルゴリズムの変更は別として、速度最適化バージョンは単純な方法で回転を実現しますが、サイズ最適化バージョンはGolfScriptの組み込みマッピングによる並べ替えを使用します。また、繰り返しごとに状態を並べ替えるのではなく、最終状態を(比較のために)ハードコード化します。

速度が最適化されたバージョンでは、必要な反復回数が少なくなり、すべての反復が単独ではるかに高速になります。

ベンチマーク

次のコードを使用して数字の位置をランダム化し、テストを実行して、テストするバージョンに対応する行のコメントを外しました。

[{[
    0:c;10,1>{;2 32?rand}$
    #{c):c;.4rand.2/+>`{?1420344440`=}+$..$>}do
    #6,(7++:t;{.(1=.@7=9=+4\-rand+..2/+@.@>:s^[3s=0s=2s=4s=1s=]+s|.)9<\t>|}do.$>30764`*
],c+}\~*]

$.0='Min: '\+puts .-1='Max: '\+puts ..{+}*\,/'Avg: '\+puts .,2/='Med: '\+

出力には、順序付けにかかったステップの最小数と最大数、すべての実行の平均と中央値、および経過時間(秒)が表示されます。

$ TIME='\n%e s' time golfscript rotation-test-size.gs <<< 100
Min: 4652
Max: 2187030
Avg: 346668
Med: 216888

21500.10 s
$
$ TIME='\n%e s' time golfscript rotation-test-speed.gs <<< 1000
Min: 26
Max: 23963
Avg: 3036
Med: 2150

202.62 s

私のマシン(Intel Core i7-3770)では、サイズが最適化されたバージョンの平均実行時間は3.58分でした。速度最適化バージョンの平均実行時間は0.20秒でした。したがって、速度最適化バージョンは約1075倍高速です。

速度が最適化されたバージョンでは、回転が114倍少なくなります。各回転の実行は9.4倍遅くなりますが、これは主に状態の更新方法によるものです。

I / O

出力は3ビットの数値で構成されます。MSBは反時計回りに設定され、中間ビットは下の正方形に設定され、LSBは右の正方形に設定されます。したがって、0(4)は左上の正方形、1(5)右上の正方形、2(6)左下の正方形、3(7)右下の正方形です。

速度が最適化されたバージョンでは、すべての回転が1行に印刷されます。サイズが最適化されたバージョンでは、1行に1回転、その後に数字の最終位置が出力されます。

速度が最適化されたバージョンでは、評価時に1から9までの数値を含む配列が入力から得られなければなりません。サイズが最適化されたバージョンの場合、入力は最終改行のない文字列でなければなりません。評価されません。

実行例:

$ echo -n '253169748' | golfscript rotation-size.gs
3
0
123456789
$ golfscript rotation-speed.gs <<< '[5 4 7 1 2 9 3 8 6]'
2210300121312212222212211121122211122221211111122211211222112230764

サイズ最適化コード

{               #
  .             # Duplicate the state.
  4rand         # Push a randomly chosen integers between 0 and 3.
  .p            # Print that integer.
  .2/+          # Add 1 to it if it is grater than one. Possible results: 0, 1, 3, 4
  >`            # Slice the state at the above index.
  {             # Push a code block doing the following:
    ?           # Get the index of the element of the iteration in the sliced state.
    1420344440` # Push the string "14020344440".
    =           # Retrieve the element at the position of the computed index.
  }+            # Concatenate the code block with the sliced state.
  $             # Sort the state according to the above code block. See below.
  ..$>          # Push two copies of the state, sort the second and compare the arrays.
}do             # If the state is not sorted, repeat the loop.

状態の更新は、次の方法で実現されます。

回転2は、1を追加した後に整数3を生成します。状態が「123456789」の場合、状態をスライスすると「456789」が生成されます。

「$」を実行する直前に、スタックの最上位要素は次のとおりです。

[ 1 2 3 4 5 6 7 8 9 ] { [ 4 5 6 7 8 9 ] ? "1420344440" = }

「$」は、要素自体をプッシュした後、ソートされる配列のすべての要素に対してブロックを1回実行します。

「[4 5 6 7 8 9]」の1のインデックスは-1(存在しない)であるため、「1420344440」の最後の要素がプッシュされます。これにより、文字0に対応するASCIIコードである48が生成されます。2および3の場合、48もプッシュされます。

4、5、6、7、8、および9にプッシュされた整数は、49、52、50、48、51、および52です。

ソート後、状態の最初の要素は48を生成する要素の1つになります。組み込みのソートは一般的に不安定ですが、この特定のケースでは安定していることを経験的に検証しました。

結果は「[1 2 3 7 4 6 8 5 9]」で、これは左下の正方形の時計回りの回転に対応します。

速度最適化コード

6,(7++:t;       # Save [ 1 2 3 4 5 7 ] in variable “t” and discard it.
~               # Interpret the input string.
{               #
  :s            # Duplicate the current state.
  (1=           # Unshift the first element and push 1 if it is equal to 1 and 0 otherwise.
  .@            # Duplicate the boolean and rotate the unshifted array on top of it.
  7=9=          # Push 1 if the eighth element of “s” is equal to 9 and 0 otherwise.
  +4\-          # Add the booleans and subtract their sum from 4.
  rand          # Push a randomly chosen integers between 0 and the result from above.
  +.            # Add this integer to the first boolean and duplicate it for the output.
  .2/+          # Add 1 to the result if it is grater than one. Possible results: 0, 1, 3, 4
  @.            # Rotate the state on top of the stack and duplicate it.
  @>:s          # Slice the state at the integer from above and save the result in “s”.
  ^             # Compute the symmetric difference of state and sliced state.
  [             # Apply a clockwise rotation to the sliced array:
    3s=         # The fourth element becomes the first.
    0s=         # The first element becomes the second.
    2s=         # The third element remains the same.
    4s=         # The fifth element becomes the fourth.
    1s=         # The second element becomes the fifth.
  ]             # Collect the results into an array.
  +             # Concatenate with array of elements preceding the slice.
  s|            # Perform set union to add the remaining elements of “s”.
  .             # Duplicate the updated state.
  )9<           # Pop the last element; push 0 if it is equal to 9 and 1 otherwise.
  \t            # Swap the popped state on top and push [ 1 2 3 4 5 7 ].
  >             # Push 0 if the state begins with [ 1 2 3 4 5 6 ] and 1 otherwise.
  |             # Take the logical OR of the booleans.
}do             # If the resulting boolean is 1, repeat the loop.
.$              # Duplicate the state and sort it.
>30764`*        # If the state was not sorted, 7 and 8 are swapped, so push "30764".

回転3、0、7、6、4は、残りの7つの要素の位置を変更せずに、位置7と8の要素を交換することに注意してください。


速度が最適化されていますか?それは... Golfscriptだ
ɐɔıʇǝɥʇuʎs

1
@Synthetica:それにもかかわらず、これはこれまでに投稿された最速のソリューションです。
デニス

4

Numpyを使用したPython – 158

from numpy import*
A=input()
while any(A.flat>range(1,10)):i,j,k=random.randint(0,2,3);A[i:i+2,j:j+2]=rot90(A[i:i+2,j:j+2],1+2*k);print"tb"[i]+"lr"[j]+"wc"[k]

入力は次の形式である必要があります。

array([[1,2,5],[4,3,6],[7,8,9]])

各出力行は、trwまたはblcなどの文字列にエンコードされた移動であり、次のように読み取られます。

  • t: 上
  • b:下
  • l:左
  • r: 正しい
  • c:時計回り
  • w:反時計回り(widdershins)

このプログラムは、ターゲット構成に達するまでランダムな移動を実行します。すべての動きには1/9の独立した確率があるという近似的な仮定の下で!ターゲット構成¹に到達するために、解が分布する前の回転数は、平均9(つまり平均移動数)で指数関数的に分布します!≈3.6・10⁵。これは、短い実験(20回実行)に基づいています。

¹9!構成の総数です。


2
それで、本質的に、それは解決を達成するまでランダムな動きを試みますか?
ジョーZ.

私のために働く。解決に達する前に予想される回転数に興味がありますが。
ジョーZ.

@JoeZ .:投稿の編集をご覧ください。
Wrzlprmft

すごい。
カイルカノス

4

C ++最少移動ソリューション-幅優先(1847文字)

もう少し考えた後、私はこれをはるかに効率的かつ賢明に行ったと思います。この解決策は、確かにこのゴルフに勝ってはいませんが、ボードを解決する回転の最短数を見つけようとする唯一の方法です。これまでのところ、それは私が9回以下の動きで投げたすべてのランダムなボードを解決します。また、私の最後のものよりも大幅に優れたパフォーマンスを発揮し、できれば以下のデニスのコメントに対処します。

以前のソリューションからの最大の変更点は、主要な履歴をボード状態(BS)から特定の深さ(DKH)で履歴を保存する新しいクラスに移動することでした。アプリケーションが移動するたびに、その深さとすべての深さで履歴がチェックされ、評価されているかどうかが確認されます。評価されている場合は、キューに再び追加されません。これは、キューのストレージを大幅に削減するようです(ボード状態自体からこの履歴をすべて削除することにより)。したがって、コードがメモリ不足にならないようにするために必要な愚かな刈り込みをほとんど削減します。さらに、キューにコピーする量がはるかに少ないため、実行速度が大幅に向上します。

さて、これは、さまざまな取締役会の状態に関する単純な幅優先検索です。さらに、判明したように、キーセット(現在はベース9の数字のセットとして格納され、それぞれがボードのベース9表現としてBS :: keyによって計算されます)をビットセットに変更したい9を持っている!ビットは不要なようです。ただし、テスト/トグルするビットセットのビットを計算するために使用できる「階乗法」でキーを計算する方法を見つけました。

したがって、最新のソリューションは次のとおりです。

#include <iostream>
#include <list>
#include <set>
#include <vector>
using namespace std;
struct BS{
#define LPB(i) for(int*i=b;i-b<9;i++)
struct ROP{int t, d;};
typedef vector<ROP> SV;
typedef unsigned int KEY;
typedef set<KEY> KH;
BS(const int*d){const int*x=d;int*y=b;for(;x-d<9;x++,y++)*y=*x;}
BS(){LPB(i)*i=i-b+1;}
bool solved(){LPB(i)if(i-b+1!=*i)return 0;return 1;}
void rot(int t, int d){return rot((ROP){t,d});}
void rot(ROP r){rotb(r);s.push_back(r);}
bool undo(){if (s.empty())return false;ROP &u=s.back();u.d*=-1;rotb(u);s.pop_back();return true;}
SV &sol(){return s;}
KEY key(){KEY rv=0;LPB(i){rv*=9;rv+=*i-1;}return rv;}
int b[9];
SV s;
void rotb(ROP r){int c=r.t<2?r.t:r.t+1;int bi=(r.d>0?3:4)+c;const int*ri=r.d>0?(const int[]){0,1,4}:(const int[]){1,0,3};for(int i=0;i<3;i++)swap(b[bi],b[c+ri[i]]);}
};
ostream &operator<<(ostream &o, BS::ROP r){static const char *s[]={"tl","tr","bl","br"};o<<s[r.t]<<(r.d<0?"w":"c");return o;}
struct DKH{
~DKH(){for(HV::iterator i=h.begin();i<h.end();++i)if(*i!=NULL)delete *i;}
void add(int d,BS b){h.resize(d+1);if(h[d]==NULL)h[d]=new BS::KH();h[d]->insert(b.key());}
bool exists(BS &b){BS::KEY k=b.key();size_t d=min(b.sol().size(),h.size()-1);do if (h[d]->find(k)!=h[d]->end())return true;while(d--!=0);return false;}
typedef vector<BS::KH *> HV;HV h;
};
static bool solve(BS &b)
{
const BS::ROP v[8]={{0,-1},{0,1},{1,-1},{1,1},{2,-1},{2,1},{3,-1},{3,1}};
DKH h;h.add(0,b);
list<BS> q;q.push_back(b);
while (!q.empty())
{
BS qb=q.front();q.pop_front();
if (qb.solved()){b=qb;return true;}
int d=qb.sol().size()+1;
for (int m=0;m<8;++m){qb.rot(v[m]);if (!h.exists(qb)){h.add(d,qb);q.push_back(qb);}qb.undo();}
}
return false;
}
int main()
{
BS b((const int[]){4,9,2,3,5,7,8,1,6});
if (solve(b)){BS::SV s=b.sol();for(BS::SV::iterator i=s.begin();i!=s.end();++i)cout<<*i<<" ";cout<<endl;}
}

1
代わりに、CのC ++のようなあなたのコードのルックス
user12205

@aceは実際に修正されています。
ドリームウォーリアー

他の誰かがこれをg ++でコンパイルする際に問題がある場合、int[]toのすべてのインスタンスを変更しconst int[]てflagを設定する必要がありました-fpermissive
デニス

@Dennis、申し訳ありませんが、2つの異なるg ++コンパイラでコンパイルしましたが、どちらも気にしなかったようです。しかし、私は、より新しい、より厳格なバージョンがどのように泣き言を言うのかを見ることができます。ありがとう。
ドリームウォーリアー

すぐにコンパイルでき、はるかに高速です。質問から削除したコメントへの対応:11の手順が必要と思われる順列がいくつかあります。978654321はその1つです。
デニス

1

CJam-39

l{4mr_o_1>+_@m<_[Z0Y4X]\f=\5>+m>__$>}g;

別のランダムソルバー:)
492357816などの文字列を受け取り、0から3までの(長い)一連の数字を出力します。各数字は、ブロックの時計回りの回転を表します。0=左上、1 =右上、2 =下-左、3 =右下。

簡単な説明:

4mr0から3までの乱数を生成し、
_1>+それが1より大きい場合(したがって
m<、0、1、3、または4-4 つのブロックの開始インデックス)、文字列を左に回転させます(492357816->など) 923578164、ブロック回転ではありません)ブロックを最初の位置に回転させるために
[Z0Y4X]\f=、ブロックの回転を行います。これは、12345-> 41352などの最初の5文字に影響します。
X = 1、Y = 2、Z = 3であるため、[Z0Y4X]は実際には[3 0 2 4 1]であり、これらは回転タイルの0ベースのインデックスで
5>あり、文字列の残りの部分が
m>(変更された)文字列を回転させます権利
__$>は、文字列がソートされているかどうかをチェックします(停止条件です)


1

Mathematica、104文字

順列グループの言語でタスクを解釈できます。4つの回転は、対称グループS 9を生成する4つの順列であり、タスクは、ジェネレータの積として順列を記述することです。Mathematicaにはこれを行うための組み込み関数があります。

i={1,2,5,4};GroupElementToWord[PermutationGroup[Cycles/@({i}+#&/@i-1)],Input[]~FindPermutation~Range@9]

例:

入力:

{4, 9, 2, 8, 3, 7, 1, 5, 6}

出力:

{-2, -3, -4, 2, 4, 1, 4, -1, -2, 3, 2, -4, 3, 4, -3, -3, -4, -4, -2, -2, -3, -2, 3, -1}
  • 1:時計回りに左上
  • 2:右上の時計回り
  • 3:右下に右
  • 4:時計回りに左下
  • -1:左上を反時計回り
  • -2:右上の反時計回り
  • -3:左下の反時計回り
  • -4:左下を反時計回り
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.