Code Snippet:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
type SeqMonad() =
  member t.Bind(m,f) = Seq.concat(Seq.map f m)
  member t.Return v = seq{ yield v }
let seqMonad = SeqMonad()

let permutations ls = 
  let rec insertions x = function
    | []             -> [[x]]
    | (y :: ys) as l -> (x::l)::(List.map (fun x -> y::x) (insertions x ys))
  let rec permutations' = function
    | []      -> seq [ [] ]
    | x :: xs -> Seq.concat (Seq.map (insertions x) (permutations' xs))
  ls |> permutations'

let md5 s =
  System.BitConverter
    .ToString(
      System.Security.Cryptography.MD5
        .Create()
        .ComputeHash(buffer = System.Text.Encoding.UTF8.GetBytes(s = s)))
    .Replace("-", System.String.Empty)
    .ToLower()

let factorial n = 
  let rec fact acc = function | 0 -> acc | i -> fact (acc * i) (i - 1)
  (1,n) ||> fact

let unitTestPermutations () = 
  "FooBar" 
  |> Seq.toList
  |> fun xs -> xs |> permutations |> Seq.length,
               xs |> Seq.length   |> factorial
  |> fun (x,y) -> x = y

let unitTestMD5 () =  
  // [ mon@mbai7 tmp ] md5 -s "FooBar"
  // MD5 ("FooBar") = f32a26e2a3a8aa338cd77b6e1263c535
  "FooBar" |> md5 |> fun x -> x = "f32a26e2a3a8aa338cd77b6e1263c535"

(unitTestPermutations() && unitTestMD5()) |> function 
  | true -> () 
  | false -> failwith "Must be n! permuations per string"

let cache file =
  use reader = System.IO.File.OpenText(file)
  let rec cache' acc = function
    | true -> acc
    | false -> cache' (acc |> Set.add(reader.ReadLine())) reader.EndOfStream
  cache' Set.empty reader.EndOfStream

let root     = __SOURCE_DIRECTORY__
let wordList = System.IO.Path.Combine(root,"wordlist.txt")
let anagram  = @"poultry outwits ants"
let hash     = @"4624d200580677270a54ccff86b9610e"
let words    = anagram.Split(char " ")
let cached   = wordList |> cache

words |> Array.map(fun x  -> x  |> Seq.toList |> permutations)
      |> Array.map(fun xs -> xs |> Seq.map(fun ys -> ys |> List.map string))
      |> Array.map(fun xs -> xs |> Seq.map(fun ys -> ys |> List.reduce(+)))
      |> Array.map(fun xs -> xs |> Seq.filter(fun x -> (x,cached) ||> Set.contains))
      |> fun xs -> xs.[0],xs.[1],xs.[2]
      |> fun (xs,ys,zs) -> seqMonad{let! x = xs
                                    let! y = ys
                                    let! z = zs
                                    return (x,y,z)}
      |> Seq.map(fun (x,y,z) -> x + " " + y + " " + z)
      |> Seq.map(fun x -> x |> md5, x)
      |> Seq.filter(fun (x,y) -> x = hash)
      |> Seq.map(fun (x,y) -> y)
      |> Seq.truncate 1
      |> fun x -> x |> printfn "%A"

Code output:

type SeqMonad =
  class
    new : unit -> SeqMonad
    member Bind : m:seq<'b> * f:('b -> #seq<'d>) -> seq<'d>
    member Return : v:'a -> seq<'a>
  end
val seqMonad : SeqMonad
val permutations : ls:'a list -> seq<'a list>
val md5 : s:string -> string
val factorial : n:int -> int
val unitTestPermutations : unit -> bool
val unitTestMD5 : unit -> bool
val cache : file:string -> Set<string>
val root : string = "/Users/mon/tmp"
val wordList : string = "/Users/mon/tmp/wordlist.txt"
val anagram : string = "poultry outwits ants"
val hash : string = "4624d200580677270a54ccff86b9610e"
val words : string [] = [|"poultry"; "outwits"; "ants"|]
val cached : Set<string> =
  set
    ["a"; "a's"; "ab's"; "abaci"; "aback"; "abacus"; "abacus's"; "abacuses";
     "abaft"; ...]

Code result:

> seq []
> val it : unit = ()

References: