[英]How to write a Haskell function that determine if a number is even or odd?
我試圖理解 Haskell 語言,但我對它很困惑,因為我有以下練習,甚至無法從它開始:
evenNat :: Integer -> Bool
,它以一個非負整數作為輸入並判斷它是否為偶數。
(還有更多,但讓我們現在專注於這個)
在所有這些程序中,您都不允許使用內置的乘法、除法或模函數。
我應該如何開始,你能給我一些如何學習這門語言的建議嗎? 謝謝!
整數是偶數當且僅當其低位為 0 時。當且僅當其低位為 1 時才為奇數。您可以使用Data.Bits
的簡單操作來測試這一點。
import Data.Bits
import Prelude hiding (even, odd)
even, odd :: Integer -> Bool
even x = ??
odd x = ??
也就是說,我認為 Robin Zigmond 對這次練習的意圖可能是正確的。 為了更直接地了解這個意圖,您可以定義自己的數字。
import Prelude hiding (Integer, even, odd)
data PNat
= One
| Succ PNat
data Integer
= Negative PNat
| Zero
| Positive PNat
現在定義
evenNat :: PNat -> Bool
等等。
evenNat :: Integer -> Bool
,它以一個非負整數作為輸入並判斷它是否為偶數。
寫下你所知道的:
evenNat :: Integer -> Bool
evenNat n | n < 0 = error "forbidden call to evenNat"
evenNat 0 = True
evenNat 1 = False
evenNat 2 = True
evenNat 3 = .....
.....
這是一個非常好的開始,除了我們沒有希望明確列舉所有必要的情況來涵蓋所有可能的輸入。
但是讓我們停一下。 我們將如何完成evenNat 3 = ...
定義? 我們當然可以把明確的答案放在那里, False
。 但是我們也可以注意到False == not True
和True == evenNat 2
,所以
evenNat 3 = False
= not True
= not (evenNat 2)
= not (evenNat (3 - 1))
實際上,
evenNat 2 = True
= not False
= not (evenNat 1)
= not (evenNat (2 - 1))
完全遵循相同的模式。
你有它! 剩下的就是概括! 通過將具體數據轉化為變量,這樣我們就可以得到更多——終極——子句,
evenNat n = not (...... ( ... - ... ))
就是這樣。
讓我們接受 Robin 的前提,即遞歸是這里的實際主題。
遞歸包括用一個更簡單的問題,或者一個更簡單的問題列表替換一個相對困難的問題。
例如, evenNat 43
是evenNat 42
的否定。 如果我們沿着這條路線走下去,經過 40 多個步驟后,我們會到達evenNat 0
,它是已知的True
。 該方案有效,但對於大量數字可能有點慢。
一種更快的方法可以包括在每一步中從手頭的數字中減去 2 的最大可能冪。 這種減法不會改變數字的偶數字符。 所以 43 是偶數當且僅當 (43-32) = 11 是偶數。 接下來,我們考慮 (11-8) = 3。所以它看起來確實是一種更快但更復雜的方法。
首先,我們需要 2 的冪序列,直到手頭的數字。 當然,這可以遞歸地完成:
powersOf2UpToN :: Integer -> [Integer]
powersOf2UpToN n =
let -- defining auxiliary function sq:
sq m pwrs = if (head pwrs > m) -- if reached big enough
then (tail pwrs) -- then stop and return result
else -- augment:
let hps = head pwrs in sq m ((hps+hps):pwrs)
in (sq n [2])
請注意,我使用的是(hps+hps)
而不是(2*hps)
因為練習規則禁止本機乘法。
本質上,只要我們保持在手頭的數字以下,我們就會在冪序列中添加越來越大的 2 冪。 回想一下 Haskell 中的內容,將元素添加到列表中是一種廉價的操作。
在ghci
解釋器下測試:
$ ghci
λ>
λ> :load q64544005.hs
Ok, one module loaded.
λ>
λ> powersOf2UpToN 337
[256,128,64,32,16,8,4,2]
λ>
接下來,我們必須實現的功能substracts 2.這樣的功能盡可能大的功率會采取(號,2的權力)對,並返回一個“改良”對,那是一個小數目和縮短電力列表. 類型簽名如下:
reduce2 :: (Integer, [Integer]) -> (Integer, [Integer])`
總體邏輯是:
reduce2 (n, pwrs) =
if (null pwrs) -- did we just run out of powers of 2 ?
then (n, []) -- nothing left to do
else if ((head pwrs) <= n) -- return a lower n if possible
...
請在此處暫停閱讀,看看您是否可以填空。
解決方案:如果可能,我們減去 2 的最大剩余冪,它恰好位於(遞減)列表的頭部。
reduce2 (n, pwrs) =
if (null pwrs)
then (n, []) -- nothing left to do
else if ((head pwrs) <= n) -- return a lower n if possible
then reduce2 ((n - (head pwrs)), tail pwrs)
else reduce2 (n, tail pwrs)
reduce2
是關鍵。 因此,為了幫助理解,讓我們利用 Haskell 方便的小跟蹤工具。
在下面的代碼中,表達式DT.trace msg res
計算結果僅為res
,具有打印msg
的副作用。 是的,Haskell 中通常禁止副作用,但跟蹤工具享有特殊特權。
所以這是我們的reduce2
函數的跟蹤版本:
import qualified Debug.Trace as DT
type DebugMsg = String
traceReduce2 :: DebugMsg -> (Integer, [Integer]) -> (Integer, [Integer])
traceReduce2 msg (n, pwrs) =
let res = if (null pwrs)
then (n, [])
else let msg1 = (show n) ++ " " ++ (show pwrs)
rem = n - (head pwrs)
in if ((head pwrs) <= n)
then traceReduce2 msg1 (rem, tail pwrs)
else traceReduce2 msg1 (n, tail pwrs)
in DT.trace msg res
測試我們的跟蹤版本:
λ>
λ> traceReduce2 "TOP" (337, (powersOf2UpToN 337))
TOP
337 [256,128,64,32,16,8,4,2]
81 [128,64,32,16,8,4,2]
81 [64,32,16,8,4,2]
17 [32,16,8,4,2]
17 [16,8,4,2]
1 [8,4,2]
1 [4,2]
1 [2]
(1,[])
λ>
所以我們差不多完成了。 處理完 2 的所有冪后,值 0 或 1 保留為對的左側。 我們可以繼續編寫我們的evenNat
函數:
evenNat :: Integer -> Bool
evenNat n =
let (remainder, ys) = reduce2 (n, powersOf2UpToN n)
in
-- here, remainder is set to 0 if and only if n is even
...
其余的應該很容易。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.