F#List.exists在两个列表中

时间:2017-11-16 18:27:46

标签: f#

我有两个列表listAlistB,如果true包含listB中的任何元素,我想返回listA

let listA = ["A";"B";"C"]
let listB = ["D";"E";"A"]

在这种情况下应该返回true。我觉得这应该很容易解决,我在某处遗漏了一些基本的东西。

例如,为什么我不能这样做?

let testIntersect = for elem in listA do List.exists (fun x -> x = elem) listB

3 个答案:

答案 0 :(得分:3)

您可以使用的一个函数是List.except,它尚未记录(!),但可以在几年前合并的this pull request中看到。您可能会像这样使用它:

let testIntersect a b =
    let b' = b |> List.except a
    // If b' is shorter than b, then b contained at least one element of a
    List.length b' < List.length b

但是,这会在列表B中运行三次,一次执行except算法,一次执行length次调用。所以另一种方法可能是做你做的事情,但把列表A变成一个集合,以便exists调用不会是O(N):

let testIntersect a b =
    let setA = a |> Set.ofList
    match b |> List.tryFind (fun x -> setA |> Set.contains x) with
    | Some _ -> true
    | None   -> false

我使用tryFind的原因是因为如果谓词与列表中的任何项匹配,List.find会抛出异常。

修改:更好的方法是使用我暂时忘记的List.exists(感谢Honza Brestan提醒我):

let testIntersect a b =
    let setA = a |> Set.ofList
    b |> List.exists (fun x -> setA |> Set.contains x)

当然,这正是您最初希望在testIntersect代码示例中执行的操作。唯一的区别是您在代码示例中使用了for ... in语法,这种方法不起作用。在F#中,对于返回for的表达式,unit循环独占(因此可能有副作用)。如果您想返回一个值,for循环不会这样做。因此,使用返回值的函数(如List.exists)就是您要采用的方法。

答案 1 :(得分:3)

您不能编写类似于示例代码的内容,因为普通for没有返回结果,它只是为表达式的副作用计算表达式。您可以用for理解来编写代码:

let testIntersect listA listB = 
    [for elem in listA do yield List.exists (fun x -> x = elem) listB]

当然,这会返回bool list而不是单bool

val testIntersect :
  listA:seq<'a> -> listB:'a list -> bool list when 'a : equality

let listA = ["A";"B";"C"]
let listB = ["D";"E";"A"]

testIntersect listA listB
val it : bool list = [true; false; false]

因此,我们可以使用List.exists函数确保true至少出现一次:

let testIntersect listA listB = 
    [for elem in listA do yield List.exists (fun x -> x = elem) listB]
    |> List.exists id
val testIntersect :
  listA:seq<'a> -> listB:'a list -> bool list when 'a : equality

val listA : string list = ["A"; "B"; "C"]
val listB : string list = ["D"; "E"; "A"]
val it : bool = false

使用List解决此问题的效率非常低,但使用Set会更好。使用Set,您可以在 O(log N * log M)时间内计算交点,而不是 O(N * M)

let testSetIntersect listA listB =
    Set.intersect (Set.ofList listA) (Set.ofList listB)
    |> Set.isEmpty
    |> not

答案 2 :(得分:2)

let testIntersect listA listB = 
    (Set.ofList listA) - (Set.ofList listB) |> Set.isEmpty |> not