34

I want to create a list of integers from 1 to n. I can do this in Python using range(1, n+1), and in Haskell using: take n (iterate (1+) 1).

What is the right OCaml idiom for this?

glennsl
  • 28,186
  • 12
  • 57
  • 75
Pramod
  • 9,256
  • 4
  • 26
  • 27

15 Answers15

27

There is no idiom that I know of, but here is a fairly natural definition using an infix operator:

# let (--) i j = 
    let rec aux n acc =
      if n < i then acc else aux (n-1) (n :: acc)
    in aux j [] ;;
val ( -- ) : int -> int -> int list = <fun>
# 1--2;;
- : int list = [1; 2]
# 1--5;;
- : int list = [1; 2; 3; 4; 5]
# 5--10;;
- : int list = [5; 6; 7; 8; 9; 10]

Alternatively, the comprehensions syntax extension (which gives the syntax [i .. j] for the above) is likely to be included in a future release of the "community version" of OCaml, so that may become idiomatic. I don't recommend you start playing with syntax extensions if you are new to the language, though.

Chris Conway
  • 55,321
  • 43
  • 129
  • 155
  • 1
    The `--` operator is implemented in Batteries Included, although it produces an enum rather than a list. – Michael Ekstrand Aug 14 '10 at 11:44
  • Python's range function doesn't include the upper bound, as yours does, but easy enough to fix by calling aux with (j-1) instead of j – Nate Parsons Nov 18 '12 at 03:51
  • 1
    The question asks to "create a list of integers from 1 to n" not to duplicate the behavior of range(1,n) in Python. Your suggested edit is not a natural definition of a (--) operator. – Chris Conway Nov 18 '12 at 15:20
14

With Batteries Included, you can write

let nums = List.of_enum (1--10);;

The -- operator generates an enumeration from the first value to the second. The --^ operator is similar, but enumerates a half-open interval (1--^10 will enumerate from 1 through 9).

Michael Ekstrand
  • 28,379
  • 9
  • 61
  • 93
  • Not sure I like -- for that, is it possible to define a .. operator? – aneccodeal Aug 13 '10 at 02:01
  • 3
    @aneccodeal No. OCaml does not allow operators starting with '.' (although they may contain '.' after the first character). The allowed characters for operators are defined in OCaml's lexical documentation here: http://caml.inria.fr/pub/docs/manual-ocaml/lex.html – Michael Ekstrand Aug 14 '10 at 11:42
14

This works in base OCaml:

# List.init 5 (fun x -> x + 1);;
- : int list = [1; 2; 3; 4; 5]
Kevin Ji
  • 10,479
  • 4
  • 40
  • 63
Alex Coventry
  • 68,681
  • 4
  • 36
  • 40
  • 1
    Note that `List.init` is available starting from [OCaml 4.06.0](https://caml.inria.fr/pub/docs/manual-ocaml/libref/List.html#VALinit). – Jonathan Ballet May 05 '19 at 15:24
12

Here you go:

let rec range i j = 
  if i > j then [] 
  else i :: range (i+1) j

Note that this is not tail-recursive. Modern Python versions even have a lazy range.

Chris
  • 26,361
  • 5
  • 21
  • 42
Thedric Walker
  • 1,817
  • 15
  • 23
  • 6
    Not quite -- Python range(1,3) returns [1,2] while your (range 1 3) returns [1;2;3]. Change > to >=. – Darius Bacon Oct 29 '08 at 16:45
  • @DariusBacon Nope your wrong. The `range` functions returns generators, not lists. This is the main problem with all of the solutions presented here (they all require the runtime to allocate space for some ints). – Gark Garcia Jan 28 '20 at 19:51
  • 2
    @GarkGarcia at the time `xrange` was the generator version. – Darius Bacon Mar 19 '22 at 19:15
10

Following on Alex Coventry from above, but even shorter.

let range n = List.init n succ;;    
> val range : int -> int list = <fun>   
range 3;;                           
> - : int list = [1; 2; 3]              
Travis S
  • 166
  • 1
  • 4
  • 2
    thanks! For posterity, `succ` (short for successor) is part of OCaml's Int module and is equivalent to `((+) 1)`. Ref: https://ocaml.org/releases/4.10/htmlman/libref/Int.html – JP Lew Apr 04 '21 at 08:18
6

If you intend to emulate the lazy behavior of range, I would actually recommend using the Stream module. Something like:

let range (start: int) (step: int) (stop: int): int stream =
    Stream.from (fun i -> let j = i * step + start in if j < stop then Some j else None)
glennsl
  • 28,186
  • 12
  • 57
  • 75
Gark Garcia
  • 450
  • 6
  • 14
  • 2
    This answer is really underrated. I really like the Stream approach. PS.: the function's return type should be `int Stream.t` instead of `int stream` – hbobenicio Jan 13 '22 at 19:37
4

OCaml has special syntax for pattern matching on ranges:

let () =
  let my_char = 'a' in
  let is_lower_case = match my_char with
  | 'a'..'z' -> true (* Two dots define a range pattern *)
  | _ -> false
  in
  printf "result: %b" is_lower_case

To create a range, you can use Core:

List.range 0 1000
Matthias Braun
  • 32,039
  • 22
  • 142
  • 171
4

Came up with this:

let range a b =
  List.init (b - a) ((+) a)
Nondv
  • 769
  • 6
  • 11
3

A little late to the game here but here's my implementation:

let rec range ?(start=0) len =
    if start >= len
    then []
    else start :: (range len ~start:(start+1))

You can then use it very much like the python function:

range 10 
     (* equals: [0; 1; 2; 3; 4; 5; 6; 7; 8; 9] *)

range ~start:(-3) 3 
     (* equals: [-3; -2; -1; 0; 1; 2] *)

naturally I think the best answer is to simply use Core, but this might be better if you only need one function and you're trying to avoid the full framework.

JustGage
  • 1,534
  • 17
  • 20
3

For fun, here's a very Python-like implementation of range using a lazy sequence:

let range ?(from=0) until ?(step=1) =
  let cmp = match step with
    | i when i < 0 -> (>)
    | i when i > 0 -> (<)
    | _ -> raise (Invalid_argument "step must not be zero")
  in
  Seq.unfold (function
        i when cmp i until -> Some (i, i + step) | _ -> None
    ) from

So you can get a list of integers from 1 to n by:

# let n = 10;;
val n : int = 10
# List.of_seq @@ range ~from:1 (n + 1);;
- : int list = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]

