在F#中,是否有一种将平面项目数组转换为一组项目数组的功能方法?

ada*_*per 4 f#

在F#中,假设我们有一个字节数组,表示按RGB顺序每像素三个字节的像素数据:

[| 255; 0;   0; //Solid red
   0;   255; 0; //Solid green
   0;   0;   255; //Solid blue
   1;   72;  9; 
   34;  15;  155
... |]
Run Code Online (Sandbox Code Playgroud)

我很难知道如何按原样对这些数据进行功能操作,因为单个项目实际上是数组中三个元素的连续块.

所以,我需要首先将数组中的三元组分组为:

[| 
   [| 255; 0;   0   |];
   [| 0;   255; 0   |];
   [| 0;   0;   255 |];
   [| 1;   72;  9   |];
   [| 34;  15;  155 |]
... |]
Run Code Online (Sandbox Code Playgroud)

现在,将三元组收集到子数组中很容易用for循环,但我很好奇 - 有没有一种功能方法来收集F#中的数组元素组?我的最终目标不仅仅是如上所述转换数据,而是以更具声明性和功能性的方式解决问题.但我还没有找到一个如何在没有命令性循环的情况下做到这一点的例子.

Dan*_*iel 5

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
Run Code Online (Sandbox Code Playgroud)

这是相反的:

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
Run Code Online (Sandbox Code Playgroud)

编辑

如果您正在处理大量数据,这里是基于序列的方法,使用恒定内存(它创建的所有options和tuples对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()
Run Code Online (Sandbox Code Playgroud)

编辑2

ebb关于内存使用的问题促使我进行测试,我发现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()
Run Code Online (Sandbox Code Playgroud)

与基于列表或基于数组的方法相比,这具有相对恒定的内存使用率,因为a)如果你有一个seq开头的整个序列,则不必将其插入列表/数组; 并且b)它还返回一个序列,使其变得懒惰,并避免分配另一个列表/数组.