关于列表

时间:2015-11-25 14:23:41

标签: f# ocaml

大家早上好,

我必须做一个编程练习,但我被卡住了!

嗯,练习需要一个函数,给出一个不是空的整数列表,返回第一个出现次数最多的数字。

例如:

  • mode [1; 2; 5; 1; 2; 3; 4; 5; 5; 4:5; 5] ==> 5
  • mode [2; 1; 2; 1; 1; 2] ==> 2
  • mode [-1; 2; 1; 2; 5; -1; 5; 5; 2] ==> 2
  • mode [7] ==> 7

重要:练习必须在函数式编程中

我的想法是:

let rec occurences_counter xs i =  match xs with
                                |[] -> failwith "Error"
                                |x :: xs when x = i -> 1 + occurences_counter xs i
                                |x :: xs -> occurences_counter xs i;;

在这个功能中我被卡住了:

let rec mode (l : int list) : int = match l with
                                 |[] -> failwith "Error"
                                 |[x] -> x
                                 |x::y::l when occurences_counter l x >=  occurences_counter l y -> x :: mode l
                                 |x::y::l when occurences_counter l y > occurences_counter l x -> y :: mode l;;

先谢谢,我是编程和stackoverflow的新手 对不起我的英文

4 个答案:

答案 0 :(得分:0)

一种解决方案:首先计算夫妻名单(数量,出现次数)。 提示:使用List.assoc。

然后,循环遍历该夫妇列表以查找最大值,然后返回该号码。

答案 1 :(得分:0)

您需要在保持状态的同时处理输入列表,该状态存储每个数字的出现次数。基本上,状态可以是map,其中键位于列表元素的域中,值在自然数域中。如果您使用Map,则算法的复杂度为O(NlogN)。您还可以使用关联列表(即类型('key,'value) list的列表)来实现映射。这将导致二次复杂性。另一种方法是使用散列表或长度等于输入域大小的数组。两者都会给你线性的复杂性。

收集统计数据后(即,从元素到其出现次数的映射),您需要浏览一组获胜者,然后选择列表中的第一个。

在OCaml中,解决方案如下所示:

open Core_kernel.Std

let mode xs : int =
  List.fold xs ~init:Int.Map.empty ~f:(fun stat x ->
      Map.change stat x (function
          | None -> Some 1
          | Some n -> Some (n+1))) |>
  Map.fold ~init:Int.Map.empty ~f:(fun ~key:x ~data:n modes ->
      Map.add_multi modes ~key:n ~data:x) |>
  Map.max_elt |> function
  | None  -> invalid_arg "mode: empty list"
  | Some (_,ms) -> List.find_exn xs ~f:(List.mem ms)

算法如下:

  1. 运行每个元素的输入和计算频率
  2. 运行统计和计算频谱(即从频率到元素的映射)。
  3. 获取频率最高的元素集,并在输入列表中找到该集合中的元素。
  4. 例如,如果我们采样[1;2;5;1;2;3;4;5;5;4;5;5]

    1. stats = {1 => 2; 2 => 2; 3 => 1; 4 => 2; 5 => 5}
    2. mods = {1 => [3]; 2 => [1;2]; 5 => [5]}
    3. 您需要安装core库才能使用它。使用coretop在顶层中使用此功能。或corebuild编译它,如下所示:

      corebuild test.byte --
      

      如果源代码存储在test.ml

答案 2 :(得分:0)

一个建议:

如果您之前对列表进行排序,则可以简化您的算法。这具有O(N log(N))复杂度。然后测量相同数字的最长序列。

这是一个很好的策略,因为您将工作的艰难部分委托给众所周知的算法。

答案 3 :(得分:0)

这可能不是最漂亮的代码,但这是我的出现(F#)。首先,我将每个元素转换为中间格式。此格式包含元素本身,它出现的位置及其发生的数量。

type T<'a> = {
    Element:  'a
    Position: int
    Occurred: int
}

这个想法是可以添加那些记录。因此,您可以先转换每个元素,然后将它们一起添加。所以像

这样的列表
[1;3]

将首先转换为

[{Element=1;Position=0;Occurred=1}; {Element=3;Position=1;Occurred=1}]

通过将两个加在一起,您只能添加具有相同“元素”的那些。采用两者中较低数字的位置,并且发生的只是加在一起。所以如果你有例如

{Element=3;Position=1;Occurred=2} {Element=3;Position=3;Occurred=2}

结果将是

{Element=3;Position=1;Occurred=4}

我想到的想法是一个Monoid。但是在一个真正的Monoid你必须提出,你也可以添加不同的元素。通过尝试一些东西,我觉得限制只是添加相同的元素更方便的方式。我用类型创建了一个小模块。包括一些辅助函数,用于创建,添加和比较。

module Occurred =
    type T<'a> = {
        Element:  'a
        Position: int
        Occurred:  int
    }

    let create x pos occ = {Element=x; Position=pos; Occurred=occ}
    let sameElements x y = x.Element = y.Element
    let add x y =
        if not <| sameElements x y then failwith "Cannot add two different Occurred"
        create x.Element (min x.Position y.Position) (x.Occurred + y.Occurred)
    let compareOccurredPosition x y =
        let occ = compare x.Occurred y.Occurred
        let pos = compare x.Position y.Position
        match occ,pos with
        | 0,x -> x * -1
        | x,_ -> x

通过这个设置,我现在写了两个额外的功能。一个aggregate函数首先将每个元素转换为Occurred.T,将它们按x.Element分组(结果是列表列表)。然后它使用内部列表中的List.reduce将发生的相同元素添加到一起。结果是一个List,其中每个Element只包含一个Occurred.T,其中包含第一个Position和Occurred items的数量。

let aggregate =
    List.mapi (fun i x -> Occurred.create x i 1)
    >> List.groupBy (fun occ -> occ.Element)
    >> List.map (fun (x,occ) -> List.reduce Occurred.add occ)

您可以使用该聚合函数来实现不同的聚合逻辑。在您的情况下,您只需要具有最高发生次数和最低位置的那个。我写了另一个功能。

let firstMostOccurred =
    List.sortWith (fun x y -> (Occurred.compareOccurredPosition x y) * -1) >> List.head >> (fun x -> x.Element)

一个注意事项。写出Occurred.compareOccurredPosition,它按升序排序所有内容。我认为人们期望它按此顺序默认为最小到最大元素。因此,默认情况下,第一个元素是具有最低出现次数和最大位置的元素。通过将其结果乘以-1,您可以将该函数转换为降序排序函数。我这样做的原因是我可以使用List.head。我也可以使用List.last来获取最后一个元素,但我觉得最好不要再次浏览整个列表以获得最后一个元素。最重要的是,你不想要一个Occurred.T你想要的元素本身,所以我打开Element来获取数字。

以下是行动中的一切

let ll = [
    [1;2;5;1;2;3;4;5;5;4;5;5]
    [2;1;2;1;1;2]
    [-1;2;1;2;5;-1;5;5;2]
    [7]
]

ll
|> List.map aggregate
|> List.map firstMostOccurred
|> List.iter (printfn "%d")

此代码现在将打印

5
2
2
7

它还有一些粗糙的边缘,比如

    如果您尝试添加发生了不同的元素,则
  1. Occurred.add会引发异常
  2. List.head会为空列表引发异常
  3. 在这两种情况下都没有编写代码来处理这些情况或确保不会引发异常。