在F#中,是否有一种实用的方法可以将一组平面项目转换为一组项目的数组? [英] In F#, is there a functional way to converting a flat array of items into an array of a group of items?
问题描述
在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
编辑
如果您要处理大量数据,这是一种基于序列的方法不断使用内存(它创建的所有option
和tuple
都会对GC-参见下面的更好版本):
EDIT
If you're dealing with huge amounts of data, here's a sequence-based approach with constant memory use (all the option
s and tuple
s 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屋!