Cycling with Rubik's

44

7

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 'R':steps.add(Rotation.R);break;
                case 'L':steps.add(Rotation.L);break;
                case 'U':steps.add(Rotation.U);break;
                case 'D':steps.add(Rotation.D);break;
                case 'F':steps.add(Rotation.F);break;
                case 'B':steps.add(Rotation.B);break;
                case '\'':
                    steps.add(steps.get(steps.size()-1));
                case '2':
                    steps.add(steps.get(steps.size()-1));
                    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;
        }
    }
}

Geobits

Posted 2016-02-03T18:36:20.033

Reputation: 19 061

"clockwise" means "clockwise when you're facing it" I assume? – msh210 – 2016-02-03T20:49:06.590

@msh210 Correct. – Geobits – 2016-02-03T20:51:51.530

7On 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... – Peter Taylor – 2016-02-03T21:31:48.153

2@PeterTaylor Pedantry accepted. – Geobits – 2016-02-03T21:41:41.320

4I may offer a 500 point bounty for a solution in Shuffle. Not sure yet. – lirtosiast – 2016-02-03T22:32:02.307

Out of interest, what's the correct answer for LFLF? Although repeating the sequence 105 times returns you to the original state, so does repeating it 52.5 times... – Neil – 2016-02-04T11:30:50.453

@Neil Assume it's integer output only. – Geobits – 2016-02-04T13:04:44.977

@ThomasKwa I don't think Shuffle is anywhere near Turing complete. I'm pretty sure this is impossible. – Martin Ender – 2016-02-05T09:05:27.233

So what's the longest a sequence of moves could take? Could that be calculated easily using abstract algebra? Perhaps that's a question for math.SE. – mbomb007 – 2019-04-05T14:12:55.747

@mbomb007 1260, based on group theory. Not an expert, but maybe reddit can help :)

– Geobits – 2019-04-05T18:09:33.670

Answers

16

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

Jakube

Posted 2016-02-03T18:36:20.033

Reputation: 21 462

13

GAP, 792 783 782 749 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);
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 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;

Liam

Posted 2016-02-03T18:36:20.033

Reputation: 3 035

Every move is a fourth root of the identity, so your Inverse is unnecessary. – Neil – 2016-02-04T11:32:47.220

You can probably replace 45 with 5 in your permutations, and save three bytes. – Lynn – 2016-02-04T12:58:33.643

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 – 2016-02-04T13:11:53.043

Does GAP really not let you write ^-1 for inverses, BTW? – Lynn – 2016-02-04T13:15:28.327

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 – 2016-02-04T18:25:44.683

@Lynn I' m not sure I understand what you mean by replacing 45 with 5. – Liam – 2016-02-04T18:40:06.713

@Liam What Lynn means is that the centre squares never move so you shouldn't number them as that wastes your small numbers (in fact you can save a couple more bytes by starting the numbering from 0). – Neil – 2016-02-04T19:15:12.013

@Neil ah yeah I see what you're saying. Yeah I probably would be able to, although GAP takes issue if it finds a 0 in a cycle or if I try to declare a permutation with nondisjoint cycles – Liam – 2016-02-04T19:17:07.467

I dislike if b=true even in ungolfed code. Why not let b be 1 or -1 and do p:=p*t^b? $A$ has order 2, so you can say D:=U^A (and then inline and get rid of $A$). Using a dictionary is clean, but doesn't seem to help getting short code. The function could just end with return Order(p); – Christian Sievers – 2016-07-23T15:17:18.770

@ChristianSievers I'll be honest I've never truly been interested in golfing as such, so I usually just make a nominal effort. You have my permission to make edits though! (If I had more time right now, I would go through it myself) – Liam – 2016-07-23T15:26:11.243

Okay, I made an edit. I didn't change using a dictionary, and I changed the handling of inverses in a way different from what I suggested. Most of the changes I would do even when not golfing. However, there must be something wrong (already with the old code), as some test cases don't give the expected result. Maybe some primitive moves are not in the correct direction? – Christian Sievers – 2016-07-23T21:53:08.553

