Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

return a list of element from a list in OCaml

I am new to OCaml, and I am now trying to implement a function that returns a list of elements of a given list x at indexes in list y.

For example, that function should perform the following computation: [5,6,7,8], [0, 3] => [5, 8]

I am not sure how to store temp variables in ML and don't have a clear idea how it works. I do know how to find a element from a list given specified index, though.

Any idea will be appreciated, but I'd like to use recursive functions and avoid the List module.

like image 382
Allan Jiang Avatar asked Mar 21 '12 03:03

Allan Jiang


People also ask

How do I split a list in OCaml?

Just have a function that takes the original list and a empty list. Build up the empty list(with the first half of the original list) until you reach the halfway point and then use the remainder of the original list and the built up empty list to generate your pairs.

What does list fold_left do in OCaml?

So fold_left is "folding in" elements of the list from the left to the right, combining each new element using the operator.

Can you index a list in OCaml?

You cannot have O(1) indexing of linked lists. You must linearly traverse them. However, OCaml does have arrays, which are fixed-size containers with O(1) indexing. Arrays are written [|1; 2; 3|] and accessed with array.

How do I return an OCaml value?

OCaml doesn't have a return keyword — the last expression in a function becomes the result of the function automatically.


2 Answers

No need for temporary variables, just use recursion!

# let rec indices xs = function
    | i :: is -> (List.nth xs i) :: indices xs is
    | [] -> []
  ;;
val indices : 'a list -> int list -> 'a list = <fun>

# indices [5;6;7;8] [0;3] ;;
- int list = [5; 8]

It builds up the list by going through each of the indexes provided and then consing that onto the list returned by the next step.

Hopefully this is also optimised into a tail recursive form, but I'm not so sure about that. You may want to change it to be properly tail-recursive, but I'll leave that up to you.

like image 158
mange Avatar answered Oct 05 '22 18:10

mange


I was tempted and implemented the zipper solution that I suggested to @ftk.

(* A 'zipper' on the data structure "foo" is a derived data structure
   that represents a position in the data structure "foo", that can be
   filled with an element. You can think of this as a "cursor" on some
   element in the structure, that can moved in various directions to
   point to other elements of the structure. If the structure "foo"
   does not have random access, keeping a zipper allows to access the
   pointed element quickly (instead of having to look at it from the
   top of the structure again each time); its neighbors can be
   acccessed as well by moving the zipper.

   A cursor on a list has this form:

        cursor
          v
   [a; b; c; d; e; f]

   It can be represented as a value of type
     'a list * 'a * 'a list

   where the first list denotes the element before the cursor, and the
   second the elements after it. To be able to access the left
   neighbor of the cursor in constant time, we store the left elements
   in reverse order (rightmost first), so the zipper actually is

   ([b; a], c, [d; e; f])

   Zippers can be adapted to lot of data structures, including in
   particular trees. There are neat ways to define them as the
   "derivative" (in a calculus-like sense) of datatypes. See
   http://en.wikipedia.org/wiki/Zipper_(data_structure) for more
   information.
*)
let move_right = function
  | (left, x, x' :: right) -> (x :: left, x', right)
  | (_, _, []) -> raise Not_found

let move_left = function
  | (x' :: left, x, right) -> (left, x', x :: right)
  | ([], _, _) -> raise Not_found

let elem (left, e, right) = e

(* zipper of a list *)
let zipper = function
  | [] -> raise Not_found
  | x :: xs -> ([], x, xs)

let rec iterate f x = function
  | 0 -> x
  | n -> iterate f (f x) (n - 1)

let get_all data indices =
  let get_next index (pos, zip, acc) =
    let zip' =
      let move = if index < pos then move_left else move_right in
      try iterate move zip (abs (index - pos))
      with Not_found -> invalid_arg ("invalid index " ^ string_of_int index) in
    (index, zip', elem zip' :: acc) in
  let (_pos, _zip, result) =
    List.fold_right get_next indices (0, zipper data, []) in
  result

An example of use:

# get_all [0;2;4;6;8;10] [2;0;1;4];;
- : int list = [4; 0; 2; 8]
# get_all [0;2;4;6;8;10] [2;0;1;6;4];;
Exception: Invalid_argument "invalid index 6".

If the list from where to get the elements is large, it can be noticeably faster than List.map (List.nth data):

let slow_get_all data indices = List.map (List.nth data) indices

let init n = Array.to_list (Array.init n (fun i -> i))
let data = init 100_000
let indices = List.map (( * ) 100) (init 1000)

(* some seconds *)
let _ = slow_get_all data indices

(* immediate *)
let _ = get_all data indices

Of course, this is all an exercise, as in practice if performance are any important you will transform you data into data structures that are more efficient for random access, such as arrays.

like image 20
gasche Avatar answered Oct 05 '22 16:10

gasche