繁体   English   中英

如何将数字列表转换为 Haskell 中的范围列表?

[英]How do you convert a list of numbers into a list of ranges in haskell?

假设你有一个数字列表, [1,2,3,5,6,7,8,9,11,12,15,16,17]

并且您想要一个将其作为输入并返回类似内容的函数

[[1,3],[5,9],[11,12],[15,17]]或者[(1,3), (5,9), (11,12), (15,17)]

这将如何完成? 我在网上找到的所有解决方案都非常长而且非常复杂,这对于像 haskell 这样的函数式语言来说似乎是一个简单的问题

所以我们有一个数字列表,

xs = [1,2,3,5,6,7,8,9,11,12,14,16,17]     -- 14 sic!

我们把它变成一个段列表,

ys = [[x,x+1] | x <- xs]
  -- [[1,2], [2,3], [3,4], [5,6], ..., [11,12], [12,13], [14,15], [16,17], [17,18] ]

我们加入感人的片段,

zs = foldr g [] ys
  -- [[1,4], [5,10], [11,13], [14,15], [16,18]]
  where
  g [a,b] []                  =  [[a,b]]
  g [a,b] r@([c,d]:t) | b==c  =  [a,d]:t
                 | otherwise  =  [a,b]:r

我们从每个段的结束值中减去1

ws = [[a,b-1] | [a,b] <- zs]
  -- [[1,3], [5,9], [11,12], [14,14], [16,17]]

总之我们得到

ranges :: (Num t, Eq t) => [t] -> [[t]]
ranges  =  map (\[a,b] -> [a,b-1]) . foldr g [] . map (\x -> [x,x+1]) 
  where
  g [a,b] []                  =  [[a,b]]
  g [a,b] r@([c,d]:t) | b==c  =  [a,d]:t
                 | otherwise  =  [a,b]:r

简单明了。

编辑:或者,要适当地懒惰,

  where
  g [a,b] r = [a,x]:y
       where
       (x,y) = case r of ([c,d]:t) | b==c  -> (d,t)   -- delay forcing
                         _                 -> (b,r)

更新:正如dfeuer 所指出的, (a,a)类型比[a,a]更好。 无论[P,Q]出现在此代码中的何处,都将其替换为(P,Q) 这将改进代码,零成本的可读性。

因此,人们可能会像@Will Ness关于有状态折叠的想法那样做,或者在相同的答案下进行我想法 所有的解释都可以在那里找到。 此外,如果您感到好奇并想阅读更多相关信息,请查看Haskell Continuation Passing Style 页面 我目前正试图以这样一种方式来实现这一点,即我们可以以有状态的方式拥有foldr1的变体。 A foldS :: Foldable t => (a -> a -> b) -> ta -> b 然而,这仍然不是一般的有状态折叠。 它只是针对这个问题量身定制的。

ranges :: (Ord a, Num a) => [a] -> [[a]]
ranges xs = foldr go return xs $ []
            where
            go :: (Ord a, Num a) => a -> ([a] -> [[a]]) -> ([a] -> [[a]])
            go c f = \ps -> let rrs@(r:rs) = f [c] 
                            in case ps of 
                               []  -> [c]:r:rs
                               [p] -> if p + 1 == c then rrs else [p]:(c:r):rs

*Main> ranges [1,2,3,5,6,7,8,9,11,12,15,16,17]
[[1,3],[5,9],[11,12],[15,17]]

我没有时间测试任何边缘情况。 欢迎所有建议。

与您提供的第一个表示相比,我绝对更喜欢替代表示。

ranges :: (Num a, Eq a) => [a] -> [(a,a)]
ranges [] = []
ranges (a : as) = ranges1 a as

-- | A version of 'ranges' for non-empty lists, where
-- the first element is supplied separately.
ranges1 :: (Num a, Eq a) => a -> [a] -> [(a,a)]
ranges1 a as = (a, b) : bs
  where
    -- Calculate the right endpoint and the rest of the
    -- result lazily, when needed.
    (b, bs) = finish a as

-- | This takes the left end of the current interval
-- and the rest of the list and produces the right endpoint of
-- that interval and the rest of the result.
finish :: (Num a, Eq a) => a -> [a] -> (a, [(a, a)])
finish l [] = (l, [])
finish l (x : xs)
  | x == l + 1 = finish x xs
  | otherwise = (l, ranges1 x xs)

为了解决罗塞塔代码问题在上面的评论链接,这是不是真的的最佳表征。 稍后我将尝试解释如何更精确地匹配表示。

暂无
暂无

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

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