# Cycling with Rubik's

While idly twisting my Rubik's cube around, my son noticed that it kept going back to the solved state. I'm pretty sure he thought this was some sort of voodoo magic at first, but I explained that if you keep repeating the same sequence of moves, it will always return to its original state. Eventually.

Of course, being a kid, he had to try it out for himself and picked a "random" sequence he thought would be tricky. He lost track after ten repeats or so, and asked me how many times he would have to repeat it. Not knowing the sequence he was using, I told him I didn't know, but that we could write a program to find out.

This is where you come in. Of course, I could just whip something up, but he'd like to type it in himself. He isn't a very fast typist yet, though, so I need the shortest program possible.

### Objective

Given a sequence of turns, output the fewest number of times it must be performed to return the cube to its original state. This is code golf, so least bytes wins. You can write a program or function, and all the other usual defaults apply.

### Input

Input is a sequence of moves, taken as a string, list, or other format suitable for your language. Feel free to use a separator (or not) between moves if in string form.

There are six "basic" moves that must be taken into account, along with their inverses:

R - Turn the right face clockwise
L - Turn the left face clockwise
U - Turn the up (top) face clockwise
D - Turn the down (bottom) face clockwise
F - Turn the front face clockwise
B - Turn the back face clockwise


The inverses are represented by adding a prime mark ' after the letter. This indicates you turn that face counterclockwise, so F' turns the front face counterclockwise, and F F' would return it to the original state right away.

For the interested, this challenge is using a limited set of Singmaster Notation. Ruwix has some nice animations if you'd like to see it in action.

### Output

Output is simply the minimum number of times the input sequence must be performed.

### Examples

Input                Output

FF'               ->      1
R                 ->      4
RUR'U'            ->      6
LLUUFFUURRUU      ->     12
LUFFRDRBF         ->     56
LF                ->    105
UFFR'DBBRL'       ->    120
FRBL              ->    315


Here's a (quite naive) solver to compare your answers to, written in Java. It also accepts 2 for double moves (so the fourth case is equivalent to L2U2F2U2R2U2).

import java.util.ArrayList;
import java.util.List;

public class CycleCounter{

public static void main(String[] args){
int[] cube = new int[54];
for(int i=0;i<54;i++)
cube[i] = i;

String test = args.length > 0 ? args[0] : "RUR'U'";
List<Rotation> steps = parse(test);
System.out.println(steps.toString());

int count = 0;
do{
for(Rotation step : steps)
cube = step.getRotated(cube);
count++;
}while(!isSorted(cube));

System.out.println("Cycle length for " + test + " is " + count);
}

static List<Rotation> parse(String in){
List<Rotation> steps = new ArrayList<Rotation>();
for(char c : in.toUpperCase().toCharArray())
switch(c){
case '\'':
case '2':
break;
}
return steps;
}

static boolean isSorted(int[] in){for(int i=0;i<in.length-1;i++)if(in[i]>in[i+1])return false;return true;}

enum Rotation{
R(new int[]{-1,-1,42,-1,-1,39,-1,-1,36, -1,-1,2,-1,-1,5,-1,-1,8, 20,23,26,19,-1,25,18,21,24, -1,-1,11,-1,-1,14,-1,-1,17, 35,-1,-1,32,-1,-1,29,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1}),
L(new int[]{9,-1,-1,12,-1,-1,15,-1,-1, 27,-1,-1,30,-1,-1,33,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1, 44,-1,-1,41,-1,-1,38,-1,-1, -1,-1,6,-1,-1,3,-1,-1,0, 47,50,53,46,-1,52,45,48,51}),
U(new int[]{2,5,8,1,-1,7,0,3,6, 45,46,47,-1,-1,-1,-1,-1,-1, 9,10,11,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1, 18,19,20,-1,-1,-1,-1,-1,-1, 36,37,38,-1,-1,-1,-1,-1,-1}),
D(new int[]{-1,-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,24,25,26, -1,-1,-1,-1,-1,-1,42,43,44, 29,32,35,28,-1,34,27,30,33, -1,-1,-1,-1,-1,-1,51,52,53, -1,-1,-1,-1,-1,-1,15,16,17}),
F(new int[]{-1,-1,-1,-1,-1,-1,18,21,24, 11,14,17,10,-1,16,9,12,15, 29,-1,-1,28,-1,-1,27,-1,-1, 47,50,53,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,8,-1,-1,7,-1,-1,6}),
B(new int[]{51,48,45,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,0,-1,-1,1,-1,-1,2, -1,-1,-1,-1,-1,-1,26,23,20, 38,41,44,37,-1,43,36,39,42, 33,-1,-1,34,-1,-1,35,-1,-1});

private final int[] moves;
Rotation(int[] moves){
this.moves = moves;
}

public int[] getRotated(int[] cube){
int[] newCube = new int[54];
for(int i=0;i<54;i++)
if(moves[i]<0)
newCube[i] = cube[i];
else
newCube[moves[i]] = cube[i];
return newCube;
}
}
}

