Ocaml 如何在每次折叠时折叠列表中的x个元素
因此,假设我们有如下列表:Ocaml 如何在每次折叠时折叠列表中的x个元素,ocaml,fold,foldleft,Ocaml,Fold,Foldleft,因此,假设我们有如下列表:[1;2;3;4;5;6],并且假设我想在函数的每次调用中折叠2个元素 因此,我将依次在(1,2)、(3,4)和(5,6)上应用函数 以下是我尝试使用的函数: let fold_left_multiple(func:'a->'b list->'a)(base:'a)(lst:'b list)(每个折叠的项目:int):'a*'b list= let(acc,rements,uu)=List.fold_left(乐趣(acc,cur_fold_acc,cur_num)el
[1;2;3;4;5;6]
,并且假设我想在函数的每次调用中折叠2个元素
因此,我将依次在(1,2)
、(3,4)
和(5,6)
上应用函数
以下是我尝试使用的函数:
let fold_left_multiple(func:'a->'b list->'a)(base:'a)(lst:'b list)(每个折叠的项目:int):'a*'b list=
let(acc,rements,uu)=List.fold_left(乐趣(acc,cur_fold_acc,cur_num)el->
如果cur_num mod items_per_fold=0,则(func acc(List.rev(el::cur_fold_acc)),[],1)
其他(acc,el::cur_fold_acc,cur_num+1)
)(基准,[],1)第1英寸(附件,剩余部分)
这有点管用;然而,问题是在函数中使用这些元素并不容易
我更喜欢的实现是使用元组或数组来简化元素访问
下面是一个更好的输入/输出示例(使用
utop
语法)。在本例中,我对每一对元素进行求和
#左折#多个(有趣的lst(e1,e2,e3)->(e1+e2+e3)::lst)[[1;2;3;4;5;6;7;8]3;;
-:int list*int list=([15;6],[7;8])
这里,如果列表的长度不能被n
平均整除,则剩余的元素被放入元组的第二个元素中
(我不介意在解决方案中是否反转此余数。)您只需修改标准的
fold_left
函数即可对多个元素进行操作。这里有一个是成对操作的:
let rec fold_left_2 f acc l =
match l with
| a::b::rest -> fold_left_2 f (f acc a b) rest
| remainder -> (acc, remainder)
编辑:修改为按要求返回余数,而不是忽略它
为了说明我在评论中的观点,将其推广到任意数量的元素是可能的,但不是非常有益,下面是一个允许使用split函数任意拆分输入列表的实现:
let rec fold_left_n splitf f acc l =
match splitf l with
| None, remainder -> (acc, remainder)
| Some x, rest -> fold_left_n splitf f (f acc x) rest
使用您的示例对其进行调用:
fold_left_n
(function a::b::c::rest -> (Some (a, b, c), rest) | remainder -> (None, remainder))
(fun lst (e1, e2, e3) -> (e1 + e2 + e3)::lst) [] [1; 2; 3; 4; 5; 6; 7; 8];;
类似地,可以编写一个函数来提取任意长度的子列表,我没有费心去实现它,但它的调用如下所示:
fold_left_n 3
(fun lst -> function
| [e1, e2, e3] -> (e1 + e2 + e3)::lst
| _ -> lst (* we assume we're getting a 3-element list, but the compiler doesn't know that so we need to ignore everything else *)
) [] [1; 2; 3; 4; 5; 6; 7; 8];;
(* take and drop seem to be missing in ocamls half full batteries...
maybe because it is not idiomatic or efficient or both...
*)
let take n lst =
let rec loop acc n l =
match n with
| 0 -> List.rev acc
| x ->
match l with
| [] -> List.rev acc
| x::xs -> loop (x::acc) (n-1) (List.tl l) in
loop [] n lst
let drop n lst =
let rec loop n l =
match n with
| 0 -> l
| _ ->
match l with
| [] -> l
| _::_ -> loop (n-1) (List.tl l) in
loop n lst
let fold_windowed folder wsize acc lst =
let rec loop acc l =
match l with
| [] -> List.rev acc
| _::_ ->
loop (folder acc (take wsize l)) (List.tl l) in
loop acc lst
它们在使用中都非常复杂且冗长,与编写专门的实现相比几乎没有什么好处。这可能有助于确定您希望函数具有的类型 即使所有元素都是int,也不存在表示元素数不同的元组的类型。每个元素数都是不同的类型:
int*int
,int*int*int
,等等
如果您想编写一个通用函数,那么折叠函数将需要以元组以外的其他形式获取其输入—可能是一个列表。如果插槽数量已知且有限,则元组非常方便。但一旦情况并非如此,它们确实变得相当笨拙。因此,我认为让folder函数接收输入列表的子列表没有什么错 函数式语言中获取前n个元素(或更少)的常用方法是 一个名为
take
的函数的方法。分别地,去除前n个元素(或更少)的常用方法是使用名为drop
的函数
在这两个函数的帮助下,您想要的函数可以实现如下:
fold_left_n 3
(fun lst -> function
| [e1, e2, e3] -> (e1 + e2 + e3)::lst
| _ -> lst (* we assume we're getting a 3-element list, but the compiler doesn't know that so we need to ignore everything else *)
) [] [1; 2; 3; 4; 5; 6; 7; 8];;
(* take and drop seem to be missing in ocamls half full batteries...
maybe because it is not idiomatic or efficient or both...
*)
let take n lst =
let rec loop acc n l =
match n with
| 0 -> List.rev acc
| x ->
match l with
| [] -> List.rev acc
| x::xs -> loop (x::acc) (n-1) (List.tl l) in
loop [] n lst
let drop n lst =
let rec loop n l =
match n with
| 0 -> l
| _ ->
match l with
| [] -> l
| _::_ -> loop (n-1) (List.tl l) in
loop n lst
let fold_windowed folder wsize acc lst =
let rec loop acc l =
match l with
| [] -> List.rev acc
| _::_ ->
loop (folder acc (take wsize l)) (List.tl l) in
loop acc lst
借助一些我在F#中使用过但在Ocaml中找不到的附加函数,您可以使用fold_windowed
,如下所示:
let id x = x (* ocaml should have that right out of the box... *)
(* shamelessly derived from F# List.init, with the diff, that the name initializer
seems to be reserved in ocaml, hence the somewhat silly name 'initor'
*)
let list_init n initor =
let rec loop acc i =
match i with
| 0 -> acc
| _ -> loop ((initor i)::acc) (i-1) in
loop [] n
#折叠窗口(趣味acc l->l::acc)3[](列表初始10 id)代码>
_:int list=
[1;2;3];[2;3;4];[3;4;5];[4;5;6;7];[6;7;8];[7;8;9];
[8;9;10];[9;10];[10]]
您可以使用firstList.map
将其转换为n个整数的列表,然后在n个整数的列表上使用List.fold
。@anthonysecama我无法想象我将如何进行映射,因为n取决于输入的内容。@SabdulUlahi您应该尝试创建一个函数,获取列表的前n个元素并返回这些元素和列表的其余部分,然后将其插入一个fold_left_n
实现,类似于@glennsl答案中的fold_left_2
。@MartinJambon您的评论的第一部分是有意义的,而且非常简单,但是当匹配模式依赖于长度时,您如何制作一个类似glennsl的fold_left_n
实现呢?此外,函数需要接受n
参数,而不是像元组这样的数据结构来包含这些参数,这对于较大的n
@SabdulUlahi来说有点不方便。当然,元组是无法实现的。相反,你会使用列表或数组。这对成对的人来说非常有效!尽管如此,我还是希望将它进一步推广到每n个元素,而这似乎只适用于n=2。我认为这里没有太多的东西可以推广。不是以一种可以减少代码量的方式,消费者至少需要编写一段代码。您可以编写一个fold函数来接受另一个将列表拆分为元组的函数,也可以编写一个将提取任意长度的子列表并将其传递给fold函数的函数,但无论哪种方式,使用者都必须在列表上进行模式匹配,以提取元素,并考虑列表中元素数量少于预期数量的可能性。这最终与编写定制的折叠函数一样多的代码。我更新了答案,以说明这可能是什么样子,以及为什么我认为它没有好处。答案很好,但它并没有完全回答问题。子列表不应该重叠。哦-好吧-这是一个最小的变化。。。只需将(List.tl)
替换为`(drop wsize l)`即可使其不重叠。