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?
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?
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.
This works in base OCaml:
# List.init 5 (fun x -> x + 1);;
- : int list = [1; 2; 3; 4; 5]
List.init
is available starting from OCaml 4.06.0. –
Uniformity 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).
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.
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 xrange
was the generator version. –
Crat 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]
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.html –
Emelineemelita 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)
int Stream.t
instead of int stream
–
Lennox 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
Came up with this:
let range a b =
List.init (b - a) ((+) a)
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.
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".
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)).
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)
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
);;
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]
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
© 2022 - 2024 — McMap. All rights reserved.
--
operator is implemented in Batteries Included, although it produces an enum rather than a list. – Valaria