• "clockwise" means "clockwise when you're facing it" I assume? Feb 3, 2016 at 20:49
• @msh210 Correct. Feb 3, 2016 at 20:51
• On a point of pedantry, I think you should make it explicit that you want the smallest number which suffices. Otherwise I could just output the size of the group and cite Lagrange's theorem... Feb 3, 2016 at 21:31
• @PeterTaylor Pedantry accepted. Feb 3, 2016 at 21:41
• I may offer a 500 point bounty for a solution in Shuffle. Not sure yet. Feb 3, 2016 at 22:32

# Pyth, 66 63 bytes

l.uum.rW}Hdd@_sm_B.iFP.>c3Zk3xZHG_r_Xz\'\39Nf!s}RTcZ2y=Z"UDLRFB


Try it online: Demonstration or Test Suite. Notice that the program is kinda slow and the online compiler is not able to compute the answer for RU2D'BD'. But be assured, that it can compute it on my laptop in about 12 seconds.

The program (accidentally) also accepts 2 for double moves.

### Full Explanation:

Parse scramble:

First I'll deal with the prime marks ' in the input strings. I simply replace these with 3 and run-length decode this string. Since Pyth's decoding format requires the number in front of the char, I reverse the string beforehand. _r_Xz\'\39. So afterwards I reverse it back.

Describe the solved cube state:

=Z"UDLRFB assigns the string with all 6 moves to Z.

We can describe a cube state by describing the location for each cube piece. For instance we can say that the edge, that should be at UL (Up-Left) is currently at FR (Front-Right). For this I need to generate all pieces of the solved cube: f!s}RTcZ2yZ. yZ generates all possible subsets of "UDLRFB". This obviously also generates the subset "UDLRFB" and the subset "UD". The first one doesn't make any sense, since there is no piece that is visible from all 6 sides, and the second one doesn't make any sense, since there is no edge piece, that is visible from the top and the bottom. Therefore I remove all the subsets, that contain the subsequence "UD", "LR" or "FB". This gives me the following 27 pieces:

'', 'U', 'D', 'L', 'R', 'F', 'B', 'UL', 'UR', 'UF', 'UB', 'DL', 'DR', 'DF', 'DB',
'LF', 'LB', 'RF', 'RB', 'ULF', 'ULB', 'URF', 'URB', 'DLF', 'DLB', 'DRF', 'DRB'


