OCaml中类似于Python的range函数的惯用语是什么?

34

我想创建一个从1到n的整数列表。在Python中,我可以使用range(1, n+1)来实现,在Haskell中可以使用:take n (iterate (1+) 1)

在OCaml中,正确的做法是什么?

15个回答

27

我不知道任何成语,但这里有一个使用中缀运算符的相当自然的定义:

# 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]

另外,comprehensions 语法扩展(它为上述语法提供了 [i .. j])有可能在未来版本的 OCaml 的“社区版”中被包括进去,以便成为惯用语。不过,如果你刚开始接触这门语言,我不建议你开始尝试语法扩展。


您应该将指向社区OCaml的链接指向以下网址: http://forge.ocamlcore.org/projects/batteries/ - Thelema
1
“--” 运算符在 Batteries Included 中有实现,尽管它产生的是枚举而不是列表。 - Michael Ekstrand
Python的range函数不包括上限,而你的函数包括,但只需通过使用(j-1)而不是j来调用aux即可轻松解决。 - Nate Parsons
1
该问题要求“创建一个从1到n的整数列表”,而不是复制Python中range(1,n)的行为。您建议的编辑不是(--)运算符的自然定义。 - Chris Conway
1
你为什么注释掉了第一行 # let (--) i j = - Clojurevangelist
这不是注释,而是OCaml交互环境中的命令提示符。语法检测效果不太好。 - Chris Conway

14

通过Batteries Included,你可以编写更多的程序。

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

-- 操作符会从第一个值生成一个枚举到第二个值。 --^ 操作符类似,但会枚举一个半开区间(1--^10 将会枚举 1 到 9)。


我不确定我喜欢“--”,有可能定义一个“..”运算符吗? - aneccodeal
3
@aneccodeal 不行。OCaml 不允许以 '.' 开头的运算符(虽然它们可以在第一个字符后包含 '.')。运算符的允许字符在 OCaml 的词法文档中定义,链接为:http://caml.inria.fr/pub/docs/manual-ocaml/lex.html。 - Michael Ekstrand

14

这在基本的OCaml中可以工作:

List.init 5 (fun x -> x + 1);;
- : int list = [1; 2; 3; 4; 5]

1
请注意,List.init 函数从 OCaml 4.06.0 版本开始提供。 - Jonathan Ballet

12

给你:

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

请注意,这不是尾递归。现代 Python 版本甚至有一个惰性的范围。


6
没问题,Python中的range(1,3)返回的是 [1,2],而你的(range 1 3)返回的是[1;2;3]。将 > 更改为 >= 即可。 - Darius Bacon
@DariusBacon 不对,range函数返回生成器而不是列表。这就是所有这里提出的解决方案的主要问题(它们都需要运行时为一些整数分配空间)。 - Gark Garcia
2
@GarkGarcia 在那个时候,xrange 是生成器版本。 - Darius Bacon

10

继上文提到的Alex Coventry之后,但更加简短。

let range n = List.init n succ;;    
> val range : int -> int list = <fun>   
range 3;;                           
> - : int list = [1; 2; 3]              

2
感谢!为了后人,succ(即 successor 的缩写)是 OCaml 的 Int 模块的一部分,等同于 ((+) 1)。参考:https://ocaml.org/releases/4.10/htmlman/libref/Int.html - JP Lew

6
如果你想要模仿range的懒惰行为,我建议使用Stream模块。例如:
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)

2
这个答案真的被低估了。我非常喜欢Stream方法。附注:函数的返回类型应该是int Stream.t而不是int stream - hbobenicio

4

OCaml有针对范围进行模式匹配的特殊语法:

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

要创建一个范围,可以使用 Core
List.range 0 1000

4
想到了这个:
let range a b =
  List.init (b - a) ((+) a)

3
有点晚了,但这是我的实现方式:
let rec range ?(start=0) len =
    if start >= len
    then []
    else start :: (range len ~start:(start+1))

你可以像使用Python函数一样使用它:
range 10 
     (* equals: [0; 1; 2; 3; 4; 5; 6; 7; 8; 9] *)

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

自然而然,我认为最好的答案是直接使用Core,但如果您只需要一个函数并且想要避免完整框架,则此方法可能更好。


3

有趣的是,下面是使用惰性序列实现类似 Python 的 range 函数的代码:

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

你可以通过以下方式获取从1到n的整数列表:

# 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]

它还提供了其他类似Python的行为,例如默认情况下简洁地从0计数:
# List.of_seq @@ range 5;;
- : int list = [0; 1; 2; 3; 4]

...或倒数:

# 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".

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接