簡體   English   中英

如何編寫一個 Haskell 函數來確定一個數字是偶數還是奇數?

[英]How to write a Haskell function that determine if a number is even or odd?

我試圖理解 Haskell 語言,但我對它很困惑,因為我有以下練習,甚至無法從它開始:

  1. 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

等等。

  1. 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 TrueTrue == 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 43evenNat 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM