Wednesday, May 12, 2010

Problem 12: What is the value of the first triangle number to have over five hundred divisors?

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

2 comments:

  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.

    let 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 )

    ReplyDelete
  2. Thanks Timothy, I've enjoyed making it and I'm glad you're enjoying it too.

    Nice and fast solution! Though I admit the theory behind your factor function alludes me at first glance.

    ReplyDelete