嗯,这比我想象的要容易。这将在我家里的慢速 PC 上在 0.05 秒内完成 1000 次 Hammings。今天下午在工作中,不到 600 次的更快 PC 时间以零秒出现。
这从 Hammings 那里得到了 Hammings。它基于在 Excel 中执行速度最快。
我在 250000 之后得到了错误的数字,Int。数字增长得非常快非常快,所以必须使用Integer,因为Int 是有界的。
mkHamm :: [Integer] -> [Integer] -> [Integer] -> [Integer]
-> Int -> (Integer, [Int])
mkHamm ml (x:xs) (y:ys) (z:zs) n =
if n <= 1
then (last ml, map length [(x:xs), (y:ys), (z:zs)])
else mkHamm (ml++[m]) as bs cs (n-1)
where
m = minimum [x,y,z]
as = if x == m then xs ++ [m*2] else (x:xs) ++ [m*2]
bs = if y == m then ys ++ [m*3] else (y:ys) ++ [m*3]
cs = if z == m then zs ++ [m*5] else (z:zs) ++ [m*5]
测试,
> mkHamm [1] [2] [3] [5] 5000
(50837316566580,[306,479,692]) -- (0.41 secs)
> mkHamm [1] [2] [3] [5] 10000
(288325195312500000,[488,767,1109]) -- (1.79 secs)
> logBase 2 (1.79/0.41) -- log of times ratio =
2.1262637726461726 -- empirical order of growth
> map (logBase 2) [488/306, 767/479, 1109/692] :: [Float]
[0.6733495, 0.6792009, 0.68041545] -- leftovers sizes ratios
这意味着此代码的运行时间 empirical order of growth 高于二次(~n^2.13 在 GHCi 提示符下测量、解释)。
此外,序列中三个悬垂的过度生产片段的大小各为 ~n^0.67,即 ~n^(2/3)。
此外,这段代码是非惰性的:结果序列的第一个元素只能在最后一个元素被计算后才能访问。
问题中最先进的代码是线性的,在兴趣点之后正好过量生成 0 个元素,并且非常懒惰:它立即开始生成数字。
因此,尽管这张海报对之前的答案有很大的改进,但它仍然比原来的答案差很多,更不用说它出现在前两个答案中的改进了。
12.31.2018
只有最优秀的人才接受教育。 @Will Ness 还在 GoalKicker.com “Haskell for Professionals”中撰写或共同撰写了 19 个章节。免费的书是宝藏。
我已经提出了一个可以做到这一点的函数的想法,就像这样。我很担心,因为我认为它会像某些现代语言一样复杂且涉及逻辑。我决定开始写作,并惊讶于 Haskell 可以轻松实现甚至是糟糕的想法。
生成唯一列表没有任何困难。我的问题是我生成的列表不能很好地结束。即使当我使用对角化时,它们也会留下剩余值,从而使它们的使用充其量是不可靠的。
这是一个重新设计的 3 和 5 列表,最后没有任何残留。去国有化是为了减少剩余价值,而不是为了消除无论如何都不会包含的重复项。
g3s5s n=[t*b|(a,b)<-[ (((d+n)-(d*2)), 5^d) | d <- [0..n]],
t <-[ 3^e | e <- [0..a+8]],
(t*b)<-(3^(n+6))+a]
ham2 n = take n $ ham2' (drop 1.sort.g3s5s $ 48) [1]
ham2' o@(f:fo) e@(h:hx) = if h == min h f
then h:ham2' o (hx ++ [h*2])
else f:ham2' fo ( e ++ [f*2])
twos 列表可以通过所有2^es 乘以每个3s5s 来生成,但是当包括身份2^0 时,总共是Hammings。
2019 年 3 月 25 日
好吧,最后。我前段时间知道这一点,但最终如果没有多余的价值就无法实施。问题是如何不产生笛卡尔积导致的多余部分。我经常使用 Excel,但看不到要从笛卡尔积工作表中排除的值模式。然后,尤里卡!这些函数生成每个领先因素的列表。限制每个列表中的值的值是第一个列表的终点。完成此操作后,所有的 Hammings 都会生成,没有多余的。
Hammings 的两个函数。第一个是新的 3 和 5 列表,然后用于创建与 2 的倍数。倍数是 Hammings。
h35r x = h3s5s x (5^x)
h3s5s x c = [t| n<-[3^e|e<-[0..x]],
m<-[5^e|e<-[0..x]],
t<-[n*m],
t <= c ]
a2r n = sort $ a2s n (2^n)
a2s n c = [h| b<-h35r n,
a<-[2^e| e<-[0..n]],
h<-[a*b],
h <= c ]
last $ a2r 50
1125899906842624
(0.16 秒,321,326,648 字节)
2^50
1125899906842624
(0.00 秒,95,424 字节
这是一种替代的、更干净、更快且内存使用量更少的实现。
gnf n f = scanl (*) 1 $ replicate f n
mk35 n = (\c-> [m| t<- gnf 3 n, f<- gnf 5 n, m<- [t*f], m<= c]) (2^(n+1))
mkHams n = (\c-> sort [m| t<- mk35 n, f<- gnf 2 (n+1), m<- [t*f], m<= c]) (2^(n+1))
last $ mkHams 50
2251799813685248
(0.03 秒,12,869,000 字节)
2^51
2251799813685248
2019 年 5 月 6 日
嗯,我尝试过不同的限制,但总是回到最简单的。我选择了最少的内存使用,因为它似乎也是最快的。
我还选择使用带有隐式参数的map。
我还发现Data.List.Ordered 中的mergeAll 比sort 或sort 和concat 更快。
我也喜欢创建子列表,这样我可以更轻松地分析数据。
然后,因为@Will Ness 切换到iterate 而不是scanl,所以代码更简洁。也因为@Will Ness,我停止使用最后一个 2s 列表并切换到一个确定所有长度的值。
我确实认为递归定义的列表更有效,前一个数字乘以一个因子。
只是将函数分成两个并没有什么区别,所以 3 和 5 的倍数是
m35 lim = mergeAll $
map (takeWhile (<=lim).iterate (*3)) $
takeWhile (<=lim).iterate (*5) $ 1
2s 分别乘以 3s 和 5s 的乘积
ham n = mergeAll $
map (takeWhile (<=lim).iterate (*2)) $ m35 lim
where lim= 2^n
编辑函数后我运行它
last $ ham 50
1125899906842624
(0.00 秒,7,029,728 字节)
然后
last $ ham 100
1267650600228229401496703205376
(0.03 秒,64,395,928 字节)
使用10^n 可能更好,但为了比较,我再次使用了2^n
2019 年 5 月 11 日
因为我更喜欢无限和递归列表,所以我有点痴迷于使这些无限。
@Daniel Wagner 和他的Data.Universe.Helpers 给我留下了深刻的印象和启发,我开始使用+*+ 和+++,但后来添加了我自己的无限列表。我必须 mergeAll 我的列表才能工作,但后来意识到无限的 3 和 5 倍数正是它们应该是的。所以,我添加了 2s 和 mergeAlld 一切,它们就出来了。之前,我愚蠢地认为mergeAll 不会处理无限列表,但它确实做到了。
当 Haskell 中的列表是无限的时,Haskell 只计算所需的内容,即惰性列表。附加条件是它确实从一开始就计算。
现在,由于 Haskell 倍数直到想要的极限,函数中不需要限制,即不再需要 takeWhile。速度提升令人难以置信,内存也降低了,
以下是在我的 3GB RAM 的慢速家用 PC 上。
tia = mergeAll.map (iterate (*2)) $
mergeAll.map (iterate (*3)) $ iterate (*5) 1
最后$ 10000 tia
288325195312500000
(0.02 秒,5,861,656 字节)
2019 年 6 月 5 日
我学会了如何ghc -02 所以以下是 50000 Hammings 到 2.38E+30。这进一步证明了我的代码是垃圾。
INIT time 0.000s ( 0.000s elapsed)
MUT time 0.000s ( 0.916s elapsed)
GC time 0.047s ( 0.041s elapsed)
EXIT time 0.000s ( 0.005s elapsed)
Total time 0.047s ( 0.962s elapsed)
Alloc rate 0 bytes per MUT second
Productivity 0.0% of total user, 95.8% of total elapsed
6.13.2019
@Will Ness rawks。他对上面的tia 进行了简洁而优雅的修订,事实证明它的速度是GHCi 的五倍。当我ghc -O2 +RTS -s他对抗我的时候,我的速度是我的几倍。必须妥协。
所以,我开始阅读我在 R. Bird 的 Thinking Functional with Haskell 中遇到的关于融合的内容,并且几乎立即尝试了这个。
mai n = mergeAll.map (iterate (*n))
mai 2 $ mai 3 $ iterate (*5) 1
它与 Will 在 GHCi 中的 100K Hammings 匹配为 0.08,但真正让我感到惊讶的是(也适用于 100K Hammings。)这一点,尤其是经过的时间。 100K最高可达2.9e+38。
TASKS: 3 (1 bound, 2 peak workers (2 total), using -N1)
SPARKS: 0 (0 converted, 0 overflowed, 0 dud, 0 GC'd, 0 fizzled)
INIT time 0.000s ( 0.000s elapsed)
MUT time 0.000s ( 0.002s elapsed)
GC time 0.000s ( 0.000s elapsed)
EXIT time 0.000s ( 0.000s elapsed)
Total time 0.000s ( 0.002s elapsed)
Alloc rate 0 bytes per MUT second
Productivity 100.0% of total user, 90.2% of total elapsed