我需要你的优化帮助。这是一个问题:
使用数字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 的结果。我该如何优化它?
答案 0 :(得分:2)
我们可以在生成部分答案集方面解决问题:num k 1
是集合{k},num 2
是将num k 1
与{{{}}组合而生成的所有数字的集合{1}},num k 1
是将num k 3
与num k 1
相结合生成的所有数字的集合,依此类推。每个步骤使用先前步骤中计算的集合,并应用一个运算符。这是前3个步骤。请注意,每个数字是使用两个先前生成的数字和一个运算符计算的。
num k 2
= {3} num 3 1
= {3-3 = 0,3 / 3 = 1,3 = 3,3 + 3 = 6,3 * 3 = 9} 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} 基于列表的num 3 3
功能正在重新计算之前的步骤,原因有两个。
num
从头开始重新计算所有先前的步骤。例如,n
将计算num x 4
,num x 1
和num x 2
。然后,num x 3
将再次计算num x 3
和num 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
,因为这会做多余的工作。不是递归调用,而是保存num
,num k 1
,... num k 2
的结果列表,并将此列表传递给num k (n-1)
。另外,使用num k n
模块删除重复值,而不是调用Data.Set
。