优化欧拉#4的F#答案

时间:2014-04-18 18:42:36

标签: performance algorithm f#

我最近开始学习F#。希望用它来在C#应用程序中执行任何数学上繁重的算法并扩展我的知识

到目前为止,我已经避免了StackOverflow,因为在我自己找到它之前我不想看到答案。

我希望能够编写非常高效的F#代码,专注于性能,然后可能以其他方式,例如简明地写入F#(行数等)。

项目欧拉问题4:

  

回文数字两种方式相同。由两个2位数字的乘积制成的最大回文是9009 = 91×99。

     

查找由两个3位数字的乘积制成的最大回文。

我的回答:

let IsPalindrome (x:int) = if x.ToString().ToCharArray() = Array.rev(x.ToString().ToCharArray()) then x else 0

let euler4 = [for i in [100..999] do
            for j in [i..999] do yield i*j]
                |> Seq.filter(fun x -> x = IsPalindrome(x)) |> Seq.max |> printf "Largest product of two 3-digit numbers is %d"

我尝试使用option并在Some(x)中返回NoneIsPalindrome,但在传递int并返回{{}时不断编译错误1}}。尝试返回int option时出现NullRefenceException。 相反,如果数字不是回文,我会返回0,不幸的是,这些0进入了序列。

也许我可以订购序列然后获得最高价值?而不是使用Seq.Max?或者过滤掉结果> 1?

这会更好吗?任何建议都会非常感激,即使它是一般的F#建议。

6 个答案:

答案 0 :(得分:4)

效率是一个主要问题,使用字符串分配/操作来查找数字回文似乎是错误的 - 这是我的方法:

module NumericLiteralG =
    let inline FromZero () = LanguagePrimitives.GenericZero
    let inline FromOne () = LanguagePrimitives.GenericOne

module Euler =
    let inline isNumPalindrome number =
        let ten = 1G + 1G + 1G + 1G + 1G + 1G + 1G + 1G + 1G + 1G
        let hundred = ten * ten
        let rec findHighDiv div =
            let div' = div * ten
            if number / div' = 0G then div else findHighDiv div'
        let rec impl n div =
            div = 0G || n / div = n % ten && impl (n % div / ten) (div / hundred)
        findHighDiv 1G |> impl number

    let problem004 () =
        { 100 .. 999 }
        |> Seq.collect (fun n -> Seq.init (1000 - n) ((+) n >> (*) n))
        |> Seq.filter isNumPalindrome
        |> Seq.max

答案 1 :(得分:3)

这是一种方法:

/// handy extension for reversing a string
type System.String with
    member s.Reverse() = String(Array.rev (s.ToCharArray()))

let isPalindrome x = let s = string x in s = s.Reverse()

seq {
    for i in 100..999 do
    for j in   i..999 -> i * j
}
|> Seq.filter isPalindrome
|> Seq.max
|> printfn "The answer is: %d"

答案 2 :(得分:1)

let IsPalindrom (str:string)=
  let rec fn(a,b)=a>b||str.[a]=str.[b]&&fn(a+1,b-1)
  fn(0,str.Length-1)
let IsIntPalindrome = (string>>IsPalindrom)
let sq={100..999}
sq|>Seq.map (fun x->sq|>Seq.map (fun y->(x,y),x*y))
  |>Seq.concat|>Seq.filter (snd>>IsIntPalindrome)|>Seq.maxBy (snd)

答案 3 :(得分:0)

只是我的解决方案:

let isPalin x =
    x.ToString() = new string(Array.rev (x.ToString().ToCharArray()))
let isGood num seq1 = Seq.exists (fun elem -> (num % elem = 0 && (num / elem) < 999)) seq1
{998001 .. -1 .. 10000} |> Seq.filter(fun x -> isPalin x) |> Seq.filter(fun x -> isGood x {999 .. -1 .. 100}) |> Seq.nth 0

答案 4 :(得分:0)

  • 最简单的方法是从999到100,因为很可能是两个大数字的产物。
  • j然后可以从i开始,因为其他方式已经过测试
  • 其他优化将进入多方位降序的方向,但这会使一切变得更加困难。通常,它表示为列表合并。

Haskell(我在函数式编程中的最佳尝试)

