您将如何用代码表示魔方?
如果您正在开发解决魔方问题的软件,您会如何表示魔方?
If you were developing software to solve a Rubik's Cube, how would you represent the cube?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
如果您正在开发解决魔方问题的软件,您会如何表示魔方?
If you were developing software to solve a Rubik's Cube, how would you represent the cube?
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
接受
或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
发布评论
评论(13)
有 20 个立方体很重要。 因此,一种方法是使用包含 20 个字符串的数组。 这些字符串将包含 2 或 3 个表示颜色的字符。 任何单个移动都会影响 7 个立方体。 所以你只需要为六个面的每一个重新映射器。
注意:此解决方案无法记住白色中心徽标贴纸的方向。
顺便说一句,我曾经帮助某人做过一个魔方软件,大概是 15 年前,但我不记得我们是如何表现它的。
There are 20 cubies that matter. So one way to do it is as an array of 20 strings. The strings would hold 2 or 3 characters indicating the colors. Any single move affects 7 of the cubies. So you just need a remapper for each of the six sides.
Note: This solution doesn't manage to remember the orientation of the logo sticker that's on the white center.
By the way, I helped someone do a software Rubik's cube once, maybe 15 years ago, but I can't remember how we represented it.
您可以将立方体想象为三个垂直的圆形链表,它们与三个水平链表相交。
每当立方体的某一行旋转时,您只需旋转相应的指针即可。
它看起来像这样:
您实际上可能不需要 2 个“最后”指针。
[ 我使用 C 语言完成了此操作,但也可以在 Java 或 C# 中完成,只需使用cubeLinkedListNode 的一个简单类,每个类都保存对其他节点的引用。 ]
请记住,有六个互锁的循环链表。 3 垂直 3 水平。
对于每次旋转,您只需循环遍历相应的圆形链表,依次移动旋转圆的链接以及连接圆。
至少是这样的……
You could imagine the cube as three vertical circular linked lists, which intersect three horizontal linked lists.
Whenever a certain row of the cube is rotated you would just rotate the corresponding pointers.
It would look like this:
You might not actually need the 2 'last'-pointers.
[ I did this with C, but it could be done in Java or C# just using a simple class for cubeLinkedListNode, with each class holding references to other nodes. ]
Remember there are six interlocking circular linked lists. 3 vertical 3 horizontal.
For each rotation you would just loop through the corresponding circular linked list sequentially shifting the links of the rotating circle, as well as the connecting circles.
Something like that, at least...
最短的表示形式如下所示: codepen.io/Omelyan/pen/BKmedK
立方体展开为一维数组(54 个元素的向量)。 几行旋转功能交换贴纸并基于立方体的对称性。 这是 C 语言的完整工作模型,我在 2007 年还是一名学生时制作的:
The shortest representation is something like this one: codepen.io/Omelyan/pen/BKmedK
The cube is unwrapped in 1D array (vector of 54 elements). A few-line rotation function swaps stickers and based on the cube's symmetry. Here's complete working model in C, I made it in 2007 when was a student:
魔方有:
每个角立方体可以处于 3 个方向之一:
每个边缘立方体可以处于 2 个方向之一:
中心立方体相对于彼此固定; 然而,有 24 种可能的方向(忽略各个中心的旋转,这仅在您求解图片立方体时才相关),因为有 6 种方法来选取位于立方体“上”面上的中心立方体,然后有 4 种方法选择位于“正面”面上的中心立方体。
您可以将其存储为:
总共 105 位(14 字节)。
空间优化:
由于角始终是固定的,因此您可以假设它们永远不会移动并且不需要存储。 这样,如果您想要进行
E
移动,则可以进行等效的U D'
移动对。这会将大小减少到 100 位(13 字节)。
如果将表示限制为可解立方体,则可以将立方体存储在较小的空间中,如下所示:
这又节省了 10 位,总共 90 位(12 字节)。 然而,计算出缺失信息所需的计算可能意味着这种空间优化不值得以性能损失为代价。
更多空间优化:
如果您确实想优化立方体的空间:
8! = 40320
排列,并且40320
可以用16
位表示。3^7 = 2187
,并且可以用12
位表示。12! = 479001600
排列和479001600
可以用29
位表示。总共 68 位(9 个字节)。
可解魔方的最大排列数为
(8!*3^8*12!*2^12)/12 = 43,252,003,274,489,856,000 ~= 4.3*10^19
,可以存储在 < code>66 位(9 字节),虽然可以枚举所有可能的解决方案,但不值得保存最后 2 位。A rubik cube has:
Each corner cubelet can be in one of 3 orientations:
Each edge cubelet can be in one of 2 orientations:
The centre cubelets are fixed relative to each other; however there are 24 possible orientations (ignoring rotations of individual centres, which is only relevant if you are solving a picture cube) as there are 6 ways to pick the centre cubelet that is on the "up" face of the cube and then 4 ways to pick the centre cubelet that would be on the "front" face.
You can store this as:
This gives a total of 105 bits (14 bytes).
Space optimisations:
Since the corners are always fixed then you can assume that they never move and do not need to be stored. With this, if you want to do an
E
move then do an equivalentU D'
pair of moves instead.This would reduce the size to 100 bits (13 bytes).
If you restrict the representation to solvable cubes then it is possible to store the cube in a smaller space as:
This saves a further 10 bits for a total of 90 bits (12 bytes). However, the calculations required to work out the missing information may mean that this space optimisation is not worth the performance penalty.
More Space Optimisations:
If you really want to optimise the space for the cube the:
8! = 40320
permutations and40320
can be represented in16
bits.3^7 = 2187
and can be represented in12
bits.12! = 479001600
permutations and479001600
can be represented in29
bits.11
bits.This gives a total of 68 bits (9 bytes).
The maximum number of permutations of a solvable rubik cube is
(8!*3^8*12!*2^12)/12 = 43,252,003,274,489,856,000 ~= 4.3*10^19
which can be stored in66
bits (9 bytes) and while its possible to enumerate all the possible solutions it is not worth it to save those last 2 bits.其他人很好地描述了物理立方体,但是关于立方体的状态......我会尝试使用向量变换数组来描述立方体的变化。 这样,您就可以在进行更改时保留魔方的历史记录。 我想知道你是否可以将向量乘以变换矩阵来找到最简单的解决方案?
The others well addressed describing the physical cube, but regarding the state of the cube... I would try using an array of vector transformations to describe the changes of the cube. That way you could keep the history of the rubiks cube as changes are made. And I wonder if you could multiply the vectors into a transformation matrix to find the simplest solution?
作为 48 个可以移动的面的排列。 基本旋转也是排列,排列可以组合,它们形成一个群。
在程序中,这样的排列将由包含数字 0 到 47 的 48 个元素的数组表示。与数字对应的颜色是固定的,因此可以根据排列计算视觉表示,反之亦然。
As a permutation of the 48 faces which can move. The basic rotations are also permutations, and permutations can be composed, they form a group.
In a program such a permutation would be represented by an array of 48 elements containing numbers 0 to 47. The colors corresponding to the numbers are fixed, so a visual representation can be computed from the permutation, and vice versa.
我发现将状态存储为从立方体表面外部的中心坐标到颜色的映射非常有用。 因此,例如,前右上立方体的上表面是状态[1, -1, 2] 。 因此,只需对指数进行旋转即可完成移动。
您可以在这里看到我使用它编写的一个简单模拟器: https://github.com/noamraph/cube
I found it very useful to store the state as a mapping from the coordinates of the center of the cubelet outside the face to the color. So, for example, the upper face of the front-top-right cubelet is
state[1, -1, 2]
. So moves are done by just applying rotations to the indices.You can see a simple simulator I wrote using it here: https://github.com/noamraph/cube
这篇 ACM 论文描述了几种替代方法用来表示魔方并将它们相互比较。 遗憾的是,我没有帐户来获取全文,但描述指出:
此外,这个 RubiksCube.java 文件包含一个非常干净的表示与旋转部分的相关代码(如果您正在寻找实际的代码)。 它使用单元格和面孔数组。
This ACM Paper describes several alternative ways that it has used to represent a rubik's cube and compares them against eachother. Sadly, I don't have an account to get the full text but the description states:
Also, this RubiksCube.java file contains a pretty clean representation along with the relevant code for rotating the sections (if you are looking for actual code). It uses a cell and faces array.
简而言之,这取决于您将如何解决立方体问题。 如果您的求解器要使用人工方法(例如逐层方法或 Fridrich 方法),那么底层数据结构不会产生太大影响。 即使使用最慢的编程语言,计算机也可以在可忽略不计的时间内(远低于一秒)使用人类方法解算立方体。 但是,如果您要使用计算量更大的方法(例如 Thistlethwaite 的 52 步算法、Reid 的 29 步算法或 Korf 的 20 步算法)来解魔方,那么数据结构和编程语言就至关重要。
我实现了一个魔方程序,它使用 OpenGL 渲染魔方,并且它内置了两种不同类型的解算器(Thistlethwaite 和 Korf)。 求解器必须生成数十亿次移动并比较每个立方体状态数十亿次,因此底层结构必须很快。 我尝试了以下结构:
扩展上面的方法(3),立方体的每个面都由 9 个贴纸组成,但中心是固定的,因此只需要存储 8 个。 有 6 种颜色,因此每种颜色都适合一个字节。 给定这些颜色定义:
一张脸可能看起来像这样,存储在单个 64 位整数中:
解码为:
使用此结构的优点是
rolq
和rorq 按位运算符可用于移动面。 滚动 16 位会实现 90 度旋转; 滚动 32 位会产生 180 度转弯。 相邻的块需要手动维护——即旋转顶面后,前、左、后、右面的顶层也需要移动。 这样的翻脸速度实在是太快了。 例如,滚动
16 位会产生
Decoded,如下所示:
另一个优点是,在某些情况下,可以使用一些巧妙的位掩码和标准整数比较来比较立方体状态。 对于求解器来说,这可能是一个相当大的加速。
无论如何,我的实现在 github 上: https://github.com/benbotto /rubiks-cube-cracker/tree/2.2.0 请参阅
Model/RubiksCubeModel.{h,cpp}
。扩展上述方法 (4),一些以编程方式求解魔方的算法使用带有 A* 的迭代加深深度优先搜索,并使用模式数据库作为启发式。 例如,Korf 算法使用三种模式数据库:一个存储 8 个角块的索引和方向;另一个存储 8 个角块的索引和方向;另一个存储 8 个角块的索引和方向。 其中一个存储 12 个边缘块中的 6 个边缘块的索引和方向; 最后一个存储其他 6 个边的索引和方向。 使用模式数据库时,一种快速方法是将立方体存储为一组索引和方向。
任意定义约定,边缘立方体可以按如下方式索引。
所以红-黄边立方体在索引 0 处,白-绿边立方体在索引 4 处。同样,角立方体可以这样索引:
所以红-蓝-黄角立方体在索引 0 处,并且橙-绿-黄角立方体位于索引 6。
每个立方体的方向也需要保持。 边缘件可以处于两个方向之一(定向或翻转),而角件可以处于三个不同的方向(定向、旋转一次或旋转两次)。 有关部件方向的更多详细信息,请参见:http://cube.rider .biz/zz.php?p=eoline#eo_detection 使用此模型,旋转面意味着更新索引和方向。 这种表示是最困难的,因为人类(至少对我来说)很难查看一大堆索引和方向数并验证它们的正确性。 话虽如此,该模型比使用上述其他模型之一动态计算索引和方向要快得多,因此它是使用模式数据库时的最佳选择。 您可以在此处查看此模型的实现: https://github .com/benbotto/rubiks-cube-cracker/tree/4.0.0/Model(请参阅
RubiksCubeIndexModel.{h,cpp}
)。如前所述,该程序还渲染立方体。 我对该部分使用了不同的结构。 我定义了一个“cubie”类,它是六个正方形,分别具有 1、2 或 3 个彩色面作为中心、边缘和角块。 魔方则由 26 个立方体组成。 使用四元数旋转面。 立方体和立方体的代码位于:https:/ /github.com/benbotto/rubiks-cube-cracker/tree/4.0.0/Model/WorldObject
如果您对我的魔方解算器程序感兴趣,YouTube 上有一个高级概述视频:https://www.youtube.com/watch?v=ZtlMkzix7Bw&feature=youtu .be 我在 中。
The short answer is that it depends on how you're going to solve the cube. If your solver is going to use a human method like the layer-by-layer approach or the Fridrich method then the underlying data structure won't make much of a difference. A computer can solve a cube using a human method in negligible time (well under a second) even in the slowest of programming languages. But if you are going to solve the cube using a more computationally intensive method such as Thistlethwaite's 52-move algorithm, Reid's 29-move algorithm, or Korf's 20-move algorithm, then the data structure and programming language are of utmost importance.
I implemented a Rubik's Cube program that renders the cube using OpenGL, and it has two different types of solvers built in (Thistlethwaite and Korf). The solver has to generate billions of moves and compare each cube state billions of times, so the underlying structure has to be fast. I tried the following structures:
Expanding on method (3) above, each face of the cube is made up of 9 stickers, but the center is stationary so only 8 need to be stored. And there are 6 colors, so each color fits in a byte. Given these color definitions:
A face might look like this, stored in a single 64-bit integer:
Which is decoded as:
An advantage of using this structure is that the
rolq
androrq
bit-wise operators can be used to move a face. Rolling by 16 bits effects a 90-degree rotation; rolling by 32 bits gives a 180-degree turn. The adjacent pieces need to be up-kept manually--i.e. after rotating the top face, the top layer of the front, left, back, and right faces need to be moved, too. Turning faces in this manner is really fast. For example, rollingby 16 bits yields
Decoded, that looks like this:
Another advantage is that comparing cube states can in some instances be done using some clever bit masks and standard integer comparisons. That can be a pretty big speed-up for a solver.
Anyway, my implementation is on github: https://github.com/benbotto/rubiks-cube-cracker/tree/2.2.0 See
Model/RubiksCubeModel.{h,cpp}
.Expanding on method (4) above, some of the algorithms for programmatically solving the Rubik's Cube use an iterative deepening depth-first search with A*, using pattern databases as a heuristic. For example, Korf's algorithm utilizes three pattern databases: one stores the index and orientation of the 8 corner cubies; one stores the index and orientation of 6 of the 12 edge pieces; the last stores the index and orientation of the other 6 edges. When using pattern databases, a fast approach is to store the cube as a set of indexes and orientations.
Arbitrarily defining a convention, the edge cubies could be indexed as follows.
So the red-yellow edge cubie is at index 0, and the white-green edge cubie is at index 4. Likewise, the corner cubies might be indexed like so:
So the red-blue-yellow corner cubie is at index 0, and the orange-green-yellow corner cubie is at index 6.
The orientation of each cubie needs to be kept as well. An edge piece can be in one of two orientations (oriented or flipped), while a corner piece can be in three different orientations (oriented, rotated once, or rotated twice). More details about the orientation of pieces can be found here: http://cube.rider.biz/zz.php?p=eoline#eo_detection With this model, rotating a face means updating indexes and orientations. This representation is the most difficult because it's hard for a human (for me at least) to look at a big blob of index and orientation numbers and verify their correctness. That being said, this model is significantly faster than dynamically calculating indexes and orientations using one of the other models described above, and so it's the best choice when using pattern databases. You can see an implementation of this model here: https://github.com/benbotto/rubiks-cube-cracker/tree/4.0.0/Model (see
RubiksCubeIndexModel.{h,cpp}
).As mentioned, the program also renders the cube. I used a different structure for that part. I defined a "cubie" class, which is six squares with 1, 2, or 3 colored faces for center, edge, and corner pieces, respectively. The Rubik's Cube is then composed of 26 cubies. The faces are rotated using quaternions. The code for the cubies and cube is here: https://github.com/benbotto/rubiks-cube-cracker/tree/4.0.0/Model/WorldObject
If you're interested in my Rubik's Cube solver program, there's a high-level overview video on YouTube: https://www.youtube.com/watch?v=ZtlMkzix7Bw&feature=youtu.be I also have a more extensive write-up on solving the Rubik's Cube programmatically on Medium.
一种方法是关注视觉外观。
立方体有六个面,每个面都是一个三乘三的正方形阵列。 那么
每一步都是一种排列一组特定彩色方块的方法。
One way would be to focus on the visual appearance.
A cube has six faces and each face is a three-by-three array of squares. So
Then each move is a method that permutes a specific set of colored squares.
避免优化; 使其面向对象。 我使用的伪代码类大纲是:
使用的内存量非常小,处理量也非常小,以至于完全没有必要进行优化,特别是当您牺牲代码可用性时。
Eschew optimisation; make it object-oriented. A pseudocode class outline I've used is:
The amount of memory used is so small and processing so minimal that optimisation is totally unnecessary, especially when you sacrifice code usability.
“Cube Explorer”软件使用了一种有趣的方法来表示立方体。 该方法使用大量巧妙的数学运算,仅使用 5 个整数即可表示立方体。 作者在他的网站上解释了他的程序背后的数学原理。 据作者称,该表示法适合实现快速求解器。
An interesting method to represent the cube is used by the software "Cube Explorer". Using a lot of clever maths that method can represent the cube using only 5 integers. The author explains the maths behind his program on his website. According to the author the representation is suited to implement fast solvers.
有很多方法可以做到这一点。 有些方法比其他方法更有效地利用内存。
我见过人们使用 3 x 3 x 3 长方体对象数组,其中长方体对象需要存储颜色信息(是的,从未使用过中心对象)。 我见过人们使用 6 个数组,每个数组都是 3 x 3 长方体数组。 我见过 3 x 18 的长方体阵列。 有很多可能性。
可能更关心的是如何表示各种变换。 旋转物理立方体的单个面(所有立方体移动本质上都是单个面的旋转)必须通过交换大量长方体对象来表示。
您的选择应该对您正在编写的任何应用程序都有意义。 您可能只渲染立方体。 可能是没有UI。 您可能正在解决立方体。
我会选择 3 x 18 阵列。
There are many ways to do this. Some ways are make more efficient use of memory than others.
I have seen people use a 3 x 3 x 3 array of cuboid objects, where the cuboid object needs to store color information (and yes, that center object is never used). I have seen people use 6 arrays, each of which is a 3 x 3 array of cuboids. I have seen a 3 x 18 array of cuboids. There are many possibilities.
Probably a bigger concern is how to represent the various transforms. Rotating a single face of a physical cube (all cube moves are essentially rotations of a single face) would have to be represented by swapping around a lot of cuboid objects.
Your choice should be one that makes sense for whatever application you are writing. It may be that you are only rendering the cube. It may be that there is no UI. You may be solving the cube.
I would choose the 3 x 18 array.