解决旋转难题


14

在一些旧的诺基亚手机上,有一个名为“旋转”的十五个谜题的变体。在此变体中,您不必一次滑动一个图块,而是在一个方向上一次旋转了四个图块。

在这个游戏中,您将从这样的棋盘开始:

4 9 2
3 5 7
8 1 6

通过顺时针旋转左下块两次,并顺时针旋转左上块,您将得到以下信息:

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

这是“原始”配置。

您的任务是构建一个程序,该程序将输入从3到3的1到9的数字网格(以您选择的任何格式),并作为输出返回一系列动作,这些动作表示将板恢复到原始状态所必须采取的动作配置(同样,可以选择任何格式)。合法移动定义为将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 ”?我不确定如何阅读。
Undergroundmonorail

是的,我的意思是返回到1 2 3 4 5 6 7 8 9
乔Z.

1
我认为示例中的第二板和第三板应该交换3和5。
马丁·恩德

@JoeZ。我建议对其进行修改,以声明解决方案必须具有有限的最坏情况性能。
HostileFork说不要信任2014年

Answers:


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`*

速度与大小

尺寸优化的版本随机选择顺时针旋转,直到实现所需的排列。这是足够的,因为逆时针旋转等于相同正方形的三个连续的顺时针旋转。

除以下内容外,速度优化版本的功能相同:

  1. 如果数字1位于左上角,则不会再旋转左上角的正方形。

  2. 如果数字9位于右下角,则不会再旋转右下角的正方形。

  3. 交换位置7和8的步骤是硬编码的,因此有两个位置允许循环中断。

除了更改算法以外,速度优化版本还可以直接实现旋转,而尺寸优化版本则使用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倍,这主要是由于状态的更新方式所致。

输入输出

输出由3位数字组成。MSB设置为逆时针旋转,中间位设置为较低的正方形,而LSB设置为右正方形。因此,0(4)是左上角的正方形,1(5)是右上角的正方形,2(6)是左下角的正方形,而3(7)是右下角的正方形。

速度优化版本将所有旋转打印在一行上。尺寸优化的版本每行打印一圈,然后是数字的最终位置。

对于速度优化的版本,在评估时,输入必须产生一个包含数字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" = }

在推入元素本身之后,“ $”会对要排序的数组的每个元素执行一次块。

“ [4 5 6 7 8 9]”中的索引1为-1(不存在),因此将压入最后一个元素“ 1420344440”。这将产生48,即与字符0相对应的ASCII码。对于2和3,也会推入48。

推入4、5、6、7、8和9的整数是49、52、50、48、51和52。

排序后,状态的第一个元素将是产生48的元素之一;最后一个将是那些产生52的那些。内置排序通常是不稳定的,但是我已经通过经验证明了在这种特殊情况下它是稳定的。

结果是“ [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和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]])

每条输出线都是用字符串编码的移动,例如trwblc,其读取方式如下:

  • t: 最佳
  • b:底部
  • l: 剩下
  • r: 对
  • c: 顺时针
  • w:逆时针(widdershins)

该程序执行随机移动,直到达到目标配置。在近似假设下,每一步都有1/9的独立概率!为了达到目标配置¹,解决方案之前的旋转次数呈指数分布,平均数(即平均移动次数)为9!≈3.6·10⁵。这是根据一个简短的实验(运行20次)得出的。

¹9!是配置的总数。


2
因此,从本质上讲,它会尝试随机移动直到获得解决方案?
Joe Z.

为我工作。尽管我对达到解决方案之前的预期轮换次数感兴趣。
乔Z.

@JoeZ .:查看对我的帖子所做的编辑。
Wrzlprmft

棒极了。
凯尔·坎诺斯

4

C ++最少移动解决方案-广度优先(1847个字符)

经过一番思考,我认为我的工作效率更高,更明智。这种解决方案虽然肯定不能赢得本场高尔夫比赛,但到目前为止,它是唯一尝试找到最短旋转次数来解决该板问题的解决方案。到目前为止,它可以解决我用九个或更少的招数扔给它的所有随机棋盘。它的性能也比我的上一个要好得多,并希望能在下面解决Dennis的评论。

在以前的解决方案中,最大的更改是将密钥历史记录从板状态(BS)移到了一个新类,该类将历史记录存储在给定深度(DKH)中。每当应用程序移动时,它都会检查该深度和所有深度的历史记录,然后再查看是否曾经被评估过,如果是这样,它将不会再次添加到队列中。这似乎大大减少了队列上的存储(通过从板状态本身中删除所有这些历史记录),因此减少了我为防止代码耗尽内存而必须进行的所有愚蠢的修剪。再加上它运行起来要快得多,因为要复制到队列中的内容要少得多。

现在,这是在各种董事会状态下进行的最简单的广度优先搜索。另外,事实证明,我想将密钥集(当前存储为以9为底的数字集,每个数字都由BS :: key计算为板的以9为底的表示形式)转换为一个位集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,确实是正确的。
DreamWarrior 2014年

如果任何人有相克编译此++,我不得不改变所有实例的问题int[]const int[],并设置标志-fpermissive
丹尼斯

@Dennis,对不起,我已经用两个截然不同的g ++编译器对其进行了编译,但似乎都没有。但是,我可以看到更新,更严格的版本会发出怎样的抱怨。谢谢。
DreamWarrior 2014年

现在编译良好,并且速度更快。解决您从问题中删除的注释:有些排列似乎需要11个步骤。978654321是其中之一。
丹尼斯2014年

1

果酱-39

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

另一个随机求解器:)
接受诸如492357816之类的字符串,并输出从0到3的(长)数字系列,每个数字代表一个块的顺时针旋转:0 =左上角,1 =右上角,2 =底部-左,3 =右下。

简要说明:

4mr生成一个从0到3的随机数,
_1>+如果它大于1,则递增该数字(所以我们
m<以0、1、3 或4结尾-4个块的起始索引)将字符串向左旋转(例如492357816-> 923578164,而不是块旋转),以便使块在第一个位置旋转。
[Z0Y4X]\f=该块旋转会影响前5个字符,例如12345-> 41352;
X = 1,Y = 2,Z = 3,因此[Z0Y4X]实际上是[3 0 2 4 1],这些是旋转图块的基于0的索引,
5>复制其余字符串
m>将(修改后的)字符串旋转回右边
__$>检查字符串是否已排序(这是停止条件)


1

Mathematica,104个字符

我们可以用排列组的语言来解释任务。四个旋转只是生成对称组S 9的四个置换,而任务只是将置换写为生成器的乘积。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:逆时针左下
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.