I have this little exercise writing a binary trie in F#. In C# it can be done in few lines, but here it became more complicated than I had expected (especially the add
function):
open System
type BitTrie =
| BitTrie of Zero:BitTrie * One:BitTrie
| NullTrie
module BitTrie =
let ubound = 31 // 32 bits integer
let add value trie =
let rec adder shift curTrie =
if shift < 0 then curTrie
else
match (value >>> shift) &&& 1, curTrie with
| 0, BitTrie(zero, one) ->
match zero with
| BitTrie(_) -> BitTrie(adder (shift - 1) zero, one)
| NullTrie -> BitTrie(adder (shift - 1) (BitTrie(NullTrie, NullTrie)), one)
| 1, BitTrie(zero, one) ->
match one with
| BitTrie(_) -> BitTrie(zero, adder (shift - 1) one)
| NullTrie -> BitTrie(zero, adder (shift - 1) (BitTrie(NullTrie, NullTrie)))
| bit, _ -> raise (InvalidOperationException(sprintf "Invalid bit value: %d" bit))
adder ubound trie
let contains value trie =
let rec searcher shift curTrie =
if shift < 0 then curTrie <> NullTrie
else
match (value >>> shift) &&& 1, curTrie with
| _, NullTrie -> false
| 0, BitTrie(zero, _) -> searcher (shift - 1) zero
| 1, BitTrie(_, one) -> searcher (shift - 1) one
| bit, _ -> raise (InvalidOperationException(sprintf "Invalid bit value: %d" bit))
searcher ubound trie
let isEmpty trie =
match trie with
| NullTrie -> true
| BitTrie(NullTrie, NullTrie) -> true
| BitTrie(_) -> false
let create () = BitTrie(NullTrie, NullTrie)
let addValues values trie =
values |> Seq.fold (fun accTrie value -> add value accTrie) trie
let fromValues values = create() |> addValues values
let toSeq trie =
let rec iterator value curTrie =
seq {
match curTrie with
| NullTrie -> ()
| BitTrie(NullTrie, NullTrie) -> yield value
| BitTrie(zero, one) ->
yield! (iterator (value <<< 1) zero)
yield! (iterator ((value <<< 1) + 1) one)
}
iterator 0 trie
let toList trie = trie |> toSeq |> Seq.toList
let toArray trie = trie |> toSeq |> Seq.toArray
let iter action trie = trie |> toSeq |> Seq.iter action
I know this pattern match
bit, _ -> raise (InvalidOperationException(sprintf "Invalid bit value: %d" bit))
will never happen, but hate to leave the compiler unsatisfied.
Here are some test cases:
let testBitTrie () =
let values = [ 0b1011001; 0b1101101; 0b1011011; Int32.MaxValue; (Int32.MaxValue >>> 16) - 1; 0; -1; Int32.MinValue ]
let trie = values |> BitTrie.fromValues
trie |> BitTrie.iter (fun v -> printfn "%s -> %d" (Convert.ToString(v, 2)) v)
printfn ""
trie
|> BitTrie.addValues [ 35; 45 ]
|> BitTrie.iter (fun v -> printfn "%s -> %d" (Convert.ToString(v, 2)) v)
//values @ [1; 2; 4; 100] |> Seq.iter (fun v -> printfn "%d: %b" v (trie |> BitTrie.contains (v)))
let solveXorMax () =
//let rand = new Random(1);
//let values = Enumerable.Range(0, 5000).Select(fun x -> rand.Next(rand.Next(0, 10000000))).ToList();
//let trie = values |> BitTrie.fromValues
let values = [ 3; 10; 5; 2; 25; 8 ]
let trie = values |> BitTrie.fromValues
let folder (max: int) value =
let rec iter shift xor curTrie : int =
if shift <= 0 then Math.Max(max, xor)
else
match (value >>> shift) &&& 1, curTrie with
| 0, BitTrie(zero, one) ->
match zero, one with
| _, BitTrie(_) -> iter (shift - 1) (xor + (1 <<< shift)) one
| BitTrie(_), _ -> iter (shift - 1) xor zero
| 1, BitTrie(zero, one) ->
match zero, one with
| BitTrie(_), _ -> iter (shift - 1) (xor + (1 <<< shift)) zero
| _, BitTrie(_) -> iter (shift - 1) xor one
iter BitTrie.ubound 0 trie
let max = values |> Seq.fold folder (Int32.MinValue)
printfn "%d" max
solveXorMax ()
finds the maximum xor value of two values in the input set - taken from this question
I'm interested in any comment, but especially if you can see a smarter/shorter approach for the add
function. My self-imposed restriction was to use a discriminated union type as data type for the trie node.