merge f x [] = x
merge f [] y = y
merge f (x:xs) (y:ys)
               | f x y     =  x : merge f xs     (y:ys) 
               | otherwise =  y : merge f (x:xs)   ys 

compare_tuples (a,b) (c,d) = a*b >= c*d

gen_mul n = (n,n) : merge compare_tuples 
                         ( gen_mul (n-1) ) 
                         ( map (\x -> (n,x)) [n-1,n-2 .. 1] )

is_product_palindrome (a,b) = x == reverse x where x = show (a*b)

main = print $ take 10 $  map ( \(a,b)->(a,b,a*b) ) 
   $ filter is_product_palindrome $  gen_mul 9999

输出(小于1s) - 前10个回文=&gt;

[(9999,9901,99000099),
 (9967,9867,98344389),
 (9999,9811,98100189),
 (9999,9721,97200279),
 (9999,9631,96300369),
 (9999,9541,95400459),
 (9999,9451,94500549),
 (9767,9647,94222249),
 (9867,9547,94200249),
 (9999,9361,93600639)]

可以看出这个序列是从大到小生成的延迟

答案 5 :(得分:0)

优化版本:

let Euler dgt=
  let [mine;maxe]=[dgt-1;dgt]|>List.map (fun x->String.replicate x "9"|>int)
  let IsPalindrom (str:string)=
    let rec fn(a,b)=a>b||str.[a]=str.[b]&&fn(a+1,b-1)
    fn(0,str.Length-1)
  let IsIntPalindrome = (string>>IsPalindrom)
  let rec fn=function
    |x,y,max,a,_ when a=mine->x,y,max
    |x,y,max,a,b when b=mine->fn(x,y,max,a-1,maxe)
    |x,y,max,a,b->a*b|>function
                   |m when b=maxe&&m<max->x,y,max
                   |m when m>max&&IsIntPalindrome(m)->fn(a,b,m,a-1,maxe)
                   |m when m>max->fn(x,y,max,a,b-1)
                   |_->fn(x,y,max,a-1,maxe)
  fn(0,0,0,maxe,maxe)

记录(开启#time):

> Euler 2;;
Real: 00:00:00.004, CPU: 00:00:00.015, GC gen0: 0, gen1: 0, gen2: 0
val it : int * int * int = (99, 91, 9009)
> Euler 3;;
Real: 00:00:00.004, CPU: 00:00:00.015, GC gen0: 0, gen1: 0, gen2: 0
val it : int * int * int = (993, 913, 906609)
> Euler 4;;
Real: 00:00:00.002, CPU: 00:00:00.000, GC gen0: 0, gen1: 0, gen2: 0
val it : int * int * int = (9999, 9901, 99000099)
> Euler 5;;
Real: 00:00:00.702, CPU: 00:00:00.686, GC gen0: 108, gen1: 1, gen2: 0
val it : int * int * int = (99793, 99041, 1293663921) //int32 overflow

BigInteger的外部:

let Euler dgt=
  let [mine;maxe]=[dgt-1;dgt]|>List.map (fun x->new System.Numerics.BigInteger(String.replicate x "9"|>int))
  let IsPalindrom (str:string)=
    let rec fn(a,b)=a>b||str.[a]=str.[b]&&fn(a+1,b-1)
    fn(0,str.Length-1)
  let IsIntPalindrome = (string>>IsPalindrom)
  let rec fn=function
    |x,y,max,a,_ when a=mine->x,y,max
    |x,y,max,a,b when b=mine->fn(x,y,max,a-1I,maxe)
    |x,y,max,a,b->a*b|>function
                   |m when b=maxe&&m<max->x,y,max
                   |m when m>max&&IsIntPalindrome(m)->fn(a,b,m,a-1I,maxe)
                   |m when m>max->fn(x,y,max,a,b-1I)
                   |_->fn(x,y,max,a-1I,maxe)
  fn(0I,0I,0I,maxe,maxe)

检查:

Euler 5;;
Real: 00:00:02.658, CPU: 00:00:02.605, GC gen0: 592, gen1: 1, gen2: 0
val it :
  System.Numerics.BigInteger * System.Numerics.BigInteger *
  System.Numerics.BigInteger =
  (99979 {...}, 99681 {...}, 9966006699 {...})