将整数列表转换为颜色列表

时间:2016-06-15 21:29:04

标签: rgb monads elm maybe

我将图片存储为非常大的List Int,我想将它们变成List Color但请记住 rgb 需要3个参数并且 rgba 需要4.所以让我们试试:

toColor : List Int -> List Color
toColor x =
  if List.isEmpty x then
    []
  else
    List.append ( rgba <| List.take 4 x ) ( toColor <| List.drop 4 x )

这个定义是递归的。我们选择4个数字,创建一个rgb颜色并附加结果。但是,如果xList Int,我们就无法写下:

rgba <| List.take 4 x

这是我们得到的一种错误。 rgb 期待三个号码,而不是列表

71|                   rgb <| List.take 4 x 
                             ^^^^^^^^^^^^^
(<|) is expecting the right argument to be a:

    Int

But the right argument is:

    List a

我想知道取出List Int的第一个元素会返回Maybe Int

head : List a -> Maybe a

> head [1,2,3,4]
Just 1 : Maybe.Maybe number

以下是 rgb 的修改,将3 Maybe Int转换为Color。现在,阅读图像数据,我认为 rgba 是必要的,但我只需再添加一个。

rgb' : Maybe Int -> Maybe Int -> Maybe Int -> Color

rgb' a b c =
  case a of
    Nothing -> rgb 0 0 0
    Just a' -> case b of
      Nothing -> rgb 0 0 0
      Just b' -> case c of
        Nothing -> rgb 0 0 0
        Just c' -> rgb a' b' c'

这是在我将这个d3js示例翻译成Elm时开始的,我注意到它使用了Elm目前不支持的一些功能。特别是ctx.getImageData(),因为您无法导入和映像到Canvas。所以这是我的生产班制解决方案的一部分。

2 个答案:

答案 0 :(得分:3)

在我看来,你正在寻找一种非常干净的方式

  1. List Int折叠为List (List Int),如果您想rgbrgba,则每个子列表最多包含3个或4个成员。
  2. 将每个List Int传递给一个函数,该函数将使用rgb转换它,处理最终条目中没有足够Ints的情况。
  3. 第一步,您可以编写一个名为groupList的函数:

    groupsOf : Int -> List a -> List (List a)
    groupsOf size list =
      let
        group =
          List.take size list
    
        rest =
          List.drop size list
      in
        if List.length group > 0 then
          group :: groupsOf size rest
        else
          []
    

    您还可以使用elm-community/list-extra包中的greedyGroupsOf功能获得此功能。

    对于第二步,对列表值本身的结构进行模式匹配会更加清晰,而不是使用List.head并匹配Maybe

    rgbFromList : List Int -> Color
    rgbFromList values =
      case values of
        r::g::b::[] ->
          rgb r g b
        _ ->
          rgb 0 0 0
    

    当列表中恰好有3个条目时,第一个案例将匹配,其他所有内容将落到将{0}交给rgb

    您可以通过执行以下操作将所有这些内容放在一起:

    toColor : List Int -> List Color
    toColor x =
      x
        |> groupsOf 3
        |> List.map rgbFromList
    

    或者,如果您希望完全排除它们,而不是以rgb 0 0 0结束无效颜色,则可以使用List.filterMap功能删除您不想要的内容。我们可以将rgbFromList更改为

    rgbFromList : List Int -> Maybe Color
    rgbFromList values =
      case values of
        r::g::b::[] ->
          Just <| rgb r g b
        _ ->
          Nothing
    

    然后将其称为

    toColor : List Int -> List Color
    toColor x =
      x
        |> groupsOf 3
        |> List.filterMap rgbFromList
    

    请注意,由于此处的groupsOf函数以及elm-community / list-extra中的greedyGroupsOf是递归的,因此对于非常大的列表,它将失败。

    编辑:适用于非常大的列表

    对于非常大的列表,很容易遇到递归问题。你最好的选择是折叠列表并在折叠时管理一些中间状态:

    groupsOf : Int -> List a -> List (List a)
    groupsOf size list =
      let
        update next state =
          if (List.length state.current) == size then
            { current = [next], output = state.current :: state.output }
          else
            { state | current = state.current ++ [next] }
    
        result =
          List.foldl update { current = [], output = [] } list
      in
        List.reverse (result.current :: result.output)
    

    这可以通过折叠列表来实现,这是一个迭代过程而不是递归过程,并且一次构建一个组。最后一步反转列表,因为它将以相反的顺序构造,以便在输出列表开始变大时进行代价而不是进行昂贵的附加。我不希望这会溢出堆栈,但它很可能非常慢。在我看来,在合理的时间内获得所需结果的最佳选择是使用for循环在JavaScript中编写groupsOf函数,然后通过端口传递结果。

答案 1 :(得分:1)

这种递归实现应该在不构建堆栈的情况下工作,因为Elm具有尾部调用优化。每个步骤从原始列表中取三个整数,并将它们附加到颜色列表中。当原始列表中的元素少于三个时,递归停止并返回颜色列表。

它使用rgb,但可以轻松修改以从列表中获取4个元素。

它也会反转顺序,因此您可能需要将它与List.reverse结合使用。

import Color exposing (rgb, Color)


listColors : List Int -> List Color
listColors = 
  listColors' []


listColors' : List Color -> List Int -> List Color
listColors' colors ints =
  case ints of
    r :: g :: b :: rest ->
      listColors' (rgb r g b :: colors) rest
    _ ->
      colors