Combina dos listas

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) 

preguntado el 01 de febrero de 12 a las 04:02

6 Respuestas

Tu función es casi tenía razón. let f = function es taquigrafía para 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]

Respondido 01 Feb 12, 08:02

Thanks for the speedy response but I don't quite understand why there is no argument . > ] How would I call the function? [ < - user1072706

You call the function as you normally would. The last line of code demonstrates usage. See este artículo de MSDN (top of page). It shows two forms of (equivalent) function declaration. - Daniel

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

Respondido el 17 de diciembre de 12 a las 11:12

+1 for giving a tail-recursive solution, though personally I would have used continuations or an accumulator + List.reverse rather than a sequence expression. - ildjarn

@ildjarn: You might be interested in the findings in esta respuesta (they tend to be consistent regardless of algo). In short, using an accumulator + List.rev generally performs much better than continuations. - Daniel

Cool, thanks for the link @Daniel. Continuations and accumulator + List.rev are interesting possibilities, but I wrote this version using Seq to keep it close to the non tail-recursive one. - almohadilla

For an example of @ildjarn 's suggestion, see mi respuesta. - Mark Seemann

You can use this opportunity to define a more general higher order function - zipWithy luego implementar interleave usándolo

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


As @pad said below, F# already has zipWith bajo el nombreList.map2. So you can rewrite interleave como sigue:

let interleave xs ys = List.map2 (fun a b -> [a; b]) xs ys |> List.concat

Respondido 05 Feb 12, 09:02

List.map2 hace lo mismo que zipWith in Haskell. And F# list is not lazy, so using zipWith as in your solution will create a temporary list. - almohadilla

@pad, ah, thanks. I had seen List.map2 before, but somehow forgot about it. Regarding creation of intermediate collection, yes I am aware of that, but this is something that's true of almost every higher order function on List. :-) - desaparecido

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]

(Nota List.transpose throws if the sequences are of differing length but Seq.transpose does not, so you need to use the latter.)

contestado el 03 de mayo de 19 a las 06:05

+1 Although the OP only requires merging/interleaving two lists this has the advantages of being able to merge multiple lists/sequences and is just using function composition. Seems to work with Mono F# 4.0 (demo on - codybartfast

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


> 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]

Respondido el 09 de diciembre de 16 a las 11:12

Gonna throw another variation into the mix. This handles lists of different lengths by simply appending the remainder of the longer list at the end.

let rec interleave lst1 lst2 = [
    match lst1 with
    | lst1H :: lst1T ->
        yield lst1H
        yield! interleave lst2 lst1T
    | [] -> yield! lst2

Cómo funciona:

Asumiendo que tenemos let a = [1;2;3]; let b = [4;5;6] y llama interleave a b.

  • We match on the first branch because the left list is not empty.
  • Cedemos el grupo of the first list (1).
  • We then recurse into the function with the second list and the tail of the first list. Note that the order of the parameters was swapped.

At this intermediate point we've two lists: the remainder of the first one [2;3] y el segundo [4;5;6]. Since we swapped the order of the arguments, we're now focusing on the second list.

  • The list is not empty so we match on the first branch.
  • Cedemos el grupo of the list (4).
  • We then recurse again, switching the parameters once more.

The lists at this point are [2;3] y la [5;6] while the output contains [1;4].

This process repeats until the operated list is empty, which matches into the second branch, yielding the remainder of the other list.

Respondido el 09 de enero de 20 a las 00:01

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.