how to merge two tuple seq with specified key gracefully?

Viewed 132

I wrote the code but looks ugly.
Image there are two seqs like:

let x = [(1,"x");(2,"y")]
let y = [(1, "xx", "xxx");(2,"yy","yyy")]  

The elements are tuples but not the same shape.
The result need to be [(1, "x", "xx", "xxx");(2, "y", "yy", "yyy")].
The first element in the tuple is the key here.

The code I wrote used map like:

let x = [(1,"x");(2,"y")]
let y = [(1, "xx", "xxx");(2,"yy","yyy")]  
let mapY = y |> Seq.map (fun (a,b,c) -> (a, (b, c))) |> Map.ofSeq

let r = [
    for (k,v) in x do
        let (v1,v2) = mapY |> Map.tryFind k |> Option.orElse (Some ("","")) |> Option.get
        yield (k, v, v1, v2)
]

printfn "%A" r

not so gracefully.
So I wonder any better solution?(The tuple element in the question can be seq type, tuple just for example).
The key question is how to merge two seq by key gracefully.

2 Answers

I would suggest using the query computation expression (see here for more info and examples). In your case, a solution would look like this:

let r =
    query {
        for (k1, v) in x do
        join (k2, v1, v2) in y on (k1 = k2)
        select(k1, v, v1, v2) }
    |> Seq.toList

Note that the join operator used here is in fact an inner join. There are a lot of other join-methods (as well as other operators) listed on the page linked to above.

I agree that the use of join as shown by @torbonde is probably the nicest way to do this.

If you preferred to stick to basic List functions, then I think what you have is probably as good as it can be. One minor tweak would be to replace orElse with defaultValue:

let (v1,v2) = mapY |> Map.tryFind k |> Option.defaultValue ("","")

Another thing I'd consider is whether you need flat tuples - if you were happy with nesting of tuples on both the input and the output side, you could do something like this (which is essentially still your original code, but a bit shorter):

let x = [(1,"x");(2,"y")]
let y = [(1, ("xx", "xxx"));(2,("yy","yyy"))]  

let mapY = Map.ofSeq y
let r = x |> List.map (fun (k, v) -> 
     k, v, defaultArg (mapY.TryFind k) ("",""))
Related