7

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.

Gilles 'SO- stop being evil'
  • 104,111
  • 38
  • 209
  • 254
Allan Jiang
  • 11,063
  • 27
  • 104
  • 165

4 Answers4

7

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.

mange
  • 3,172
  • 18
  • 27
  • The OP mentioned that he doesn't want to use the `Lisp` module (but `List.nth` is ok because he already has the corresponding function), but one could still not that this is simply `let indices xs is = List.map (List.nth xs) is`. – gasche Mar 21 '12 at 13:50
  • Honestly: this is the first Ocaml I've ever written and I couldn't work out how else to do it. I probably should have mentioned that, but I figured the OP would be able to work it out. – mange Mar 21 '12 at 20:34
  • Indeed, your solution is fine (and respects the OP's wish to have a real implementation rather than a stdlib trick). It is exactly equivalent to the `List.map` call I suggested -- for additional information. – gasche Mar 21 '12 at 21:36
3

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.

gasche
  • 31,259
  • 3
  • 78
  • 100
2

mange's answer illustrates nicely the power of functional programming. Allow me to reiterate the gist of it: no need for temporary variables, just use recursion.

Should you want to get rid of the last List library call, you could:

  1. Use mange's indices function and re-implement the List.nth function. This is not much fun, and you might prefer avoiding the systematic re-scan of your x list for each element of your y list.

  2. Use a recursive function to scan both your x and y lists simultaneously. For instance, you might want to:

    • pop elements of the x list as many times as the value of the first element of the y list.
    • in the remaining list x, reserve the first element, pop the head of y, and continue with the remainders of x and y.

I'll leave the details, as inhabited by the devil as they usually are, up to you.

ftk
  • 614
  • 6
  • 11
  • how would the second approach work if the indices to get are not ordered? (Of course you could sort them first, or use a zipper or the left list instead of just dropping elements.) – gasche Mar 21 '12 at 17:49
  • @gasche: yep, the indices need to be ordered, and the code will probably come up pretty similar to @winitzki's. I was a bit wary of providing a full solution though, given the `homework` tag. Oh, and great zipper solution! – ftk Mar 21 '12 at 23:33
1

You need a function of two lists; the second list provides indices for the first list. There are two possibilities: the second list is sorted in ascending order, or the second list is not sorted in any way. If the second list is sorted, your function will be a lot more efficient. This is so because a list can be traversed efficiently from left to right, but random access to an element with a given index is not quick.

In any case, a tail-recursive solution is possible. (I have a suspicion that this is a homework question...)

The idea is not to use any temporary variables, but to build the result as you go through the lists. Think about your problem in terms of mathematical induction. What is the base of induction? Empty list of indices gives empty result. What is the step of induction? Take the next given index from the second list, appending one element from the first list to the result, and assume (by induction) that all other indices will be handled correctly.

Here is what you can do in case the second list is sorted in ascending order, with no repeated elements. indices_tr is a tail-recursive function that has four arguments; result is the previously accumulated resulting list, xs is the remaining part of the first list, n is the current index in that list, and is is the remaining part of the list of indices.

let indices xs is = 
 let rec indices_tr result (x::xs) n = function
   | [] -> result
   | i::is when i==n -> indices_tr (x::result) xs (n+1) is
   | is -> indices_tr result xs (n+1) is
 in
 indices_tr [] xs 0 is
;;

There is a warning that the empty list is not handled.

The result is a list in reverse order:

 # indices [1;3;5;7] [0;1;2];;
 - : int list = [5; 3; 1]

You cannot do much better with a pure tail-recursive solution; you could of course apply List.rev to that.

winitzki
  • 3,179
  • 24
  • 32