从 Haskell 中的循环生成子列表
Generating a sublist from cycle in Haskell
假设我有一个包含 12 个音符的列表(它们有自己的数据类型),我想要一个函数 returns 一个以给定音符开始并循环的音符列表。
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]
这样
ghci> getNotes E
[E, F, FsGb, G, GsAb, A, AsBb, B, C, CsDb, D, DsEb]
我可以想出一些草率的方法来做到这一点,但感觉应该有一个明显的、非常 Haskellian 的方法。有什么建议吗?
我只是
getNotes root = [root .. maxBound] ++ init [minBound .. root]
但我可以看出您更喜欢循环方法。怎么样
getNotes root = map snd . take 12 $ [(0,root) .. ]
...遗憾的是,这实际上行不通:它需要一个 (Enum a, Enum b, Bounded b) => Enum (a,b)
实例,但由于某种原因未定义,至少在前奏中没有定义。
或者,您可以使用 root
的索引:
getNotes root = take 12 . drop (fromEnum root) $ cycle [minBound .. maxBound]
基于@leftaroundabout 的第二个想法,这是一个工作版本 - 以防万一你好奇并想玩它:
{-# LANGUAGE ScopedTypeVariables #-}
module Whosebug 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) .. ]
示例:
λ> getNotes E
[E,F,FsGb,G,GsAb,A,AsBb,B,C,CsDb,D,DsEb]
PS:这个想法非常聪明@leftaroundabout <- 所以大家一定要给他很多赞 ;)
您可以进行的最小更改是使用 dropWhile
:
getNotes :: Note -> [Note]
getNotes root = take 12 . dropWhile (/= root) . cycle $ [minBound .. maxBound]
怎么样
getNotes :: Note -> [Note]
getNotes root = ys ++ xs where (xs,ys) = break (==root) [minBound..maxBound]
?这与@leftaroundabout 的第一个建议或多或少相同,避免了 init
,但会导致一些相等比较:-)
假设我有一个包含 12 个音符的列表(它们有自己的数据类型),我想要一个函数 returns 一个以给定音符开始并循环的音符列表。
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]
这样
ghci> getNotes E
[E, F, FsGb, G, GsAb, A, AsBb, B, C, CsDb, D, DsEb]
我可以想出一些草率的方法来做到这一点,但感觉应该有一个明显的、非常 Haskellian 的方法。有什么建议吗?
我只是
getNotes root = [root .. maxBound] ++ init [minBound .. root]
但我可以看出您更喜欢循环方法。怎么样
getNotes root = map snd . take 12 $ [(0,root) .. ]
...遗憾的是,这实际上行不通:它需要一个 (Enum a, Enum b, Bounded b) => Enum (a,b)
实例,但由于某种原因未定义,至少在前奏中没有定义。
或者,您可以使用 root
的索引:
getNotes root = take 12 . drop (fromEnum root) $ cycle [minBound .. maxBound]
基于@leftaroundabout 的第二个想法,这是一个工作版本 - 以防万一你好奇并想玩它:
{-# LANGUAGE ScopedTypeVariables #-}
module Whosebug 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) .. ]
示例:
λ> getNotes E
[E,F,FsGb,G,GsAb,A,AsBb,B,C,CsDb,D,DsEb]
PS:这个想法非常聪明@leftaroundabout <- 所以大家一定要给他很多赞 ;)
您可以进行的最小更改是使用 dropWhile
:
getNotes :: Note -> [Note]
getNotes root = take 12 . dropWhile (/= root) . cycle $ [minBound .. maxBound]
怎么样
getNotes :: Note -> [Note]
getNotes root = ys ++ xs where (xs,ys) = break (==root) [minBound..maxBound]
?这与@leftaroundabout 的第一个建议或多或少相同,避免了 init
,但会导致一些相等比较:-)