微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

如何折叠每张列表的x个元素

如何解决如何折叠每张列表的x个元素

所以,让我们说我们有一些如下列表:[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) (items_per_fold: int): 'a * 'b list =
    let (acc,remainder,_) = List.fold_left (fun (acc,cur_fold_acc,cur_num) el ->
        if cur_num mod items_per_fold = 0 then (func acc (List.rev (el::cur_fold_acc)),[],1)
        else (acc,el::cur_fold_acc,cur_num + 1)
    ) (base,1) lst in (acc,remainder)

这有点奏效;但是,这样做的问题是在函数中使用这些元素并不容易。

我的首选实现将以某种方式使用元组或数组来简化元素访问。


下面是一个更好的输入/输出示例(使用utop语法)。在这种情况下,我要总结每对元素。

# fold_left_multiple (fun 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整除,则其余元素将放入元组的第二个元素。

(我不介意在解决方案中是否可以逆转此余数。)

解决方法

如果存在已知且数量有限的插槽,则可以方便使用。但是一旦不是这种情况,他们的确会变得笨拙。因此,我认为让文件夹功能接收输入列表的子列表并没有错。

获取函数语言中前n个(或更少)元素的常用方法是 函数take的含义。分别删除前n个元素(或更少的n个元素)的常用方法分别是使用名为drop的函数。

借助这两个功能,可以像这样实现所需的功能:

(* 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

# fold_windowed (fun acc l -> l::acc) 3 [] (list_init 10 id);;
_:int列表列表=
[[1; 2; 3]; [2; 3; 4]; [3; 4; 5]; [4; 5; 6]; [5; 6; 7]; [6; 7; 8]; [7; 8; 9];
[8; 9; 10]; [9; 10]; [10]]

,

您可以修改标准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,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];;

它们使用起来非常复杂且冗长,与仅编写专门的实现相比并没有什么好处。

,

这可能有助于确定您希望函数具有哪种类型。

即使所有元素都是整数,也没有类型可以表示具有不同数量元素的元组。每个元素的数量都是不同的类型:int * intint * int * int等。

如果要编写通用函数,则折叠后的函数将需要获取除元组以外的其他形式的输入(也许是列表)。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。