[英]Haskell - C stack overflow
我剛剛被介紹給Haskell,所以我對如何使用該語言進行編碼並不是很熟練,因此,如果這是重復的話,對不起,但是我不理解用該語言編寫的其他代碼。
我正在嘗試編寫一個算法,該算法將采用不超過給定值的正整數甚至整數的總和。 我試圖編寫代碼但是我一直在收到C堆棧溢出錯誤。
這是我的代碼:
sumint :: Int -> Int
sumint x
| x==0 = 0
| x==1 = 1
| (x `mod` 2 == 0) && (x >= 2) = x + (sumint x-2)
| (x `mod` 2 /= 0) && (x >= 1) = x + (sumint x-1)
我錯在哪里得到這個錯誤?
初始錯誤:無限遞歸
單步執行代碼:
sumint :: Int -> Int
嘿,類型簽名。 你搖滾。
sumint x
| x==0 = 0
一個基礎案例,很酷。
| x==1 = 1
完全沒必要的情況。 好的,確定......除外。 1甚至不是為什么我們將它包含在總和中? 它應該為零(或完全刪除)。
| (x `mod` 2 == 0) && (x >= 2) = x + (sumint x-2)
這個問題的關鍵在這里。 X甚至很棒。 2. X是正面的,是的。 結果是x + (sumint x) - 2
不!
x + sumint (x-2)
。 這就是堆棧溢出的原因。 sumint 2 == 2 + (sumint 2) - 2 + (sumint 2) -2 + (sumint 2) -2 + ...
,yay無限遞歸。
| (x `mod` 2 /= 0) && (x >= 1) = x + (sumint x-1)
另一種情況......賠率......積極......但為什么我們加入x? 你想添加平均值,而不是賠率。 因此,在我們得到的同時解決上述問題:
x
是奇數,請不要添加x
。 只需使用sumint (x-1)
。 那你就沒有了。 如果x不是正數,會發生什么? 你需要(另一個)案例。
| otherwise = 0
下一期:沒有積累
現在的問題是你正在構建一個大的thunk(未評估的計算),而不是通過在進展時累積結果來在恆定的空間中操作。 請注意,如果我們擴展你的計算,比如6,我們得到:
sumint 6 = 6 + sumint (6-2)
= 6 + 4 + sumint (4-2)
= 6 + 4 + 2 + sumint (2-2)
= 6 + 4 + 2 + 0
你真的不希望將所有這些添加內容分開,最好傳入一個累加器,例如:
sumint x = go x 0
where
go n accumulator
| n <= 0 = accumulator
| odd n = go (n-1) accumulator
| otherwise = go (n-2) (accumulator + n)
旁注:其他stackoverflow公民可能會提到使累加器嚴格,這是一個很好的形式。 我不想通過這里的討論分散當前提問者的注意力。 注意使用優化, -O2
就足夠了。
慣用解決方案
以上所有解決方案都相當冗長。 使用函數和累加器迭代列表的一般操作是一種fold
。 Fold是函數式編程中常見的眾多高度優化的結構遍歷之一。 在這種情況下,“嚴格左側折疊”是典型的候選者(來自Data.List
)。 這就是foldl'
'按照慣例foldl'
'( '
)意味着它是嚴格的而l
意味着離開。
sumint n = foldl' (+) 0 [val | val <- [0..n], even val]
在這里,我們折疊在列表上以獲得我們的總和。 要創建感興趣的列表中,我們使用列表理解-首先從枚舉值0..n
,並跳過不符合謂詞任何價值even
。
我們可以通過使用步長為2的sum
函數和列表推導來進一步清理它並改進它,從而只給出我們想要的平均值:
sumint n = sum [0,2..n]
它是運算符優先級問題。 在Haskell中,函數應用程序具有最高可能的優先級。 因此,當你寫sumint x - 2
,即使你將它解釋為sumint (x-2)
,Haskell也將它解釋為(sumint x) - 2
。 因此-您正在嘗試定義sumint x
直接來講sumint x
-這只是堆積起來的遞歸函數調用,直到堆棧溢出。 如果要在函數應用程序之前評估減法,則需要添加顯式括號。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.