简体   繁体   English

使用fold *在Haskell中增加列表

[英]Using fold* to grow a list in Haskell

I'm trying to solve the following problem in Haskell: given an integer return the list of its digits. 我正在尝试解决Haskell中的以下问题:给定整数返回其数字列表。 The constraint is I have to only use one of the fold* functions (* = {r,l,1,l1}). 约束是我只需要使用fold *函数之一(* = {r,l,1,l1})。

Without such constraint, the code is simple: 没有这样的约束,代码很简单:

list_digits :: Int -> [Int]
list_digits 0 = []
list_digits n = list_digits r ++ [n-10*r] 
                where
                     r = div n 10

But how do I use fold* to, essentially grow a list of digits from an empty list? 但是,我该如何使用fold *来从空列表中生长出一个数字列表呢?

Thanks in advance. 提前致谢。

Is this a homework assignment? 这是家庭作业吗? It's pretty strange for the assignment to require you to use foldr , because this is a natural use for unfoldr , not foldr . 要求您使用foldr的分配很奇怪,因为这是unfoldr而不是foldr的自然用法。 unfoldr :: (b -> Maybe (a, b)) -> b -> [a] builds a list, whereas foldr :: (a -> b -> b) -> b -> [a] -> b consumes a list. unfoldr :: (b -> Maybe (a, b)) -> b -> [a] 建立一个列表,而unfoldr :: (b -> Maybe (a, b)) -> b -> [a] foldr :: (a -> b -> b) -> b -> [a] -> b 消耗列表。 An implementation of this function using foldr would be horribly contorted. 使用foldr来实现此功能的过程会非常扭曲。

listDigits :: Int -> [Int]
listDigits = unfoldr digRem
    where digRem x
              | x <= 0 = Nothing
              | otherwise = Just (x `mod` 10, x `div` 10)

In the language of imperative programming, this is basically a while loop. 用命令式编程语言来说,这基本上是一个while循环。 Each iteration of the loop appends x `mod` 10 to the output list and passes x `div` 10 to the next iteration. 循环的每个迭代将x `mod` 10附加到输出列表,并将x `div` 10传递到下一个迭代。 In, say, Python, this'd be written as 例如,在Python中,这将写为

def list_digits(x):
    output = []
    while x > 0:
        output.append(x % 10)
        x = x // 10
    return output

But unfoldr allows us to express the loop at a much higher level. 但是unfoldr可以使我们在更高的层次上表达循环。 unfoldr captures the pattern of "building a list one item at a time" and makes it explicit. unfoldr捕获“一次建立一个列表项”的模式,并使之明确。 You don't have to think through the sequential behaviour of the loop and realise that the list is being built one element at a time, as you do with the Python code; 您无需考虑循环的顺序行为,也不必像使用Python代码那样一次一次地构建一个列表。 you just have to know what unfoldr does. 您只需要知道unfoldr器的作用即可。 Granted, programming with folds and unfolds takes a little getting used to, but it's worth it for the greater expressiveness. 当然,使用折叠和展开进行编程需要一点时间来适应,但值得一提的是它具有更高的表现力。

If your assignment is marked by machine and it really does require you to type the word foldr into your program text, (you should ask your teacher why they did that and) you can play a sneaky trick with the following " id [] -as- foldr " function: 如果您的作业是用机器标记的,并且确实需要您在程序文本中输入foldr字样(您应该问老师为什么这么做),则可以使用以下“ id [] -as来偷偷摸摸的把戏-文件foldr “功能:

obfuscatedId = foldr (:) []
listDigits = obfuscatedId . unfoldr digRem

Though unfoldr is probably what the assignment meant, you can write this using foldr if you use foldr as a hylomorphism , that is, building up one list while it tears another down. 虽然unfoldr大概是什么意思任务,你可以写这个使用foldr ,如果您使用foldrhylomorphism,那就是建立一个列表,而这泪水另一下来。

digits :: Int -> [Int]
digits n = snd $ foldr go (n, []) places where
  places = replicate num_digits ()
  num_digits | n > 0     = 1 + floor (logBase 10 $ fromIntegral n)
             | otherwise = 0
  go () (n, ds) = let (q,r) = n `quotRem` 10 in (q, r : ds)

Effectively, what we're doing here is using foldr as "map-with-state". 实际上,我们在这里所做的是将文件foldr用作“状态映射”。 We know ahead of time how many digits we need to output (using log10) just not what those digits are, so we use unit ( () ) values as stand-ins for those digits. 我们提前知道需要输出多少个数字(使用log10),而不是这些数字是什么,因此我们将unit( () )值用作这些数字的替代。

If your teacher's a stickler for just having a foldr at the top-level, you can get away with making go partial: 如果你的老师只是有一个坚持己见foldr ,在顶层,你可以逃脱做go部分:

digits' :: Int -> [Int]
digits' n = foldr go [n] places where
  places = replicate num_digits ()
  num_digits | n > 0     = floor (logBase 10 $ fromIntegral n)
             | otherwise = 0
  go () (n:ds) = let (q,r) = n `quotRem` 10 in (q:r:ds)

This has slightly different behaviour on non-positive numbers: 这对非正数的行为略有不同:

>>> digits 1234567890
[1,2,3,4,5,6,7,8,9,0]
>>> digits' 1234567890
[1,2,3,4,5,6,7,8,9,0]
>>> digits 0
[]
>>> digits' 0
[0]
>>> digits (negate 1234567890)
[]
>>> digits' (negate 1234567890)
[-1234567890]

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM