What is the OCaml idiom equivalent to Python's range function?
Asked Answered
C

15

35

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?

Chandless answered 28/10, 2008 at 16:6 Comment(0)
K
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.

Klockau answered 28/10, 2008 at 17:13 Comment(3)
The -- operator is implemented in Batteries Included, although it produces an enum rather than a list.Valaria
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 jFelix
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.Klockau
A
15

This works in base OCaml:

# List.init 5 (fun x -> x + 1);;
- : int list = [1; 2; 3; 4; 5]
Aerospace answered 11/4, 2018 at 17:48 Comment(1)
Note that List.init is available starting from OCaml 4.06.0.Uniformity
V
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).

Valaria answered 28/5, 2010 at 1:33 Comment(2)
Not sure I like -- for that, is it possible to define a .. operator?Precondition
@Precondition 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: caml.inria.fr/pub/docs/manual-ocaml/lex.htmlValaria
V
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.

Venator answered 28/10, 2008 at 17:7 Comment(3)
Not quite -- Python range(1,3) returns [1,2] while your (range 1 3) returns [1;2;3]. Change > to >=.Crat
@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).Kraigkrait
@GarkGarcia at the time xrange was the generator version.Crat
R
11

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]              
Rationale answered 22/4, 2019 at 7:28 Comment(1)
thanks! For posterity, succ (short for successor) is part of OCaml's Int module and is equivalent to ((+) 1). Ref: ocaml.org/releases/4.10/htmlman/libref/Int.htmlEmelineemelita
K
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)
Kraigkrait answered 28/1, 2020 at 20:6 Comment(1)
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 streamLennox
E
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
Euxenite answered 7/10, 2015 at 19:25 Comment(0)
P
4

Came up with this:

let range a b =
  List.init (b - a) ((+) a)
Perse answered 6/7, 2019 at 11:49 Comment(0)
M
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.

Martel answered 19/3, 2016 at 5:41 Comment(0)
B
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".
Babylonian answered 6/1, 2022 at 20:13 Comment(0)
D
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)).

Dumps answered 28/12, 2014 at 6:33 Comment(0)
T
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)
Trevatrevah answered 26/4, 2018 at 19:36 Comment(0)
C
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
);;
Collectivize answered 30/1, 2020 at 7:31 Comment(0)
C
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]
Circumgyration answered 13/6, 2022 at 12:22 Comment(0)
P
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
Prestonprestress answered 16/6, 2023 at 5:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.