2

In Python enumerate works as follows:

a_list = ['a', 'b', 'c']
    
for i, x in enumerate(a_list): 
    print(i, x)

The output will be:

0 a
1 b
2 c

Thus, enumerate actually returns a generator (pretty much a lazy sequence) of pairs of the form (i, x) where i ranges over 0, 1, 2, ... and x are the elements of the list in order.

So far I have come up with this definition for lists, which does not produce a "generator" but also a list of pairs:


let enumerate (a_list: 'a list): (int * 'a) list =
  let rec _enumerar (a_list: 'a list) (accum: (int * 'a) list) (i: int): (int * 'a) list =
    match a_list with
    | [] -> accum
    | x::xs -> _enumerar  xs ((i, x)::accum) (i+1)
  in
  _enumerar a_list [] 0 |> List.rev

Example usage:

# enumerate ['a'; 'b'; 'c'];;
- : (int * char) list = [(0, 'a'); (1, 'b'); (2, 'c')]

Any ideas whether this function perhaps with a different name is implemented anywhere in the standard library or in Base?

What about a lazy version using Sequence or Stream?

Chris
  • 26,361
  • 5
  • 21
  • 42
Mateo
  • 1,494
  • 1
  • 18
  • 27

3 Answers3

6

The simplest OCaml equivalent would be:

List.iteri (Printf.printf "%d %c\n") ['a'; 'b'; 'c']

Python is using a for loop and enumerate due to its limited support for higher-order functions. By contrast, most enumerable container types in OCaml tend to directly offer a i version of iterators, maps and folds.

If you want a more direct translation from Python, the OCaml version of a generator is a Seq.t since both are external iterator:

   Seq.iter (fun (i,x) -> Printf.printf "%d %c\n" i x)
@@ Seq.mapi (fun i x -> i, x) 
@@ List.to_seq ['a';'b';'c']

(forgetting for a time that Seq.iteri exists too as of OCaml 4.14)

octachron
  • 17,178
  • 2
  • 16
  • 23
  • What do you mean by "Python is using a for loop and enumerate due to its limited support for higher-order functions."? – juanpa.arrivillaga Jun 14 '22 at 19:52
  • Higher-order functions work better when one can easily define function arguments. Python doesn't have a good support for neither anonymous functions (Python lambda are limited to a single statement) nor partial application. This limits the usability of higher-order functions. Consequently, Python code is often structured to use for-loop with on generators rather than higher-order functions. – octachron Jun 14 '22 at 20:04
  • 1
    Also `List.mapi`: https://v2.ocaml.org/api/List.html#VALmapi. – Dogbert Jun 14 '22 at 20:08
  • 2
    True. One difference however is that `List.mapi` creates an intermediary list, which is not the case for `Seq.mapi` and `enumerate`. – octachron Jun 14 '22 at 20:12
  • I am not able to find `Seq.mapi`. This is supposed to be in the regular standard library, not in Base, right? I am using OCaml version 4.13.1 and `#show Seq;;` doesn't show this function... – Mateo Jun 15 '22 at 17:31
  • It wasn't added until 4.14. – Chris Jun 15 '22 at 17:59
2

If you want to add indices to a list, you can easily do this with List.mapi (fun i x -> (i,x)), so that

List.mapi (fun i x -> (i,x)) ['a';'b';'c']

will return a new list [0,'a'; 1, 'b'; 2, 'c']. It is basically what your enumerate function is doing.

Of course, it is not a lazy generator. There is the lazy generator module Seq in the standard library, but it was added pretty recently so the interface is still developing. Alternatively, you can use the Sequence module available in Janestreet's Base (and its extension, Core), e.g.,

open Base

let enumerate xs =
  Sequence.of_list xs |>
  Sequence.mapi ~f:Tuple.T2.create

The generated sequence is lazy so the function enumerate xs is O(1) that doesn't iterate the list until the sequence is used, e.g.,

open Base

let () = enumerate ['a'; 'b'; 'c'] |> Sequence.iter ~f:(fun (i,c) -> 
  printf "%d %c\n" i c

Of course, the same could be done easily with, the Sequence.iteri or even with List.iteri iterators. This brings us to the topic of iterators. In OCaml, as well as in many other functional programming languages, we use iterators, such as iter, map, and fold to express iteration over containers. Most of the tasks could be easily expressed using one of these (and derived from them) iterators so it is rarely necessary to use the pythonic approach that will generate a lot of intermediate data structures.

ivg
  • 34,431
  • 2
  • 35
  • 63
0

So from @octachron's answer it seems that the simplest (non lazy) version, without using Base, would be:

 # let enumerate_list (lst: 'a list) = List.mapi lst ~f:( fun i x -> (i, x)) 
val enumerate_list : 'a list -> (int * 'a) list = <fun>

And if we want a lazy version that most closely resembles Python's generator approach, using Base.Sequence it would be:

 # open Base
 # let enumerate(lst: 'a list) =  Sequence.mapi ~f:(fun i x -> (i, x)) 
                                    (Sequence.of_list lst) ;;

This way we can make

# let enumerated_seq = enumerate ['a'; 'b'; 'c']
val a : (int * char) Sequence.t = <abstr>

and if we wanted to materialize a list

# let enumerated_list = Sequence.to_list @@ enumerate ['a'; 'b'; 'c']
val a : (int * char) list = [(0, 'a'); (1, 'b'); (2, 'c')]
Mateo
  • 1,494
  • 1
  • 18
  • 27