好吧,这比我想象的要容易。这将在我家里的慢速 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
这意味着此代码的运行时增长的经验顺序高于二次(~n^2.13
在 GHCi 提示下测量、解释)。
此外,该序列的三个悬垂的过度生产片段的大小各为~n^0.67
ie ~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^e
s 乘以每个 s来生成,3s5s
但是当2^0
包括恒等式时,总的来说,它是 Hammings。
2019 年 3 月 25 日
嗯,最后。我前段时间知道这一点,但最终如果没有多余的价值就无法实施。问题是如何不产生笛卡尔积导致的多余部分。我经常使用 Excel,但看不到要从笛卡尔积工作表中排除的值模式。然后,尤里卡!这些函数生成每个领先因素的列表。限制每个列表中的值的值是第一个列表的终点。完成此操作后,所有的 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
隐式参数。
我还发现mergeAll
fromData.List.Ordered
比sort
or sort
and更快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 日
因为我更喜欢无限和递归列表,所以我有点痴迷于使这些无限。
Data.Universe.Helpers
我开始使用@Daniel Wagner 和他的作品给我留下了深刻的印象和启发,+*+
但+++
后来又添加了我自己的无限列表。我必须按照mergeAll
我的清单工作,但后来意识到无限的 3 和 5 倍数正是它们应该是的。所以,我添加了 2s 和mergeAll
d 一切,它们就出来了。以前,我愚蠢地认为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 字节)
6.5.2019
我学会了如何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
2019 年 6 月 13 日
@Will Ness rawks。他对上述内容进行了简洁而优雅的修改,tia
事实证明它的速度是GHCi
. 当我ghc -O2 +RTS -s
反对我的时候,我的速度是我的几倍。必须达成妥协。
所以,我开始阅读我在 R. Bird's Thinking Functionally with Haskell中遇到的关于融合的内容,并且几乎立即尝试了这个。
mai n = mergeAll.map (iterate (*n))
mai 2 $ mai 3 $ iterate (*5) 1
对于 100K Hammings,它与 Will's 匹配为 0.08,GHCi
但真正让我感到惊讶的是(也适用于 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