{"id":13992416,"url":"https://github.com/GuidoDipietro/julia-small-rubiks-cube-model","last_synced_at":"2025-07-22T15:32:21.321Z","repository":{"id":128532198,"uuid":"303012765","full_name":"GuidoDipietro/julia-small-rubiks-cube-model","owner":"GuidoDipietro","description":"A 2x2x2 Rubik's Cube (or just the corners of a 3x3x3 Cube) modelled in the Julia Programming Language. (Not a solver!)","archived":false,"fork":false,"pushed_at":"2021-09-27T14:21:11.000Z","size":232,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-08-10T14:10:08.761Z","etag":null,"topics":["julia","julia-language","permutations","rubiks-cube","rubiks-cube-model"],"latest_commit_sha":null,"homepage":"","language":"Julia","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/GuidoDipietro.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2020-10-11T00:22:23.000Z","updated_at":"2023-08-28T09:42:06.000Z","dependencies_parsed_at":"2023-04-05T13:48:28.973Z","dependency_job_id":null,"html_url":"https://github.com/GuidoDipietro/julia-small-rubiks-cube-model","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GuidoDipietro%2Fjulia-small-rubiks-cube-model","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GuidoDipietro%2Fjulia-small-rubiks-cube-model/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GuidoDipietro%2Fjulia-small-rubiks-cube-model/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GuidoDipietro%2Fjulia-small-rubiks-cube-model/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/GuidoDipietro","download_url":"https://codeload.github.com/GuidoDipietro/julia-small-rubiks-cube-model/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":227133798,"owners_count":17735809,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["julia","julia-language","permutations","rubiks-cube","rubiks-cube-model"],"created_at":"2024-08-09T14:01:59.085Z","updated_at":"2024-11-29T13:30:37.125Z","avatar_url":"https://github.com/GuidoDipietro.png","language":"Julia","funding_links":[],"categories":["Julia"],"sub_categories":[],"readme":"# 2x2x2 Rubik's Cube modelled in Julia\n\nI tried to make the simplest and most elegant model of a 2x2x2 Rubik's Cube puzzle I could, with every face turn and axis rotation defined (even though 3 faces suffice).\n\n## Defining the cube\n\nThis puzzle consists of 8 total pieces (all corner pieces with 3 sides each).  \nWe can represent every possible state using two arrays:  \n- A **position array**, defining where each piece is located\n- An **orientation array**, defining \"how a certain corner is rotated\"\n\nThe first one is simply an 8-uple that will get its elements moved around accordingly:\n```julia\ncp = (1, 2, 3, 4, 5, 6, 7, 8)\n```\nThe numbers follow the Speffz scheme, which looks like this in case you're not familiar with it:\n\n![Image describing Speffz scheme, UBL UBR UFR UFL DFL DFR DBR DBL](img/speffz.png)\n\nThe second one is also (of course) an 8-uple that can only hold `0, 1, 2`:\n```julia\nco = (0, 0, 0, 0, 0, 0, 0, 0)\n```\n\nA corner has orientation `0` if its Up/Down solved colour is facing Up or Down, orientation `1` if it is rotated clockwise once, and orientation `2` if it is rotated counter-clockwise once (or clockwise twice, it's obviously the same).\n\nThese two 8-uples are then grouped together in a single tuple, for easier manipulation:\n\n```julia\ncube = (cp, co)\n```\n\n## Defining the moves\n\nIn brief, the Rubik's cube notation is as follows:\n\n| Letter |   Description   |\n|:------:|:---------------:|\n|    R   |    right face   |\n|    U   |     up face     |\n|    F   |    front face   |\n|    L   |    left face    |\n|    D   |    down face    |\n|    B   |    back face    |\n|    x   | whole cube as R |\n|    y   | whole cube as U |\n|    z   | whole cube as F |\n\nThe letter on its own means 90 degrees clockwise; adding a prime `'` means 90 degrees counter-clockwise, adding a `2` means 180 degrees.\n\nEach move applies certain permutation to both tuples, then adds the corresponding change in orientation to the second tuple.\n\nFor example, the move `R` (a 90 degree clockwise turn of the right face) changes the pieces as follows:\n\n- Piece in 3 goes to 2, then gets rotated clockwise (+1)\n- Piece in 6 goes to 3, then gets rotated counter-clockwise (+2)\n- Piece in 7 goes to 6, then gets rotated clockwise (+1)\n- Piece in 2 goes to 7, then gets rotated counter-clockwise (+2)\n\nThe way this gets represented is with two functions: one that applies the permutation change, and one that applies the orientation change:\n\n```julia\nRp((a,b,c,d,e,f,g,h)) = (a,c,f,d,e,g,b,h)\nRo(a) = @. (a + (0,1,2,0,0,1,2,0)) % 3\n```\n\nUsing the wonders of Julia such as destructuration and the `@.` macro that vectorizes functions, this is effortless.\n\nWhat's only left is defining the function `R` that applies the permutation `Rp` to both tuples, then the orientation `Ro` to the second one:\n\n```julia\nR((p,o)) = Rp(p), (Ro∘Rp)(o)\n```\n\nDon't tell me that doesn't look _exceedingly_ cool!  \nFunction composition is super easy in Julia, just using the `\\circ` symbol (this thing: `∘`), same as you would do if you wrote a mathematical function!\n\nNow, we should do the same for the remaining 17 face turns..... If we were drunk or silly, that is. 🍻\n\nA better approach is knowing that by the use of just two moves and a rotation we can define every other move and rotation! \u003csub\u003eActually, this is achievable by just 2 permutations since the cube group is 2-gen, but let's stick to single moves.\u003c/sub\u003e\n\nIn this case, I defined the move `U` and the rotation `x` as my \"atoms\", just as I showed you with the move `R`.  \n\nAfter that, defining other moves is just beautiful:\n\n```julia\nD = x ∘ x ∘ U ∘ x ∘ x # since D = x2 U x2\nF = x ∘ x ∘ x ∘ U ∘ x # since F = x U x'\n# ... etc\n```\n\nNote that `F` seems to be defined as `x x x U x` instead of `x U x x x`, but that's just because the order of execution is the inverse. It works just like any composition:\n\n```julia\nh(x) = (f ∘ g)(x) # h(x) = f(g(x)), g gets applied before f\n```\n\nWhat a charm! Every single move and rotation has been defined without a single 'sigh'.\n\n## String to function\n\nNaturally, you wouldn't be writing move sequences as `(R ∘ U ∘ B ∘ B ∘ B ∘ F)(cube)`, but rather, you'd want to give some function a string like `\"F B' U R\"` and get all that applied for you.\n\nMy solution for this was defining a function that received a \"single move string\" as input (such as `\"R\"`, `\"F2\"`, `\"B'\"`) and output a function that represents that permutation, so that you could use it like this:\n\n```julia\nsingle_move_string_to_func(\"F2\")(cube)\n# the same as F(F(cube))\n```\n\nThe name I used was actually `m_to_mf` instead of all that word soup in my example.\n\nThat function would simply check if the length of the given string is 2, then if the 2nd element is a `2` it would return the function twice, otherwise thrice, or if the length is `1`, just once. \u003csub\u003eWhich function? The function corresponding to that face turn, retrieved using `getfield`. Defining a dictionary that holds the corresponding function for each move was equally efficient when benchmarked using BenchmarkTools.\u003c/sub\u003e\n\n```julia\nfunction m_to_mf(m)\n    func = getfield(Main,Symbol(m[1]))\n    length(m)==2 ? (m[2]=='2' ? func∘func : func∘func∘func) : func\nend\n```\n\nThe \"double ternary operator\" looks a bit strange, but I decided this usage of it was simple enough to understand, so I left it like that. Oh also, remember that in Julia indexing begins in `1` rather than in `0`.\n\nNote that this doesn't actually check if a move is a prime (like `R'`), but just assumes that if the move is of length 2 and the second character isn't a `2`, then it has to be a `'`.\n\n\u003e\"If you write stuff the wrong way, it isn't my problem to handle it 😛\"\n\u003e -My program\n\nThen, converting a string of such \"single move string\" separated by spaces (a.k.a. the good ol' regular scramble string) to a single function is a piece of 🍰: you just split the string, map `m_to_mf`, then fold that array (the inverse, rather) of functions using composition:\n\n```julia\nstr_to_func(string) = foldl(∘, map(m_to_mf, reverse(split(string))))\n```\n\n## The extreme efficiency of Julia\n\nThe code is wonderful, but how does it perform? Well let's try it!\n\nI defined a scramble and a solution (it's actually a 3x3x3 FMC solve, but it will work anyway):\n\n```julia\nscramble = \"R' U' F D2 B2 U2 L2 B2 U2 F2 R' B2 R' U' F' R D2 L D L' U2 F R' U' F\"\nsolution = \"U F2 B U B D R2 L2 F2 U' F2 L2 D F2 D' R2 D' B D L B'\"\n```\n\nNow, you could define the function that represents the state after you apply `scramble` and `solution` like this:\n\n```julia\npos = str_to_func(scramble*\" \"*solution)\n# use it as pos(cube) !\n```\n\nBut instead, to benchmark using BenchmarkTools, I'll do this:\n\n```julia\n@btime _pos(cube) setup=(_pos=str_to_func(scramble*\" \"*solution))\n```\n\nHoly sh*^%$#$%!!! Look at the result:\n\n```\n19.999 μs (1 allocation: 144 bytes)\n((1, 2, 3, 4, 5, 6, 7, 8), (0, 0, 0, 0, 0, 0, 0, 0))\n```\n\n**????????????** That is just ridiculous.  \nIn case you don't know, 1 μs is 1 second divided into **a million**.\n\n## Are you still baffled?\n\nYou should be.\n\n## Thank\n\n_Julia_ for existing, _alg.cubing.net_ for the images, _dillinger.io_ for being a cool Markdown editor, _you_ for the read!\n\nAlso, huge thanks to Tao Yu for the Biang Biang noodles he provided.\n\nThis complete piece of README couldn't have been possible without Antonio Kam's issue.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FGuidoDipietro%2Fjulia-small-rubiks-cube-model","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FGuidoDipietro%2Fjulia-small-rubiks-cube-model","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FGuidoDipietro%2Fjulia-small-rubiks-cube-model/lists"}