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]]


您可以使用first
List.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)`即可使其不重叠。