Reputation: 2765
I have two lists listA
and listB
where I want to return true
if listB
contains any element also in listA
.
let listA = ["A";"B";"C"]
let listB = ["D";"E";"A"]
Should return true
in this case. I feel like this should be easy to solve and I'm missing something fundamental somewhere.
For example, why can't I do like this?
let testIntersect = for elem in listA do List.exists (fun x -> x = elem) listB
Upvotes: 1
Views: 1694
Reputation: 36688
One function that you could use is List.except
, which is not yet documented (!) but can be seen in this pull request that was merged a couple of years ago. You'd probably use it like this:
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
However, this runs through list B about three times, once to do the except
algorithm and once each to do both the length
calls. So another approach might be to do what you did, but turn list A into a set so that the exists
call won't be 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
The reason I used tryFind
is because List.find
would throw an exception if the predicate didn't match any items of the list.
Edit: An even better approach is to use List.exists
, which I temporarily forgot about (thanks to Honza Brestan for reminding me about it):
let testIntersect a b =
let setA = a |> Set.ofList
b |> List.exists (fun x -> setA |> Set.contains x)
Which, of course, is pretty much what you were originally wanting to do in your testIntersect
code sample. The only difference is that you were using the for ... in
syntax in your code sample, which wouldn't work. In F#, the for
loop is exclusively for expressions that return unit
(and thus, probably have side effects). If you want to return a value, the for
loop won't do that. So using the functions that do return value, like List.exists
, is the approach you want to take.
Upvotes: 3
Reputation: 12184
You can't write something like your example code because a plain for
doesn't return a result, it just evaluates an expression for its side-effects. You could write the code in a for
comprehension:
let testIntersect listA listB =
[for elem in listA do yield List.exists (fun x -> x = elem) listB]
Of course, this then returns a bool list
rather than a single 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]
So, we can use the List.exists
function to ensure that a true
occurs at least once:
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
It's pretty inefficient to solve this problem using List
though, it's better to use Set
. With Set
, you can calculate intersection in O(log N * log M) time rather than O(N*M).
let testSetIntersect listA listB =
Set.intersect (Set.ofList listA) (Set.ofList listB)
|> Set.isEmpty
|> not
Upvotes: 4
Reputation: 4280
let testIntersect listA listB =
(Set.ofList listA) - (Set.ofList listB) |> Set.isEmpty |> not
Upvotes: 2