OCaml:在对列表中计算不同的值

OCaml:在对列表中计算不同的值,ocaml,Ocaml,我有一张成双的单子 let myList=[(0,1);(0,2);(0,3);(1,5);(2,4);(3,5);(5,4);(5,6);(4,3)];; 对于列表中存在的每个不同值的计数,我有以下步骤 let rec flat lst visited = match lst with []->visited | (x,y)::xs -> flat xs (x::y::visited)) ;; let newLst = flat myList [];; val newLs

我有一张成双的单子

let myList=[(0,1);(0,2);(0,3);(1,5);(2,4);(3,5);(5,4);(5,6);(4,3)];;
对于列表中存在的每个不同值的计数,我有以下步骤

let rec flat lst  visited =
match lst with
[]->visited
| (x,y)::xs -> flat xs (x::y::visited)) ;;


let newLst = flat myList [];;

val newLst : int list =
  [4; 3; 5; 6; 5; 4; 3; 5; 2; 4; 1; 5; 0; 3; 0; 2; 0; 1]

let rec count lista = 
match lista with  
[]->0
| x::xs -> 
if (List.mem x xs) then count xs
else 1+count xs;;

count newLst;;
- : int = 7
代码运行正确,但我的问题是:

有没有更优雅、更有效的方法
例如,一个独特的功能而不是两个优雅没有特定的含义,因此很难回答

我认为这是一个相当好的解决问题的方法。如果你想象你有很多不同的结构(成对列表、树等),那么将int转换成平面列表然后以不同的方式处理列表的想法会让你感觉很好

您的解决方案的一个问题是,在最坏的情况下,它是二次的,因为您正在搜索长度为0、1、2。。。n*2表示n对

我怀疑这不应该是生产代码,因此计算复杂性可能无关紧要


如果要在列表很长且效率很重要的生产代码中执行此操作,则可以直接在对列表上进行计数。而且你不会一直在列表中搜索重复项。你会使用某种集合(甚至有点像矢量集合)来跟踪你所看到的东西。对于您的预期用途来说,这很可能是过火了(对我来说,这看起来像是一个课堂作业)。

我也不会为优雅而争论。。。 编写代码的另一种方式:使用折叠操作。 您的展开函数可以这样编写:

let flat  = List.fold_left (fun acc (x,y) -> x::y::acc) [] ;;

您的解决方案基本上是如何做到这一点,而无需大量使用库函数(并以二次型最坏情况性能为代价)。您可以使用
列表
库中的函数来获得更简单的解决方案,但尽管这有点简单,但它将主要教您如何使用该库,而较少教您如何将OCaml作为一种语言[1]。也就是说,这里有一个解决方案:

let myList=[(0,1);(0,2);(0,3);(1,5);(2,4);(3,5);(5,4);(5,6);(4,3)]

let count l =
  let open List in
  let (a, b) = split l in length (sort_uniq compare (a @ b))

let () =
  Printf.printf "=> %d\n" (count myList)
这将使用
List.split
和List append操作符
@
将整数对列表转换为整数列表,然后对其排序并删除重复项(
List.sort\u uniq
),然后使用
List.length
对结果进行计数。由于
sort\u uniq
,这将在时间O(n*log(n))内运行

替代解决方案是使用
Set
Hashtbl
模块以比
List.mem
更有效的方式跟踪重复项,从而也避免了二次最坏情况时间(但也使过程中的代码更复杂)


[1] 我在这里假设您正在学习OCaml,因此,根据您所在的位置,工业级解决方案不一定是帮助您学习OCaml的最佳解决方案。

您的方法可行,简单易懂。它唯一的缺点是,您的代码使用了。这意味着,处理时间表现为列表大小的二次函数

如果要删除它,可以使用:将列表中的所有数字添加到一个集合并计算其大小。现在,时间性能在n log(n)中,并且扩展得更好

let myList=[(0,1);(0,2);(0,3);(1,5);(2,4);(3,5);(5,4);(5,6);(4,3)]

module IntegerSet = Set.Make(struct
    type t = int
    let compare = Pervasives.compare
  end)

let count lst0 =
  let rec loop acc lst =
    match lst with
    | [] -> IntegerSet.cardinal acc
    | (a,b)::tl -> loop IntegerSet.(add b (add a acc)) tl
  in
  loop IntegerSet.empty lst0
此代码使用累加器acc,该累加器acc通过迭代填充
在名单上。读取所有列表后,将返回累加器中的元素数。

您可以编写一个直接处理对的函数,而不是列出所有值。它将更加高效,因为它不必创建中间列表。但是优雅和效率经常是冲突的:-)非常有趣的解决方案