I am looking to merge 2 lists in F# in a purely functional way. I am having a hard time understanding the syntax.
Let say I have a tuple ([5;3;8],[2;9;4])
When I call the function, it should return [5;2;3;9;8;4]
Here is why I have so far, which is wrong I am sure. If someone could explain it in a simple way I would be grateful.
let rec interleave (xs,ys) = function
|([], ys) -> ys
|(x::xs, y::ys) -> x :: y:: interleave (xs,ys)
Your function is almost right. let f = function
is shorthand for let f x = match x with
so you don't need explicit args. Also, your algorithm needs some tweaking.
let rec interleave = function //same as: let rec interleave (xs, ys) = match xs, ys with
|([], ys) -> ys
|(xs, []) -> xs
|(x::xs, y::ys) -> x :: y :: interleave (xs,ys)
interleave ([5;3;8],[2;9;4]) //output: [5; 2; 3; 9; 8; 4]
One important point is that the function is not correct. It fails with the input ([1;2;3], [])
since you missed the case of (xs, [])
in pattern matching. Moreover, arguments are better in the curried form in order that it's easier to use with partial application. Here is the corrected version:
let rec interleave xs ys =
match xs, ys with
| [], ys -> ys
| xs, [] -> xs
| x::xs', y::ys' -> x::y::interleave xs' ys'
You can see that the function is not tail-recursive since it applies cons (::)
constructor twice after the recursive call returned. One interesting way to make it tail-recursive is using sequence expression:
let interleave xs ys =
let rec loop xs ys =
seq {
match xs, ys with
| [], ys -> yield! ys
| xs, [] -> yield! xs
| x::xs', y::ys' ->
yield x
yield y
yield! loop xs' ys'
}
loop xs ys |> List.ofSeq
You can use this opportunity to define a more general higher order function - zipWith
, and then implement interleave
using it.
let rec zipWith f xlist ylist =
match f, xlist, ylist with
| f, (x :: xs), (y :: ys) -> f x y :: zipWith f xs ys
| _, _, _ -> []
let interleave xs ys = zipWith (fun a b -> [a; b]) xs ys |> List.concat
Edit:
As @pad said below, F# already has zipWith
under the nameList.map2
. So you can rewrite interleave
as follows:
let interleave xs ys = List.map2 (fun a b -> [a; b]) xs ys |> List.concat
From the OP it's not clear what should happen if the lists have different lengths, but here's a generic, tail-recursive implementation that fully consumes both lists:
// 'a list -> 'a list -> 'a list
let interleave xs ys =
let rec imp xs ys acc =
match xs, ys with
| [], [] -> acc
| x::xs, [] -> imp xs [] (x::acc)
| [], y::ys -> imp [] ys (y::acc)
| x::xs, y::ys -> imp xs ys (y::x::acc)
imp xs ys [] |> List.rev
Examples:
> interleave [5;3;8] [2;9;4];;
val it : int list = [5; 2; 3; 9; 8; 4]
> interleave [] [1..3];;
val it : int list = [1; 2; 3]
> interleave [1..3] [42];;
val it : int list = [1; 42; 2; 3]
> interleave [1..3] [42;1337];;
val it : int list = [1; 42; 2; 1337; 3]
> interleave [42; 1337] [1..3];;
val it : int list = [42; 1; 1337; 2; 3]
Since F# 4.5 (I think), assuming you want to continue yielding elements from the longer sequence when the shorter is exhausted, you can just do:
let interleave = Seq.transpose >> Seq.concat >> Seq.toList
> interleave [ [5;3;8]; [2;9;4] ];;
val it : int list = [5; 2; 3; 9; 8; 4]
> interleave [ [1;2;3]; [4;5]; [6;7;8;9] ];; // also works for any number of lists
val it : int list = [1; 4; 6; 2; 5; 7; 3; 8; 9]
(Note List.transpose
throws if the sequences are of differing length but Seq.transpose
does not, so you need to use the latter.)
来源:https://stackoverflow.com/questions/9090799/merge-two-lists