В F # существует ли функциональный способ преобразования плоского массива элементов в массив группы элементов? - PullRequest
4 голосов
/ 12 августа 2011

В F # представьте, что у нас есть массив байтов, представляющих данные пикселей с тремя байтами на пиксель в порядке RGB:

[| 255; 0;   0; //Solid red
   0;   255; 0; //Solid green
   0;   0;   255; //Solid blue
   1;   72;  9; 
   34;  15;  155
... |]

Мне трудно понять, как функционально работать с этими данными, как-is, так как один элемент действительно является последовательным блоком из трех элементов в массиве.

Итак, мне нужно сначала сгруппировать тройки в массиве в нечто вроде этого:

[| 
   [| 255; 0;   0   |];
   [| 0;   255; 0   |];
   [| 0;   0;   255 |];
   [| 1;   72;  9   |];
   [| 34;  15;  155 |]
... |]

Теперь собрать тройки в подмассивы достаточно просто, если использовать цикл for, но мне любопытно - существует ли функциональный способ сбора групп элементов массива вF #? Моя конечная цель - не просто преобразовать данные, как показано выше, но решить проблему более декларативным и функциональным образом.Но мне еще предстоит найти пример того, как это сделать без обязательного цикла.

Ответы [ 5 ]

5 голосов
/ 12 августа 2011

KVB ответ может не дать вам то, что вы хотите. Seq.windowed возвращает скользящее окно значений , например, [1; 2; 3; 4] становится [[1; 2; 3]; [2; 3; 4]]. Кажется, что вы хотите, чтобы это раскололось на смежные куски. Следующая функция берет список и возвращает список троек ('T list -> ('T * 'T * 'T) list).

let toTriples list = 
  let rec aux f = function
    | a :: b :: c :: rest -> aux (fun acc -> f ((a, b, c) :: acc)) rest
    | _ -> f []
  aux id list

Вот обратное:

let ofTriples triples =
  let rec aux f = function
    | (a, b, c) :: rest -> aux (fun acc -> f (a :: b :: c :: acc)) rest
    | [] -> f []
  aux id triples

EDIT

Если вы имеете дело с огромными объемами данных, вот основанный на последовательности подход с постоянным использованием памяти (все создаваемые им option s и tuple s оказывают негативное влияние на GC - см. ниже для лучшей версии):

let (|Next|_|) (e:IEnumerator<_>) =
  if e.MoveNext() then Some e.Current
  else None

let (|Triple|_|) = function
  | Next a & Next b & Next c -> Some (a, b, c) //change to [|a;b;c|] if you like
  | _ -> None

let toSeqTriples (items:seq<_>) =
  use e = items.GetEnumerator()
  let rec loop() =
    seq {
      match e with
      | Triple (a, b, c) -> 
        yield a, b, c
        yield! loop()
      | _ -> ()
    }
  loop()

РЕДАКТИРОВАТЬ 2

Вопрос Эбба об использовании памяти побудил меня протестировать, и я обнаружил, что toSeqTriples медленный и вызывает удивительно частые GC. Следующая версия устраняет эти проблемы и почти в 4 раза быстрее, чем версия на основе списка.

let toSeqTriplesFast (items:seq<_>) =
  use e = items.GetEnumerator()
  let rec loop() =
    seq {
      if e.MoveNext() then
        let a = e.Current
        if e.MoveNext() then 
          let b = e.Current
          if e.MoveNext() then
            let c = e.Current
            yield (a, b, c)
            yield! loop()
    }
  loop()

Это имеет относительно постоянное использование памяти по сравнению со списком или подходом на основе массива, потому что a) если у вас есть seq, чтобы начать со всей последовательности, не нужно впадать в список / массив; и б) он также возвращает последовательность, делая ее ленивой и избегая выделения еще одного списка / массива.

4 голосов
/ 14 августа 2011

Мне нужно сначала сгруппировать тройки в массиве примерно так:

Если вы знаете, что они всегда будут тройками, то представление в виде кортежа int * int * int более «типизировано», чем использование массива, потому что оно говорит о том, что в мире существует всего три элемента.

Другие люди описали различные способы обработки данных, но я бы на самом деле рекомендовал не беспокоиться (если только это не то, что вы описали). Я бы выбрал функцию для деструктурирования вашего массива как есть:

let get i = a.[3*i], a.[3*i+1], a.[3*i+2]

Если вы действительно хотите изменить представление, тогда вы можете сделать:

let b = Array.init (a.Length/3) get

Ответ действительно зависит от того, что вы хотите сделать дальше, хотя ...

2 голосов
/ 16 октября 2015

(Наконечник шляпы: Скотт Влашин ) Начиная с F # 4.0, вы можете использовать Array.chunkBySize().Он делает именно то, что вам нужно:

let bs = [| 255;   0;   0; //Solid red
              0; 255;   0; //Solid green
              0;   0; 255; //Solid blue
              1;  72;   9; 
             34;  15; 155 |]
let grouped = bs |> Array.chunkBySize 3
// [| [|255;   0;   0|]
//    [|  0; 255;   0|]
//    [|  0;   0; 255|]
//    [|  1;  72;   9|]
//    [| 34;  15; 155|] |]

Модули List и Seq также имеют chunkBySize() в F # 4.0.На момент написания этой статьи документы MSDN нигде не показывали chunkBySize(), но они есть , если вы используете F # 4.0.

2 голосов
/ 13 августа 2011

Другой подход, который принимает и выдает массивы напрямую:

let splitArrays n arr =
    match Array.length arr with
    | 0 ->
        invalidArg "arr" "array is empty"
    | x when x % n <> 0 ->
        invalidArg "arr" "array length is not evenly divisible by n"
    | arrLen ->
        let ret = arrLen / n |> Array.zeroCreate
        let rec loop idx =
            ret.[idx] <- Array.sub arr (idx * n) n
            match idx + 1 with
            | idx' when idx' <> ret.Length -> loop idx'
            | _                            -> ret
        loop 0

Или еще один:

let splitArray n arr =
    match Array.length arr with
    | 0 ->
        invalidArg "arr" "array is empty"
    | x when x % n <> 0 ->
        invalidArg "arr" "array length is not evenly divisible by n"
    | arrLen ->
        let rec loop idx = seq {
            yield Array.sub arr idx n
            let idx' = idx + n
            if idx' <> arrLen then
                yield! loop idx' }
        loop 0 |> Seq.toArray
2 голосов
/ 12 августа 2011

ОБНОВЛЕНИЕ: Как указал Даниэль, этот ответ неверен, поскольку создает скользящее окно.

Вы можете использовать функцию Seq.windowed из библиотеки.Например,

let rgbPix = rawValues |> Seq.windowed 3

Возвращает последовательность, а не массив, поэтому, если вам нужен произвольный доступ, вы можете выполнить это с помощью вызова Seq.toArray.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...