将列表(生成器a)转换为生成器(列表a)

时间:2017-03-14 20:51:32

标签: functional-programming elm

以下是我的问题的简化版本:生成一个随机值列表,其中每个连续值取决于前一个值。

例如,生成一个随机Int列表,其中每个连续值将为下一步建立最小值。我们假设从value = 0开始,最大值始终为currentValue + 5

  1. 第一步:Random.int 0 5 => 3
  2. 下一个:Random.int 3 8 => 4
  3. 下一个:Random.int 4 9 => 8
  4. 这是我的方法:

    intGen : Int -> List (Rnd.Generator Int) -> List (Rnd.Generator Int)
    intGen value list =
      if length list == 10 then
        list
      else
        let newValue = Rnd.int value (value + 5)
            newList = newValue :: list
        in intGen newValue newList
    

    让我们将其转换为Rnd.Generator (List Int)

    listToGen : List (Rnd.Generator Int) -> Rnd.Generator (List Int)
    listToGen list =
      foldr
        (Rnd.map2 (::))
        (Rnd.list 0 (Rnd.int 0 1))
        list
    

    我不喜欢这部分:(Rnd.list 0 (Rnd.int 0 1))。它生成类型Rnd.Generator (List Int)的初始值,其中(Rnd.int 0 1)实际上从未使用过,但是类型检查需要它。我想以某种方式跳过这部分或用更通用的东西替换它。是可能还是我的实施是错误的?

1 个答案:

答案 0 :(得分:3)

以下是一种使用andThenmap的解决方案。第一个参数是列表中所需的元素数。第二个参数是起始值。

intGen : Int -> Int -> Rnd.Generator (List Int)
intGen num value =
    if num <= 0 then
        constant []
    else
        Rnd.int value (value + 5)
            |> Rnd.andThen (\i -> intGen (num-1) i
            |> Rnd.map (\rest -> i :: rest))

要匹配以0开头的大小为10的列表示例作为第一个低值,您可以将其称为intGen 10 0

constant是来自elm-community/random-extra的生成器,或者可以像这样定义它(因为它没有在核心Elm代码库中公开):

constant : a -> Rnd.Generator a
constant a = Rnd.map (\_ -> a) (Rnd.int 0 1)

关于你的例子,我不认为你会想要使用List (Rnd.Generator Int),因为这意味着一系列不以任何方式捆绑在一起的生成器。这就是为什么我们需要使用andThen来提取刚刚生成的随机值,递归调用intGen减1,然后使用map将列表放在一起。