在F#中,是否有一种实用的方法可以将一组平面项目转换为一组项目的数组? [英] In F#, is there a functional way to converting a flat array of items into an array of a group of items?

查看:63
本文介绍了在F#中,是否有一种实用的方法可以将一组平面项目转换为一组项目的数组?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

In F#, imagine we have an array of bytes representing pixel data with three bytes per pixel in RGB order:

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

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

I'm having a hard time knowing how to functionally operate on this data as-is, since a single item is really a consecutive block of three elements in the array.

因此,我需要首先将数组中的三元组归类为以下内容:

So, I need to first group the triples in the array into something like this:

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

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

Now, gathering up the triples into sub-arrays is easy enough to do with a for loop, but I'm curious--is there a functional way to gather up groups of array elements in F#? My ultimate goal is not simply to convert the data as illustrated above, but to solve the problem in a more declarative and functional manner. But I have yet to find an example of how to do this without an imperative loop.

推荐答案

kvb的答案可能无法满足您的需求. Seq.windowed返回值的滑动窗口,例如[1; 2; 3; 4]变为[[1; 2; 3]; [2; 3; 4]].似乎您希望将其拆分为连续的块.以下函数获取一个列表,并返回一个三元组('T list -> ('T * 'T * 'T) list)列表.

kvb's answer may not give you what you want. Seq.windowed returns a sliding window of values, e.g., [1; 2; 3; 4] becomes [[1; 2; 3]; [2; 3; 4]]. It seems like you want it split into contiguous chunks. The following function takes a list and returns a list of triples ('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

编辑

如果您要处理大量数据,这是一种基于序列的方法不断使用内存(它创建的所有optiontuple都会对GC-参见下面的更好版本):

EDIT

If you're dealing with huge amounts of data, here's a sequence-based approach with constant memory use (all the options and tuples it creates have a negative impact on GC--see below for a better version):

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

ebb关于内存使用的问题促使我进行测试,我发现toSeqTriples速度慢并且会导致令人惊讶的频繁GC.以下版本可解决这些问题,并且比基于列表的版本快将近4倍.

ebb's question about memory use prompted me to test and I found toSeqTriples to be slow and cause surprisingly frequent GCs. The following version fixes those issues and is almost 4x faster than the list-based version.

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以整个序列开头,则不必将其插入列表/数组; b)它还返回一个序列,使其变得懒惰,并避免分配另一个列表/数组.

This has relatively constant memory usage vs a list or array-based approach because a) if you have a seq to start with the entire sequence doesn't have to be slurped into a list/array; and b) it also returns a sequence, making it lazy, and avoiding allocating yet another list/array.

这篇关于在F#中,是否有一种实用的方法可以将一组平面项目转换为一组项目的数组?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