如何更简洁编写单输入输出的Haskell十进制转二进制函数?
优化Haskell十进制转二进制函数的函数式写法
我们需要实现一个dec2Bin :: Int -> [Int]函数,将十进制整数转换为二进制数位的列表(例如dec2Bin 5应返回[1,0,1])。先看两种初始实现:
初始实现1:带辅助函数的递归
dec2BinHelper :: (Int,[Int]) -> (Int,[Int]) dec2BinHelper (n,l) | n <= 0 = (n,l) | otherwise = dec2BinHelper (fst $ division n, (snd $ division n):l) where division a = divMod a 2 dec2Bin :: Int -> [Int] dec2Bin n = snd $ dec2BinHelper (n,[])
初始实现2:使用until函数整合
dec2Bin :: Int -> [Int] dec2Bin num = snd $ until (\(n,l) -> n <=0) -- 终止条件 (\(n,l) -> (fst $ division n, (snd $ division n):l)) -- 迭代逻辑 (num,[]) -- 初始状态 where division a = divMod a 2
以上两种实现都有简化空间,下面给出更符合函数式编程风格、简洁易读的写法:
写法1:简化递归(无辅助元组)
直接递归生成二进制位列表,同时处理0的边界情况(避免返回空列表):
dec2Bin :: Int -> [Int] dec2Bin 0 = [0] dec2Bin n = reverse $ go n where go 0 = [] go n = let (q, r) = divMod n 2 in r : go q
这里用go作为内部递归函数,直接生成低位在前的列表,最后用reverse得到高位在前的结果,逻辑清晰,没有冗余的元组状态。
写法2:使用unfoldr(函数式生成列表的典型方式)
Data.List中的unfoldr可以从一个种子值逐步生成列表元素,非常适合这种迭代生成序列的场景:
import Data.List (unfoldr) dec2Bin :: Int -> [Int] dec2Bin 0 = [0] dec2Bin n = reverse $ unfoldr step n where step 0 = Nothing step n = let (q, r) = divMod n 2 in Just (r, q)
unfoldr的step函数每次返回当前位和下一个种子值,直到种子为0时终止,最后反转得到正确的顺序。
写法3:无反转的递归(直接生成高位在前)
如果不想用反转,可以先计算二进制的最高位权值,再逐步取位:
dec2Bin :: Int -> [Int] dec2Bin 0 = [0] dec2Bin n = go (highestPower n) n where highestPower n = head $ dropWhile (\x -> x * 2 <= n) (iterate (*2) 1) go 0 _ = [] go power num = let (q, r) = divMod num power in q : go (power `div` 2) r
这种写法直接从最高位开始生成,不需要反转,但需要额外计算最高位权值,适合对顺序有严格要求且不想用反转的场景。
需要注意的是,以上实现都处理了输入为0的情况(返回[0]),这比原始实现更严谨——原始输入0会返回空列表,不符合常规的二进制表示。
内容的提问来源于stack exchange,提问作者Κωστής Καρβουνιάρης
相关产品推荐
相关产品推荐