This also includes the empty string and all the six 1-letter strings. We could interpret them as the piece in the middle of the cube and the 6 center pieces. Obviously they are not required (since they don't move), but I'll keep them.

Doing some moves:

I'll do some string translations to perform a move. To visualize the idea look at the corner piece in URF. What happens to it, when I do an R move? The sticker on the U face moves to the B face, the sticker F moves to the U face and the sticker on the R face stays at the R face. We can say, that the piece at URF moves to the position BRU. This pattern is true for all the pieces on the right side. Every sticker that is on the F face moves to the U face when an R move is performed, every sticker that is on the U face moves to the B face, every sticker on the B moves to D and every sticker on D moves to F. We can decode the changes of an R move as FUBD.

The following code generates all the 6 necessary codes:

_sm_B.iFP.>c3Zk3
['BRFL', 'LFRB', 'DBUF', 'FUBD', 'RDLU', 'ULDR']
^       ^       ^       ^       ^       ^
U move  D move  L move  R move  F move  B move


And we perform a move H to the cube state G as followed:

m.rW}Hdd@...xZHG
m              G   map each piece d in G to:
.rW   d              perform a rotated translation to d, but only if:
}Hd                  H appears in d (d is currently on the face H)
xZH           get the index of H in Z
@...              and choose the code in the list of 6 (see above)


Count the number of repeats:

The rest is pretty much trivial. I simply perform the input scramble to the solved cube over and over until I reach a position that I previously visited.

l.uu<apply move H to G><parsed scramble>N<solved state>
u...N   performs all moves of the scramble to the state N
.u...   do this until cycle detected, this returns all intermediate states
l       print the length


# GAP, 792 783 782749 650 Bytes

This seems to be working. If it messes up with something let me know.

Thanks to @Lynn for suggesting that I decompose some of the primitive moves.

Thanks to @Neil for suggesting that instead of Inverse(X) I use X^3.

Usage example: f("R");

R:=(3,39,21,48)(6,42,24,51)(9,45,27,54)(10,12,18,16)(13,11,15,17);L:=(1,46,19,37)(4,49,22,40)(7,52,25,43)(30,36,34,28)(29,33,35,31);U:=(1,10,27,28)(2,11,26,29)(3,12,25,30)(37,43,45,39)(40,44,42,38);A:=R*L^3*F*F*B*B*R*L^3;D:=A*U*A;;F:=(1,3,9,7)(2,6,8,4)(10,48,36,43)(13,47,33,44)(16,46,30,45);B:=(27,25,19,21)(26,22,20,24)(39,28,52,18)(38,31,53,15)(37,34,54,12);d:=NewDictionary((),true);AddDictionary(d,'R',R);AddDictionary(d,'L',L);AddDictionary(d,'U',U);AddDictionary(d,'D',D);AddDictionary(d,'F',F);AddDictionary(d,'B',B);f:=function(s) local i,p,b,t;p:=();
for c in s do if c='\'' then t:=t^2;else t:=LookupDictionary(d,c);fi;p:=p*t;od;return Order(p);end;


Here is the ungolfed code with a bit of explanation

  # Here we define the primitive moves
R:=(3,39,21,48)(6,42,24,51)(9,45,27,54)(10,12,18,16)(13,11,15,17);
L:=(1,46,19,37)(4,49,22,40)(7,52,25,43)(30,36,34,28)(29,33,35,31);
U:=(1,10,27,28)(2,11,26,29)(3,12,25,30)(37,43,45,39)(40,44,42,38);
#D:=(7,34,21,16)(8,35,20,17)(9,36,19,18)(48,46,52,54)(47,49,53,51);
F:=(1,3,9,7)(2,6,8,4)(10,48,36,43)(13,47,33,44)(16,46,30,45);
B:=(27,25,19,21)(26,22,20,24)(39,28,52,18)(38,31,53,15)(37,34,54,12);

# Here we define D in terms of other primitive moves, saving on bytes
# Thanks @Lynn
# This is actually doable with a maximum of 3 of the primitive moves
# if a short enough sequence can be found.
D:=U^(R*L^3*F*F*B*B*R*L^3);

# create dictionary and add moves to it with appropriate char labels
d:=NewDictionary((),true);

f:=function(s)
local c,p,t;

# p will become the actual permutation passed to the function
p:=();

