简体   繁体   中英

Simple exercise of OCaml about list

Good Morning everyone,

I must do an exercise of Programming, but i'm stuck!

Well, the exercise requires a function that given a list not empty of integers, return the first number with maximum number of occurrences.

For example:

  • 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

Important : the exercise must be in functional programming

My idea is:

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;;

In this function i'm stuck:

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;;

Thanks in advance, i'm newbie in programming and in stackoverflow Sorry for my english

one solution : calculate first a list of couples (number , occurences). hint : use List.assoc.

Then, loop over that list of couple to find the max occurrence and then return the number.

You need to process you input list while maintaining a state, that stores the number of occurrences of each number. Basically, the state can be a map , where keys are in the domain of list elements, and values are in domain of natural numbers. If you will use Map the algorithm would be of O(NlogN) complexity. You can also use associative list (ie, a list of type ('key,'value) list ) to implement map. This will lead to quadratic complexity. Another approach is to use hash table or an array of the length equal to the size of the input domain. Both will give you a linear complexity.

After you collected the statistics, (ie, a mapping from element to the number of its occurrences) you need to go through the set of winners, and choose the one, that was first on the list.

In OCaml the solution would look like this:

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)

The algorithm is the following:

  1. Run through input and compute frequency of each element
  2. Run through statistics and compute spectrum (ie, a mapping from frequency to elements).
  3. Get the set of elements that has the highest frequency, and find an element in the input list, that is in this set.

For example, if we take sample [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]}

You need to install core library to play with it. Use coretop to play with this function in the toplevel. Or corebuild to compile it, like this:

corebuild test.byte --

if the source code is stored in test.ml

One suggestion:

your algorithm could be simplified if you sort the list before. This has O(N log(N)) complexity. Then measure the longest sequence of identical numbers.

This is a good strategy because you delegate the hard part of the work to a well known algorithm.

It is probably not the most beautiful code, but here is with what i came up (F#). At first i transform every element to an intermediate format. This format contains the element itself, the position of it occurrence and the amount it occurred.

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

The idea is that those Records can be added. So you can first transform every element, and then add them together. So a list like

[1;3]

will be first transformed to

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

By adding two together you only can add those with the same "Element". The Position with the lower number from both is taken, and Occurred is just added together. So if you for example have

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

the result will be

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

The idea that i had in mind was a Monoid. But in a real Monoid you had to come up that you also could add different Elements together. By trying some stuff out i feel that the restriction of just adding the same Element where way more easier. I created a small Module with the type. Including some helper functions for creating, adding and comparing.

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

With this setup i now wrote two additional function. One aggregate function that first turns every element into a Occurred.T , group them by x.Element (the result is a list of list). And then it uses List.reduce on the inner list to add the Occurred with the same Element together. The result is a List that Contains only a single Occurred.T for every Element with the first Position and the amount of 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)

You could use that aggregate function to now implement different aggregation logic. In your case you only wanted the one with the highest Occurrences and the lowest position. I wrote another function that did that.

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

One note. Occurred.compareOccurredPosition is written that it sorts everything in ascending order. I think people expecting it in this order to go to the smallest to the biggest element by default. So by default the first element would be the element with the lowest occurrence and the biggest Position. By multiplying the result of it with -1 you turn that function into a descending sorting function. The reason why i did that is that i could use List.head . I also could use List.last to get the last element, but i felt that it would be better not to go through the whole list again just to get the last element. On top of it, you didn't wanted an Occurred.T you wanted the element itself, so i unwrap the Element to get the number.

Here is everything in action

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")

This code will now print

5
2
2
7

It has still some rough edges like

  1. Occurred.add throws an exception if you try to add Occurred with different Elements
  2. List.head throws an exception for empty lists

And in both cases no code is written to handle those cases or making sure an exception will not raise.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM