- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我正在做一些 Project Euler项目(不是家庭作业,只是为了娱乐/学习),我正在学习 Haskell。其中一个问题是找到起始数在 100 万以下的最大 Collatz 序列 ( http://projecteuler.net/problem=14 )
所以无论如何,我能够做到,并且我的算法可以运行并且在编译时相当快地得到正确的答案。但是,它使用了 1000000 深度递归。
所以我的问题是:我这样做对吗?照原样,是正确的 Haskell 方法吗?我怎样才能让它更快?另外,对于内存使用,递归实际上是如何在底层实现的?至于它如何使用内存?
(剧透警告:如果你想自己解决 Project Euler 的问题 #14 而不看答案,请不要看这个。)
--haskell脚本--问题:找到小于 200 万的数字的最长 collatz 链。
collatzLength x| x == 1 = 1
| otherwise = 1 + collatzLength(nextStep x)
longestChain (num, numLength) bound counter
| counter >= bound = (num, numLength)
| otherwise = longestChain (longerOf (num,numLength)
(counter, (collatzLength counter)) ) bound (counter + 1)
--I know this is a messy function, but I was doing this problem just
--for myself, so I didn't bother making some utility functions for it.
--also, I split the big line in half to display on here nicer, would
--it actually run with this line split?
longerOf (a1,a2) (b1,b2)| a2 > b2 = (a1,a2)
| otherwise = (b1,b2)
nextStep n | mod n 2 == 0 = (n `div` 2)
| otherwise = 3*n + 1
main = print (longestChain (0,0) 1000000 1)
使用 -O2 编译时,程序运行时间约为 7.5 秒。
那么,有什么意见/建议吗?我想尝试让程序以更少的内存使用量运行得更快,我想以一种非常 Haskellian(应该是一个词)的方式来实现。
提前致谢!
最佳答案
编辑以回答问题
did I do this right?
几乎,正如评论所说,你构建了一个很大的 1+(1+(1+...))
- 改用严格的累加器或为您处理事情的高级函数。还有其他一些小事,比如定义一个函数来比较第二个元素而不是使用 maximumBy (comparing snd)
但这更具风格。
As is, is the the proper Haskell way to do it?
这是可以接受的惯用 Haskell 代码。
How could I make it faster?
请参阅我的以下基准。欧拉性能问题的最常见答案是:
rem
而不是 mod
当所有的值都是正数时。对于您的情况,了解或发现 div
也很有用。倾向于编译成比 quot
慢的东西.Also, with memory usage, how is the recursion actually implemented on the low-level? As in how does it use memory?
这两个问题都非常广泛。完整的答案可能需要解决惰性评估、尾部调用优化、工作人员转换、垃圾收集等问题。我建议您随着时间的推移更深入地探索这些答案(或者希望这里有人给出我正在避免的完整答案)。
原帖 - 基准数据
原文:
$ ghc -O2 so.hs ; time ./so
[1 of 1] Compiling Main ( so.hs, so.o )
Linking so ...
(837799,525)
real 0m5.971s
user 0m5.940s
sys 0m0.019s
为 collatzLength
使用带累加器的辅助函数:
$ ghc -O2 so.hs ; time ./so
[1 of 1] Compiling Main ( so.hs, so.o )
Linking so ...
(837799,525)
real 0m5.617s
user 0m5.590s
sys 0m0.012s
使用 Int
而不是默认为 Integer
- 使用类型签名也更容易阅读!
$ ghc -O2 so.hs ; time ./so
[1 of 1] Compiling Main ( so.hs, so.o )
Linking so ...
(837799,525)
real 0m2.937s
user 0m2.932s
sys 0m0.001s
使用 rem
而不是 mod
:
$ ghc -O2 so.hs ; time ./so
[1 of 1] Compiling Main ( so.hs, so.o )
Linking so ...
(837799,525)
real 0m2.436s
user 0m2.431s
sys 0m0.001s
使用 quotRem
而不是 rem
然后 div
:
$ ghc -O2 so.hs ; time ./so
[1 of 1] Compiling Main ( so.hs, so.o )
Linking so ...
(837799,525)
real 0m1.672s
user 0m1.669s
sys 0m0.002s
这与之前的问题非常相似:Speed comparison with Project Euler: C vs Python vs Erlang vs Haskell
编辑:是的,正如 Daniel Fischer 建议的那样,使用 .&.
的位操作和 shiftR
改进了 quotRem
:
$ ghc -O2 so.hs ; time ./so
(837799,525)
real 0m0.314s
user 0m0.312s
sys 0m0.001s
或者你可以只使用 LLVM 让它发挥它的魔力(注意这个版本仍然使用 quotRem
)
$ time ./so
(837799,525)
real 0m0.286s
user 0m0.283s
sys 0m0.002s
LLVM 实际上做得很好,只要你避免 mod
的丑陋行为。 ,并使用 rem
优化基于守卫的代码或 even
与手动优化的一样好 .&.
与 shiftR
.
结果比原来快 20 倍左右。
编辑:人们惊讶于 quotRem 在面对 Int
时表现得和位操作一样好。 .代码已包含在内,但我不清楚是否会感到惊讶:仅仅因为某些东西可能是负面的并不意味着您无法使用非常相似的位操作来处理它,这些位操作在正确的硬件上可能具有相同的成本。 nextStep
的所有三个版本似乎表现相同( ghc -O2 -fforce-recomp -fllvm
,ghc 版本 7.6.3,LLVM 3.3,x86-64)。
{-# LANGUAGE BangPatterns, UnboxedTuples #-}
import Data.Bits
collatzLength :: Int -> Int
collatzLength x| x == 1 = 1
| otherwise = go x 0
where
go 1 a = a + 1
go x !a = go (nextStep x) (a+1)
longestChain :: (Int, Int) -> Int -> Int -> (Int,Int)
longestChain (num, numLength) bound !counter
| counter >= bound = (num, numLength)
| otherwise = longestChain (longerOf (num,numLength) (counter, collatzLength counter)) bound (counter + 1)
--I know this is a messy function, but I was doing this problem just
--for myself, so I didn't bother making some utility functions for it.
--also, I split the big line in half to display on here nicer, would
--it actually run with this line split?
longerOf :: (Int,Int) -> (Int,Int) -> (Int,Int)
longerOf (a1,a2) (b1,b2)| a2 > b2 = (a1,a2)
| otherwise = (b1,b2)
{-# INLINE longerOf #-}
nextStep :: Int -> Int
-- Version 'bits'
nextStep n = if 0 == n .&. 1 then n `shiftR` 1 else 3*n+1
-- Version 'quotRem'
-- nextStep n = let (q,r) = quotRem n 2 in if r == 0 then q else 3*n+1
-- Version 'almost the original'
-- nextStep n | even n = quot n 2
-- | otherwise = 3*n + 1
{-# INLINE nextStep #-}
main = print (longestChain (0,0) 1000000 1)
关于Haskell递归效率,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17155328/
第一个 .on 函数比第二个更有效吗? $( "div.container" ).on( "click", "p", function(){ }); $( "body" ).on( "click",
已关闭。此问题不符合Stack Overflow guidelines 。目前不接受答案。 这个问题似乎与 help center 中定义的范围内的编程无关。 . 已关闭 7 年前。 Improve
我有这样的查询: $('#tabContainer li'); JetBrains WebStorm IDE 将其突出显示为低效查询。它建议我改用这个: $('#tabContainer').find
我刚刚在 coursera ( https://www.coursera.org/saas/) 上听了一个讲座,教授说 Ruby 中的一切都是对象,每个方法调用都是在对象上调用发送方法,将一些参数传递
这可能是用户“不喜欢”的另一个问题,因为它更多的是与建议相关而不是与问题相关。 我有一个在保存和工作簿打开时触发的代码。 它在 f(白天与夜晚,日期与实际日期)中选择正确的工作表。 周一到周三我的情况
这只是我的好奇心,但是更有效的是递归还是循环? 给定两个功能(使用通用lisp): (defun factorial_recursion (x) (if (> x 0) (*
这可能是一个愚蠢的问题,但是while循环的效率与for循环的效率相比如何?我一直被教导,如果可以使用for循环,那我应该这样做。但是,实际上之间的区别是什么: $i = 0; while($i <
我有一个Elasticsearch索引,其中包含几百万条记录。 (基于时间戳的日志记录) 我需要首先显示最新记录(即,按时间戳降序排列的记录) 在时间戳上排序desc是否比使用时间戳的函数计分功能更有
使用Point2D而不是double x和y值时,效率有很大差异吗? 我正在开发一个程序,该程序有许多圆圈在屏幕上移动。他们各自从一个点出发,并越来越接近目的地(最后,他们停下来)。 使用 .getC
我正在编写一个游戏,并且有一个名为 GameObject 的抽象类和三个扩展它的类(Player、Wall 和 Enemy)。 我有一个定义为包含游戏中所有对象的列表。 List objects; 当
我是 Backbone 的初学者,想知道两者中哪一个更有效以及预期的做事方式。 A 型:创建一个新集合,接受先前操作的结果并从新集合中提取 key result = new Backbone.Coll
最近,关于使用 LIKE 和通配符搜索 MS SQL 数据库的最有效方法存在争论。我们正在使用 %abc%、%abc 和 abc% 进行比较。有人说过,术语末尾应该始终有通配符 (abc%)。因此,根
关闭。这个问题是opinion-based 。目前不接受答案。 想要改进这个问题吗?更新问题,以便 editing this post 可以用事实和引文来回答它。 . 已关闭 8 年前。 Improv
我想知道,这样做会更有效率吗: setVisible(false) // if the component is invisible 或者像这样: if(isVisible()){
我有一个静态方法可以打开到 SQL Server 的连接、写入日志消息并关闭连接。我在整个代码中多次调用此方法(平均每 2 秒一次)。 问题是 - 它有效率吗?我想也许积累一些日志并用一个连接插入它们
这个问题在这里已经有了答案: Best practice to avoid memory or performance issues related to binding a large numbe
我为我的 CS 课(高中四年级)制作了一个石头剪刀布游戏,我的老师给我的 shell 文件指出我必须将 do while 循环放入运行者中,但我不明白为什么?我的代码可以工作,但她说最好把它写在运行者
我正在编写一个需要通用列表的 Java 应用程序。该列表需要能够经常动态地调整大小,对此的明显答案是通用的Linkedlist。不幸的是,它还需要像通过调用索引添加/删除值一样频繁地获取/设置值。 A
我的 Mysql 语句遇到了真正的问题,我需要将几个表连接在一起,查询它们并按另一个表中值的平均值进行排序。这就是我所拥有的... SELECT ROUND(avg(re.rating
这个问题在这里已经有了答案: 关闭 10 年前。 Possible Duplicate: Is there a difference between i==0 and 0==i? 以下编码风格有什么
我是一名优秀的程序员,十分优秀!