停止此函数递归的更多haskellish方法是什么?目前我使用嵌套的if / else,如果下一个组合"溢出"则返回一个空列表。
nextcomb [] [] = []
nextcomb lst maxes | length lst == length maxes =
let lastel = last lst
in if lastel < last maxes
then (init lst) ++ [lastel+1]
else let higherbit = (nextcomb (init lst) (init maxes))
in if higherbit == []
then []
else higherbit ++ [1]
nextcomb lst maxes | otherwise = []
为了澄清,它的作用是需要一个像[1,1,1,1]这样的数字列表并将它增加如下:
[1,1,1,1] - &gt; [1,1,1,2]
...
[1,1,1,9] - &gt; [1,1,2,1]
...
[1,1,9,9] - &gt; [1,2,1,1]
等
但是,第二个参数是一个列表,指示每列的maxmum值。因此,如果最大值为[2,3],并且初始列表为[1,1],那么进展将是:
[1,1] - &gt; [1,2]
[1,2] - &gt; [1,3]
[1,3] - &gt; [2,1]
[2,1] - &gt; [2,2]
[2,2] - &gt; [2,3]
[2,3] - &gt; []
编辑:&#34; Little Endian&#34; chepner推荐的版本
nextcomb' [] [] = []
nextcomb' lst maxes | length lst /= length maxes = []
nextcomb' lst maxes =
let firstel = head lst
in if firstel < head maxes
then (firstel+1) : (tail lst)
else let higherbit = (nextcomb' (tail lst) (tail maxes))
in if higherbit == []
then []
else 1 : higherbit
答案 0 :(得分:3)
你应该使非法国家无法代表
因此,不使用两个列表,而是使用元组列表。例如,每个元组中的第一个值可以是最大值,第二个值是实际值。
这也极大地简化了逻辑,因为错误&#34; maxes太长&#34;并且&#34; maxes太短&#34;不可能发生。
答案 1 :(得分:2)
你的if
表达式只是隐藏真实的基本情况,即如果 参数为空,则返回空列表。
nextcomb [] [] = []
nextcomb lst maxes | length lst != length maxes = []
nextcomb lst maxes = let lastel = last lst
in if lastel < last maxes
then (init lst) ++ [lastel+1]
else let higherbit = (nextcomb (init lst) (init maxes))
in if higherbit == []
then []
else higherbit ++ [1]
我可能会像这样重写逻辑。 (注意,我离Haskell专家很远,并倾向于回答这些问题作为我自己的练习:)
-- Reversing the arguments and the ultimate return value
-- lets you work with the head of each list, rather than the last
-- element
nextcomb lst maxes = reverse $ nextcomb' (reverse lst) (reverse maxes)
-- The real work. The base case is two empty lists
nextcomb' [] [] = []
-- If one list runs out before the other, it's an error. I think
-- it's faster to check if one argument is empty when the other is not
-- than to check the length of each at each level of recursion.
nextcomb' [] _ = error "maxes too long"
nextcomb' _ [] = error "maxes too short"
-- Otherwise, it's just a matter of handling the least-significant
-- bit correctly. Either abort, increment, or reset and recurse
nextcomb' (x:xs) (m:ms) | x > m = error "digit too large"
| x < m = (x+1):xs -- just increment
| otherwise = 0:(nextcomb' xs ms) -- reset and recurse
(实际上,请注意,如果您在最后一位数后没有递归,nextcomb' [] _
将不会触发。您可能会认为过长maxes
并不是什么大问题。我留下这个不固定,因为下一部分正确处理它。)
或者,您可以在初始调用中验证长度是否匹配;那么你可以假设它们会同时变空。
nextcomb lst maxes | length lst == length maxes = reverse $ nextcomb' (reverse lst) (reverse maxes)
| otherwise = error "length mixmatch"
nextcomb' [] [] = []
nextcomb' (x:xs) (m:ms) | x > m = error "digit too large"
| x < m = (x+1):xs
| otherwise = 0:(nextcomb' xs ms)
以下是使用Either
报告错误的示例。除了说它进行类型检查和运行之外,我不会保证设计。它与以前的代码没有什么不同;它只使用<$>
提升reverse
和(0:)
来处理Either String [a]
类型的参数,而不是类型为[a]
的参数。
import Control.Applicative
nextcombE lst maxes = reverse <$> nextcombE' (reverse lst) (reverse maxes)
nextcombE' [] [] = Right []
nextcombE' [] _ = Left "maxes too long"
nextcombE' _ [] = Left "maxes too short"
nextcombE' (x:xs) (m:ms) | x > m = Left "digit too large"
| x < m = Right ((x+1):xs)
| otherwise = (0:) <$> (nextcombE' xs ms)
答案 2 :(得分:2)
请检查下一个实现是否对您有用,因为更多的“haskellish”方式(至少对我而言)是使用内置的递归函数来实现相同的目标
nextcomb [] [] = []
nextcomb lst maxes
| length lst /= length maxes = []
| lst == maxes = []
| otherwise = fst $ foldr f ([],True) $ zip lst maxes
where
f (l,m) (acc, mustGrow)
| mustGrow && l < m = (l + 1:acc, False)
| mustGrow = (1:acc, True)
| otherwise = (l:acc, False)
(编辑)如果需要捕获错误,那么可以试试这个:
nextcomb [] _ = Left "Initial is empty"
nextcomb _ [] = Left "Maximus size are empty"
nextcomb lst maxes
| length lst /= length maxes = Left "List must be same length"
| lst == maxes = Left "Initial already reach the limit given by Maximus"
| otherwise = Right $ fst $ foldr f ([],True) $ zip lst maxes
where
f (l,m) (acc, mustGrow)
| mustGrow && l < m = (l + 1:acc, False)
| mustGrow = (1:acc, True)
| otherwise = (l:acc, False)
答案 3 :(得分:1)
让我们画一幅图!我将做出与初始问题略有不同的假设:
[0, base)
范围内的数字。这是图表:
digits = [d0, d1, ..., dn]
bases = [b0, b1, ..., bn]
--------------------------
result = [r0, r1, ..., rn]
现在我们可以问:对于结果的每个数字ri
,它的值取决于什么?好吧,这些东西:
di
bi
r
是否导致了进位所以我们可以把它写成一个函数:
import Control.Monad.State -- gonna use this a bit later
type Base = Int
type Digit = Int
type Carry = Bool
-- | Increment a single digit, given all the contextual information.
singleDigit' :: Base -> Digit -> Carry -> (Digit, Carry)
singleDigit' base digit carry = (digit', carry')
where sum = digit + if carry then 1 else 0
digit' = if sum < base then sum else sum - base
carry' = base <= sum
请注意,我注意确保singleDigit'
函数的类型以Carry -> (Digit, Carry)
结尾。这是因为它符合状态monad典型的state -> (result, state)
模式:
-- | Wrap the `singleDigit'` function into the state monad.
singleDigit :: Base -> Digit -> State Carry Digit
singleDigit base digit = state (singleDigit' base digit)
现在我们可以编写以下函数:
increment :: [Base] -> [Digit] -> [Digit]
increment bases digits = evalState (sequence steps) True
where steps :: [State Carry Digit]
steps = zipWith singleDigit bases digits
我们在这里做的是:
zipWith
将基数和数字列表“拼接”在相应的元素上。此列表的元素对应于计算的单个steps
。sequence :: [State Carry Digit] -> State Carry [Digit]
将所有单个步骤链接到一个通过中间Carry
状态的大步骤。True
作为该大步骤的初始Carry
输入(导致链增加)。示例调用:
>>> take 20 (iterate (increment [3,4,5,10]) [0,0,0,0])
[[0,0,0,0],[1,0,0,0],[2,0,0,0]
,[0,1,0,0],[1,1,0,0],[2,1,0,0]
,[0,2,0,0],[1,2,0,0],[2,2,0,0]
,[0,3,0,0],[1,3,0,0],[2,3,0,0]
,[0,0,1,0],[1,0,1,0],[2,0,1,0]
,[0,1,1,0],[1,1,1,0],[2,1,1,0]
,[0,2,1,0],[1,2,1,0]
]
我要强调的课程:
singleDigit'
函数。singleDigit
操作,计算的“形状” - 如何将各个步骤组合成一个大解决方案 - 由State
monad和{{0}}提供。 {1}}操作。sequence
,zipWith
,sequence
和take
等库函数。你要求一个更惯用的Haskell解决方案,这就是:复杂的递归函数定义不像使用封装常见递归模式的库函数那样惯用。iterate
之类的标准monad来表达它们,你可以重用像State
这样的通用函数来很容易地解决它们。这是一个很高的学习曲线,但结果是值得的!