我一直在寻找一种优雅的方式来编写这段代码:
import Data.List
import Data.Maybe
combi = [(x,y) | x <- [2..100], y <- [x..100]]
gsp = group (sort [x*y | (x,y) <- combi])
counts = zip (map head gsp) (map length gsp)
multipleProducts x = (fromJust (lookup x counts)) > 1
possibleComb1 = [(x,y) | (x,y) <- combi, multipleProducts (x*y)]
由于我多次重复使用相同的模式,但基于不同的输入集而不是[x*y | (x,y) <- combi]
,我得出了这段代码。
import Data.List
import Data.Maybe
combi = [(x,y) | x <- [2..100], y <- [x..100]]
onlyOneEl e x = (fromJust (lookup x counts)) == 1
where gs = group (sort e)
counts = zip (map head gs) (map length gs)
multipleProducts = not.(onlyOneEl [x*y | (x,y) <- combi])
possibleComb1 = [(x,y) | (x,y) <- combi, multipleProducts (x*y)]
然而,Haskell似乎计算gs
和count
每次调用multipleProducts,花费大量时间,而不是只计算一次,因为e的值总是与multipleProducts相同。
避免重新计算的最优雅方法是什么?
有没有什么比使用一个函数预先计算counts
并将其存储在局部变量中,然后将其传递给onlyOneEl
而没有哪里更好?
因为我后来根据不同的集合重用onlyOneEl
,我想避免使用多个counts
变量。
我理解here为什么每个函数没有对它进行一次评估,但是,我不使用x作为我的最后一个参数,因此不能完全按照这种方式进行。
提前致谢!
答案 0 :(得分:3)
你可以用更多的目标来重写它。没有进入数学,只需生成数据和过滤,您就可以用更少的计算来实现相同的目标。
生成产品时,也要将乘数加到元组中,即
combi n = [((x,y),x*y) | x<-[2..n], y<-[x..n]]
现在您可以根据产品进行排序和分组
multi = filter ((>1) . length) . groupBy ((==) `on` snd) . sortBy (comparing snd) . combi
并提取元组的第一个元素,它将是(x,y)对,不止一次地提供相同的产品。
map (map fst) (multi 100)
如果你不关心分组,你可以压扁结果,即
concatMap (map fst) (multi 100)
答案 1 :(得分:2)
定义
e
说“给定x
和gs
,设置counts
和fromJust (lookup x counts) == 1
的计算,并使用他们(懒惰计算)的结果来计算表达式{{1}您可以将其完全等效地写为
onlyOneEl e x =
let gs = ...
counts = ...
in fromJust ...
另一方面,如果使用lambda表达式将x
移到另一侧,
onlyOneEl e = \x -> fromJust ...
where ...
然后将gs
和counts
拉入外部范围。此代码等同于
onlyOneEl e =
let gs = ...
counts = ...
in \x -> fromJust ...
所以gs
和counts
只会在onlyOneEl
的每个应用中计算一次。
GHC支持一种称为“完全懒惰”的转换,它进行了这种修改,当它认为这是一个好主意时它适用。显然,GHC在这种情况下做出了错误的判断。