【问题标题】:How to implement OCaml function of type (string*int) list -> (string * int list) list where the output list is a tally of the items in the input如何实现 (string*int) list -> (string * int list) list 类型的 OCaml 函数,其中输出列表是输入中项目的计数
【发布时间】:2019-02-23 04:51:03
【问题描述】:

我的问题是如何将字符串和整数对的列表转换为字符串和整数列表对的列表。

例如,如果我有列表[("hello",1) ; ("hi", 1) ; ("hello", 1) ; ("hi", 1) ; ("hey",1 )],那么我应该返回[("hello",[1;1]) ; ("hi", [1;1]) ; ("hey",[1])],基本上从我之前编写的在列表中创建字符串* int 对的函数中,我想将每个相同的字符串分组到一对,其中包含一个长度 = 该确切字符串在输入列表中的一对中出现的次数。对不起,如果我的措辞令人困惑,但我对这个功能很迷茫。下面是我目前写的代码:

let transform5 (lst: (string *int) list) : (string *int list) list = 
                match lst with
                   | (hd,n)::(tl,n) -> let x,[o] = List.fold_left (fun (x,[o]) y -> if y = x then x,[o]@[1] else 
(x,[o])::y,[o]) (hd,[o]) tl in (x,[1])::(tl,[1])

感谢任何帮助!

【问题讨论】:

  • 这是作业还是你可以使用Map?看到这个tutorial
  • @DanRobertson 看起来这是一个家庭作业问题,并且是 stackoverflow.com/questions/54794793/… 的副本(我之前错过了)。前面的问题指定了解决方案应该以折叠的形式实现。
  • 我可以使用地图,老实说我不知道​​为什么我以前没有想到。

标签: list ocaml higher-order-functions


【解决方案1】:

关于如何提高对核心概念的理解的一般建议:

代码建议您可以多练习解构和操作列表。我建议阅读Real World Ocaml 中关于Lists and Patterns 的章节,并花一些时间阅读前20 个左右的99 OCaml Problems

关于您目前编写的代码的一些提示:

我已将您的代码重新组织成一个严格等效的函数,并带有一些说明问题区域的注释:

let transform5 : (string * int) list -> (string * int list) list =
  fun lst ->
  let f (x, [o]) y =
    if y = x then          (* The two branches of this conditional are values of different types *)
      (x, [o] @ [1])       (* : ('a * int list) *)
    else
      (x, [o]) :: (y, [o]) (* : ('a * int list) list *)
  in
  match lst with
  | (hd, n) :: (tl, n) ->                      (* This will only match a list with two tuples *)
    let x, [o] = List.fold_left f (hd, [o]) tl (* [o] can only match a singleton list *)
    in (x, [1]) :: (tl, [1])                   (* Doesn't use the value of o, so that info is lost*)
   (* case analysis in match expressions should be exhaustive, but this omits
      matches for, [], [_], and (_ :: _ :: _) *)

如果您在utop 中加载代码或将其编译到文件中,您应该会收到许多警告和类型错误,以帮助指出问题区域。通过逐条处理这些信息并弄清楚它们所表示的内容,您可以学到很多东西。

重构问题

使用折叠输入列表来解决您的问题可能是正确的方法。但是编写使用显式递归并将任务分解为多个子问题的解决方案通常有助于研究问题并使底层机制非常清晰。

一般'a -> 'b类型的函数可以理解为一个问题:

给定一个x : 'a,构造一个y : 'b,其中...

我们的函数类型为(string * int) list -> (string * int list) list,而你 很清楚地说明问题,但我已经编辑了一些以适应格式:

给定xs : (string * int) list,构造ys: (string * int list) list 我想将xs中的每个字符串组合成一对 (string * int list)ys 中有一个长度列表= 如何 很多次,确切的字符串从xs 中成对出现。

我们可以将其分解为两个子问题:

给定xs : (string * int) list,构造ys : (string * int) list list,其中ys中的每个y : (string * int) listxs中具有相同string的一组项目。


let rec group : (string * int) list -> (string * int) list list = function
  | [] -> []
  | x :: xs ->
    let (grouped, rest) = List.partition (fun y -> y = x) xs in
    (x :: grouped) :: group rest

给定xs : (string * int) list list,构造ys : (string * int list) list,其中xs 中的每个组(string, int) listys 中有一个(s : string, n : int list),其中s 是确定组的字符串,n 是一个列表持有群内所有1s。

let rec tally : (string * int) list list -> (string * int list) list = function
  | [] -> []
  | group :: xs ->
    match group with
    | [] -> tally xs (* This case shouldn't arise, but we match it to be complete *)
    | (s, _) :: _ ->
      let ones = List.map (fun (_, one) -> one) group in
      (s, ones) :: tally xs

最初的问题的解决方案就是这两个子问题的组合:

let transform5 : (string * int) list -> (string * int list) list =
  fun xs -> (tally (group xs))

希望这是对分解此类问题的一种方法的有用说明。但是,我编写的代码有一些明显的缺陷:它效率低下,因为它创建了一个中间数据结构,并且必须反复遍历第一个列表以形成其组,然后才能最终计算结果。它还采用显式递归,而最好使用高阶函数来为我们处理列表的迭代(正如您在示例中所尝试的那样)。尝试修复这些缺陷可能是有益的。

重新考虑我们的背景

您在这个 SO 问题中提出的问题是您所追求的整体任务中最好的子问题吗?我想到了两个问题:

为什么,你有一个(string * int) list,其中int 的值总是1?这实际上是否包含比string list 更多的信息?

一般来说,我们可以用一个int list 来表示任何n : int,它只包含1s 并且有length = n。为什么不在这里使用 n 呢?

【讨论】:

  • 有什么方法可以在不使用递归函数的情况下做到这一点?
  • 是的!但是您的问题并未表明此限制,因此在我的回答中没有解决:)要编写折叠来解决问题,您需要考虑最后要获得什么价值,以及如何逐步构建它连续给定列表的每个元素的值。定义一个执行此增量更新的函数,您就有了折叠配方。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-06-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多