Mark Karavan
Mark Karavan

Reputation: 2674

Generating a sublist from cycle in Haskell

Let's say I have a list of twelve musical notes (which have their own data type), and I want a function that returns a list of notes starting with a given note and looping around.

data Note = C | CsDb | D | DsEb | E | F | FsGb | G | GsAb | A | AsBb | B deriving (Read, Eq, Ord, Enum, Bounded)
getNotes :: Note -> [Note]
getNotes root = take 12 $ doSomething root $ cycle noteList
    where noteList :: [Note]
          noteList = [minBound..maxBound]

such that

ghci> getNotes E
[E, F, FsGb, G, GsAb, A, AsBb, B, C, CsDb, D, DsEb] 

I can think of a few sloppy ways to do this, but it feels like there should be an obvious, very Haskellian way. Any recommendations?

Upvotes: 2

Views: 87

Answers (4)

yatima2975
yatima2975

Reputation: 6610

How about

getNotes :: Note -> [Note]
getNotes root = ys ++ xs where (xs,ys) = break (==root) [minBound..maxBound]

? This is more-or-less the same as @leftaroundabout's first suggestion, avoids the init, but incurs a number of equality comparisons :-)

Upvotes: 0

Daniel Wagner
Daniel Wagner

Reputation: 152682

The smallest change you can make that works is to use dropWhile:

getNotes :: Note -> [Note]
getNotes root = take 12 . dropWhile (/= root) . cycle $ [minBound .. maxBound]

Upvotes: 2

Random Dev
Random Dev

Reputation: 52270

Based on the second idea of @leftaroundabout this is a working version - just in case you are curious and want to play with it:

{-# LANGUAGE ScopedTypeVariables #-}

module Stackoverflow where

data Note = C | CsDb | D | DsEb | E | F | FsGb | G | GsAb | A | AsBb | B
          deriving (Show, Enum, Bounded)

instance (Enum a, Enum b, Bounded b) => Enum (a,b) where
  toEnum i =
    let (d,m) = i `divMod` (fromEnum (maxBound :: b) + 1)
    in (toEnum d, toEnum m)
  fromEnum (a, b) = fromEnum a * (fromEnum (maxBound :: b) + 1) + fromEnum b

getNotes :: Note -> [Note]
getNotes root = map snd . take 12 $ [(0,root) .. ]

example:

λ> getNotes E
[E,F,FsGb,G,GsAb,A,AsBb,B,C,CsDb,D,DsEb]

PS: the idea is extremely smart @leftaroundabout <- so guys make sure to give him lot's of upvotes ;)

Upvotes: 2

leftaroundabout
leftaroundabout

Reputation: 120711

I'd just

getNotes root = [root .. maxBound] ++ init [minBound .. root]

but I can see how you prefer the cyclic approach. How about

getNotes root = map snd . take 12 $ [(0,root) .. ]

...sadly, that doesn't in fact work: it would need a (Enum a, Enum b, Bounded b) => Enum (a,b) instance, which for some reason isn't defined, at least not in the prelude.

Alternatively, you can use the index of root:

getNotes root = take 12 . drop (fromEnum root) $ cycle [minBound .. maxBound]

Upvotes: 3

Related Questions