如何实现类型为(string * int)list->(string * int list)list的OCaml函数,其中输出列表是输入中各项的计数

时间:2019-02-23 04:51:03

标签: list ocaml higher-order-functions

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

例如,如果我有列表[("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])

感谢您的帮助!

1 个答案:

答案 0 :(得分:0)

有关如何增进对核心概念的理解的一般建议:

该代码建议您可以对销毁和操纵列表进行更多的练习。我建议阅读 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,其中y : (string * int) list中的每个ysxs中具有相同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,对于(string, int) list中的每个组xs,在(s : string, n : int list)中有一个ys,其中{{1} }是确定组的字符串,而s是包含该组中所有n的列表。

1

您最初的问题的解决方案将只是以下两个子问题的组合:

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

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

重新考虑背景

您在本SO问题中提出的问题是您要完成的总体任务中最好的子问题吗?这是我遇到的两个问题:

为什么您有一个let transform5 : (string * int) list -> (string * int list) list = fun xs -> (tally (group xs)) ,其中(string * int) list的值始终始终是int?它实际上比1携带更多的信息吗?

通常,我们可以用string list来表示任何n : int,该int list仅包含1并具有length = n。为什么不在这里仅使用n?