在F#中,是否有一种功能性方法可以将项目的平面数组转换为一组项目的数组?
在F#中,假设我们有一个表示像素数据的字节数组,每个像素按RGB顺序有三个字节:在F#中,是否有一种功能性方法可以将项目的平面数组转换为一组项目的数组?,f#,F#,在F#中,假设我们有一个表示像素数据的字节数组,每个像素按RGB顺序有三个字节: [| 255; 0; 0; //Solid red 0; 255; 0; //Solid green 0; 0; 255; //Solid blue 1; 72; 9; 34; 15; 155 ... |] 我很难知道如何按原样对这些数据进行功能操作,因为单个项实际上是数组中三个元素的连续块 因此,我需要首先将数组中的三元组分组为如下内容: [| [|
[| 255; 0; 0; //Solid red
0; 255; 0; //Solid green
0; 0; 255; //Solid blue
1; 72; 9;
34; 15; 155
... |]
我很难知道如何按原样对这些数据进行功能操作,因为单个项实际上是数组中三个元素的连续块
因此,我需要首先将数组中的三元组分组为如下内容:
[|
[| 255; 0; 0 |];
[| 0; 255; 0 |];
[| 0; 0; 255 |];
[| 1; 72; 9 |];
[| 34; 15; 155 |]
... |]
现在,使用for循环将三元组收集到子数组非常容易,但我很好奇--是否有一种功能性的方法来收集F#中的数组元素组?我的最终目标不是像上面所示那样简单地转换数据,而是以更具声明性和功能性的方式解决问题。但是我还没有找到一个没有命令循环的例子。正如Daniel指出的,这个答案是错误的,因为它创建了一个滑动窗口 您可以使用库中的
Seq.windowed
函数。例如
let rgbPix = rawValues |> Seq.windowed 3
这将返回一个序列而不是数组,因此如果您需要随机访问,可以通过调用
Seq.toArray
kvb的答案可能无法满足您的需要<代码>顺序加窗返回值的滑动窗口,例如,[1;2;3;4]
变为[[1;2;3];[2;3;4]
。似乎您希望将其拆分为连续的块。下面的函数获取一个列表并返回一个三元组列表('T list->('T*'T*'T)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
编辑
如果您正在处理大量数据,这里有一种基于序列的方法,可以持续使用内存(它创建的所有选项
s和元组
s都会对GC产生负面影响——请参阅下面的更好版本):
与基于列表或数组的方法相比,这种方法具有相对恒定的内存使用率,因为a)如果您有一个
seq
来开始整个序列,则不必将其插入列表/数组中;b)它还返回一个序列,使其变为惰性,并避免分配另一个列表/数组。另一种直接获取和生成数组的方法:
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
让拆分数组n arr=
将Array.length arr与
| 0 ->
invalidArg“arr”数组为空
|x时x%n 0->
invalidArg“arr”数组长度不能被n整除
|阿伦->
让ret=arrLen/n |>Array.zeroCreate
让rec循环idx=
返回[idx]循环idx'
|_uu->ret
循环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
让拆分数组n arr=
将Array.length arr与
| 0 ->
invalidArg“arr”数组为空
|x时x%n 0->
invalidArg“arr”数组长度不能被n整除
|阿伦->
让rec循环idx=seq{
收益率数组.sub arr idx n
设idx'=idx+n
如果idx’arrLen那么
屈服!循环idx'}
循环0 |>顺序toArray
我需要首先将数组中的三元组分组为如下内容:
[|
[| 255; 0; 0 |];
[| 0; 255; 0 |];
[| 0; 0; 255 |];
[| 1; 72; 9 |];
[| 34; 15; 155 |]
... |]
如果您知道它们总是三元组,那么将它们表示为元组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
答案确实取决于你下一步想做什么…(帽子提示:)从F#4.0开始,你可以使用。它正是您想要的:
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|] |]
F#4.0中的
列表
和Seq
模块。在撰写本文时,MSDN上的文档没有在任何地方显示chunkBySize()
,但如果您使用的是F#4.0,它就在那里。我的下一个问题是如何做Seq.windowed的逆操作,将所有子数组展平成一个数组。看起来您可以使用Seq.concat来执行此操作。您可以使用“let rgbPix=rawValues |>Seq.windowed 3 |>Seq.mapi(fun i x->(i,x))|>Seq.filter(fun(a,|)->a%3=0)”-但这将是一种效率低下的做法,不客气。当然,您可以使用List.ofArray
/List.toArray
来转换输入/输出。很好。你完全正确,我的答案在这种情况下是错误的。@Daniel-我不知道我是否理解为什么第二个例子与第一个相比是“持续内存使用”。。你能澄清一下吗?@ebb:我更新了我的答案,补充了一些细节。它回答了你的问题吗?我用基于序列的方法更新了我的答案,如果这对你有用的话。你简单的“let get”对我很有吸引力,因为它不需要重新创建整个原始数据集,而只是提供了查看现有数据的方便方式。在高性能处理循环的中间,为每个像素获取创建元组的性能含义是什么?@ Adjjopope:您不想在高性能循环的中间创建元组。您可以尝试改用struct
类型(值类型),因为这样可以避免此类堆分配。
let b = Array.init (a.Length/3) get
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|] |]