for c in s do
if c='\'' then
# The last generator we mutiplied (that we still have in t)
# should have been its inverse. Compensate by preparing to
# multiply it two more times to get t^3=t^-1. Thanks @Neil.
t:=t^2;
else
t:=LookupDictionary(d,c);
fi;
p:=p*t;
od;

return Order(p);

end;

• Every move is a fourth root of the identity, so your Inverse is unnecessary.
– Neil
Feb 4, 2016 at 11:32
• You can probably replace 45 with 5 in your permutations, and save three bytes.
– Lynn
Feb 4, 2016 at 12:58
• A result by Benson I found in Singmaster, 1981 says: “Let A = RL⁻¹F²B²RL⁻¹, then AUA = D.” Indeed, A:=R*L*L*L*F*F*B*B*R*L*L*L;D:=A*U*A; is shorter than your definition for D (but I can’t test it...)
– Lynn
Feb 4, 2016 at 13:11
• Does GAP really not let you write ^-1 for inverses, BTW?
– Lynn
Feb 4, 2016 at 13:15
• Yeah I totally spaced on using ^-1. Which I assume is pretty much the same thing @Neil was saying, except with ^3 instead (which is actually the shortest). Also, yeah I could decompose moves into other moves, and I should be able to save several bytes by doing so, it would just be a matter of finding the shortest decomposition.
– Liam
Feb 4, 2016 at 18:25

# Mathematica, 413 401 bytes