@ChristianSievers thanks for doing that. Which cases are failing? I'll take a look hopefully later tonight. – Liam – 2016-07-23T21:59:49.817

LUFFRDRBF gives 240 but should be 56, UFFR'DBBRL' gives 252 but should be 120 (both from the task description), RUUD'BD' gives 720 but should give 1260 (from the Haskell entry) - instead RUUDBD gives 1260, but only changing D to D' doesn't help in the other cases. - Oh, also changing U and U' works. In the code, only changing this will also fix D. I'll do that. – Christian Sievers – 2016-07-23T22:26:08.710

No, I won't. I thought I could continue editing my version, but I can't. (Why?) – Christian Sievers – 2016-07-23T22:34:21.253

I'm on my phone so I haven't/can't do anything. Do you have enough rep yet? If not, go to chat and ask someone to approve the edit. – Liam – 2016-07-23T22:37:17.347

I think I'll just wait for you. – Christian Sievers – 2016-07-23T23:02:05.113

Let us continue this discussion in chat.

– Liam – 2016-07-24T02:43:05.473

My changes are still hidden in the rejected edit you linked to in chat. Maybe you can just copy them from there? – Christian Sievers – 2016-12-02T00:03:31.337

@ChristianSievers I'm really sorry, it completely slipped my mind. Thank you for the reminder. I also still haven't checked the validity cases you mentioned. – Liam – 2016-12-02T01:10:54.587

No problem. I think my final result was that the definition of U needs to be replaced with its inverse. – Christian Sievers – 2016-12-02T02:20:59.847

10

Mathematica, 413 401 bytes

Evaluate[f/@Characters@"RFLBUD"]=LetterNumber@"ABFEJNRMDAEHIMQPCDHGLPTOBCGFKOSNADCBILKJEFGHQRST"~ArrayReshape~{6,2,4};
r[c_,l_]:=(b=Permute[c,Cycles@f@l];MapThread[(b[[#,2]]=Mod[b[[#,2]]+{"F","B","L","R"}~Count~l{-1,1,-1,1},#2])&,{f@l,{3,2}}];b);
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 *)

njpipeorgan

Posted 2016-02-03T18:36:20.033

Reputation: 2 992

7

Haskell, 252 bytes

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.

Anders Kaseorg

Posted 2016-02-03T18:36:20.033

Reputation: 29 242

This is really clever! I think replacing c:"'" with c:_ saves two bytes. – Lynn – 2016-02-05T00:46:18.857

Thanks! I was looking for a 1260 sequence for the test cases, but couldn't be bothered looking it up :) – Geobits – 2016-02-05T15:00:29.640

@Lynn, that doesn’t work because _ also matches the empty list. – Anders Kaseorg – 2016-02-05T16:34:47.080

This is great, but it seems very similar to this answer to another question http://codegolf.stackexchange.com/a/44775/15599 . If you were inspired by that you should acknowledge it.

– Level River St – 2016-02-06T13:32:38.993

@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.) – Anders Kaseorg – 2016-02-07T21:59:52.853

7

Python 2, 343 bytes

def M(o,v,e):
 k=1
 for m in e:
  for c in'ouf|/[bPcU`Dkqbx-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

Online Demonstration and Test Suite.

primo

Posted 2016-02-03T18:36:20.033

Reputation: 30 891

3Nice choice of function name and arguments! – Neil – 2016-02-07T20:08:25.720

7

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

Level River St

Posted 2016-02-03T18:36:20.033

Reputation: 22 049

3

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".

NikoNyrh

Posted 2016-02-03T18:36:20.033

Reputation: 2 361

3

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

MD XF

Posted 2016-02-03T18:36:20.033

Reputation: 11 605

3All these new-fangled esolangs. Back in my day, -7 meant subtract seven not add one angrily shakes walker – caird coinheringaahing – 2017-12-02T00:25:12.613

@cairdcoinheringaahing Indeed. :P Added some explanation around that. – MD XF – 2017-12-02T00:31:04.460

1

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!

Οurous

Posted 2016-02-03T18:36:20.033

Reputation: 7 916