Triostrong
Triostrong

Reputation: 107

Haskell take ascending (+1) part of list

I'm trying to take init part of list while elements differs from previous only by one. A simple task could be implemented like:

takeAsc:: (Eq a, Enum a) => [a] -> [a]
takeAsc [] = []
takeAsc [x] = [x]
takeAsc (x:y:xs)
  |y == succ x = x: takeAsc(y:xs)
  |otherwise   = [x]

But that's just hurt my feelings. I believe, that's pretty wide used pattern, and I'm just missing some specific function. I've tried to use groupBy or takeWhile, but it looks like they do not do what I want. Could anybody point more elegant solution for that?

Upvotes: 2

Views: 317

Answers (2)

daniel gratzer
daniel gratzer

Reputation: 53881

A simple way to do this would be

takeAsc :: (Enum a, Eq a) => [a] -> [a]
takeAsc []     = []
takeAsc (x:xs) = (x:) . map fst . takeWhile pred . zip xs $ x:xs
  where pred (a, b) = a == succ b

Or in English,

  1. On an empty list, empty list
  2. Pair each element of the list with previous element in the list, elide the first element
  3. Take pairs while the succ of b is a
  4. Remove the second element of the pair
  5. Stick the first element back on

It avoids somewhat icky explicit recursion and GHC should be clever enough to optimize this pretty well.

Upvotes: 2

bheklilr
bheklilr

Reputation: 54058

You could implement it as

takeAsc [] = []
takeAsc xss@(x:xs) = (x:) $ map fst $ takeWhile (uncurry (==)) $ zip xs $ map succ xss

You still have to include both cases, though.

> takeAsc [1, 2, 3, 4]
[1, 2, 3, 4]
> takeAsc [1, 3, 4, 5]
[1]

If you don't like that it always returns at least 1 element for a non-empty list, you would have to use a more verbose solution like you have above, but if you're going for a one-liner this is the best I can come up with.

Upvotes: 3

Related Questions