Превращение списка целых чисел в список цветов

У меня есть изображение, сохраненное как очень большое 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 и добавляем результаты. Однако, если x равно List 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 и заметил, что в нем используются некоторые функции, которые не t в настоящее время поддерживается в Elm. В частности, ctx.getImageData(), поскольку вы не можете импортировать изображения и изображения в Canvas. Так что это часть моего импровизированного решения.


person john mangual    schedule 15.06.2016    source источник
comment
прямо сейчас у меня есть решение с Elm Array тип, который, как я думал, может быть упрощен с помощью списка, но, похоже, тот же самый :-/   -  person john mangual    schedule 16.06.2016
comment
в настоящее время недостаточно ясно, что вы пытаетесь выяснить. можете ли вы отредактировать свой пост, чтобы он содержал четкий вопрос, который точно описывает, что вы пытаетесь сделать?   -  person lukewestby    schedule 16.06.2016
comment
@lukewestby Хорошо, я попробую... кстати, ты программируешь на Elm?   -  person john mangual    schedule 16.06.2016
comment
конечно да! github.com/lukewestby?tab=repositories я думаю, что хорошо понимаю, что вы ищете достаточно, чтобы дать ответ, но все же было бы полезно для других, которые придут к этому позже, если бы вы все равно уточнили вопрос.   -  person lukewestby    schedule 16.06.2016


Ответы (2)


Мне кажется, что вы ищете действительно чистый способ

  1. Сверните List Int в List (List Int), где каждый дочерний список имеет не более 3 или 4 элементов, если вы хотите сделать rgb или rgba.
  2. Передайте каждый List Int в функцию, которая преобразует его с помощью rgb, обрабатывая случай, когда в окончательной записи недостаточно целых чисел.

Для первого шага вы можете написать функцию с именем 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
      []

Вы также можете получить эту функциональность, используя функцию greedyGroupsOf из elm- пакет community/list-extra.

Для второго шага будет намного чище сопоставлять шаблоны со структурой самого значения списка, а не использовать 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 здесь, а также greedyGroupsOf в elm-community/list-extra является рекурсивной, она не будет работать для очень больших списков.

Изменить: для очень больших списков

Для очень больших списков легко получить проблемы с рекурсией. Лучше всего свернуть список и управлять некоторым промежуточным состоянием, пока вы сворачиваете:

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)

Это работает путем свертки списка, что является итеративным процессом, а не рекурсивным, и создания групп по одной за раз. На последнем шаге список переворачивается, потому что он будет построен в обратном порядке, чтобы избежать затрат вместо дорогостоящих добавлений, как только выходной список начнет увеличиваться. Я не ожидаю, что это приведет к переполнению стека, но вполне вероятно, что это будет очень медленно. На мой взгляд, лучший способ получить желаемый результат за разумное время — это написать функцию groupsOf в JavaScript с использованием цикла for, а затем передать результат через порт.

person lukewestby    schedule 15.06.2016
comment
мой список имеет длину 2 миллиона. это результат ctx.getImageData() - person john mangual; 16.06.2016
comment
в некотором смысле цвета не имеют значения, это абстрактная проблема группировки элементов списков в 4. в моем случае rbg требует 3 элемента, и я должен пропустить 4-й или просто использовать rgba. - person john mangual; 16.06.2016
comment
Попался! я могу отредактировать свой ответ, чтобы показать, как вы могли бы сгруппировать 2 миллиона элементов в группы по 4, не переполняя стек в дополнение к уже имеющейся информации. это сработает? - person lukewestby; 16.06.2016

Эта рекурсивная реализация должна работать без создания стека, потому что в 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
person Andrey Kuzmin    schedule 19.06.2016
comment
Я не знал, что r::g::b::[] был типом! - person john mangual; 19.06.2016
comment
Рекурсия @johnmangual в сочетании с сопоставлением с образцом в списках — довольно мощный инструмент. - person Andrey Kuzmin; 20.06.2016