OCaml:使用 fold_left 从列表创建元组列表

Posted

技术标签:

【中文标题】OCaml:使用 fold_left 从列表创建元组列表【英文标题】:OCaml: create a tuple list from a list using fold_left 【发布时间】:2022-01-12 18:43:00 【问题描述】:

如何从一个列表创建一个元组列表,如下所示: [1; 2; 4; 6] -> [(1, 2); (4, 6)]

我想使用函数List.fold_left 来完成它,因为我目前正在尝试学习但不知道如何...有办法吗?还是我应该这样?

这是一个不使用List.fold_left的工作代码:

let rec create_tuple acc l = match l with
  | [] -> acc
  | x :: y :: l' -> create_tuple (acc @ [(x, y)]) l'
  | _ -> acc

【问题讨论】:

“或者我应该这样吗?”这是什么问题?做你想做的……除了老师之外,没有任何高级 OCaml 权威会责怪你在不使用 List.fold_left 的情况下解决这个问题……? 除了您的工作代码(List.fold_left 将面临同样的问题):请注意,您当前的代码对于长列表(二次时间)非常低效,因为列表连接所花费的时间与其左侧的长度。一次构建一个列表的常用技巧是添加新元素 ((x, y) :: acc) 而不是附加它们 (acc @ [ (x, y) ]),然后在最后反转整个累加器。 List.fold_left 对列表中的第一个元素和一个累加器进行操作。您无法直接匹配列表中的第一个 第二个元素。关键是你使用的累加器。 我应该说我有点厌倦了你的问题,这些问题看起来都像学生问题,没有显示任何(或很少)以前的工作并且不清楚所问的内容。我通常对 SO 并不严厉,但你没有做任何努力让我想回答你,我认为任何人都不应该这样做,因为你的问题并不是关于 SO 的那种被接受的问题。请考虑一下您在问什么,您是如何问的,以及我们回答您所需的最小努力。 我总体上同意,但这里问题很明确且有道理。 【参考方案1】:

List.fold_left 一个一个地读取元素。没有直接的方法让它两两读取元素。

这确实是毫无意义的复杂化(虽然非常适合教学),但如果你绝对想在这里使用List.fold_left,你的累加器需要以某种方式记录遍历的状态:

到目前为止,您已经阅读了偶数个元素, 或者您读取了一个奇数,然后您必须记录您读取的最后一个元素是什么,以便在读取下一个元素时,您可以将它们配对。

这是一种方法。我使用代数数据类型来表示状态。

(* This is the type that we’ll use for the accumulator;
   the option component is the state of the traversal.
   (None, acc) means that we have read an even number of elements so far;
   (Some x, acc) means that we have read an odd number of elements so far,
   the last of which being x. *)
type 'a accumulator = 'a option * ('a * 'a) list

let folder (state, acc) x =
  match state with
  | None   -> (Some x, acc)
  | Some y -> (None, (y,x)::acc)

let create_pairs l =
  let (_, acc) = List.fold_left folder (None, []) l in
  List.rev acc

还要注意我如何避免我在评论中概述的复杂性错误:我以相反的顺序添加元素(即在累积列表的 head 处),最后我将其反转列表。

【讨论】:

值得注意的是选项类型值已被丢弃,如果您需要跟踪此内容,如果您需要知道列表是否实际包含,您可能希望保留此内容偶数个元素。例如。 let (flag, acc) = List.fold_left folder (None, []) l in match flag with None -> Some (List.rev acc) | Some _ -> None【参考方案2】:

@Maëlan 的答案很漂亮,但是如果我们想要得到三元组而不是成对呢?有没有办法我们可以使用List.fold_left 来更通用地处理这个问题?

let chunks n lst =
  let (_, _, acc) = List.fold_left 
    (fun (counter, chunk, lst') x ->
       if counter = n - 1 then
         (0, [], List.rev (x :: chunk) :: lst')
       else
         (counter + 1, x :: chunk, lst'))
    (0, [], [])
    lst 
  in
  List.rev acc

使用这个,chunks 2 [1; 2; 4; 6] 返回[[1; 2]; [4; 6]]。我们可以使用一个非常简单的函数将其映射到您正在寻找的结果,该函数接受一个包含两个元素的列表并创建一个包含两个元素的元组。

chunks 2 [1; 2; 4; 6] |> List.map (fun [x; y] -> (x, y))

我们得到:

[(1, 2), (4, 6)]

这可以用来实现三元组函数。

let create_triples lst =
  chunks 3 lst |> List.map (fun [x; y; z] -> (x, y, z));;

现在create_triples [1; 2; 3; 4; 5; 6; 7; 8; 9] 返回[(1, 2, 3); (4, 5, 6); (7, 8, 9)]

【讨论】:

【参考方案3】:

我试过这个问题(使用 List.fold_left),这是我能想到的最好的:

type 'a node = First of 'a | Second of ('a * 'a)

let ans =
  List.fold_left
    (
      fun a e ->
        match a with
        | [] -> (First e)::a
        | (First f)::tl -> Second(f, e)::tl
        | (Second n)::tl -> (First e)::(Second n)::tl
    )
    []
    [1; 2; 3; 4; 5; 6; ]

let () =
  List.iter
    (
      fun e ->
        match e with
        | First f ->
          print_endline(string_of_int f)
        | Second (f, s) ->
          Printf.printf "(%d, %d)" f s
    )
    (List.rev ans)

只是为了让我的答案都在那里......

type 'a node = One of 'a | Two of ('a * 'a)

let ans =
  (List.map
     (
       fun e ->
         match e with
         | One _ -> failwith "Should only be Two's"
         | Two (f, s) -> (f, s)
     )
     (List.filter
        (
          fun e ->
            match e with
            | One _ -> false
            | Two _ -> true
        )
        (List.rev 
           (List.fold_left
              (
                fun a e ->
                  match a with
                  | [] -> (One e)::[]
                  | (One o)::tl -> (Two (o, e))::tl
                  | (Two t)::tl -> (One e)::(Two t)::tl
              )
              []
              (List.init 10 (fun x -> x + 1))
           )
        )
     )
  )

let () =
  List.iter
    (fun (f, s) -> Printf.printf "(%d, %d) " f s)
    ans

【讨论】:

如果你把ans倒过来,把它从Second (x, y)映射到(x, y),你就在那里。

以上是关于OCaml:使用 fold_left 从列表创建元组列表的主要内容,如果未能解决你的问题,请参考以下文章

OCaml: fold_left, fold_right 转换

如何在 Ocaml 中解决这个字典问题?

如何使用 OCaml 将两个列表中的每个单独元素压缩到一个列表中

OCaml 和创建列表列表

从 Ocaml 中的列表列表中删除重复项?

在 OCaml 中访问 (int * float) 列表中的浮点数