【问题标题】:How can I filter a random Haskell list so that each element is no more than distance n apart from the next?如何过滤随机 Haskell 列表,使每个元素与下一个元素的距离不超过 n?
【发布时间】:2013-12-19 20:36:44
【问题描述】:

我正在用 Haskell 编写一个程序,该程序会生成随机的整数三和弦,以便使用 Euterpea 将它们作为和弦演奏。这是我的列表列表的样子(显然,它是无限的,但我会take 10 $ chop 3 $ randInts,其中chop 只是一个将列表拆分为(有序)三个分组的函数,而randPitch 只是在间隔24 上生成整数- 84):

[[27,33,48],[53,59,82],[31,49,62],[50,60,63],[51,56,79],[57,59,60],[52,63,69],[52,77,79],[32,32,37],[39,77,84]] 

为了了解一点音乐理论,我对这些三和弦应用了各种过滤器。给我带来麻烦的一个(至少在 Haskell 中实现,这对我来说是一门新语言)是 Dmitri Tymoczko 所说的“结合旋律符号”——即旋律(在这种情况下,顶部的“音符”或音高三元组)应该只移动短距离。

我想做的是编写一个函数,它递归地过滤无穷无尽的三和弦列表,结果是一个三和弦列表,其顶部音符仅在每个三和弦之间移动 n和弦:本质上,一种随机游走,你只能在任何一步移动n个半音。以下是我目前拥有的功能:

jumpSize :: (Num a, Ord a) => [[a]] -> [[a]]
jumpSize (_:[]) = []
jumpSize (x:y:[]) = (abs (maximum x - maximum y) : x) : []
jumpSize (x:y:zs) = (abs (maximum x - maximum y) : x) : jumpSize (y:zs)

这将在列表 x 的开头添加从三元组 x 到 y 的距离。所以take 5 $ jumpSize $ chop 3 $ randPitch给了我:

[[34,27,33,48],[20,53,59,82],[1,31,49,62],[16,50,60,63],[19,51,56,79]]      

我尝试编写一个递归函数,它过滤掉所有跳跃大于 n 的和弦,重新计算新的 jumpSizes,然后再次应用自己。但是,我遇到了麻烦,因为此功能要么使 GHCi 崩溃,要么给我带来各种问题。在结构上,我仍然想通过过滤随机数组来产生和弦,但我认为我错过了一些关于如何用函数式语言最好地做到这一点的东西。也许我需要提供一个初始的发电机弦?谢谢!

jumpRecur :: (Num a, Ord a) => a -> [[a]] -> [[a]]
jumpRecur n (xs)
    | [x | x <- xs, head x > n] == []   = xs
    | otherwise                 = jumpRecur n $ jumpSize $ filter (\x -> head x <= n) xs

这是一个示例输出:

*Main> jumpRecur 5 $ jumpSize $ chop 3 $ take 1000 $ randPitch
[[2,2,2,2,2,2,2,2,70,70,76],[1,1,1,1,1,1,1,1,55,74,74],[1,1,1,1,1,1,1,5,26,28,73],         [3,3,3,3,3,3,0,1,26,69,74],[5,5,5,5,5,5,5,5,33,43,77],[0,0,0,0,0,0,0,0,47,67,82],[2,2,2,2,2,2,4,4,37,66,82],[3,3,3,3,3,3,3,3,59,69,84],[4,4,4,4,4,4,4,4,59,79,81],[4,4,5,5,5,5,5,5,28,69,77],[5,5,5,5,5,5,5,5,54,68,73],[0,0,0,0,0,0,0,0,32,73,78],[5,5,5,5,5,5,1,4,52,62,78],[0,0,0,0,0,0,0,0,58,71,73],[3,3,3,3,3,3,4,4,25,64,73],[1,1,1,1,1,3,3,3,35,42,76],[4,4,4,4,4,4,4,4,35,39,77]]

【问题讨论】:

  • 为什么不只生成该范围内的随机数呢?在每次迭代中,您可以生成一个随机数 randomR (-n, n) gen 并将其添加到上一个前注中。
  • 我的意思是这正是我在命令式语言中的做法。我想我喜欢在一些巨大的无限阵列中想象所有可能的和弦,然后将它们过滤掉。
  • 如果你可以用命令式语言来做,为什么不能用 Haskell 呢?不过,处理随机数生成有一些注意事项。我建议使用 MonadRandom 包中的 Rand monad。我将发布一个如何使用它的示例。
  • @todkwxrtvwmzonunswam Here's 一个似乎运行良好的示例。就像comingstorm 说的那样,您需要对其进行测试并对其进行修改以适合您的口味。随机数的生成有点棘手,因为您必须为和弦中的每个音符计算一个新的范围,但我添加了很多 cmets 来提供帮助。如果你有问题,就问吧。希望这对您有所帮助
  • State monad 看似简单,它所做的只是跟上随时间变化的值。使用随机数生成,这个值就是你的种子(或生成器)。因为您希望种子在每次使用后都发生变化(否则您不会得到随机值),您要么必须处理IO,要么管理作为不断变化的种子的“状态”。 Rand monad 只是为您实现了这种行为,并且可以轻松获取随机数。如果其中任何一个还没有意义,请不要担心,一开始几乎每个人都会感到困惑。

标签: haskell recursion


【解决方案1】:

概括过滤器可能是一个很好的 Haskell 习惯用法,如下所示:

jumpBy :: (a -> a -> Bool) -> [a] -> [a]

jumpBy ok (x:y:zs) | ok x y    = x:jumpBy ok (y:zs)  -- accept y & step
                   | otherwise = jumpBy ok (x:zs)    -- reject y & retry
jumpBy _ xs = xs

如果您确定所有输入都是无限列表,那么您实际上并不需要处理有限列表末端的最后一行,但处理所有情况是一种很好的做法。

然后,将您的通用过滤器与您的特定测试一起使用:

jumpSize = jumpBy (\x y -> abs(maximum x - maximum y) < n)

【讨论】:

    猜你喜欢
    • 2011-09-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-14
    • 1970-01-01
    相关资源
    最近更新 更多