我正在编写一个程序来生成形式的前k个数字
其中 a 和 b 是非负整数。
我正在使用的算法利用了以下事实:该系列中的每个数字都是通过将1或sqrt(2)与该系列中的前一个数字相加而生成的,该系列从0开始。我们将在数组中的序列,其中每个元素均应按需延迟评估。系列中的第一个数字为0,因此我们将数组的第一个元素初始化为0。我们将维护两个指针 i 和 j ,它们均从索引0开始(系列中的第一个数字)。系列中的下一个数字是 min(A [i] +1,A [j] + sqrt(2))。如果系列中的下一个数字来自指针 i ,则指针 i 将递增(因为我们从不希望添加 再次从1到A [i]),并且类似地,如果系列中的下一个数字来自指针 j ,则 j 将递增以指向下一个索引,即 j +1 。如果 A [i] + 1 = A [j] + sqrt(2),则 i 和 j 都将增加。
我认为下面的代码片段捕获了上述算法,但是,在ghci中运行代码时,似乎函数f
的参数1 0 0被重复调用,程序永不结束。尽管好像是v A.! i
触发了该调用,但我不知道如何进行此调用。
import qualified Data.Array as A
import Debug.Trace
compute :: Int -> Int -> Double
compute x y = (fromIntegral x) + (fromIntegral y) * (sqrt 2)
firstK :: Int -> [(Int,Int)]
firstK k = A.elems v where
v = A.listArray (0,k-1) $ (0,0):f 1 0 0
f c i j
| c == k = []
| otherwise = traceShow (c,i,j) $
let (ix,iy) = v A.! i
(jx,jy) = v A.! j
iv = compute (ix+1) iy
jv = compute jx (jy+1)
in if iv < jv
then (ix+1,iy):f (c+1) (i+1) j
else if iv > jv
then (jx,jy+1):f (c+1) i (j+1)
else (ix+1,iy):f (c+1) (i+1) (j+1)
输出-
λ> firstK 50
(1,0,0)
(1,0,0)
(1,0,0)
(1,0,0)
(1,0,0)
(1,0,0)
(1,0,0)
(1,0,0)
(1,0,0)
(1,0,0)
(1,0,0)
(1,0,0)
(1,0,0)
(1,0,0)
...
...
答案 0 :(得分:4)
chi的答案详细讨论了代码的确切问题,以及可以使您保留实现想法但得到希望的答案的修复程序。但是,我想看看母语为Haskell的讲者将如何处理相同的算法也很有趣:我将只使用本地列表,而不是使用数组指针,而是使用列表指针本身来跟踪我们在哪里。看起来像这样:
vs :: [(Int,Int)]
vs = (0,0) : merge [(a+1, b) | (a, b) <- vs] [(a, b+1) | (a, b) <- vs] where
compute (a,b) = fromIntegral a + fromIntegral b * sqrt 2
merge xs@(xh:xt) ys@(yh:yt) = case compare (compute xh) (compute yh) of
LT -> xh:merge xt ys
EQ -> xh:merge xt yt
GT -> yh:merge xs yt
在对merge
的递归调用中,使用xt
(而不是xs
)类似于递增i
指针;使用yt
(而不是ys
)类似于增加j
指针。
关于vs
的一件好事是,您不必提前声明所需的对-您只需获得一个懒惰的无穷列表即可(使用{{1 }} Double
!)。该代码也大大缩短了。这是在ghci中运行的示例:
compute
答案 1 :(得分:1)
问题在于arrayList
在实际构建数组之前过早强制列表的所有书脊。这使得递归循环永远存在。
但是,我们可以使用以下辅助功能来解决此问题。
-- A spine-lazy version of arrayList
arrayListLazy :: (Int, Int) -> [a] -> A.Array Int a
arrayListLazy (lo,hi) xs = A.array (lo,hi) $ go lo xs
where
go i _ | i > hi = []
go i ~(e:ys) = (i, e) : go (succ i) ys
firstK :: Int -> [(Int,Int)]
firstK k = A.elems v where
v = arrayListLazy (0,k-1) $ (0,0):f 1 0 0
f c i j
| c == k = []
| otherwise = traceShow (c,i,j) $
let (ix,iy) = v A.! i
(jx,jy) = v A.! j
iv = compute (ix+1) iy
jv = compute jx (jy+1)
in if iv < jv
then (ix+1,iy):f (c+1) (i+1) j
else if iv > jv
then (jx,jy+1):f (c+1) i (j+1)
else (ix+1,iy):f (c+1) (i+1) (j+1)
小测试:
> firstK 10
[(0,0),(1,0),(0,1),(2,0),(1,1),(0,2),(3,0),(2,1),(1,2),(4,0)]
答案 2 :(得分:1)
使用相同想法的另一种方法,
firstK k = take k $ l
where l = (0,0) : unfoldr (\(i,j) -> Just $ case compare (uncurry compute $ first (1+) $ l!!i) (uncurry compute $ second (1+) $ l!!j) of
LT -> (first (1+) $ l!!i, (i+1,j))
EQ -> (first (1+) $ l!!i, (i+1,j+1))
GT -> (second (1+) $ l!!j, (i,j+1))) (0,0)
并不是特别整洁,但是您可以看到它的去向。特别是,与case compare _ _ of ...
匹配的模式比if a > b else if a < b else ..
更好。
编辑:尽管时间复杂度较差。