如何在Haskell中优化此代码

时间:2014-06-04 21:10:59

标签: haskell optimization

我需要你的优化帮助。这是一个问题:

  

使用数字3的10倍,与通常的算术运算符相结合,无法获得的最小(严格为正)自然数是多少?

这是我的解决方案:

import Ratio

num x 1 = [x]
num x n = num'' $ concat $ [ num' a b | i <- [1..n`quot`2], a <- num x i, b <- num x (n-i) ]
  where  num' a b | a==0      = [b] 
                  | b==0      = [a]
                  | otherwise = [a+b,abs(a-b),a*b,a/b,b/a]
         num'' (x:r) = x : num'' (filter (x/=) r)
         num'' _      = []

cint x = map numerator . filter ((1==) . denominator) . num x

firstNumber x n = take 1 [ i | i <- [1..], i `notElem` (cint x n) ]

但效率低下。例如,当我调用 firstNumber 3 7 时,查看结果需要将近30秒。但是,虽然我已经等了很长时间,但我还没有看到 firstNumber 3 10 的结果。我该如何优化它?

1 个答案:

答案 0 :(得分:2)

我们可以在生成部分答案集方面解决问题:num k 1是集合{k},num 2是将num k 1与{{{}}组合而生成的所有数字的集合{1}},num k 1是将num k 3num k 1相结合生成的所有数字的集合,依此类推。每个步骤使用先前步骤中计算的集合,并应用一个运算符。这是前3个步骤。请注意,每个数字是使用两个先前生成的数字和一个运算符计算的。

  1. num k 2 = {3}
  2. num 3 1 = {3-3 = 0,3 / 3 = 1,3 = 3,3 + 3 = 6,3 * 3 = 9}
  3. num 3 2 = {3-9 = -6,3-6 = -3,1-3 = -2,0 = 0,1 / 3 = 1 / 3,3 / 6 = 1/2 ,1 = 1,6 / 3 = 2,3 = 3,3 + 1 = 4,6 = 6,9 = 9,3 + 9 = 12,3 * 6 = 18,3 * 9 = 27}
  4. 基于列表的num 3 3功能正在重新计算之前的步骤,原因有两个。

    • 步骤num从头开始重新计算所有先前的步骤。例如,n将计算num x 4num x 1num x 2。然后,num x 3将再次计算num x 3num x 1
    • 在内循环中对num x 2进行递归调用。具体来说,您有num。这将为[... | ... a <- num x i, b <- num x (n-i) ]的每个值重新计算num x (n-i)。您可以通过编写a将递归调用移出内循环。 (编译器优化可以自动执行此操作,但您不应该依赖它。)

    您应该保存并重复使用它们,而不是重新计算以前的结果。最简单的方法是在算法进行时保存先前结果的列表。转换代码以保存以前的结果是一种更通用的技术实例,称为 memoization

    效率低下的另一个原因是[... | ... let b_input = num x (n-i), a <- num x i, b <- b_input],它会搜索整个列表以删除二次时间内的重复项。使用num''模块中的集合,可以在n * log(n)时间内删除重复项。

    总之,在Data.Set中,不要递归调用num k n,因为这会做多余的工作。不是递归调用,而是保存numnum k 1,... num k 2的结果列表,并将此列表传递给num k (n-1)。另外,使用num k n模块删除重复值,而不是调用Data.Set