It also gives other Python-like behaviours, such as concisely counting from 0 by default:

# List.of_seq @@ range 5;;
- : int list = [0; 1; 2; 3; 4]

...or counting backwards:

# List.of_seq @@ range ~from:20 2 ~step:(-3);;
- : int list = [20; 17; 14; 11; 8; 5]

(* you have to use a negative step *)
# List.of_seq @@ range ~from:20 2;;
- : int list = []

# List.of_seq @@ range 10 ~step:0;;
Exception: Invalid_argument "step must not be zero".
Anentropic
  • 32,188
  • 12
  • 99
  • 147
2

If you use open Batteries (which is a community version of the standard library), you can do range(1,n+1) by List.range 1 `To n (notice the backquote before To).

A more general way (also need batteries) is to use List.init n f which returns a list containing (f 0) (f 1) ... (f (n-1)).

user69818
  • 403
  • 6
  • 13
0

If you don't need a "step" parameter, one easy way to implement this function would be:

let range start stop = 
  List.init (abs @@ stop - start) (fun i -> i + start)
Chris
  • 26,361
  • 5
  • 21
  • 42
rdavison
  • 71
  • 2
  • 5
0

Personally I use the range library of OCaml for that.

(* print sum of all values between 1 and 50, adding 4 to all elements and excluding 53 *)
Range.(
  from 1 50 
  |> map ((+) 4) 
  |> filter ((!=) 53) 
  |> fold (+) 0 
  |> print_int
);;
Chris
  • 26,361
  • 5
  • 21
  • 42
Aldrik
  • 136
  • 6
0

Here is my version using Base.Sequence

open Base

let pylike_range ?(from=0) ?(step=1) (until: int) : int Sequence.t = 
  Sequence.range ~stride:step ~start:`inclusive ~stop:`exclusive from until

let range_list ?(from=0) ?(step=1) (until: int) : int list = 
  pylike_range ~from:from ~step:step until 
  |> Sequence.to_list 

Example usages:

# range_list 10;;
- : int list = [0; 1; 2; 3; 4; 5; 6; 7; 8; 9]

# range_list 10 ~from:3;;
- : int list = [3; 4; 5; 6; 7; 8; 9]

# range_list 10 ~from:3 ~step:2;;
- : int list = [3; 5; 7; 9]
Mateo
  • 1,494
  • 1
  • 18
  • 27
0

A lot of answers here describe either strictly evaluated or lazy ways to get a list from A to B, and sometimes by a step C. However, there is one critical aspect of Python's range that has not been covered: in Python 3.x checking for membership in a range is an O(1) operation, while checking for membership in a list (or a lazy sequence) is an O(n) operation.

If we implement ranges as an abstract type, we can implement a mem function which checks for membership in a range in an O(1) fashion.

module type RANGE_TYPE = sig
  type t
  val make : int -> int -> int -> t
  val mem : int -> t -> bool
  val to_seq : t -> int Seq.t
  val to_list : t -> int list
end

module Range : RANGE_TYPE = struct
  type t = { start: int; stop: int; by: int }
  
  let make start stop by =
    {start; stop; by}
    
  let mem x {start; stop; by} =
    if by > 0 then
      x >= start && x < stop && (x - start) mod by = 0 
    else
      x <= start && x > stop && (start - x) mod abs by = 0
      
  let to_seq {start; stop; by} =
    let rec aux n () =
      if (by > 0 && n >= stop) || (by < 0 && n <= stop) then 
        Seq.Nil  
      else 
        Seq.Cons (n, aux (n + by))      
    in
    aux start    
    
  let to_list t = 
    t 
    |> to_seq 
    |> List.of_seq  
end
Chris
  • 26,361
  • 5
  • 21
  • 42