4

我需要检查一个列表是否以另一个较短的列表开头。守卫时使用该功能是微不足道的:

let rec startsWith l1 l2 =
  match l1, l2 with
  | [], _ | _, [] -> true
  | x::xs, y::ys when x = y -> startsWith xs ys
  | _ -> false

let lst1 = [ 1; 2; 1 ]
let lst2 = [ 1; 2; 1; 2; 3; ]
let lst3 = [ 1; 3; 1; 2; 3; ]

let c1 = startsWith lst1 lst2  // true
let c2 = startsWith lst1 lst3  // false

但是无论我尝试什么活动模式:

let (|HeadsMatch|) (l1 : ('a) list) (l2 : ('a) list) = 
  if l1.Head = l2.Head then Some(l1.Tail, l2.Tail) else None

let rec startsWith l1 l2 =
   match l1, l2 with
   | [], _ | _, [] -> true
   | HeadsMatch /* need to capture the result */ -> startsWith t1 t2
   | _ -> false

我无法编译。如何使用 Active 模式制作此功能的版本?如果这是不可能的,你能解释为什么吗?

PS 还有其他写上述函数的好方法吗?

编辑:我从丹尼尔的回答中摘录了片段,以免分散真实问题的注意力。

编辑:我的问题从一开始就开始了。我已将活动模式功能定义为

let (|HeadsMatch|_|) lst1 lst2 =

但它应该是

let (|HeadsMatch|_|) (lst1, lst2) =

在这种情况下,它将与接受的答案一样匹配。

4

2 回答 2

5

我想最好的方法可能是

let startsWith = Seq.forall2 (=)

如果要从头开始编写,则需要在两个列表上进行匹配:

let rec startsWith l1 l2 =
  match l1, l2 with
  | [], _ | _, [] -> true
  | x::xs, y::ys when x = y -> startsWith xs ys
  | _ -> false

如果您想使用主动模式来编写它以用于学习目的,使用Tarmil 的定义,它将是

let rec startsWith l1 l2 =
  match l1, l2 with
  | [], _ | _, [] -> true
  | HeadsMatch(xs, ys) -> startsWith xs ys
  | _ -> false
于 2014-04-01T16:00:12.013 回答
2

活动模式的定义中有两个错误:

  1. 这是一个部分活动模式(因为它可能不匹配)所以语法是(|HeadsMatch|_|).

  2. 您需要将这两个列表作为一对,因为您想要匹配lst1, lst2

有了这些,代码将编译。但是它会在运行时抛出异常,因为当你不知道它们是否有任何元素时,你在列表上使用.Headand ;.Tail如果列表之一为空,则您没有定义行为。

这是一个惯用的实现HeadsMatch

let (|HeadsMatch|_|) (lst1, lst2) =
    match (lst1, lst2) with
    | (x :: xs, y :: ys) when x = y -> Some (xs, ys)
    | _ -> None

无防护:

let (|HeadsMatch|_|) (lst1, lst2) =
    match (lst1, lst2) with
    | (x :: xs, y :: ys) ->
        if x = y then Some (xs, ys) else None
    | _ -> None

作为旁注,您的第一个实现有同样的问题。以下将引发运行时异常:

startsWith [1;2] [1]

因为你在使用之前没有检查它是否lst2为空。一般来说,您应该几乎一直避免使用这两种方法.Head.Tail

于 2014-04-01T16:09:32.930 回答