列表 OCaml 的第一个和最后一个元素

First and last element of list OCaml

我试图在 OCaml 中获取列表的第一个和最后一个元素。我希望我的功能会像

'a list -> 'a * 'a

我想做的是

let lista = [1;2;3;4;6;0];;

let rec first_last myList =
        match myList with
        [x] -> (List.hd lista,x)
        | head::tail ->     
                  first_last tail;;

first_last lista;;

当然,因为我将列表设为整数,所以我正在执行类似

的语法
*int list -> int * 'a

关键是我不知道如何为 'a 执行此功能。

方向是什么?

方向是编写两个不同的函数firstlast并实现first_and_last函数为:

let first_and_last xs = first xs, last xs

另一种只有一个功能的可能性:

let rec first_last = function
    | [] -> failwith "too bad"
    | [e] -> failwith "too bad"
    | [e1;e2] -> (e1,e2) 
    | e1 :: _ :: r -> first_last (e1::r)

你可能更喜欢这样:

let rec first_last myList = match myList with
    | [] -> failwith "too bad"
    | [e] -> failwith "too bad"
    | [e1;e2] -> (e1,e2) 
    | e1 :: _ :: r -> first_last (e1::r)

您可以为 return 第一个元素和最后一个元素创建两个单独的函数,然后在您的 first_and_last 函数中 return 一个元组(first_element、last_element).

let rec first_element list = 
    match list with 
       | [] -> failwith "List is empty"
       | first_el::rest_of_list -> first_el


let rec last_element list = 
    match list with 
       | [] -> failwith "List is empty"
       | [x] -> x
       | first_el::rest_of_list -> last_element rest_of_list

您可以创建一个辅助函数,它具有空列表的基本情况 - 它 return 自身,否则检查下一个递归调用是否 return 一个空列表列表。如果是,return 当前元素(根据定义是列表中的最后一个元素),如果不是,return 递归调用 return 的内容。

对于常规(非辅助)方法,如果列表至少有一个元素长(即 hd::tl = hd::[]),那么您只需将从 last 函数获得的列表连接到来自 ls.

的头像

可以这样实现:

let rec last ls = 
    match ls with
    | [] -> []
    | hd::tl -> let next = last tl in
        if next = [] then [hd]
    else next
;;
let first_last ls =
    match ls with
    | [] -> failwith "Oh no!!!!! Empty list!"
    | hd::tl -> hd::last tl
;;

另一个解决这个问题的方法。

let first_last xs =
  let rec last_non_empty = function
    | [x]     -> x
    | _ :: xs' -> last_non_empty xs'
    | []      -> failwith "first_last: impossible case!"
  in
    match xs with
      | []   -> failwith "first_last"
      | x::_ -> (x, last_non_empty xs)

此实现的一些属性: (1) 符合规范'a list -> 'a * 'a:

utop > #typeof "first_last";;
val first_last : 'a list -> 'a * 'a 

(2) 适用于单例列表:first_last [x] = (x,x):

utop> first_last [1];;
- : int * int = (1, 1)                                                                                                                                  utop> first_last ["str"];;
- : bytes * bytes = ("str", "str")

(3) 它是尾递归的(因此对于足够大的列表,它不会导致 堆栈溢出 ):

utop > first_last (Array.to_list (Array.init 1000000 (fun x -> x+1)));;
- : int * int = (1, 1000000)

(4) 只遍历输入列表一次; (5) 它避免在递归阶梯下创建新列表; (6) 它避免了污染命名空间(代价是不允许重用像 last 这样的函数)。


另一个相当简单的变体,来自第一原则(我试图本着 SICP 书的精神来说明 "wishful thinking"):

(* Not tail-recursive, might result in stack overflow *)
let rec first_last = function
  | [] -> failwith "first_last"
  | [x] -> (x,x)
  | x :: xs -> (x, snd (first_last xs))

你可以这样写:

let first_last = function
  | [] -> assert false
  | x :: xs -> (x, List.fold_left (fun _ y -> y) x xs)

或者,如果你使用的是Base库,你可以这样写:

let first_last xs = (List.hd_exn xs, List.reduce_exn ~f:(fun _ y -> y) xs)

基本思想是 List.fold_left (fun _ y -> y) x xs 将计算 x :: xs 的最后一个元素。你可以通过对 xs 的归纳来证明这一点: if xs = [] then List.fold_left (fun _ y -> y) x [] = x,这是 x :: [] 的最后一个元素;此外,如果 xs = x' :: xs' 那么 List.fold_left (fun _ y -> y) x (x' :: xs') 可以改写为 List.fold_left (fun _ y -> y) x' xs',因为 List.fold_left f acc (x :: xs) = List.fold_left (f acc x) xs,因此我们完成了,因为这是我们的 x' :: xs' 的最后一个元素归纳假设。