如何在 Haskell 中将列表中的连续数字组合成一个范围?

Bob*_*son 5 haskell list

我正在努力解决 Haskell 问题,但我很难确定此特定任务的一般程序/算法。我想要做的基本上是给 Haskell 一个列表 [1,2,3,5,6,9,16,17,18,19] 并让它给我回 [1-3, 5, 6, 9, 16 -19] 因此基本上将三个或更多连续数字转换为最低数字 - 最高数字风格的范围。我想我对它的问题是与 Haskell 的功能范式打交道的普遍困难。因此,我真的很感激通用算法或从“Haskellian”的角度了解如何看待这一点的见解。

提前致谢。

Ste*_*ans 4

如果我正确理解这个问题,其想法是将输入列表分解为块,其中块要么是单个输入元素,要么是至少三个连续元素的范围。

因此,让我们首先定义一个用于表示此类块的数据类型:

data Chunk a = Single a | Range a a
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,类型在输入元素的类型中是参数化的。

接下来,我们定义一个函数chunks来实际从输入元素列表构造块列表。为此,我们需要能够比较输入元素并获取给定输入元素(即其后继元素)的直接连续元素。因此,函数的类型为

chunks :: (Eq a, Enum a) => [a] -> [Chunk a]
Run Code Online (Sandbox Code Playgroud)

实现相对简单:

chunks = foldr go []
 where
  go x (Single y : Single z : cs) | y == succ x && z == succ y = Range x z : cs
  go x (Range y z : cs) | y == succ x = Range x z : cs
  go x cs                             = Single x : cs
Run Code Online (Sandbox Code Playgroud)

我们从右向左遍历列表,边遍历边生成块。如果输入元素位于其两个直接连续元素之前(辅助函数的第一种情况go),或者它位于以其直接连续元素开头的范围之前(第二种情况),则我们生成一个范围。否则,我们生成一个元素(最终情况)。

为了安排漂亮的输出,我们将类型构造函数的应用程序声明Chunk为该类的实例Show(假定输入元素的类型位于 中Show):

instance Show a => Show (Chunk a) where
  show (Single x ) = show x
  show (Range x y) = show x ++ "-" ++ show y
Run Code Online (Sandbox Code Playgroud)

回到问题中的例子,我们有:

> chunks [1,2,3,5,6,9,16,17,18,19]
[1-3,5,6,9,16-19]
Run Code Online (Sandbox Code Playgroud)

不幸的是,如果我们需要考虑有界元素类型,事情会稍微复杂一些;此类类型的最大元素未定义succ

> chunks [maxBound, 1, 2, 3] :: [Chunk Int]
*** Exception: Prelude.Enum.succ{Int}: tried to take `succ' of maxBound
Run Code Online (Sandbox Code Playgroud)

这表明我们应该从确定一个元素是否继承另一个元素的具体方法中抽象出来:

chunksBy :: (a -> a -> Bool) -> [a] -> [Chunk a]
chunksBy succeeds = foldr go []
 where
  go x (Single y : Single z : cs) | y `succeeds` x && z `succeeds` y =
    Range x z : cs
  go x (Range y z : cs) | y `succeeds` x = Range x z : cs
  go x cs = Single x : cs
Run Code Online (Sandbox Code Playgroud)

现在,上面给出的版本可以简单地通过写作chunks来表达chunksBy

chunks :: (Eq a, Enum a) => [a] -> [Chunk a]
chunks = chunksBy (\y x -> y == succ x)
Run Code Online (Sandbox Code Playgroud)

此外,我们现在还可以实现有界输入类型的版本:

chunks' :: (Eq a, Enum a, Bounded a) => [a] -> [Chunk a]
chunks' = chunksBy (\y x -> x /= maxBound && y == succ x)
Run Code Online (Sandbox Code Playgroud)

这给我们带来了快乐:

> chunks' [maxBound, 1, 2, 3] :: [Chunk Int]
[9223372036854775807,1-3]
Run Code Online (Sandbox Code Playgroud)

  • 这对于有界类型来说是错误的。`chunks ([127, 1, 2, 3] :: [Int8])` 应该是 `[127, 1-3]`,但实际上会出现 ``*** Exception: Enum.succ{Int8}:尝试获取 maxBound 的“succ”。 (2认同)
  • @StefanHoldermans 在问了[我自己的问题](/sf/ask/4318212741/)之后,事实证明有一个更好的方法: `chunksBy (\yx -> case [x..y] [_,_] -> True; _ -> False)` 而且它甚至不需要 `Eq` 约束! (2认同)