cotb_rubik_talk



cotb_rubik_talk

0 0


cotb_rubik_talk


On Github StachuK1992 / cotb_rubik_talk

type Person =  {First: string;   Last: string;}Full name: Document.Person
Person.First: string
Multiple itemsval string : value:'T -> stringFull name: Microsoft.FSharp.Core.Operators.string--------------------type string = System.StringFull name: Microsoft.FSharp.Core.string
Person.Last: string
type Employee =  | Worker of Person  | Manager of (Person * Employee list)Full name: Document.Employee
union case Employee.Worker: Person -> Employee
union case Employee.Manager: (Person * Employee list) -> Employee
type 'T list = List<'T>Full name: Microsoft.FSharp.Collections.list<_>
type CubeState =  {EdgePositions: int array;   EdgeFlips: int array;   CornerPositions: int array;   CornerTwists: int array;}Full name: Document.CubeState
CubeState.EdgePositions: int array
Multiple itemsval int : value:'T -> int (requires member op_Explicit)Full name: Microsoft.FSharp.Core.Operators.int--------------------type int = int32Full name: Microsoft.FSharp.Core.int--------------------type int<'Measure> = intFull name: Microsoft.FSharp.Core.int<_>
type 'T array = 'T []Full name: Microsoft.FSharp.Core.array<_>
CubeState.EdgeFlips: int array
CubeState.CornerPositions: int array
CubeState.CornerTwists: int array
val solvedCube : CubeStateFull name: Document.solvedCube
module Arrayfrom Microsoft.FSharp.Collections
val create : count:int -> value:'T -> 'T []Full name: Microsoft.FSharp.Collections.Array.create
type CubeTransformation =  {Label: string;   Transformation: CubeState;}Full name: Document.CubeTransformation
CubeTransformation.Label: string
CubeTransformation.Transformation: CubeState
val ( Solved cube has 12 edges ) : unit -> 'aFull name: Document.( Solved cube has 12 edges )
property System.Array.Length: int
val ( Solved cube has 8 corners ) : unit -> 'aFull name: Document.( Solved cube has 8 corners )
val Execute : cubeState:CubeState -> t:CubeState -> CubeStateFull name: Document.Execute
val cubeState : CubeState
val t : CubeState
val inner : (int [] -> int [] -> int [] -> int [] -> int -> (int * int) [])
val permA : int []
val permB : int []
val orientA : int []
val orientB : int []
val n : int
val map : mapping:('T -> 'U) -> array:'T [] -> 'U []Full name: Microsoft.FSharp.Collections.Array.map
val i : int
val edges : (int * int) []
val corners : (int * int) []
val i : int * int
val fst : tuple:('T1 * 'T2) -> 'T1Full name: Microsoft.FSharp.Core.Operators.fst
val snd : tuple:('T1 * 'T2) -> 'T2Full name: Microsoft.FSharp.Core.Operators.snd
val ( Solved cube performed on self is solved ) : unit -> 'aFull name: Document.( Solved cube performed on self is solved )
val R : CubeStateFull name: Document.R
val U : 'aFull name: Document.U
val F : 'aFull name: Document.F
val D : 'aFull name: Document.D
val L : 'aFull name: Document.L
val B : 'aFull name: Document.B
val ExecuteN : cubeState:CubeState -> transformation:CubeState -> n:int -> CubeStateFull name: Document.ExecuteN
val transformation : CubeState
val failwith : message:string -> 'TFull name: Microsoft.FSharp.Core.Operators.failwith
val Combine : moves:CubeState list -> CubeStateFull name: Document.Combine
val moves : CubeState list
Multiple itemsmodule Listfrom Microsoft.FSharp.Collections--------------------type List<'T> =  | ( [] )  | ( :: ) of Head: 'T * Tail: 'T list  interface IEnumerable  interface IEnumerable<'T>  member Head : 'T  member IsEmpty : bool  member Item : index:int -> 'T with get  member Length : int  member Tail : 'T list  static member Cons : head:'T * tail:'T list -> 'T list  static member Empty : 'T listFull name: Microsoft.FSharp.Collections.List<_>
val fold : folder:('State -> 'T -> 'State) -> state:'State -> list:'T list -> 'StateFull name: Microsoft.FSharp.Collections.List.fold
property List.Head: CubeState
property List.Tail: CubeState list
val R2 : CubeStateFull name: Document.R2
val R' : CubeStateFull name: Document.R'
type MoveSets =  static member All : CubeTransformation listFull name: Document.MoveSets
static member MoveSets.All : CubeTransformation listFull name: Document.MoveSets.All
val ( Given a solved cube, R should mess the cube up ) : unit -> 'aFull name: Document.( Given a solved cube, R should mess the cube up )
val ( n four times should result in a solved cube ) : unit -> unitFull name: Document.( n four times should result in a solved cube )
val movesToTry : CubeState []
val move : CubeState
val ( T-perm twice results in a solved cube ) : unit -> 'aFull name: Document.( T-perm twice results in a solved cube )
val tPerm : CubeState
val endState : CubeState
val Scramble : cubeState:CubeState -> movesAllowed:CubeTransformation list -> n:int -> CubeState * stringFull name: Document.Scramble
val movesAllowed : CubeTransformation list
val rnd : System.Random
namespace System
Multiple itemstype Random =  new : unit -> Random + 1 overload  member Next : unit -> int + 2 overloads  member NextBytes : buffer:byte[] -> unit  member NextDouble : unit -> floatFull name: System.Random--------------------System.Random() : unitSystem.Random(Seed: int) : unit
val mutable scrambledCube : CubeState
val mutable movesPerformed : string list
val nextMove : CubeTransformation
System.Random.Next() : intSystem.Random.Next(maxValue: int) : intSystem.Random.Next(minValue: int, maxValue: int) : int
property List.Length: int
Multiple itemstype String =  new : value:char -> string + 7 overloads  member Chars : int -> char  member Clone : unit -> obj  member CompareTo : value:obj -> int + 1 overload  member Contains : value:string -> bool  member CopyTo : sourceIndex:int * destination:char[] * destinationIndex:int * count:int -> unit  member EndsWith : value:string -> bool + 2 overloads  member Equals : obj:obj -> bool + 2 overloads  member GetEnumerator : unit -> CharEnumerator  member GetHashCode : unit -> int  ...Full name: System.String--------------------System.String(value: nativeptr<char>) : unitSystem.String(value: nativeptr<sbyte>) : unitSystem.String(value: char []) : unitSystem.String(c: char, count: int) : unitSystem.String(value: nativeptr<char>, startIndex: int, length: int) : unitSystem.String(value: nativeptr<sbyte>, startIndex: int, length: int) : unitSystem.String(value: char [], startIndex: int, length: int) : unitSystem.String(value: nativeptr<sbyte>, startIndex: int, length: int, enc: System.Text.Encoding) : unit
System.String.Join(separator: string, values: System.Collections.Generic.IEnumerable<string>) : stringSystem.String.Join<'T>(separator: string, values: System.Collections.Generic.IEnumerable<'T>) : stringSystem.String.Join(separator: string, params values: obj []) : stringSystem.String.Join(separator: string, params value: string []) : stringSystem.String.Join(separator: string, value: string [], startIndex: int, count: int) : string
val ( Scrambles should result in a scrambled cube ) : unit -> 'aFull name: Document.( Scrambles should result in a scrambled cube )
val BasicTreeSearch : scrambledState:CubeState -> maxDepth:int -> movesAllowed:CubeTransformation list -> string listFull name: Document.BasicTreeSearch
val scrambledState : CubeState
val maxDepth : int
val solutions : string list ref
Multiple itemsval ref : value:'T -> 'T refFull name: Microsoft.FSharp.Core.Operators.ref--------------------type 'T ref = Ref<'T>Full name: Microsoft.FSharp.Core.ref<_>
val BasicTreeSearch_Internal : (CubeState -> int -> string list -> unit)
val d : int
val movesSoFar : string list
module Stringfrom Microsoft.FSharp.Core
val concat : sep:string -> strings:seq<string> -> stringFull name: Microsoft.FSharp.Core.String.concat
val iter : action:('T -> unit) -> list:'T list -> unitFull name: Microsoft.FSharp.Collections.List.iter
val move : CubeTransformation
val newState : CubeState
property Ref.Value: string list
val IDASearch : scrambledState:CubeState -> maxDepth:int -> movesAllowed:CubeTransformation list -> string listFull name: Document.IDASearch
val mutable solutions : string list
val a : int32
property List.IsEmpty: bool
val treeSearchResponse : string list
val not : value:bool -> boolFull name: Microsoft.FSharp.Core.Operators.not
val ( Scrambled cube should be solvable ) : unit -> 'aFull name: Document.( Scrambled cube should be solvable )
val depth : int
val scrambledCube : CubeState * string
val actualCube : CubeState
val response : string list
val length : list:'T list -> intFull name: Microsoft.FSharp.Collections.List.length

Solving Rubik's Cubes with F#

- Stachu Korick

Stachu

  • I solve Rubik's Cubes sometimes
  • I work at AcademyOne in Malvern, PA
    • They make me write C#, but it's not that bad
  • I like weird tech:
    • F#, Haskell, LISP, Scala
    • Neo4J, RavenDb
    • Meta-programming
    • Machine Learning

Goals of Today

  • Introduce you to Rubik's Cube domain knowledge
  • Sell F# and Property-Based Testing to you
  • Represent a Rubik's Cube in F#
    • Group Theory, anyone?
  • Turn the cube
  • Scramble the cube
  • Solve the cube
    • (really inefficiently!)
  • Do some (unit, hacky performance, property-based) testing
  • Talk about how the program could be better
  • Lots of unit tests along the way!

Domain-Specific Knowledge

What makes up a

Rubik's Cube?

Centers

Edges

Corners

How does this thing move?

Since there are 6 Centers, there are 6 faces we can twist

  • R(ight)
  • L(eft)
  • U(p)
  • D(own)
  • F(ront)
  • B(ack)

There are 18 basic turns on a Rubik's Cube

  • R, R', R2
  • L, L', R2
  • U, U', R2
  • D, D', R2
  • F, F', R2
  • B, B', R2

An example algorithm

F R U R' U' F'

  • Turn the F(ront) face clockwise
  • Turn the R(ight) face clockwise
  • Turn the U(p) face clockwise
  • Turn the R(ight) face counter-clockwise
  • Turn the U(p) face counter-clockwise
  • Turn the F(ront) face counter-clockwise

Labeling pieces by their location

  • The edge in the Up-Right part of the cube is labeled the UR piece.

  • The corner in the Up-Right-Front part of the cube is labeled the UFR piece.

How do humans solve this thing?

(Demonstrating the Roux Method)

Basically the same way you solve a jigsaw puzzle

Solve a 1x2x3 Block

... and another

Then the 4 U-Layer corners

Then the rest

What is F#?

  • .NET language
    • (compiles to IL like C#/VB)
  • Shipped with VS
    • (F# 4.0 just came out!)
  • Functional-First
    • Very accepting of Imperative and OOP paradigms!
  • Statically-Typed
  • Has a REPL (we'll see that later)
  • It's like C#, but has less bugs with less code

What is Functional Programming?

  • Functions?
  • Purity!?
  • MONADS?!
  • CATEGORY THEORY!?!??

Who cares?!

(F# is awesome even without FP!)

Mo'nads, mo' problems.

Complex Things are Simple!

1: 
2: 
3: 
4: 
5: 
type Person = { First : string; Last : string }

type Employee =
    | Worker of Person
    | Manager of (Person * Employee list)

I don't even want to think about the class hierarchy in C#...

  • Base abstract "Employee" class
  • "Worker" class
  • "Manager" class
  • ... and probably all in separate .cs files!

F# can do anything C# can do

  • (abstract) Classes, Interfaces, Inheritance
  • LINQ(++), Events, Rx, etc.
  • Functions
  • XML comments
  • Web
    • MVC, Web API, NancyFx, Suave.io, Freya, Frank, SignalR, ServiceStack, etc.
    • Heck, it'll even compile down to JavaScript!
      • WebSharper, Pit, FunScript
  • WinForms/ETO/WPF
  • Xamarin

Specflow

TickSpeck

Core Domain Models

Representing a Rubik's Cube in F#

...But first, Group Theory!

Consider the numbers 0..5, under addition modulo 6.

This set has 6 numbers.

Given 2 numbers in this set, and the given operator, we will always produce another element in our group

(1 + 2) % 6 -> 3 % 6 -> 3
(4 + 5) % 6 -> 9 % 6 -> 3

Creating a CubeState Type

1: 
2: 
3: 
4: 
5: 
type CubeState = 
    { EdgePositions   : int array
      EdgeFlips       : int array
      CornerPositions : int array
      CornerTwists    : int array }

Defining a Solved Rubik's Cube

1: 
2: 
3: 
4: 
5: 
let solvedCube  =
    { EdgePositions   = [| 0 .. 11 |]
      EdgeFlips       = Array.create 12 0
      CornerPositions = [| 0 .. 7 |] 
      CornerTwists    = Array.create 8 0 }

Creating a CubeTransformation Type

1: 
2: 
3: 
4: 
type CubeTransformation = {
    Label : string
    Transformation : CubeState
}

Hold up, buddy!

Your test coverage is 0%!

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
open Xunit
open FsUnit.Xunit
open Cube

[<Fact>]
let ``Solved cube has 12 edges`` () =
    solvedCube.EdgePositions.Length |> should equal 12
    solvedCube.EdgeFlips.Length     |> should equal 12


[<Fact>]
let ``Solved cube has 8 corners`` () =
    solvedCube.CornerPositions.Length |> should equal 8
    solvedCube.CornerTwists.Length    |> should equal 8

Creating our Execute function

Porting some C# code ...

public void Multiply(CoordCube b)
{
  var edgeMult = Multiply(ep, eo, b.ep, b.eo);
  var cornerMult = Multiply(cp, co, b.cp, b.co);
  ep = edgeMult.Item1;    eo = edgeMult.Item2;
  cp = cornerMult.Item1;  co = cornerMult.Item2;
}

private Tuple<byte[], byte[]> Multiply(byte[] permA, byte[] orieA, byte[] permB, byte[] orieB)
{
  var mod = permA.Length == 8 ? 3 : 2;

  var AxB = new byte[permA.Length];
  var AxBo = new byte[permA.Length];

  for (int i = 0; i < permA.Length; i++) {
    AxB[i] = permA[permB[i]];
    AxBo[i] = (byte)((orieA[permB[i]] + orieB[i]) % mod);
  }

  return new Tuple<byte[], byte[]>(AxB, AxBo);
}

... to F#

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
let Execute (cubeState:CubeState) (t : CubeState) = 

    let inner
      (permA : int[]) (permB : int[])  
      (orientA : int[]) (orientB : int[]) 
      (n : int) =
        [|0 .. permA.Length-1|]
        |> Array.map (fun i -> 
            permA.[permB.[i]], 
            (orientA.[permB.[i]] + orientB.[i]) % n
        ) 

    let edges = inner cubeState.EP t.EP cubeState.EO t.EO 2
    let corners = inner cubeState.CP t.CP cubeState.CO t.CO 3

    { EdgePositions   = edges   |> Array.map(fun i -> fst i)
      EdgeFlips       = edges   |> Array.map(fun i -> snd i)
      CornerPositions = corners |> Array.map(fun i -> fst i)
      CornerTwists    = corners |> Array.map(fun i -> snd i) }

Testing the Identity

1: 
2: 
3: 
4: 
5: 
[<Fact>]
let ``Solved cube performed on self is solved`` () =
    solvedCube 
    |> Execute solvedCube 
    |> should equal solvedCube

Defining our Core 6 Turns

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
let R =
    { EdgePositions   = [| 8; 1; 2; 3; 11; 5; 6; 7; 4; 9; 10; 0 |] 
      EdgeFlips       = Array.create 12 0                           
      CornerPositions = [| 4; 1; 2; 0; 7; 5; 6; 3 |]                  
      CornerTwists    = [| 2; 0; 0; 1; 1; 0; 0; 2 |] }

let U = ...
let F = ...
let D = ...
let L = ...
let B = ...

Creating some Execution Helpers

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
let rec ExecuteN cubeState transformation n =
    match n with
    | 0 -> cubeState
    | 1 -> Execute cubeState transformation
    | n when n > 1 -> 
        ExecuteN cubeState transformation (n-1)
        |> Execute transformation
    | _ -> failwith "need moar n"


let Combine (moves : CubeState list) = 
    List.fold Execute moves.Head moves.Tail

Deriving the other 12 Turns

1: 
2: 
let R2 = ExecuteN solvedCube R 2
let R' = ExecuteN solvedCube R 3

...

Creating a Moveset

1: 
2: 
3: 
4: 
5: 
6: 
7: 
type MoveSets =
    static member All = 
        [ { Label  = "F";  Transformation = F }    
          { Label  = "F'"; Transformation = F' }
          { Label  = "F2"; Transformation = F2 }
        ...
          { Label  = "B2"; Transformation = B2 } ]

More Tests!

(Fun with Structural Equality!)

1: 
2: 
3: 
4: 
[<Fact>]
let ``Given a solved cube, R should mess the cube up`` () =
    solvedCube |> Execute R 
    |> should  not' (equal solvedCube)

More Tests (Cont.)

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
[<Fact>]
let ``n four times should result in a solved cube`` () =
    let movesToTry = [| U; U'; R; R'; F; F'; D; D'; L; L'; B; B' |]

    for move in movesToTry do
         ExecuteN solvedCube move 4 
         |> should equal solvedCube

[<Fact>]
let ``T-perm twice results in a solved cube`` () =
    let tPerm = Combine [R; U; R'; U'; R'; F; R2; U'; R'; U'; R; U; R'; F']

    let endState = ExecuteN solvedCube tPerm 2

    endState |> should equal solvedCube

Creating a Scrambler

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
let Scramble cubeState (movesAllowed : CubeTransformation list) n =
    let rnd = new System.Random()

    let mutable scrambledCube = cubeState
    let mutable movesPerformed = []

    for i = 1 to n do
        let nextMove = movesAllowed.[rnd.Next(movesAllowed.Length)]
        scrambledCube <- scrambledCube |> Execute nextMove.Transformation
        movesPerformed <- nextMove.Label :: movesPerformed

    (scrambledCube, System.String.Join(" ", movesPerformed))

A simple Scrambler Test

1: 
2: 
3: 
4: 
[<Fact>]
let ``Scrambles should result in a scrambled cube`` () =
    Scramble solvedCube Movesets.MoveSets.All 25 
    |> should not' (equal solvedCube)

OK

Let's solve it!

Basic Tree Search

What is a Basic Tree Search?

Parameters:

  • Set of allowed moves
  • N, the number of layers deep to look
 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
let BasicTreeSearch scrambledState maxDepth (movesAllowed: CubeTransformation list) =

    let solutions = ref []

    let rec BasicTreeSearch_Internal cubeState d (movesSoFar : string list) =
        if cubeState = solvedCube then 
           solutions:= (movesSoFar |> String.concat " " ) :: !solutions 

        elif d > 0 then
            movesAllowed 
            |> List.iter
                (fun move -> 
                    let newState = (cubeState |> Execute move.Transformation)

                    BasicTreeSearch_Internal newState (d-1) (move.Label :: movesSoFar) 
                ) 

    BasicTreeSearch_Internal scrambledState maxDepth []
    solutions.Value

Wait, but isn't that horrible?

Yeah.

If searching 2 deep, there are 324 possibilities

If searching 9 deep, there are 198,359,290,368 possibilities

...

Cubes can be scrambled up to 20 moves deep...

IDA to the Rescue?

Not really,

but let's look at it anyways.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
let IDASearch scrambledState maxDepth (movesAllowed: CubeTransformation list) =

    let mutable solutions = []

    for a in 1 .. maxDepth do
        if solutions.IsEmpty then

            let treeSearchResponse = BasicTreeSearch scrambledState a movesAllowed

            if not treeSearchResponse.IsEmpty then
                solutions <- treeSearchResponse

    solutions
1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
[<Fact>]
let ``Scrambled cube should be solvable`` () =
    let depth = 3

    let scrambledCube = Scramble solvedCube Movesets.MoveSets.All depth
    let actualCube = fst scrambledCube

    let response =  IDASearch actualCube depth Movesets.MoveSets.All
    (response |> List.length) > 0 |> should equal true

Ok, but how do we solve this more efficiently?

Better algorithms, of course.

  • Thistlethwaite's Algorithm
  • Kociemba's Algorithm
  • Korf's Algorithm
    • IDA*

Questions?

Time not over?

Let's look at more F#!