CodeFights: Pyraminx puzzle
Clash Royale CLAN TAG#URR8PPP
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;
up vote
6
down vote
favorite
Intro
You've mastered the Rubik's Cube and got bored solving it, so now you are looking for a new challenge. One puzzle similar to the Rubik's Cube caught your attention. It's called a Pyraminx puzzle, and is a triangular pyramid-shaped puzzle. The parts are arranged in a pyramidal pattern on each side, while the layers can be rotated with respect to each vertex, and the individual tips can be rotated as well. There are 4 faces on the Pyraminx. The puzzle should be held so that one face faces you and one face faces down, as in the image below. The four corners are then labeled U
(for up), R
(for right), L
(for left), and B
(for back). The front face thus contains the U
, R
, and L
corners.
Let's write down all possible moves for vertex U in the following notation:
- U - 120ð counterclockwise turn of topmost tip (assuming that we're looking at the pyraminx from the top, and vertex U is the topmost);
- U' - clockwise turn for the same tip;
- u - 120ð counterclockwise turn of two upper layer;
- u' - clockwise turn for the same layers.
For other vertices the moves can be described similarly
The first puzzle you bought wasn't assembled, so you get your professional pyraminx solver friend to assemble it. He does, and you wrote down all his moves notated as described above. Now the puzzle's faces have colors faceColors[0]
(front face), faceColors[1]
(bottom face), faceColors[2]
(left face), faceColors[3]
(right face). You want to know the initial state of the puzzle to repeat your friend's moves and see how he solved it.
Example
For face_colors = ['R', 'G', 'Y', 'O']
and moves = ["B", "b'", "u'", "R"]
, the output should be
pyraminxPuzzle(faceColors, moves) = [['Y', 'Y', 'Y', 'Y', 'R', 'R', 'R', 'R', 'G'],
['G', 'R', 'O', 'O', 'O', 'G', 'G', 'G', 'G'],
['Y', 'O', 'Y', 'G', 'O', 'O', 'G', 'G', 'Y'],
['R', 'O', 'O', 'R', 'O', 'Y', 'Y', 'R', 'R']]
Visual representation
Edit
As requested bij @Jan Kuijken a map for each face to the corresponding index
"""
L[2] : [ 4 | U[0] : [ 0, | R[3] : [ 8,
6, 5, 1 | 1, 2, 3, | 3, 7, 6,
8, 7, 3, 2, 0 ] | 4, 5, 6, 7, 8 ] | 0, 2, 1, 5, 4 ]
-----------------------------------------------------------------------------
B[1] : [ 8, 7, 6, 5, 4,
3, 2, 1,
0 ]
"""
Tests
import unittest
class TestPyraminx(unittest.TestCase):
def test1(self):
exp_1 = [["Y","Y","Y","Y","R","R","R","R","G"],
["G","R","O","O","O","G","G","G","G"],
["Y","O","Y","G","O","O","G","G","Y"],
["R","O","O","R","O","Y","Y","R","R"]]
colours = ["R", "G", "Y", "O"]
moves = ["B", "b'", "u'", "R"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_1)
def test2(self):
exp_2 = [["R","R","R","R","R","R","R","R","R"],
["G","G","G","G","G","G","G","G","G"],
["Y","Y","Y","Y","Y","Y","Y","Y","Y"],
["O","O","O","O","O","O","O","O","O"]]
colours = ["R", "G", "Y", "O"]
moves = ["l", "l'"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_2)
def test3(self):
exp_3 = [["Y","O","R","G","G","G","G","G","G"],
["G","O","G","Y","O","O","Y","Y","Y"],
["R","G","R","R","O","Y","Y","Y","Y"],
["R","R","R","R","O","O","O","O","R"]]
colours = ["R", "G", "Y", "O"]
moves = ["l", "l'", "u", "R", "U'", "L", "R'", "u'", "l'", "L'", "r"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_3)
def test4(self):
exp_4 = [["R","R","R","G","R","R","G","G","G"],
["G","O","G","G","O","O","O","G","G"],
["Y","Y","Y","Y","Y","Y","Y","Y","Y"],
["R","R","R","R","O","O","O","O","O"]]
colours = ["R", "G", "Y", "O"]
moves = ["r"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_4)
def test5(self):
exp_5 = [["A","A","A","A","A","A","A","A","A"],
["B","B","B","B","B","B","B","B","B"],
["C","C","C","C","C","C","C","C","C"],
["D","D","D","D","D","D","D","D","D"]]
colours = ["A", "B", "C", "D"]
moves = ["l", "l'", "r'", "r", "u", "U", "u'", "R'", "L", "R", "L'", "B'", "U'", "b", "B", "b'"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_5)
def test6(self):
exp_6 = [["E","Y","E","G","Y","Y","R","G","G"],
["Y","E","Y","R","E","E","E","R","R"],
["G","G","G","Y","R","R","E","E","E"],
["R","G","R","R","G","G","Y","Y","Y"]]
colours = ["R", "G", "Y", "E"]
moves = ["b", "l", "r", "u"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_6)
def test7(self):
exp_7 = [["E","R","R","R","L","R","R","R","U"],
["L","U","U","U","E","U","U","U","R"],
["U","L","L","L","R","L","L","L","E"],
["R","E","E","E","U","E","E","E","L"]]
colours = ["R", "U", "L", "E"]
moves = ["U", "B", "R", "L"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_7)
def test8(self):
exp_8 = [["W","W","D","A","W","W","S","A","A"],
["A","D","D","A","D","D","D","A","A"],
["S","S","S","S","S","W","A","A","S"],
["W","W","W","D","D","S","W","S","D"]]
colours = ["W", "A", "S", "D"]
moves = ["l", "r'", "U'", "u", "r'", "B", "l'", "b'"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_8)
def test9(self):
exp_9 = [["W","S","D","S","W","S","S","W","A"],
["D","W","A","A","D","A","D","W","A"],
["S","A","A","D","S","W","W","S","A"],
["W","D","D","W","S","D","A","S","D"]]
colours = ["W", "A", "S", "D"]
moves = ["B'", "R'", "L", "U'", "B", "r'", "l", "B'", "L'", "r'", "L", "U", "u'",
"U", "B'", "r", "L'", "R", "B", "r", "R'", "R", "U'", "U", "L", "r", "L",
"B'", "U", "B", "R", "R'", "R", "u'", "l", "R'", "R", "B", "R'", "U", "u",
"U", "u'", "B'", "r", "L'", "B'", "R'", "B'", "r", "R'", "r", "L", "R'",
"B", "u", "B'", "B", "L", "U", "B", "B", "L", "R", "B", "R", "u'", "R'",
"B", "u", "u'", "L'", "B", "R'", "l'", "U", "U'", "B", "r", "L'", "B", "r'",
"U", "R", "R'", "u'", "r", "R'", "u'", "r'", "L'", "R'", "r'", "U", "u'",
"B'", "U", "L'", "L'", "B"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_9)
def test10(self):
exp_10 = [["W","W","W","W","W","W","W","W","W"],
["A","A","A","A","A","A","A","A","A"],
["S","S","S","S","S","S","S","S","S"],
["D","D","D","D","D","D","D","D","D"]]
colours = ["W", "A", "S", "D"]
moves = ["L", "L", "L", "r'", "r'", "r'", "U", "U'", "U", "U'", "b", "b'", "b'", "b"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_10)
if __name__ == '__main__':
unittest.main()
Code
def pyraminx_puzzle(face_colours, moves):
move_position = "U": [[0, 0], [3, 8], [2, 4]],
"u": [[0, 0], [0, 1], [0, 2], [0, 3], [3, 8], [3, 3], [3, 7], [3, 6], [2, 4], [2, 6], [2, 5], [2, 1]],
"L": [[2, 0], [1, 8], [0, 4]],
"l": [[2, 0], [2, 1], [2, 2], [2, 3], [1, 8], [1, 3], [1, 7], [1, 6], [0, 4], [0, 6], [0, 5], [0, 1]],
"R": [[3, 0], [0, 8], [1, 4]],
"r": [[3, 0], [3, 1], [3, 2], [3, 3], [0, 3], [0, 8], [0, 7], [0, 6], [1, 4], [1, 6], [1, 5], [1, 1]],
"B": [[1, 0], [2, 8], [3, 4]],
"b": [[1, 0], [1, 1], [1, 2], [1, 3], [2, 8], [2, 3], [2, 7], [2, 6], [3, 4], [3, 6], [3, 5], [3, 1]]
puzzle = [[c for _ in range(9)] for c in face_colours]
for move in reversed(moves):
left = "'" not in move
move_colors = move: [puzzle[r][c] for r, c in move_position[move]] for move in move_position
current_color = shift_color(move_colors[move[0]], left)
for idx, pos in enumerate(move_position[move[0]]):
puzzle[pos[0]][pos[1]] = current_color[idx]
return puzzle
def shift_color(current_color, left):
i = len(current_color) // 3
if left:
return current_color[i:] + current_color[0:i]
return current_color[-i:] + current_color[:-i]
python python-3.x programming-challenge
add a comment |Â
up vote
6
down vote
favorite
Intro
You've mastered the Rubik's Cube and got bored solving it, so now you are looking for a new challenge. One puzzle similar to the Rubik's Cube caught your attention. It's called a Pyraminx puzzle, and is a triangular pyramid-shaped puzzle. The parts are arranged in a pyramidal pattern on each side, while the layers can be rotated with respect to each vertex, and the individual tips can be rotated as well. There are 4 faces on the Pyraminx. The puzzle should be held so that one face faces you and one face faces down, as in the image below. The four corners are then labeled U
(for up), R
(for right), L
(for left), and B
(for back). The front face thus contains the U
, R
, and L
corners.
Let's write down all possible moves for vertex U in the following notation:
- U - 120ð counterclockwise turn of topmost tip (assuming that we're looking at the pyraminx from the top, and vertex U is the topmost);
- U' - clockwise turn for the same tip;
- u - 120ð counterclockwise turn of two upper layer;
- u' - clockwise turn for the same layers.
For other vertices the moves can be described similarly
The first puzzle you bought wasn't assembled, so you get your professional pyraminx solver friend to assemble it. He does, and you wrote down all his moves notated as described above. Now the puzzle's faces have colors faceColors[0]
(front face), faceColors[1]
(bottom face), faceColors[2]
(left face), faceColors[3]
(right face). You want to know the initial state of the puzzle to repeat your friend's moves and see how he solved it.
Example
For face_colors = ['R', 'G', 'Y', 'O']
and moves = ["B", "b'", "u'", "R"]
, the output should be
pyraminxPuzzle(faceColors, moves) = [['Y', 'Y', 'Y', 'Y', 'R', 'R', 'R', 'R', 'G'],
['G', 'R', 'O', 'O', 'O', 'G', 'G', 'G', 'G'],
['Y', 'O', 'Y', 'G', 'O', 'O', 'G', 'G', 'Y'],
['R', 'O', 'O', 'R', 'O', 'Y', 'Y', 'R', 'R']]
Visual representation
Edit
As requested bij @Jan Kuijken a map for each face to the corresponding index
"""
L[2] : [ 4 | U[0] : [ 0, | R[3] : [ 8,
6, 5, 1 | 1, 2, 3, | 3, 7, 6,
8, 7, 3, 2, 0 ] | 4, 5, 6, 7, 8 ] | 0, 2, 1, 5, 4 ]
-----------------------------------------------------------------------------
B[1] : [ 8, 7, 6, 5, 4,
3, 2, 1,
0 ]
"""
Tests
import unittest
class TestPyraminx(unittest.TestCase):
def test1(self):
exp_1 = [["Y","Y","Y","Y","R","R","R","R","G"],
["G","R","O","O","O","G","G","G","G"],
["Y","O","Y","G","O","O","G","G","Y"],
["R","O","O","R","O","Y","Y","R","R"]]
colours = ["R", "G", "Y", "O"]
moves = ["B", "b'", "u'", "R"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_1)
def test2(self):
exp_2 = [["R","R","R","R","R","R","R","R","R"],
["G","G","G","G","G","G","G","G","G"],
["Y","Y","Y","Y","Y","Y","Y","Y","Y"],
["O","O","O","O","O","O","O","O","O"]]
colours = ["R", "G", "Y", "O"]
moves = ["l", "l'"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_2)
def test3(self):
exp_3 = [["Y","O","R","G","G","G","G","G","G"],
["G","O","G","Y","O","O","Y","Y","Y"],
["R","G","R","R","O","Y","Y","Y","Y"],
["R","R","R","R","O","O","O","O","R"]]
colours = ["R", "G", "Y", "O"]
moves = ["l", "l'", "u", "R", "U'", "L", "R'", "u'", "l'", "L'", "r"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_3)
def test4(self):
exp_4 = [["R","R","R","G","R","R","G","G","G"],
["G","O","G","G","O","O","O","G","G"],
["Y","Y","Y","Y","Y","Y","Y","Y","Y"],
["R","R","R","R","O","O","O","O","O"]]
colours = ["R", "G", "Y", "O"]
moves = ["r"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_4)
def test5(self):
exp_5 = [["A","A","A","A","A","A","A","A","A"],
["B","B","B","B","B","B","B","B","B"],
["C","C","C","C","C","C","C","C","C"],
["D","D","D","D","D","D","D","D","D"]]
colours = ["A", "B", "C", "D"]
moves = ["l", "l'", "r'", "r", "u", "U", "u'", "R'", "L", "R", "L'", "B'", "U'", "b", "B", "b'"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_5)
def test6(self):
exp_6 = [["E","Y","E","G","Y","Y","R","G","G"],
["Y","E","Y","R","E","E","E","R","R"],
["G","G","G","Y","R","R","E","E","E"],
["R","G","R","R","G","G","Y","Y","Y"]]
colours = ["R", "G", "Y", "E"]
moves = ["b", "l", "r", "u"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_6)
def test7(self):
exp_7 = [["E","R","R","R","L","R","R","R","U"],
["L","U","U","U","E","U","U","U","R"],
["U","L","L","L","R","L","L","L","E"],
["R","E","E","E","U","E","E","E","L"]]
colours = ["R", "U", "L", "E"]
moves = ["U", "B", "R", "L"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_7)
def test8(self):
exp_8 = [["W","W","D","A","W","W","S","A","A"],
["A","D","D","A","D","D","D","A","A"],
["S","S","S","S","S","W","A","A","S"],
["W","W","W","D","D","S","W","S","D"]]
colours = ["W", "A", "S", "D"]
moves = ["l", "r'", "U'", "u", "r'", "B", "l'", "b'"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_8)
def test9(self):
exp_9 = [["W","S","D","S","W","S","S","W","A"],
["D","W","A","A","D","A","D","W","A"],
["S","A","A","D","S","W","W","S","A"],
["W","D","D","W","S","D","A","S","D"]]
colours = ["W", "A", "S", "D"]
moves = ["B'", "R'", "L", "U'", "B", "r'", "l", "B'", "L'", "r'", "L", "U", "u'",
"U", "B'", "r", "L'", "R", "B", "r", "R'", "R", "U'", "U", "L", "r", "L",
"B'", "U", "B", "R", "R'", "R", "u'", "l", "R'", "R", "B", "R'", "U", "u",
"U", "u'", "B'", "r", "L'", "B'", "R'", "B'", "r", "R'", "r", "L", "R'",
"B", "u", "B'", "B", "L", "U", "B", "B", "L", "R", "B", "R", "u'", "R'",
"B", "u", "u'", "L'", "B", "R'", "l'", "U", "U'", "B", "r", "L'", "B", "r'",
"U", "R", "R'", "u'", "r", "R'", "u'", "r'", "L'", "R'", "r'", "U", "u'",
"B'", "U", "L'", "L'", "B"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_9)
def test10(self):
exp_10 = [["W","W","W","W","W","W","W","W","W"],
["A","A","A","A","A","A","A","A","A"],
["S","S","S","S","S","S","S","S","S"],
["D","D","D","D","D","D","D","D","D"]]
colours = ["W", "A", "S", "D"]
moves = ["L", "L", "L", "r'", "r'", "r'", "U", "U'", "U", "U'", "b", "b'", "b'", "b"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_10)
if __name__ == '__main__':
unittest.main()
Code
def pyraminx_puzzle(face_colours, moves):
move_position = "U": [[0, 0], [3, 8], [2, 4]],
"u": [[0, 0], [0, 1], [0, 2], [0, 3], [3, 8], [3, 3], [3, 7], [3, 6], [2, 4], [2, 6], [2, 5], [2, 1]],
"L": [[2, 0], [1, 8], [0, 4]],
"l": [[2, 0], [2, 1], [2, 2], [2, 3], [1, 8], [1, 3], [1, 7], [1, 6], [0, 4], [0, 6], [0, 5], [0, 1]],
"R": [[3, 0], [0, 8], [1, 4]],
"r": [[3, 0], [3, 1], [3, 2], [3, 3], [0, 3], [0, 8], [0, 7], [0, 6], [1, 4], [1, 6], [1, 5], [1, 1]],
"B": [[1, 0], [2, 8], [3, 4]],
"b": [[1, 0], [1, 1], [1, 2], [1, 3], [2, 8], [2, 3], [2, 7], [2, 6], [3, 4], [3, 6], [3, 5], [3, 1]]
puzzle = [[c for _ in range(9)] for c in face_colours]
for move in reversed(moves):
left = "'" not in move
move_colors = move: [puzzle[r][c] for r, c in move_position[move]] for move in move_position
current_color = shift_color(move_colors[move[0]], left)
for idx, pos in enumerate(move_position[move[0]]):
puzzle[pos[0]][pos[1]] = current_color[idx]
return puzzle
def shift_color(current_color, left):
i = len(current_color) // 3
if left:
return current_color[i:] + current_color[0:i]
return current_color[-i:] + current_color[:-i]
python python-3.x programming-challenge
Nice problem, could you also provide a drawing which maps thex
index offaceColours[x]
to the actual positions on the faces? (for an easier understanding)
â Jan Kuiken
Jun 26 at 17:00
@JanKuiken I've edited that part in, I hope it is more clear now.
â Ludisposed
Jun 27 at 11:24
Why is the right face numbered so weirdly?
â 200_success
Jul 10 at 20:58
You meanimport unittest
and notimport unittests
, right?
â 200_success
Jul 10 at 21:11
@200_succes Thnx, was a typo edited it. The right face is weird indeed, but if you see theR
as the top, and go topdown left to right it makes sense.
â Ludisposed
Jul 10 at 22:25
add a comment |Â
up vote
6
down vote
favorite
up vote
6
down vote
favorite
Intro
You've mastered the Rubik's Cube and got bored solving it, so now you are looking for a new challenge. One puzzle similar to the Rubik's Cube caught your attention. It's called a Pyraminx puzzle, and is a triangular pyramid-shaped puzzle. The parts are arranged in a pyramidal pattern on each side, while the layers can be rotated with respect to each vertex, and the individual tips can be rotated as well. There are 4 faces on the Pyraminx. The puzzle should be held so that one face faces you and one face faces down, as in the image below. The four corners are then labeled U
(for up), R
(for right), L
(for left), and B
(for back). The front face thus contains the U
, R
, and L
corners.
Let's write down all possible moves for vertex U in the following notation:
- U - 120ð counterclockwise turn of topmost tip (assuming that we're looking at the pyraminx from the top, and vertex U is the topmost);
- U' - clockwise turn for the same tip;
- u - 120ð counterclockwise turn of two upper layer;
- u' - clockwise turn for the same layers.
For other vertices the moves can be described similarly
The first puzzle you bought wasn't assembled, so you get your professional pyraminx solver friend to assemble it. He does, and you wrote down all his moves notated as described above. Now the puzzle's faces have colors faceColors[0]
(front face), faceColors[1]
(bottom face), faceColors[2]
(left face), faceColors[3]
(right face). You want to know the initial state of the puzzle to repeat your friend's moves and see how he solved it.
Example
For face_colors = ['R', 'G', 'Y', 'O']
and moves = ["B", "b'", "u'", "R"]
, the output should be
pyraminxPuzzle(faceColors, moves) = [['Y', 'Y', 'Y', 'Y', 'R', 'R', 'R', 'R', 'G'],
['G', 'R', 'O', 'O', 'O', 'G', 'G', 'G', 'G'],
['Y', 'O', 'Y', 'G', 'O', 'O', 'G', 'G', 'Y'],
['R', 'O', 'O', 'R', 'O', 'Y', 'Y', 'R', 'R']]
Visual representation
Edit
As requested bij @Jan Kuijken a map for each face to the corresponding index
"""
L[2] : [ 4 | U[0] : [ 0, | R[3] : [ 8,
6, 5, 1 | 1, 2, 3, | 3, 7, 6,
8, 7, 3, 2, 0 ] | 4, 5, 6, 7, 8 ] | 0, 2, 1, 5, 4 ]
-----------------------------------------------------------------------------
B[1] : [ 8, 7, 6, 5, 4,
3, 2, 1,
0 ]
"""
Tests
import unittest
class TestPyraminx(unittest.TestCase):
def test1(self):
exp_1 = [["Y","Y","Y","Y","R","R","R","R","G"],
["G","R","O","O","O","G","G","G","G"],
["Y","O","Y","G","O","O","G","G","Y"],
["R","O","O","R","O","Y","Y","R","R"]]
colours = ["R", "G", "Y", "O"]
moves = ["B", "b'", "u'", "R"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_1)
def test2(self):
exp_2 = [["R","R","R","R","R","R","R","R","R"],
["G","G","G","G","G","G","G","G","G"],
["Y","Y","Y","Y","Y","Y","Y","Y","Y"],
["O","O","O","O","O","O","O","O","O"]]
colours = ["R", "G", "Y", "O"]
moves = ["l", "l'"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_2)
def test3(self):
exp_3 = [["Y","O","R","G","G","G","G","G","G"],
["G","O","G","Y","O","O","Y","Y","Y"],
["R","G","R","R","O","Y","Y","Y","Y"],
["R","R","R","R","O","O","O","O","R"]]
colours = ["R", "G", "Y", "O"]
moves = ["l", "l'", "u", "R", "U'", "L", "R'", "u'", "l'", "L'", "r"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_3)
def test4(self):
exp_4 = [["R","R","R","G","R","R","G","G","G"],
["G","O","G","G","O","O","O","G","G"],
["Y","Y","Y","Y","Y","Y","Y","Y","Y"],
["R","R","R","R","O","O","O","O","O"]]
colours = ["R", "G", "Y", "O"]
moves = ["r"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_4)
def test5(self):
exp_5 = [["A","A","A","A","A","A","A","A","A"],
["B","B","B","B","B","B","B","B","B"],
["C","C","C","C","C","C","C","C","C"],
["D","D","D","D","D","D","D","D","D"]]
colours = ["A", "B", "C", "D"]
moves = ["l", "l'", "r'", "r", "u", "U", "u'", "R'", "L", "R", "L'", "B'", "U'", "b", "B", "b'"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_5)
def test6(self):
exp_6 = [["E","Y","E","G","Y","Y","R","G","G"],
["Y","E","Y","R","E","E","E","R","R"],
["G","G","G","Y","R","R","E","E","E"],
["R","G","R","R","G","G","Y","Y","Y"]]
colours = ["R", "G", "Y", "E"]
moves = ["b", "l", "r", "u"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_6)
def test7(self):
exp_7 = [["E","R","R","R","L","R","R","R","U"],
["L","U","U","U","E","U","U","U","R"],
["U","L","L","L","R","L","L","L","E"],
["R","E","E","E","U","E","E","E","L"]]
colours = ["R", "U", "L", "E"]
moves = ["U", "B", "R", "L"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_7)
def test8(self):
exp_8 = [["W","W","D","A","W","W","S","A","A"],
["A","D","D","A","D","D","D","A","A"],
["S","S","S","S","S","W","A","A","S"],
["W","W","W","D","D","S","W","S","D"]]
colours = ["W", "A", "S", "D"]
moves = ["l", "r'", "U'", "u", "r'", "B", "l'", "b'"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_8)
def test9(self):
exp_9 = [["W","S","D","S","W","S","S","W","A"],
["D","W","A","A","D","A","D","W","A"],
["S","A","A","D","S","W","W","S","A"],
["W","D","D","W","S","D","A","S","D"]]
colours = ["W", "A", "S", "D"]
moves = ["B'", "R'", "L", "U'", "B", "r'", "l", "B'", "L'", "r'", "L", "U", "u'",
"U", "B'", "r", "L'", "R", "B", "r", "R'", "R", "U'", "U", "L", "r", "L",
"B'", "U", "B", "R", "R'", "R", "u'", "l", "R'", "R", "B", "R'", "U", "u",
"U", "u'", "B'", "r", "L'", "B'", "R'", "B'", "r", "R'", "r", "L", "R'",
"B", "u", "B'", "B", "L", "U", "B", "B", "L", "R", "B", "R", "u'", "R'",
"B", "u", "u'", "L'", "B", "R'", "l'", "U", "U'", "B", "r", "L'", "B", "r'",
"U", "R", "R'", "u'", "r", "R'", "u'", "r'", "L'", "R'", "r'", "U", "u'",
"B'", "U", "L'", "L'", "B"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_9)
def test10(self):
exp_10 = [["W","W","W","W","W","W","W","W","W"],
["A","A","A","A","A","A","A","A","A"],
["S","S","S","S","S","S","S","S","S"],
["D","D","D","D","D","D","D","D","D"]]
colours = ["W", "A", "S", "D"]
moves = ["L", "L", "L", "r'", "r'", "r'", "U", "U'", "U", "U'", "b", "b'", "b'", "b"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_10)
if __name__ == '__main__':
unittest.main()
Code
def pyraminx_puzzle(face_colours, moves):
move_position = "U": [[0, 0], [3, 8], [2, 4]],
"u": [[0, 0], [0, 1], [0, 2], [0, 3], [3, 8], [3, 3], [3, 7], [3, 6], [2, 4], [2, 6], [2, 5], [2, 1]],
"L": [[2, 0], [1, 8], [0, 4]],
"l": [[2, 0], [2, 1], [2, 2], [2, 3], [1, 8], [1, 3], [1, 7], [1, 6], [0, 4], [0, 6], [0, 5], [0, 1]],
"R": [[3, 0], [0, 8], [1, 4]],
"r": [[3, 0], [3, 1], [3, 2], [3, 3], [0, 3], [0, 8], [0, 7], [0, 6], [1, 4], [1, 6], [1, 5], [1, 1]],
"B": [[1, 0], [2, 8], [3, 4]],
"b": [[1, 0], [1, 1], [1, 2], [1, 3], [2, 8], [2, 3], [2, 7], [2, 6], [3, 4], [3, 6], [3, 5], [3, 1]]
puzzle = [[c for _ in range(9)] for c in face_colours]
for move in reversed(moves):
left = "'" not in move
move_colors = move: [puzzle[r][c] for r, c in move_position[move]] for move in move_position
current_color = shift_color(move_colors[move[0]], left)
for idx, pos in enumerate(move_position[move[0]]):
puzzle[pos[0]][pos[1]] = current_color[idx]
return puzzle
def shift_color(current_color, left):
i = len(current_color) // 3
if left:
return current_color[i:] + current_color[0:i]
return current_color[-i:] + current_color[:-i]
python python-3.x programming-challenge
Intro
You've mastered the Rubik's Cube and got bored solving it, so now you are looking for a new challenge. One puzzle similar to the Rubik's Cube caught your attention. It's called a Pyraminx puzzle, and is a triangular pyramid-shaped puzzle. The parts are arranged in a pyramidal pattern on each side, while the layers can be rotated with respect to each vertex, and the individual tips can be rotated as well. There are 4 faces on the Pyraminx. The puzzle should be held so that one face faces you and one face faces down, as in the image below. The four corners are then labeled U
(for up), R
(for right), L
(for left), and B
(for back). The front face thus contains the U
, R
, and L
corners.
Let's write down all possible moves for vertex U in the following notation:
- U - 120ð counterclockwise turn of topmost tip (assuming that we're looking at the pyraminx from the top, and vertex U is the topmost);
- U' - clockwise turn for the same tip;
- u - 120ð counterclockwise turn of two upper layer;
- u' - clockwise turn for the same layers.
For other vertices the moves can be described similarly
The first puzzle you bought wasn't assembled, so you get your professional pyraminx solver friend to assemble it. He does, and you wrote down all his moves notated as described above. Now the puzzle's faces have colors faceColors[0]
(front face), faceColors[1]
(bottom face), faceColors[2]
(left face), faceColors[3]
(right face). You want to know the initial state of the puzzle to repeat your friend's moves and see how he solved it.
Example
For face_colors = ['R', 'G', 'Y', 'O']
and moves = ["B", "b'", "u'", "R"]
, the output should be
pyraminxPuzzle(faceColors, moves) = [['Y', 'Y', 'Y', 'Y', 'R', 'R', 'R', 'R', 'G'],
['G', 'R', 'O', 'O', 'O', 'G', 'G', 'G', 'G'],
['Y', 'O', 'Y', 'G', 'O', 'O', 'G', 'G', 'Y'],
['R', 'O', 'O', 'R', 'O', 'Y', 'Y', 'R', 'R']]
Visual representation
Edit
As requested bij @Jan Kuijken a map for each face to the corresponding index
"""
L[2] : [ 4 | U[0] : [ 0, | R[3] : [ 8,
6, 5, 1 | 1, 2, 3, | 3, 7, 6,
8, 7, 3, 2, 0 ] | 4, 5, 6, 7, 8 ] | 0, 2, 1, 5, 4 ]
-----------------------------------------------------------------------------
B[1] : [ 8, 7, 6, 5, 4,
3, 2, 1,
0 ]
"""
Tests
import unittest
class TestPyraminx(unittest.TestCase):
def test1(self):
exp_1 = [["Y","Y","Y","Y","R","R","R","R","G"],
["G","R","O","O","O","G","G","G","G"],
["Y","O","Y","G","O","O","G","G","Y"],
["R","O","O","R","O","Y","Y","R","R"]]
colours = ["R", "G", "Y", "O"]
moves = ["B", "b'", "u'", "R"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_1)
def test2(self):
exp_2 = [["R","R","R","R","R","R","R","R","R"],
["G","G","G","G","G","G","G","G","G"],
["Y","Y","Y","Y","Y","Y","Y","Y","Y"],
["O","O","O","O","O","O","O","O","O"]]
colours = ["R", "G", "Y", "O"]
moves = ["l", "l'"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_2)
def test3(self):
exp_3 = [["Y","O","R","G","G","G","G","G","G"],
["G","O","G","Y","O","O","Y","Y","Y"],
["R","G","R","R","O","Y","Y","Y","Y"],
["R","R","R","R","O","O","O","O","R"]]
colours = ["R", "G", "Y", "O"]
moves = ["l", "l'", "u", "R", "U'", "L", "R'", "u'", "l'", "L'", "r"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_3)
def test4(self):
exp_4 = [["R","R","R","G","R","R","G","G","G"],
["G","O","G","G","O","O","O","G","G"],
["Y","Y","Y","Y","Y","Y","Y","Y","Y"],
["R","R","R","R","O","O","O","O","O"]]
colours = ["R", "G", "Y", "O"]
moves = ["r"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_4)
def test5(self):
exp_5 = [["A","A","A","A","A","A","A","A","A"],
["B","B","B","B","B","B","B","B","B"],
["C","C","C","C","C","C","C","C","C"],
["D","D","D","D","D","D","D","D","D"]]
colours = ["A", "B", "C", "D"]
moves = ["l", "l'", "r'", "r", "u", "U", "u'", "R'", "L", "R", "L'", "B'", "U'", "b", "B", "b'"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_5)
def test6(self):
exp_6 = [["E","Y","E","G","Y","Y","R","G","G"],
["Y","E","Y","R","E","E","E","R","R"],
["G","G","G","Y","R","R","E","E","E"],
["R","G","R","R","G","G","Y","Y","Y"]]
colours = ["R", "G", "Y", "E"]
moves = ["b", "l", "r", "u"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_6)
def test7(self):
exp_7 = [["E","R","R","R","L","R","R","R","U"],
["L","U","U","U","E","U","U","U","R"],
["U","L","L","L","R","L","L","L","E"],
["R","E","E","E","U","E","E","E","L"]]
colours = ["R", "U", "L", "E"]
moves = ["U", "B", "R", "L"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_7)
def test8(self):
exp_8 = [["W","W","D","A","W","W","S","A","A"],
["A","D","D","A","D","D","D","A","A"],
["S","S","S","S","S","W","A","A","S"],
["W","W","W","D","D","S","W","S","D"]]
colours = ["W", "A", "S", "D"]
moves = ["l", "r'", "U'", "u", "r'", "B", "l'", "b'"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_8)
def test9(self):
exp_9 = [["W","S","D","S","W","S","S","W","A"],
["D","W","A","A","D","A","D","W","A"],
["S","A","A","D","S","W","W","S","A"],
["W","D","D","W","S","D","A","S","D"]]
colours = ["W", "A", "S", "D"]
moves = ["B'", "R'", "L", "U'", "B", "r'", "l", "B'", "L'", "r'", "L", "U", "u'",
"U", "B'", "r", "L'", "R", "B", "r", "R'", "R", "U'", "U", "L", "r", "L",
"B'", "U", "B", "R", "R'", "R", "u'", "l", "R'", "R", "B", "R'", "U", "u",
"U", "u'", "B'", "r", "L'", "B'", "R'", "B'", "r", "R'", "r", "L", "R'",
"B", "u", "B'", "B", "L", "U", "B", "B", "L", "R", "B", "R", "u'", "R'",
"B", "u", "u'", "L'", "B", "R'", "l'", "U", "U'", "B", "r", "L'", "B", "r'",
"U", "R", "R'", "u'", "r", "R'", "u'", "r'", "L'", "R'", "r'", "U", "u'",
"B'", "U", "L'", "L'", "B"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_9)
def test10(self):
exp_10 = [["W","W","W","W","W","W","W","W","W"],
["A","A","A","A","A","A","A","A","A"],
["S","S","S","S","S","S","S","S","S"],
["D","D","D","D","D","D","D","D","D"]]
colours = ["W", "A", "S", "D"]
moves = ["L", "L", "L", "r'", "r'", "r'", "U", "U'", "U", "U'", "b", "b'", "b'", "b"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_10)
if __name__ == '__main__':
unittest.main()
Code
def pyraminx_puzzle(face_colours, moves):
move_position = "U": [[0, 0], [3, 8], [2, 4]],
"u": [[0, 0], [0, 1], [0, 2], [0, 3], [3, 8], [3, 3], [3, 7], [3, 6], [2, 4], [2, 6], [2, 5], [2, 1]],
"L": [[2, 0], [1, 8], [0, 4]],
"l": [[2, 0], [2, 1], [2, 2], [2, 3], [1, 8], [1, 3], [1, 7], [1, 6], [0, 4], [0, 6], [0, 5], [0, 1]],
"R": [[3, 0], [0, 8], [1, 4]],
"r": [[3, 0], [3, 1], [3, 2], [3, 3], [0, 3], [0, 8], [0, 7], [0, 6], [1, 4], [1, 6], [1, 5], [1, 1]],
"B": [[1, 0], [2, 8], [3, 4]],
"b": [[1, 0], [1, 1], [1, 2], [1, 3], [2, 8], [2, 3], [2, 7], [2, 6], [3, 4], [3, 6], [3, 5], [3, 1]]
puzzle = [[c for _ in range(9)] for c in face_colours]
for move in reversed(moves):
left = "'" not in move
move_colors = move: [puzzle[r][c] for r, c in move_position[move]] for move in move_position
current_color = shift_color(move_colors[move[0]], left)
for idx, pos in enumerate(move_position[move[0]]):
puzzle[pos[0]][pos[1]] = current_color[idx]
return puzzle
def shift_color(current_color, left):
i = len(current_color) // 3
if left:
return current_color[i:] + current_color[0:i]
return current_color[-i:] + current_color[:-i]
python python-3.x programming-challenge
edited Jul 10 at 22:22
asked Jun 26 at 13:54
Ludisposed
5,68621656
5,68621656
Nice problem, could you also provide a drawing which maps thex
index offaceColours[x]
to the actual positions on the faces? (for an easier understanding)
â Jan Kuiken
Jun 26 at 17:00
@JanKuiken I've edited that part in, I hope it is more clear now.
â Ludisposed
Jun 27 at 11:24
Why is the right face numbered so weirdly?
â 200_success
Jul 10 at 20:58
You meanimport unittest
and notimport unittests
, right?
â 200_success
Jul 10 at 21:11
@200_succes Thnx, was a typo edited it. The right face is weird indeed, but if you see theR
as the top, and go topdown left to right it makes sense.
â Ludisposed
Jul 10 at 22:25
add a comment |Â
Nice problem, could you also provide a drawing which maps thex
index offaceColours[x]
to the actual positions on the faces? (for an easier understanding)
â Jan Kuiken
Jun 26 at 17:00
@JanKuiken I've edited that part in, I hope it is more clear now.
â Ludisposed
Jun 27 at 11:24
Why is the right face numbered so weirdly?
â 200_success
Jul 10 at 20:58
You meanimport unittest
and notimport unittests
, right?
â 200_success
Jul 10 at 21:11
@200_succes Thnx, was a typo edited it. The right face is weird indeed, but if you see theR
as the top, and go topdown left to right it makes sense.
â Ludisposed
Jul 10 at 22:25
Nice problem, could you also provide a drawing which maps the
x
index of faceColours[x]
to the actual positions on the faces? (for an easier understanding)â Jan Kuiken
Jun 26 at 17:00
Nice problem, could you also provide a drawing which maps the
x
index of faceColours[x]
to the actual positions on the faces? (for an easier understanding)â Jan Kuiken
Jun 26 at 17:00
@JanKuiken I've edited that part in, I hope it is more clear now.
â Ludisposed
Jun 27 at 11:24
@JanKuiken I've edited that part in, I hope it is more clear now.
â Ludisposed
Jun 27 at 11:24
Why is the right face numbered so weirdly?
â 200_success
Jul 10 at 20:58
Why is the right face numbered so weirdly?
â 200_success
Jul 10 at 20:58
You mean
import unittest
and not import unittests
, right?â 200_success
Jul 10 at 21:11
You mean
import unittest
and not import unittests
, right?â 200_success
Jul 10 at 21:11
@200_succes Thnx, was a typo edited it. The right face is weird indeed, but if you see the
R
as the top, and go topdown left to right it makes sense.â Ludisposed
Jul 10 at 22:25
@200_succes Thnx, was a typo edited it. The right face is weird indeed, but if you see the
R
as the top, and go topdown left to right it makes sense.â Ludisposed
Jul 10 at 22:25
add a comment |Â
1 Answer
1
active
oldest
votes
up vote
2
down vote
accepted
Trivial observations
You have a parameter named face_colours
, and another parameter named current_color
, as well as a function named shift_color
. Pick one spelling convention and stick with it. American English generally prevails in the programming world, so I recommend that.
In move_position
, the values are lists of lists. Since each coordinate is immutable and always has length 2, you should prefer to write them as tuples.
puzzle = [[c for _ in range(9)] for c in face_colours]
would be better written as
puzzle = [[c] * 9 for c in face_colours]
for idx, pos in enumerate(move_position[move[0]]):
puzzle[pos[0]][pos[1]] = current_color[idx]
would be better written as
for (r, c), color in zip(move_position[move[0]], current_color):
puzzle[r][c] = color
Bug
You have an error that causes test6
, test8
, and test9
to fail. The cause is that in move_position["r"]
, coordinates [0, 8]
and [0, 3]
are swapped.
A contributing factor to this bug is the lack of regularity and organization in the lookup table. If the table had been written like thisâ¦
move_position =
"U": [(0, 0), (3, 8), (2, 4)],
"u": [(0, 0), (0, 1), (0, 2), (0, 3),
(3, 8), (3, 3), (3, 7), (3, 6),
(2, 4), (2, 6), (2, 5), (2, 1)],
"L": [(2, 0), (1, 8), (0, 4)],
"l": [(2, 0), (2, 1), (2, 2), (2, 3),
(1, 8), (1, 3), (1, 7), (1, 6),
(0, 4), (0, 6), (0, 5), (0, 1)],
"R": [(3, 0), (0, 8), (1, 4)],
"r": [(3, 0), (3, 1), (3, 2), (3, 3),
(0, 3), (0, 8), (0, 7), (0, 6),
(1, 4), (1, 6), (1, 5), (1, 1)],
"B": [(1, 0), (2, 8), (3, 4)],
"b": [(1, 0), (1, 1), (1, 2), (1, 3),
(2, 8), (2, 3), (2, 7), (2, 6),
(3, 4), (3, 6), (3, 5), (3, 1)],
⦠then two patterns would have been more apparent:
- Each lowercase move consists of 12 tiles being permuted: 4 tiles on each of 3 faces.
- Each uppercase move consists of 3 tiles being permuted. Furthermore, the three tiles involved in the uppercase move are a subset of the corresponding lowercase move: namely, the first coordinates of each row of the corresponding lowercase move. The pattern was broken between
move_positions["R"]
andmove_positions["r"]
.
Therefore, I suggest that the hard-coded data in the lookup table should be minimized. Rather, the code can automatically produce the uppercase moves, given the lowercase moves.
Then, if we are going to programmatically extend the lookup table, we might as well go a step further and programmatically include both clockwise and counterclockwise moves in the lookup table.
Division of labour
It's not obvious what the shift_color
function does. I would prefer to see the helpers designed such that pyraminx_puzzle
is entirely straightforward:
def pyraminx_puzzle(face_colors, moves):
puzzle = [[c] * 9 for c in face_colors]
for move in reversed(moves):
MOVES[move](puzzle)
return puzzle
Suggested solution
Most of the work goes into building the table of moves. Applying each move, then, becomes a simple matter of performing a lookup and doing some unconditional swaps.
def make_moves(moves):
"""
Extrapolate from a table of moves to build a table that includes
clockwise moves, counterclockwise moves, and corner-only moves.
Each resulting key will be the name of a move (e.g. "U'"), and each
resulting value will be a function that mutates a puzzle.
"""
def make_move(new_positions, old_positions):
def move(puzzle):
old_colors = [puzzle[r][c] for r, c in old_positions]
for (r, c), color in zip(new_positions, old_colors):
puzzle[r][c] = color
return move
for turn, pos in moves.items():
# Clockwise variant
yield turn, make_move(pos[-4:] + pos[:-4], pos)
# Counterclockwise variant
yield turn + "'", make_move(pos[4:] + pos[:4], pos)
# Corner-only clockwise variant
yield turn.upper(), make_move([pos[i] for i in range(-4, 8, 4)], pos[::4])
# Corner-only counterclockwise variant
yield turn.upper() + "'", make_move([pos[i] for i in range(-8, 4, 4)], pos[::4])
MOVES = dict(make_moves(
"u": [(0, 0), (0, 1), (0, 2), (0, 3),
(3, 8), (3, 3), (3, 7), (3, 6),
(2, 4), (2, 6), (2, 5), (2, 1)],
"l": [(2, 0), (2, 1), (2, 2), (2, 3),
(1, 8), (1, 3), (1, 7), (1, 6),
(0, 4), (0, 6), (0, 5), (0, 1)],
"r": [(3, 0), (3, 1), (3, 2), (3, 3),
(0, 8), (0, 3), (0, 7), (0, 6),
(1, 4), (1, 6), (1, 5), (1, 1)],
"b": [(1, 0), (1, 1), (1, 2), (1, 3),
(2, 8), (2, 3), (2, 7), (2, 6),
(3, 4), (3, 6), (3, 5), (3, 1)],
))
def pyraminx_puzzle(face_colors, moves):
puzzle = [[c] * 9 for c in face_colors]
for move in reversed(moves):
MOVES[move](puzzle)
return puzzle
Yes that bug is pretty bad, i fixed it... but somehow ended up in here anyway, sorry for that. I like the way youmove
, it's much clearer now.
â Ludisposed
Jul 11 at 6:33
add a comment |Â
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
2
down vote
accepted
Trivial observations
You have a parameter named face_colours
, and another parameter named current_color
, as well as a function named shift_color
. Pick one spelling convention and stick with it. American English generally prevails in the programming world, so I recommend that.
In move_position
, the values are lists of lists. Since each coordinate is immutable and always has length 2, you should prefer to write them as tuples.
puzzle = [[c for _ in range(9)] for c in face_colours]
would be better written as
puzzle = [[c] * 9 for c in face_colours]
for idx, pos in enumerate(move_position[move[0]]):
puzzle[pos[0]][pos[1]] = current_color[idx]
would be better written as
for (r, c), color in zip(move_position[move[0]], current_color):
puzzle[r][c] = color
Bug
You have an error that causes test6
, test8
, and test9
to fail. The cause is that in move_position["r"]
, coordinates [0, 8]
and [0, 3]
are swapped.
A contributing factor to this bug is the lack of regularity and organization in the lookup table. If the table had been written like thisâ¦
move_position =
"U": [(0, 0), (3, 8), (2, 4)],
"u": [(0, 0), (0, 1), (0, 2), (0, 3),
(3, 8), (3, 3), (3, 7), (3, 6),
(2, 4), (2, 6), (2, 5), (2, 1)],
"L": [(2, 0), (1, 8), (0, 4)],
"l": [(2, 0), (2, 1), (2, 2), (2, 3),
(1, 8), (1, 3), (1, 7), (1, 6),
(0, 4), (0, 6), (0, 5), (0, 1)],
"R": [(3, 0), (0, 8), (1, 4)],
"r": [(3, 0), (3, 1), (3, 2), (3, 3),
(0, 3), (0, 8), (0, 7), (0, 6),
(1, 4), (1, 6), (1, 5), (1, 1)],
"B": [(1, 0), (2, 8), (3, 4)],
"b": [(1, 0), (1, 1), (1, 2), (1, 3),
(2, 8), (2, 3), (2, 7), (2, 6),
(3, 4), (3, 6), (3, 5), (3, 1)],
⦠then two patterns would have been more apparent:
- Each lowercase move consists of 12 tiles being permuted: 4 tiles on each of 3 faces.
- Each uppercase move consists of 3 tiles being permuted. Furthermore, the three tiles involved in the uppercase move are a subset of the corresponding lowercase move: namely, the first coordinates of each row of the corresponding lowercase move. The pattern was broken between
move_positions["R"]
andmove_positions["r"]
.
Therefore, I suggest that the hard-coded data in the lookup table should be minimized. Rather, the code can automatically produce the uppercase moves, given the lowercase moves.
Then, if we are going to programmatically extend the lookup table, we might as well go a step further and programmatically include both clockwise and counterclockwise moves in the lookup table.
Division of labour
It's not obvious what the shift_color
function does. I would prefer to see the helpers designed such that pyraminx_puzzle
is entirely straightforward:
def pyraminx_puzzle(face_colors, moves):
puzzle = [[c] * 9 for c in face_colors]
for move in reversed(moves):
MOVES[move](puzzle)
return puzzle
Suggested solution
Most of the work goes into building the table of moves. Applying each move, then, becomes a simple matter of performing a lookup and doing some unconditional swaps.
def make_moves(moves):
"""
Extrapolate from a table of moves to build a table that includes
clockwise moves, counterclockwise moves, and corner-only moves.
Each resulting key will be the name of a move (e.g. "U'"), and each
resulting value will be a function that mutates a puzzle.
"""
def make_move(new_positions, old_positions):
def move(puzzle):
old_colors = [puzzle[r][c] for r, c in old_positions]
for (r, c), color in zip(new_positions, old_colors):
puzzle[r][c] = color
return move
for turn, pos in moves.items():
# Clockwise variant
yield turn, make_move(pos[-4:] + pos[:-4], pos)
# Counterclockwise variant
yield turn + "'", make_move(pos[4:] + pos[:4], pos)
# Corner-only clockwise variant
yield turn.upper(), make_move([pos[i] for i in range(-4, 8, 4)], pos[::4])
# Corner-only counterclockwise variant
yield turn.upper() + "'", make_move([pos[i] for i in range(-8, 4, 4)], pos[::4])
MOVES = dict(make_moves(
"u": [(0, 0), (0, 1), (0, 2), (0, 3),
(3, 8), (3, 3), (3, 7), (3, 6),
(2, 4), (2, 6), (2, 5), (2, 1)],
"l": [(2, 0), (2, 1), (2, 2), (2, 3),
(1, 8), (1, 3), (1, 7), (1, 6),
(0, 4), (0, 6), (0, 5), (0, 1)],
"r": [(3, 0), (3, 1), (3, 2), (3, 3),
(0, 8), (0, 3), (0, 7), (0, 6),
(1, 4), (1, 6), (1, 5), (1, 1)],
"b": [(1, 0), (1, 1), (1, 2), (1, 3),
(2, 8), (2, 3), (2, 7), (2, 6),
(3, 4), (3, 6), (3, 5), (3, 1)],
))
def pyraminx_puzzle(face_colors, moves):
puzzle = [[c] * 9 for c in face_colors]
for move in reversed(moves):
MOVES[move](puzzle)
return puzzle
Yes that bug is pretty bad, i fixed it... but somehow ended up in here anyway, sorry for that. I like the way youmove
, it's much clearer now.
â Ludisposed
Jul 11 at 6:33
add a comment |Â
up vote
2
down vote
accepted
Trivial observations
You have a parameter named face_colours
, and another parameter named current_color
, as well as a function named shift_color
. Pick one spelling convention and stick with it. American English generally prevails in the programming world, so I recommend that.
In move_position
, the values are lists of lists. Since each coordinate is immutable and always has length 2, you should prefer to write them as tuples.
puzzle = [[c for _ in range(9)] for c in face_colours]
would be better written as
puzzle = [[c] * 9 for c in face_colours]
for idx, pos in enumerate(move_position[move[0]]):
puzzle[pos[0]][pos[1]] = current_color[idx]
would be better written as
for (r, c), color in zip(move_position[move[0]], current_color):
puzzle[r][c] = color
Bug
You have an error that causes test6
, test8
, and test9
to fail. The cause is that in move_position["r"]
, coordinates [0, 8]
and [0, 3]
are swapped.
A contributing factor to this bug is the lack of regularity and organization in the lookup table. If the table had been written like thisâ¦
move_position =
"U": [(0, 0), (3, 8), (2, 4)],
"u": [(0, 0), (0, 1), (0, 2), (0, 3),
(3, 8), (3, 3), (3, 7), (3, 6),
(2, 4), (2, 6), (2, 5), (2, 1)],
"L": [(2, 0), (1, 8), (0, 4)],
"l": [(2, 0), (2, 1), (2, 2), (2, 3),
(1, 8), (1, 3), (1, 7), (1, 6),
(0, 4), (0, 6), (0, 5), (0, 1)],
"R": [(3, 0), (0, 8), (1, 4)],
"r": [(3, 0), (3, 1), (3, 2), (3, 3),
(0, 3), (0, 8), (0, 7), (0, 6),
(1, 4), (1, 6), (1, 5), (1, 1)],
"B": [(1, 0), (2, 8), (3, 4)],
"b": [(1, 0), (1, 1), (1, 2), (1, 3),
(2, 8), (2, 3), (2, 7), (2, 6),
(3, 4), (3, 6), (3, 5), (3, 1)],
⦠then two patterns would have been more apparent:
- Each lowercase move consists of 12 tiles being permuted: 4 tiles on each of 3 faces.
- Each uppercase move consists of 3 tiles being permuted. Furthermore, the three tiles involved in the uppercase move are a subset of the corresponding lowercase move: namely, the first coordinates of each row of the corresponding lowercase move. The pattern was broken between
move_positions["R"]
andmove_positions["r"]
.
Therefore, I suggest that the hard-coded data in the lookup table should be minimized. Rather, the code can automatically produce the uppercase moves, given the lowercase moves.
Then, if we are going to programmatically extend the lookup table, we might as well go a step further and programmatically include both clockwise and counterclockwise moves in the lookup table.
Division of labour
It's not obvious what the shift_color
function does. I would prefer to see the helpers designed such that pyraminx_puzzle
is entirely straightforward:
def pyraminx_puzzle(face_colors, moves):
puzzle = [[c] * 9 for c in face_colors]
for move in reversed(moves):
MOVES[move](puzzle)
return puzzle
Suggested solution
Most of the work goes into building the table of moves. Applying each move, then, becomes a simple matter of performing a lookup and doing some unconditional swaps.
def make_moves(moves):
"""
Extrapolate from a table of moves to build a table that includes
clockwise moves, counterclockwise moves, and corner-only moves.
Each resulting key will be the name of a move (e.g. "U'"), and each
resulting value will be a function that mutates a puzzle.
"""
def make_move(new_positions, old_positions):
def move(puzzle):
old_colors = [puzzle[r][c] for r, c in old_positions]
for (r, c), color in zip(new_positions, old_colors):
puzzle[r][c] = color
return move
for turn, pos in moves.items():
# Clockwise variant
yield turn, make_move(pos[-4:] + pos[:-4], pos)
# Counterclockwise variant
yield turn + "'", make_move(pos[4:] + pos[:4], pos)
# Corner-only clockwise variant
yield turn.upper(), make_move([pos[i] for i in range(-4, 8, 4)], pos[::4])
# Corner-only counterclockwise variant
yield turn.upper() + "'", make_move([pos[i] for i in range(-8, 4, 4)], pos[::4])
MOVES = dict(make_moves(
"u": [(0, 0), (0, 1), (0, 2), (0, 3),
(3, 8), (3, 3), (3, 7), (3, 6),
(2, 4), (2, 6), (2, 5), (2, 1)],
"l": [(2, 0), (2, 1), (2, 2), (2, 3),
(1, 8), (1, 3), (1, 7), (1, 6),
(0, 4), (0, 6), (0, 5), (0, 1)],
"r": [(3, 0), (3, 1), (3, 2), (3, 3),
(0, 8), (0, 3), (0, 7), (0, 6),
(1, 4), (1, 6), (1, 5), (1, 1)],
"b": [(1, 0), (1, 1), (1, 2), (1, 3),
(2, 8), (2, 3), (2, 7), (2, 6),
(3, 4), (3, 6), (3, 5), (3, 1)],
))
def pyraminx_puzzle(face_colors, moves):
puzzle = [[c] * 9 for c in face_colors]
for move in reversed(moves):
MOVES[move](puzzle)
return puzzle
Yes that bug is pretty bad, i fixed it... but somehow ended up in here anyway, sorry for that. I like the way youmove
, it's much clearer now.
â Ludisposed
Jul 11 at 6:33
add a comment |Â
up vote
2
down vote
accepted
up vote
2
down vote
accepted
Trivial observations
You have a parameter named face_colours
, and another parameter named current_color
, as well as a function named shift_color
. Pick one spelling convention and stick with it. American English generally prevails in the programming world, so I recommend that.
In move_position
, the values are lists of lists. Since each coordinate is immutable and always has length 2, you should prefer to write them as tuples.
puzzle = [[c for _ in range(9)] for c in face_colours]
would be better written as
puzzle = [[c] * 9 for c in face_colours]
for idx, pos in enumerate(move_position[move[0]]):
puzzle[pos[0]][pos[1]] = current_color[idx]
would be better written as
for (r, c), color in zip(move_position[move[0]], current_color):
puzzle[r][c] = color
Bug
You have an error that causes test6
, test8
, and test9
to fail. The cause is that in move_position["r"]
, coordinates [0, 8]
and [0, 3]
are swapped.
A contributing factor to this bug is the lack of regularity and organization in the lookup table. If the table had been written like thisâ¦
move_position =
"U": [(0, 0), (3, 8), (2, 4)],
"u": [(0, 0), (0, 1), (0, 2), (0, 3),
(3, 8), (3, 3), (3, 7), (3, 6),
(2, 4), (2, 6), (2, 5), (2, 1)],
"L": [(2, 0), (1, 8), (0, 4)],
"l": [(2, 0), (2, 1), (2, 2), (2, 3),
(1, 8), (1, 3), (1, 7), (1, 6),
(0, 4), (0, 6), (0, 5), (0, 1)],
"R": [(3, 0), (0, 8), (1, 4)],
"r": [(3, 0), (3, 1), (3, 2), (3, 3),
(0, 3), (0, 8), (0, 7), (0, 6),
(1, 4), (1, 6), (1, 5), (1, 1)],
"B": [(1, 0), (2, 8), (3, 4)],
"b": [(1, 0), (1, 1), (1, 2), (1, 3),
(2, 8), (2, 3), (2, 7), (2, 6),
(3, 4), (3, 6), (3, 5), (3, 1)],
⦠then two patterns would have been more apparent:
- Each lowercase move consists of 12 tiles being permuted: 4 tiles on each of 3 faces.
- Each uppercase move consists of 3 tiles being permuted. Furthermore, the three tiles involved in the uppercase move are a subset of the corresponding lowercase move: namely, the first coordinates of each row of the corresponding lowercase move. The pattern was broken between
move_positions["R"]
andmove_positions["r"]
.
Therefore, I suggest that the hard-coded data in the lookup table should be minimized. Rather, the code can automatically produce the uppercase moves, given the lowercase moves.
Then, if we are going to programmatically extend the lookup table, we might as well go a step further and programmatically include both clockwise and counterclockwise moves in the lookup table.
Division of labour
It's not obvious what the shift_color
function does. I would prefer to see the helpers designed such that pyraminx_puzzle
is entirely straightforward:
def pyraminx_puzzle(face_colors, moves):
puzzle = [[c] * 9 for c in face_colors]
for move in reversed(moves):
MOVES[move](puzzle)
return puzzle
Suggested solution
Most of the work goes into building the table of moves. Applying each move, then, becomes a simple matter of performing a lookup and doing some unconditional swaps.
def make_moves(moves):
"""
Extrapolate from a table of moves to build a table that includes
clockwise moves, counterclockwise moves, and corner-only moves.
Each resulting key will be the name of a move (e.g. "U'"), and each
resulting value will be a function that mutates a puzzle.
"""
def make_move(new_positions, old_positions):
def move(puzzle):
old_colors = [puzzle[r][c] for r, c in old_positions]
for (r, c), color in zip(new_positions, old_colors):
puzzle[r][c] = color
return move
for turn, pos in moves.items():
# Clockwise variant
yield turn, make_move(pos[-4:] + pos[:-4], pos)
# Counterclockwise variant
yield turn + "'", make_move(pos[4:] + pos[:4], pos)
# Corner-only clockwise variant
yield turn.upper(), make_move([pos[i] for i in range(-4, 8, 4)], pos[::4])
# Corner-only counterclockwise variant
yield turn.upper() + "'", make_move([pos[i] for i in range(-8, 4, 4)], pos[::4])
MOVES = dict(make_moves(
"u": [(0, 0), (0, 1), (0, 2), (0, 3),
(3, 8), (3, 3), (3, 7), (3, 6),
(2, 4), (2, 6), (2, 5), (2, 1)],
"l": [(2, 0), (2, 1), (2, 2), (2, 3),
(1, 8), (1, 3), (1, 7), (1, 6),
(0, 4), (0, 6), (0, 5), (0, 1)],
"r": [(3, 0), (3, 1), (3, 2), (3, 3),
(0, 8), (0, 3), (0, 7), (0, 6),
(1, 4), (1, 6), (1, 5), (1, 1)],
"b": [(1, 0), (1, 1), (1, 2), (1, 3),
(2, 8), (2, 3), (2, 7), (2, 6),
(3, 4), (3, 6), (3, 5), (3, 1)],
))
def pyraminx_puzzle(face_colors, moves):
puzzle = [[c] * 9 for c in face_colors]
for move in reversed(moves):
MOVES[move](puzzle)
return puzzle
Trivial observations
You have a parameter named face_colours
, and another parameter named current_color
, as well as a function named shift_color
. Pick one spelling convention and stick with it. American English generally prevails in the programming world, so I recommend that.
In move_position
, the values are lists of lists. Since each coordinate is immutable and always has length 2, you should prefer to write them as tuples.
puzzle = [[c for _ in range(9)] for c in face_colours]
would be better written as
puzzle = [[c] * 9 for c in face_colours]
for idx, pos in enumerate(move_position[move[0]]):
puzzle[pos[0]][pos[1]] = current_color[idx]
would be better written as
for (r, c), color in zip(move_position[move[0]], current_color):
puzzle[r][c] = color
Bug
You have an error that causes test6
, test8
, and test9
to fail. The cause is that in move_position["r"]
, coordinates [0, 8]
and [0, 3]
are swapped.
A contributing factor to this bug is the lack of regularity and organization in the lookup table. If the table had been written like thisâ¦
move_position =
"U": [(0, 0), (3, 8), (2, 4)],
"u": [(0, 0), (0, 1), (0, 2), (0, 3),
(3, 8), (3, 3), (3, 7), (3, 6),
(2, 4), (2, 6), (2, 5), (2, 1)],
"L": [(2, 0), (1, 8), (0, 4)],
"l": [(2, 0), (2, 1), (2, 2), (2, 3),
(1, 8), (1, 3), (1, 7), (1, 6),
(0, 4), (0, 6), (0, 5), (0, 1)],
"R": [(3, 0), (0, 8), (1, 4)],
"r": [(3, 0), (3, 1), (3, 2), (3, 3),
(0, 3), (0, 8), (0, 7), (0, 6),
(1, 4), (1, 6), (1, 5), (1, 1)],
"B": [(1, 0), (2, 8), (3, 4)],
"b": [(1, 0), (1, 1), (1, 2), (1, 3),
(2, 8), (2, 3), (2, 7), (2, 6),
(3, 4), (3, 6), (3, 5), (3, 1)],
⦠then two patterns would have been more apparent:
- Each lowercase move consists of 12 tiles being permuted: 4 tiles on each of 3 faces.
- Each uppercase move consists of 3 tiles being permuted. Furthermore, the three tiles involved in the uppercase move are a subset of the corresponding lowercase move: namely, the first coordinates of each row of the corresponding lowercase move. The pattern was broken between
move_positions["R"]
andmove_positions["r"]
.
Therefore, I suggest that the hard-coded data in the lookup table should be minimized. Rather, the code can automatically produce the uppercase moves, given the lowercase moves.
Then, if we are going to programmatically extend the lookup table, we might as well go a step further and programmatically include both clockwise and counterclockwise moves in the lookup table.
Division of labour
It's not obvious what the shift_color
function does. I would prefer to see the helpers designed such that pyraminx_puzzle
is entirely straightforward:
def pyraminx_puzzle(face_colors, moves):
puzzle = [[c] * 9 for c in face_colors]
for move in reversed(moves):
MOVES[move](puzzle)
return puzzle
Suggested solution
Most of the work goes into building the table of moves. Applying each move, then, becomes a simple matter of performing a lookup and doing some unconditional swaps.
def make_moves(moves):
"""
Extrapolate from a table of moves to build a table that includes
clockwise moves, counterclockwise moves, and corner-only moves.
Each resulting key will be the name of a move (e.g. "U'"), and each
resulting value will be a function that mutates a puzzle.
"""
def make_move(new_positions, old_positions):
def move(puzzle):
old_colors = [puzzle[r][c] for r, c in old_positions]
for (r, c), color in zip(new_positions, old_colors):
puzzle[r][c] = color
return move
for turn, pos in moves.items():
# Clockwise variant
yield turn, make_move(pos[-4:] + pos[:-4], pos)
# Counterclockwise variant
yield turn + "'", make_move(pos[4:] + pos[:4], pos)
# Corner-only clockwise variant
yield turn.upper(), make_move([pos[i] for i in range(-4, 8, 4)], pos[::4])
# Corner-only counterclockwise variant
yield turn.upper() + "'", make_move([pos[i] for i in range(-8, 4, 4)], pos[::4])
MOVES = dict(make_moves(
"u": [(0, 0), (0, 1), (0, 2), (0, 3),
(3, 8), (3, 3), (3, 7), (3, 6),
(2, 4), (2, 6), (2, 5), (2, 1)],
"l": [(2, 0), (2, 1), (2, 2), (2, 3),
(1, 8), (1, 3), (1, 7), (1, 6),
(0, 4), (0, 6), (0, 5), (0, 1)],
"r": [(3, 0), (3, 1), (3, 2), (3, 3),
(0, 8), (0, 3), (0, 7), (0, 6),
(1, 4), (1, 6), (1, 5), (1, 1)],
"b": [(1, 0), (1, 1), (1, 2), (1, 3),
(2, 8), (2, 3), (2, 7), (2, 6),
(3, 4), (3, 6), (3, 5), (3, 1)],
))
def pyraminx_puzzle(face_colors, moves):
puzzle = [[c] * 9 for c in face_colors]
for move in reversed(moves):
MOVES[move](puzzle)
return puzzle
answered Jul 11 at 4:04
200_success
123k14143399
123k14143399
Yes that bug is pretty bad, i fixed it... but somehow ended up in here anyway, sorry for that. I like the way youmove
, it's much clearer now.
â Ludisposed
Jul 11 at 6:33
add a comment |Â
Yes that bug is pretty bad, i fixed it... but somehow ended up in here anyway, sorry for that. I like the way youmove
, it's much clearer now.
â Ludisposed
Jul 11 at 6:33
Yes that bug is pretty bad, i fixed it... but somehow ended up in here anyway, sorry for that. I like the way you
move
, it's much clearer now.â Ludisposed
Jul 11 at 6:33
Yes that bug is pretty bad, i fixed it... but somehow ended up in here anyway, sorry for that. I like the way you
move
, it's much clearer now.â Ludisposed
Jul 11 at 6:33
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f197287%2fcodefights-pyraminx-puzzle%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Nice problem, could you also provide a drawing which maps the
x
index offaceColours[x]
to the actual positions on the faces? (for an easier understanding)â Jan Kuiken
Jun 26 at 17:00
@JanKuiken I've edited that part in, I hope it is more clear now.
â Ludisposed
Jun 27 at 11:24
Why is the right face numbered so weirdly?
â 200_success
Jul 10 at 20:58
You mean
import unittest
and notimport unittests
, right?â 200_success
Jul 10 at 21:11
@200_succes Thnx, was a typo edited it. The right face is weird indeed, but if you see the
R
as the top, and go topdown left to right it makes sense.â Ludisposed
Jul 10 at 22:25