1

I have an image stored as a very large List Int and I would like to turn them into a List Color However remember that rgb requires 3 arguments and rgba requires 4. So let's try:

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 )

This definition is recursive. We chomp 4 numbers, create an rgb color and append the results. However if x is List Int we cannot write this:

rgba <| List.take 4 x

Here is the kind of error we get. rgb is expecting three numbers and instead it gets a list

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

    Int

But the right argument is:

    List a

I wonder that taking out the first element of a List Int returns a Maybe Int

head : List a -> Maybe a

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

Here is a modification of rgb which turns 3 Maybe Int into a Color. Now, reading the image data, I think rgba is necessary but I just add one more.

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'

this started when I was translating this d3js example to Elm and I noticed it used some features which aren't currently supported in Elm. In particular ctx.getImageData() since you can't import and image to Canvas. So this is part of my make-shift solution.

john mangual
  • 7,718
  • 13
  • 56
  • 95
  • right now I have a solution with Elm's [`Array`](http://package.elm-lang.org/packages/elm-lang/core/4.0.1/Array) type which I thought might be simplified using List, but seems to amount to the same :-/ – john mangual Jun 15 '16 at 21:31
  • it's not currently clear enough what you're trying to figure out. can you edit your post to contain a clear question that describes exactly what you're trying to do? – lukewestby Jun 15 '16 at 21:53
  • @lukewestby OK I will try... btw do you program in Elm? – john mangual Jun 15 '16 at 22:23
  • sure do! https://github.com/lukewestby?tab=repositories i think i understand what you're looking for well enough to provide an answer, but it would still be helpful to others coming to this later if you clarified a question anyway. – lukewestby Jun 15 '16 at 22:27

2 Answers2

3

It seems to me that you're looking for a really clean way to

  1. Collapse a List Int into a List (List Int), where each child list has at most 3 or 4 members if you want to do rgb or rgba.
  2. Pass each List Int into a function that will convert it using rgb, handling the case where there aren't enough Ints in the final entry.

For the first step, you can write a function called 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
      []

You can also get this functionality by using the greedyGroupsOf function from the elm-community/list-extra package.

For the second step, it'll be much cleaner to pattern match on the structure of the list value itself rather than using List.head and matching on Maybe.

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

The first case will be matched when there are exactly 3 entries in the list, and everything else will fall through to handing 0s to rgb.

You can put all of these things together by doing the following:

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

Alternatively, if instead of ending up with rgb 0 0 0 for invalid colors you want to exclude them entirely, you can use the List.filterMap function to prune out things you don't want. We can change rgbFromList to look like

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

and then call it like

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

Please be advised that since the groupsOf function here as well as greedyGroupsOf in elm-community/list-extra is recursive, it will fail for very large lists.

Edit: for very large lists

For very large lists it's easy to get into trouble with recursion. Your best bet is to fold over the list and manage some intermediate state while you're folding:

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)

This works by folding over the list, which is an iterative process rather than recursive, and building up groups one at a time. The last step reverses the list because it will be constructed in reverse order in order to cons instead of doing costly appends once the output list begins to grow large. I don't expect this to overflow the stack, but it is likely that it will be very slow. In my opinion, your best option to get the outcome you are looking for in a reasonable amount of time is to write the groupsOf function in JavaScript using a for loop and then pass the result in through a port.

lukewestby
  • 1,207
  • 8
  • 15
  • my list has length 2 million. it's result of `ctx.getImageData()` – john mangual Jun 15 '16 at 23:44
  • in a way the colors are irrelevant this is an abstract problem about grouping elements of lists into 4s. in my case rbg requires 3 elements and I should skip the 4th, or just use rgba. – john mangual Jun 16 '16 at 00:07
  • gotcha! i can edit my answer to show how you would group 2 million elements into groups of 4 without overflowing the stack in addition to the info that's already there. would that work? – lukewestby Jun 16 '16 at 17:23
1

This recursive implementation should work without building the stack, because Elm has tail call optimisation. Each step takes three ints from the original list, and appends them to the list of colors. Recursion stops and returns the list of colors, when there are less than three elements in the original list.

It uses rgb, but can be easily modified to take 4 elements from the list.

It also reverses the order, so you might need to combine it with 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
Andrey Kuzmin
  • 4,479
  • 2
  • 23
  • 28