Evaluate[f/@Characters@"RFLBUD"]=LetterNumber@"ABFEJNRMDAEHIMQPCDHGLPTOBCGFKOSNADCBILKJEFGHQRST"~ArrayReshape~{6,2,4};
p@s_:=Length[c={#,0}&~Array~20;NestWhileList[Fold[r,#,Join@@StringCases[s,x_~~t:""|"'":>Table[x,3-2Boole[t==""]]]]&,c,(Length@{##}<2||c!=Last@{##})&,All]]-1


Explanations

A Rubik's Cube is made up with 20 movable cubies (8 corners, 12 edges). Each cubie can be given a number:

corners:

N   starting position
1     UFR
2     UBR
3     UBL
4     UFL
5     DFR
6     DBR
7     DBL
8     DFL


edges:

N   starting position
9     UF
10    UR
11    UB
12    UL
13    FR
14    BR
15    BL
16    FL
17    DF
18    DR
19    DB
20    DL


Note that when the cube is twisted, the cubies are generally not on their starting positions any longer. For example, when R is done, the cubie 1 moves from UFR to a new position UBR.

In such notation, a 90 degree turn can be described by 8 movements of cubies. For example, R is described by

from  to
UFR   UBR
UBR   DBR
DBR   DFR
DFR   UFR
UR    BR
BR    DR
DR    FR
FR    UR


Since each cubie has a unique starting position, each position has a unique starting cubie. That is to say, rule UFR->UBR is just 1->2 (means that R takes the cubie on the starting position of cubie 1 to the starting position of cubie 2). Thus, R can be simplified further to a cycle

Cycles[{{1,2,6,5}, {10,14,18,13}}]


To fully solve a Rubik's Cube, we also need to align the cubies to their corresponding starting orientations. The faces of a cube is painted in different colors, the scheme that I often use when solving cubes is

face color
U    yellow
D    white
F    red
B    orange
R    green
L    blue


When we analyzing the orientations of corners, colors other than yellow or white are ignored, and yellow and white are considered as the same color.

Suppose cubie 1 is on its starting position UFR, the yellow facet may be aligned to three different faces. We use an integer to represent these cases,

0  yellow on U  (correct)
1  yellow on R  (120 degree clockwise)
2  yellow on F  (120 degree counterclockwise)


Suppose cubie 1 is on DFL, its three possible orientations are

0  yellow on D  (correct)
1  yellow on L  (120 degree clockwise)
2  yellow on F  (120 degree counterclockwise)


When we analyzing the orientations of edges, red and orange are ignored, and yellow and white are ignored only if the edge has a green or blue facet.

Suppose cubie 10 is on its starting position UR, the green facet may be aligned to two different faces. Its two possible orientations are

0  green on R  (correct)
1  green on U  (180 degree)


Suppose cubie 10 is on DF, its two possible orientations are

0  green on D  (correct)
1  green on F  (180 degree)


An array is used to store the state of a cube. The starting state of a cube is

{{1,0},{2,0},{3,0},{4,0},{5,0},{6,0},{7,0},{8,0},{9,0},{10,0},{11,0},{12,0},{13,0},{14,0},{15,0},{16,0},{17,0},{18,0},{19,0},{20,0}}


which means that every cubies are on their starting position with correct orientation.

After R, the state of the cube becomes

{{5,2},{1,1},{3,0},{4,0},{6,1},{2,2},{7,0},{8,0},{9,0},{13,1},{11,0},{12,0},{18,1},{10,1},{15,0},{16,0},{17,0},{14,1},{19,0},{20,0}}


which means that cubie 5 is now on position 1 (UFR) with orientation 2, cubie 1 is now on position 2 (UBR) with orientation 1, cubie 3 is now still on position 3 (UBL) with orientation 0, and so on.

Test cases

p["FF'"]            (* 1   *)
p["R"]              (* 4   *)
p["RUR'U'"]         (* 6   *)
p["LLUUFFUURRUU"]   (* 12  *)
p["LUFFRDRBF"]      (* 56  *)
p["LF"]             (* 105 *)
p["UFFR'DBBRL'"]    (* 120 *)
p["FRBL"]           (* 315 *)


r=[-2..2]
s=mapM id[r,r,r]
t m p@[x,y,z]=case m of"R"|x>0->[x,z,-y];"L"|x<0->[x,-z,y];"U"|y>0->[-z,y,x];"D"|y<0->[z,y,-x];"F"|z>0->[y,-x,z];"B"|z<0->[-y,x,z];c:"'"->t[c]$t[c]$t[c]p;_->p
f m=length$s:fst(span(/=s)$tail$iterate(flip(foldl$flip$map.t)m)s)  Sample runs: *Main> f ["F","F'"] 1 *Main> f ["R"] 4 *Main> f ["R","U","R'","U'"] 6 *Main> f ["L","L","U","U","F","F","U","U","R","R","U","U"] 12 *Main> f ["L","U","F","F","R","D","R","B","F"] 56 *Main> f ["L","F"] 105 *Main> f ["U","F","F","R'","D","B","B","R","L'"] 120 *Main> f ["F","R","B","L"] 315 *Main> f ["R","U","U","D'","B","D'"] -- maximum possible order 1260  The key observation here is that it’s simpler to model the Rubik’s cube as a 5×5×5 grid of points rather than a 3×3×3 grid of oriented cubies. Corner cubies become cubes of 2×2×2 points, edge cubies become squares of 2×2×1 points, and moves rotate slices of 5×5×2 points. • This is really clever! I think replacing c:"'" with c:_ saves two bytes. – Lynn Feb 5, 2016 at 0:46 • Thanks! I was looking for a 1260 sequence for the test cases, but couldn't be bothered looking it up :) Feb 5, 2016 at 15:00 • @Lynn, that doesn’t work because _ also matches the empty list. Feb 5, 2016 at 16:34 • This is great, but it seems very similar to this answer to another question codegolf.stackexchange.com/a/44775/15599 . If you were inspired by that you should acknowledge it. Feb 6, 2016 at 13:32 • @steveverrill, wow, that does look impressively similar, but no, I hadn’t seen it. My answer is my own independent work. (I acknowledge, of course, that Jan Dvorak came up with most of the same ideas before I did.) Feb 7, 2016 at 21:59 # Ruby, 225 bytes ->s{n=0 a=[] b=[] 64.times{|i|a<<j=[(i&48)-16,(i&12)-4,i%4-1];b<<j*1} d=1 (n+=1 s.reverse.chars{|c|m="UFRDBL".index(c) m ?(e=m/3*2-1 b.each{|j|j[m%=3]*e>0&&(j[m-2],j[m-1]=j[m-1]*e*d,-j[m-2]*e*d)} d=1):d=-1})until n>0&&a==b n}  Similar to Anders Kaseorg's answer and inspired by Jan Dvorak's answer to a previous question. However unlike those answers, I don't need 125 cubies. I use a rubik's cube of 27 cubies, but rectangular dimensions. In the solved state the corners are at +/-1,+/-4,+/-16. I generate an array of 64 cubies, each with a centre chosen from x=[-1,0,1,2], y=[-4,0,4,8], z=[-16-0,16,32]. The cubies with coordinates of 2, 8 and 32 are unnecessary, but they do no harm, so they are left in for golfing reasons. The fact that the length, width and depth of the cubies are different: (1,4,16) means it is easy to detect if they are in the right place but with wrong orientation. Each cubie is tracked as it is moved by the faceturns. If the coordinate of a cubie in the axis corresponding to the face (multiplied by e=-1 for U,F,R or e=1 for D,B,L) is positive, then it will be rotated by swapping the coordinates in the other 2 axis and applying an appropriate sign change to one of the coordinates. This is controlled by multiplying by e*d. The input sequence is scanned in reverse order. This makes no difference, so long as the "normal" rotations are performed anticlockwise instead of clockwise. The reason for this is so that if a ' symbol is found, the value of d can be changed from 1 to -1 in order to cause rotation of the following face in the opposite direction. Ungolfed in test program f=->s{n=0 #number of repeats=0 a=[] #empty array for solved position b=[] #empty array for current position 64.times{|i| a<<j=[(i&48)-16,(i&12)-4,i%4-1] #generate 64 cubies and append them to the solved array b<<j*1 #duplicate them and append to active array } d=1 #default rotation direction anticlockwise (we scan the moves in reverse) ( #start of UNTIL loop n+=1 #increment repeat counter s.reverse.chars{|c| #reverse list of moves and iterate through it m="UFRDBL".index(c) #assign move letter to m (for ' or any other symbol m is false) m ? #if a letter (e=m/3*2-1 #e=-1 for UFR, 1 for DBL b.each{|j| #for each cubie j[m%=3]*e>0&& #m%=3 picks an axis. If the cubie is on the moving face of the cube (j[m-2],j[m-1]=j[m-1]*e*d,-j[m-2]*e*d)#rotate it: exchange the coordinates in the other 2 axes and invert the sign of one of them according to direction } #as per the values of e and d. d=1 #set d=1 (in case it was -1 at the start of the b.each loop) ): d=-1 #ELSE the input must be a ', so set d=-1 to reverse rotation of next letter } )until n>0&&a==b #end of UNTIL loop. continue until back at start position a==b n} #return n p f["FF'"] # 1 p f["R"] # 4 p f["RUR'U'"] # 6 p f["LLUUFFUURRUU"] # 12 p f["LUFFRDRBF"] # 56 p f["LF"] # 105 p f["UFFR'DBBRL'"] # 120 p f["FRBL"] # 315  # Python 2, 343 bytes def M(o,v,e): k=1 for m in e: for c in'ouf|/[bPcUDkqbx-Y:(+=P4cyrh=I;-(:R6'[m::6]:i=~ord(c)%8*k;j=(ord(c)/8-4)*k;o[i],o[j]=o[j]-m/2,o[i]+m/2;v[i],v[j]=v[j],v[i];k=-k V=range(20) o,v,e=[0]*20,V[:],[] for c in raw_input():i='FBRLUD'.find(c);e+=i<0and e[-1:]*2or[i] M(o,v,e);n=1 while any(o[i]%(2+i/12)for i in V)or v>V:M(o,v,e);n+=1 print n  Input is taken from stdin. The given sequence of twists is performed repeatedly on a virtual cube until it returns to the solved state. The cube state is stored as an orientation vector and permutation vector, both of length 20. Orientations are somewhat arbitrarily defined: an edge cubie is oriented correctly if it can be moved into place without invoking an R or L quarter turn. The orientation of the corner cubies is considered relative to the F and B faces. Sample Usage $ echo FRBL|python rubiks-cycle.py
315

$echo RULURFLF|python rubiks-cycle.py 1260  • Nice choice of function name and arguments! – Neil Feb 7, 2016 at 20:08 # Cubically, 9 6 bytes ¶-7)8%  Try it online! (Nonworking until Dennis updates the TIO Cubically interpreter) Explanation: ¶-7)8% ¶ read a string, insert into code -7 add 1 to notepad (subtracts the 7th face "sum" from notepad, defaulted to -1) )8 jump back to start of code if cube unsolved % print notepad  This language will dominate all challenges >:D • All these new-fangled esolangs. Back in my day, -7 meant subtract seven not add one angrily shakes walker Dec 2, 2017 at 0:25 • @cairdcoinheringaahing Indeed. :P Added some explanation around that. Dec 2, 2017 at 0:31 ## Clojure, 359 bytes This might be my 2nd longest codegolf. Realizing I could drop trailing zeros from vectors A to F made me very happy :D #(let[I(clojure.string/replace % #"(.)'""$1$1$1")D(range -2 3)S(for[x D y D z D][x y z])A[0 1]B[0 0 1]C[1]D[-1]E[0 -1]F[0 0 -1]](loop[P S[[n R]& Q](cycle(map{\F[A[B A D]]\B[E[F A C]]\L[D[C B E]]\R[C[C F A]]\U[B[E C B]]\D[F[A D B]]}I))c 0](if(=(> c 0)(= P S))(/ c(count I))(recur(for[p P](if(>(apply +(map * n p))0)(for[r R](apply +(map * r p)))p))Q(inc c)))))


Less golfed:

(def f #(let [I (clojure.string/replace % #"(.)'""$1$1$1") D [-2 -1 0 1 2] S (for[x D y D z D][x y z]) L {\F [[ 0 1 0][[0 0 1][ 0 1 0][-1 0 0]]] \B [[ 0 -1 0][[0 0 -1][ 0 1 0][ 1 0 0]]] \L [[-1 0 0][[1 0 0][ 0 0 1][ 0 -1 0]]] \R [[ 1 0 0][[1 0 0][ 0 0 -1][ 0 1 0]]] \U [[ 0 0 1][[0 -1 0][ 1 0 0][ 0 0 1]]] \D [[ 0 0 -1][[0 1 0][-1 0 0][ 0 0 1]]]}] (loop [P S c 0 [[n R] & Q] (cycle(map L I))] (if (and (> c 0) (= P S)) (/ c (count I)) (recur (for[p P](if(pos?(apply +(map * n p))) (for[r R](apply +(map * r p))) p)) (inc c) Q)))))  This simply implements 3D rotations of selected subsets of 5 x 5 x 5 cube. Originally I was going to use 3 x 3 x 3 and it took me a while to realize why I wasn't getting correct results. Good test cases! Some extra bytes for fist encoding "RUR'U'" as "RURRRUUU". # Clean, 255 bytes Derived separately from the almost-identical Haskell answer as an answer to this question which was closed as a duplicate when it was almost finished, so I posted the answer here. import StdEnv,StdLib a=[-2..2];b=diag3 a a a ?m=iter(size m*2-1)\p=:(x,y,z)=case m.[0]of'F'|z>0=(y,~x,z);'U'|y>0=(~z,y,x);'R'|x>0=(x,z,~y);'B'|z<0=(~y,x,z);'D'|y<0=(z,y,~x);'L'|x<0=(x,~z,y);_=p$l=length(takeWhile((<>)b)(tl(iterate(map(sseq(map?l)))b)))+1
`

Try it online!