This has been the most sophisticated problem yet.
My first solution took 2 minutes to write, but I gave up on it after 5 minutes of running.
let problem12a = let divisors n = seq {for i in 1..n/2 do if n % i = 0 then yield i} let rec find i last = if last |> divisors |> Seq.length > 500 then last else find (i+1) (last+(i+1)) find 1 1
Then I realized I had a fast function for building a list of prime factors from Problem 3 which I could use to generate a list of all divisors. I abused that into giving me the answer in about 1 minute.
let problem12b = let divisors n = let rec divisors n j list = if n = 1I then list elif n % j = 0I then divisors (n/j) 2I (list @ j::(list |> List.map (fun x -> x*j))) else divisors n (j + 1I) (list) 1I::(divisors n 2I []) |> List.toSeq |> Seq.distinct let rec find i last = if last |> divisors |> Seq.length > 500 then last else find (i+1I) (last+(i+1I)) find 1I 1I
But then I remembered my Number Theory days. Given a prime factorization p1**n1…p2**n2…p3**n3… the number of divisors is equal to (n1 + 1)*(n2 + 1)*(n3 + 1)*…
let problem12c = let factorize n = let rec factorize n j list = if n = 1 then list elif n % j = 0 then factorize (n/j) 2 (j::list) else factorize n (j + 1) (list) factorize n 2 [] let countDivisors n = let rec countDivisors flist count = match flist with | [] -> count | f::list -> let plist = list |> List.partition (fun i -> i = f) countDivisors (snd plist) (((fst plist |> List.length)+2)*count) countDivisors (factorize n) 1 let rec find i last = if last |> countDivisors > 500 then last else find (i+1) (last+(i+1)) find 1 1
That gave me the answer in less than a second.
Here’s another version of (c) using Seq.groupBy instead of recursively apply List.partition. It runs in about the same amount of time.
let problem12d = let factorize n = let rec factorize n j list = if n = 1 then list elif n % j = 0 then factorize (n/j) 2 (j::list) else factorize n (j + 1) (list) factorize n 2 [] let countDivisors n = factorize n |> Seq.groupBy id |> Seq.fold (fun acc (f,s) -> ((Seq.length s) + 1)*acc) 1 let rec find i last = if last |> countDivisors > 500 then last else find (i+1) (last+(i+1)) find 1 1
Great blog. I'm new at F# and you're ahead of me in the game, but here's my even faster solution to Puzzle 12. It exits factors loop as soon as it's got highest prime.
ReplyDeletelet Puzzle12 =
let factors num =
let rec cycle num div lastdiv cntfac acc =
if div <> lastdiv && div*lastdiv > num then acc*(cntfac+2)
elif num < div then acc*(cntfac+1)
elif num%div = 0L then cycle (num/div) div div (cntfac+1) acc
elif div = 2L then cycle num 3L lastdiv 0 (acc*(cntfac+1))
else cycle num (div+2L) lastdiv 0 (acc*(cntfac+1))
cycle num 2L 1L 0 1
{ 1L .. System.Int64.MaxValue }
|> Seq.map ( fun x -> x*(x+1L)/2L )
|> Seq.map ( fun x -> x, factors x )
|> Seq.find ( fun (x, y) -> y > 500 )
Thanks Timothy, I've enjoyed making it and I'm glad you're enjoying it too.
ReplyDeleteNice and fast solution! Though I admit the theory behind your factor function alludes me at first glance.