简体   繁体   English

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

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

Say you have a list of numbers, [1,2,3,5,6,7,8,9,11,12,15,16,17]假设你有一个数字列表, [1,2,3,5,6,7,8,9,11,12,15,16,17]

and you want a function that takes that as an input and returns something like并且您想要一个将其作为输入并返回类似内容的函数

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

how would this be done?这将如何完成? all of the solutions i've found online are very very long and quite convoluted, when this seems like such an easy problem for a functional language like haskell我在网上找到的所有解决方案都非常长而且非常复杂,这对于像 haskell 这样的函数式语言来说似乎是一个简单的问题

So we have a list of numbers,所以我们有一个数字列表,

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

We turn it into a list of segments,我们把它变成一个段列表,

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

we join the touching segments,我们加入感人的片段,

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

and we subtract 1 from each segment's ending value,我们从每个段的结束值中减去1

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

All in all we get总之我们得到

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

Simple and clear.简单明了。

edit: or, to be properly lazy,编辑:或者,要适当地懒惰,

  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)

update: as dfeuer notes, (a,a) type is better than [a,a] .更新:正如dfeuer 所指出的, (a,a)类型比[a,a]更好。 Wherever [P,Q] appears in this code, replace it with (P,Q) .无论[P,Q]出现在此代码中的何处,都将其替换为(P,Q) This will improve the code, with zero cost to readability.这将改进代码,零成本的可读性。

So one might do it like the idea from @Will Ness on the stateful folding or mine under the same answer .因此,人们可能会像@Will Ness关于有状态折叠的想法那样做,或者在相同的答案下进行我想法 All explanations are to be found there.所有的解释都可以在那里找到。 Besides, if you get curious and want to read more about it then have a look at Haskell Continuation Passing Style page .此外,如果您感到好奇并想阅读更多相关信息,请查看Haskell Continuation Passing Style 页面 I am currently trying to gerealize this in such a way that we can have a variant of foldr1 in a stateful manner.我目前正试图以这样一种方式来实现这一点,即我们可以以有状态的方式拥有foldr1的变体。 A foldS :: Foldable t => (a -> a -> b) -> ta -> b . A foldS :: Foldable t => (a -> a -> b) -> ta -> b However this is still not general stateful folding.然而,这仍然不是一般的有状态折叠。 It's just tailored to this question.它只是针对这个问题量身定制的。

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]]

I haven't had time to test any edge cases.我没有时间测试任何边缘情况。 All advices are welcome.欢迎所有建议。

I would definitely prefer the alternative representation to the first one you give.与您提供的第一个表示相比,我绝对更喜欢替代表示。

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)

To solve the Rosetta Code problem linked in the comment above, this isn't really quite an optimal representation.为了解决罗塞塔代码问题在上面的评论链接,这是不是真的的最佳表征。 I'll try to explain how to match the representation more precisely later.稍后我将尝试解释如何更精确地匹配表示。

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

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