我刚开始接触OCaml,并尝试实现一个函数,该函数返回给定列表 x
中在列表 y
的索引处的元素列表。
例如,该函数应执行以下计算:[5,6,7,8],[0,3] => [5,8]
我不确定如何在ML中存储临时变量,也不清楚它是如何工作的。尽管如此,我知道如何通过指定的索引从列表中查找元素。
任何想法都会受到赞赏,但我想使用递归函数并避免使用 List
模块。
我刚开始接触OCaml,并尝试实现一个函数,该函数返回给定列表 x
中在列表 y
的索引处的元素列表。
例如,该函数应执行以下计算:[5,6,7,8],[0,3] => [5,8]
我不确定如何在ML中存储临时变量,也不清楚它是如何工作的。尽管如此,我知道如何通过指定的索引从列表中查找元素。
任何想法都会受到赞赏,但我想使用递归函数并避免使用 List
模块。
# let rec indices xs = function
| i :: is -> (List.nth xs i) :: indices xs is
| [] -> []
;;
val indices : 'a list -> int list -> 'a list = <fun>
# indices [5;6;7;8] [0;3] ;;
- int list = [5; 8]
我被诱惑了,实现了我建议给@ftk的拉链解决方案。
(* A 'zipper' on the data structure "foo" is a derived data structure
that represents a position in the data structure "foo", that can be
filled with an element. You can think of this as a "cursor" on some
element in the structure, that can moved in various directions to
point to other elements of the structure. If the structure "foo"
does not have random access, keeping a zipper allows to access the
pointed element quickly (instead of having to look at it from the
top of the structure again each time); its neighbors can be
acccessed as well by moving the zipper.
A cursor on a list has this form:
cursor
v
[a; b; c; d; e; f]
It can be represented as a value of type
'a list * 'a * 'a list
where the first list denotes the element before the cursor, and the
second the elements after it. To be able to access the left
neighbor of the cursor in constant time, we store the left elements
in reverse order (rightmost first), so the zipper actually is
([b; a], c, [d; e; f])
Zippers can be adapted to lot of data structures, including in
particular trees. There are neat ways to define them as the
"derivative" (in a calculus-like sense) of datatypes. See
http://en.wikipedia.org/wiki/Zipper_(data_structure) for more
information.
*)
let move_right = function
| (left, x, x' :: right) -> (x :: left, x', right)
| (_, _, []) -> raise Not_found
let move_left = function
| (x' :: left, x, right) -> (left, x', x :: right)
| ([], _, _) -> raise Not_found
let elem (left, e, right) = e
(* zipper of a list *)
let zipper = function
| [] -> raise Not_found
| x :: xs -> ([], x, xs)
let rec iterate f x = function
| 0 -> x
| n -> iterate f (f x) (n - 1)
let get_all data indices =
let get_next index (pos, zip, acc) =
let zip' =
let move = if index < pos then move_left else move_right in
try iterate move zip (abs (index - pos))
with Not_found -> invalid_arg ("invalid index " ^ string_of_int index) in
(index, zip', elem zip' :: acc) in
let (_pos, _zip, result) =
List.fold_right get_next indices (0, zipper data, []) in
result
使用示例:
# get_all [0;2;4;6;8;10] [2;0;1;4];;
- : int list = [4; 0; 2; 8]
# get_all [0;2;4;6;8;10] [2;0;1;6;4];;
Exception: Invalid_argument "invalid index 6".
List.map (List.nth data)
相比之下会明显更快:let slow_get_all data indices = List.map (List.nth data) indices
let init n = Array.to_list (Array.init n (fun i -> i))
let data = init 100_000
let indices = List.map (( * ) 100) (init 1000)
(* some seconds *)
let _ = slow_get_all data indices
(* immediate *)
let _ = get_all data indices
List
库调用,你可以:
使用mange的indices
函数并重新实现List.nth
函数。这并不是很有趣,你可能更喜欢避免在每个y
列表元素中对x
列表进行系统性重新扫描。
使用递归函数同时扫描x
和y
列表。例如,你可能想:
x
列表的元素,次数等于y
列表的第一个元素的值。x
列表中,保留第一个元素,弹出y
的头部,并继续处理x
和y
的剩余部分。如果第二个列表按升序排序且没有重复元素,则可以执行以下操作。 indices_tr
是一个尾递归函数,它有四个参数; result
是先前累积的结果列表,xs
是第一个列表的剩余部分,n
是该列表中的当前索引,is
是索引列表的剩余部分。
let indices xs is =
let rec indices_tr result (x::xs) n = function
| [] -> result
| i::is when i==n -> indices_tr (x::result) xs (n+1) is
| is -> indices_tr result xs (n+1) is
in
indices_tr [] xs 0 is
;;
有一个警告,即空列表未被处理。
结果是一个反向顺序的列表:
# indices [1;3;5;7] [0;1;2];;
- : int list = [5; 3; 1]
使用纯尾递归解决方案并不能让你做得更好;当然,你可以将List.rev应用于它。
Lisp
模块(但List.nth
是可以的,因为他已经有了相应的函数),但是我们仍然可以将其简化为let indices xs is is = List.map (List.nth xs) is
。 - gascheList.map
调用完全等效--提供额外的信息。 - gasche