【问题标题】:Building a list from a non-traversable without recursion从不递归的不可遍历的列表中构建列表
【发布时间】:2020-12-21 16:58:07
【问题描述】:

我可以通过映射(mapmapM)或折叠(foldl、@987654327)来构建属于Traversable 类型类(例如ListMap)的数据结构@) 另一个可遍历的数据结构。

但是,我经常遇到需要使用Num 类型类的成员(例如Integer)构建可遍历数据结构的情况。

我这里常用的方法是使用递归操作来构建一个列表——例如:

foo :: Integer -> [Integer] -> [Integer]
foo n fs
  | m < 2 = fs
  | rem m 2 == 0 = foo (m - 1) (m:fs)
  | otherwise = foo (m - 1) fs
  where m = abs n

此函数返回可被 2 整除且介于 2 和 n(含)之间的整数的绝对值。

使用上面的示例,是否有一种惯用的方法可以在不使用递归的情况下从不可遍历的列表中构建列表?

【问题讨论】:

  • 这里的目标到底是什么?是在不使用递归的情况下重写您的foo 函数吗?你能用英语解释一下foo函数应该做什么吗?
  • foldr 可以为可折叠数据结构生成丢失。例如,在任意Foldable 数据结构上执行toList 的简单方法是foldr (:) []
  • @Aplet123 总的来说,我想了解如何在不使用递归的情况下解决这类问题,以此为例——但是,这里的目标也是重写特定的示例函数foo。将更新问题以解释该功能的作用。
  • 这个特定的函数可以非递归地写成\n fs -&gt; [2,4..n] ++ fs,但是在不知道函数做什么的情况下,无法对某个通用函数执行这样的转换。对于不那么琐碎的函数,使它们成为非递归的可能会更困难。

标签: haskell recursion enums traversable


【解决方案1】:

您正在寻求一种在不使用递归的情况下从不可遍历的对象构建列表的方法,但我认为这真的不是您想要的。毕竟,任何遍历都会使用递归——你认为mapfoldl是如何实现的?我认为您要问的更准确的问题是,是否有众所周知的函数或内置方式来表达所谓的“数字折叠”,其中递归在“幕后”,或者是隐式的,而不是显式的在您的foo 示例中。

嗯,实现这一点的一种简单方法是自己编写一个foldNum 函数。例如:

foldNum :: Num n => (n -> a -> a) -> n -> a
foldNum f n = f n (foldNum f (n - 1))

然后,您可以将foo 定义为:

foo :: Integer -> [Integer]
foo = reverse . foldNum go . abs
  where
    go n a | n < 2        = []
           | rem n 2 == 0 = n:a
           | otherwise    = a

如果您对此有点失望,我明白原因:使用foldNum 的定义并没有真正节省多少。事实上,我上面给出的定义甚至没有内置的基本情况。折叠数字的问题在于有很多方法可以做到!您可以在每个步骤中减去或添加任何数量,并且没有明确的停止位置(零似乎是一个自然的停止位置,但这仅适用于非负数)。一种方法是尝试使我们的foldNum 更加通用。怎么样:

foldNum :: (n -> a -> a) -> (n -> Bool) -> (n -> n) -> a -> n -> a
foldNum f stop step a n
  | stop n = a
  | otherwise = foldNum f stop step (f n a) (step n)

现在,我们可以将foo 写成:

foo :: Integer -> [Integer]
foo = foldNum (\x a -> if even x then x:a else a) (< 2) (subtract 1) [] . abs

也许这就是你要找的东西?


脚注: 正如列表可以向左或向右折叠(foldlfoldr),我们也可以用两种不同的方式折叠数字。您可以通过将上述foldNum 定义的最后一行替换为::

  | otherwise = f n $ foldNum f stop step a (step n)

例如,对于foo,这两者之间的区别在于结果列表的顺序。

【讨论】:

  • 是的,这正是我想要的,谢谢!关于这个主题,你有什么推荐的阅读材料吗?
  • 我手头没有任何链接可以建议,但我建议查找的关键字是“catamorphism”。 catamorphism 是折叠概念的概括。搜索“fold catamorphism”出现了几篇看起来不错的博客文章。
【解决方案2】:

由于您要从 2 变为 n,并过滤掉值,因此请使用专门为此设计的 filter 函数:

foo :: Integer -> [Integer]
foo n = filter (\x -> x `mod` 2 == 0) [2..n]

【讨论】:

    【解决方案3】:

    我认为您可能正在寻找来自Data.List 的库函数unfoldr。它有签名:

    unfoldr :: (b -> Maybe (a, b)) -> b -> [a]
    

    它从一个不可遍历的b 类型的值生成一个列表[a]。它通过重复将其第一个参数应用于b 值来获得一个新的a 值以添加到列表中,并为下一次调用更新一个b 值,直到它获得结束列表的Nothing

    请注意,它不允许您在不生成任何as 的情况下跳过某些bs,或者让您为单个b 生成多个as。但是,您可以通过返回列表列表然后连接来解决该限制。因此,您的 foo 示例如下所示:

    foo = concat . unfoldr step
      where step n | m < 2 = Nothing  -- time to stop
                   | rem m 2 == 0 = Just ([m], m-1)  -- return one element
                   | otherwise    = Just ([], m-1)   -- return no elements
              where m = abs n
    

    在许多更现实的场景中,您不需要concat 列表列表。例如:

    import Data.List
    
    bar :: Int -> [[Int]]
    bar n = unfoldr step 1
      where step k | k > n     = Nothing
                   | otherwise = Just (replicate k k, k + 2)
    
    main = do
      print $ bar 10
      -- [[1],[3,3,3],[5,5,5,5,5],[7,7,7,7,7,7,7],[9,9,9,9,9,9,9,9,9]]
    

    【讨论】:

      猜你喜欢
      • 2019-08-03
      • 2016-04-30
      • 2014-03-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-05
      • 2021-09-19
      • 2019-08-06
      • 1970-01-01
      相关资源
      最近更新 更多