Skip to content

Create example.fs #1030

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions exercises/practice/alphametics/example.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
module Alphametics

module Combinatorics =
// https://stackoverflow.com/questions/286427/calculating-permutations-in-f
let permutations list =
let rec insertions x = function
| [] -> [[x]]
| y::ys as xs -> (x::xs)::(insertions x ys |> List.map(fun xs' -> y::xs'))
list |> Seq.fold (fun accum x -> Seq.collect (insertions x) accum) (seq [List.empty])

// https://stackoverflow.com/questions/4495597/combinations-and-permutations-in-f Tomas
let rec combinations acc size set = seq {
match size, set with
| n, x::xs ->
if n > 0 then yield! combinations (x::acc) (n - 1) xs
if n >= 0 then yield! combinations acc n xs
| 0, [] -> yield acc
| _, [] -> () }

let permsOf k list =
if k < List.length list then combinations [] k list |> Seq.collect (fun p -> permutations p )
else permutations list

open Combinatorics

let unknowns = String.filter(" =+".Contains >> not) >> Seq.distinct >> List.ofSeq
// minimize summing by counting repetitions
let parse =
Array.rev
>> Array.map Seq.rev
>> Seq.transpose
>> Seq.map (fun col -> col |> Seq.head, col |> Seq.tail |> Seq.countBy id |> Array.ofSeq)
>> List.ofSeq // List much, much faster than Seq here! This was the main bottleneck!
let noLeadingZero = Array.map Seq.head >> Set

// pre-compute as much as possible including using tokens (array indices) instead of chars - for array lookup Big-O(1)
let mapCharsToTokens (chars:char list)= chars |> List.mapi (fun i c -> c,i) |> Map.ofList
let tokenise (input:string) (tokens:Map<char,int>) =
input.Replace("==","=")
|> String.filter((<>) ' ' )
|> fun compact -> compact.Split([|'+';'='|])
|> Array.map (Seq.map (fun c -> tokens.[c]))

let buildZeroMask size (noZeroSet:Set<int>) = [| for i in [0..size - 1] do (noZeroSet.Contains i) = false |]

let rec colSum chars (remain:(int*((int*int) []))list) carry (arr:int[]) =
match carry, remain with
| 0, [] -> arr |> Seq.zip chars |> Map.ofSeq |> Some // hence only done when solved
| _, [] -> None
| c, (y,x)::tail ->
let sum = x |> Array.sumBy (fun (key,count) -> count * arr.[key]) |> (+) c
if arr.[y] = (sum % 10) then colSum chars tail (sum / 10) arr else None

let solve puzzle =
let chars = unknowns puzzle
let k = chars |> List.length
let tokens = chars |> mapCharsToTokens |> tokenise puzzle
let columns = tokens |> parse
let zeroMask = tokens |> noLeadingZero |> buildZeroMask k
// Array.item is Big-O(1) >> Set.contains Big-O(log(n)) for F# Set hence zeroMask array
let canBeZero = Array.tryFindIndex ((=)0) >> Option.forall(fun i -> zeroMask.[i])

//Insertions
permsOf k [0..9]
|> Seq.map Array.ofList // array lookup Big-O(1) vs. map lookup Big-O(log(n)) & same cost for building here
|> Seq.filter canBeZero // slower to filter during permutations generation
|> Seq.tryPick (colSum chars columns 0) // uses efficient and minimal column carry short-circuit calculations