Dividir la lista en dos

I would like to implement a function that takes as input a size n and a list. This function will cut the list into two lists, one of size n and the rest in another list. I am new to this language and have a hard time learning the syntax.

The main problem I have is that is finding a way to express a size of the list without using any loops or mutable variables.

Can anyone give a me some pointers?

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

What have you tried? At least you should give us a non-working version to show your efforts? -

Hint: you do not need to express the length of the list - all you need is a way to decrement n, and to check if it has reached zero. -

This may be slightly off topic, but there was a rather neat solution (by Juliet) that split a list in two halves, without knowing/specifying the length in advance: stackoverflow.com/questions/4866640/… -

4 Respuestas

Let's start with the function's type signature. Since it gets n and a list as arguments and returns a pair of lists, you have a function split:

val split : int -> 'a list -> 'a list * 'a list

Here is one approach to implement this function:

let split n xs =
  let rec splitUtil n xs acc =
    match xs with
    | [] -> List.rev acc, []
    | _ when n = 0 -> List.rev acc, xs
    | x::xs' -> splitUtil (n-1) xs' (x::acc)
  splitUtil n xs []

The idea is using an accumulator acc to hold elements you have traversed and decreasing n a long the way. Because elements are prepended to acc, in the end you have to reverse it to get the correct order.

The function has two base cases to terminate:

  • There's no element left to traverse (xs = [] en ese punto).
  • You have gone through the first n elements of the list (n disminuye a 0 at that time).

Here is a short illustration of how split computes the result:

   split 2 [1; 2; 3] // call the auxiliary function splitUtil
~> splitUtil 2 [1; 2; 3] [] // match the 3rd case of x::xs'
~> splitUtil 1 [2; 3] [1] // match the 3rd case of x::xs'
~> splitUtil 0 [3] [2; 1] // match the 2nd case of n = 0 (base case)
~> List.rev [2; 1], [3] // call List.rev on acc
~> [1; 2], [3]

Respondido 03 Feb 12, 03:02

Gracias por la explicación detallada. - user1072706

what role does the last "splitUtil n xs []" play in the function? - user1072706

@user1072706: it is an auxiliar function to avoid declaring split with an extra argument which is always an empty list. You can use splitUtil directly, then everything starts from the 2nd line. - almohadilla

@user1072706, it calls the inner function (splitUtil) with the initial parameters of the outer function (split), plus an empty acc to fill. - Benjol

let split n list =
  let rec not_a_loop xs = function
    | (0, ys) | (_, ([] as ys)) -> (List.rev xs), ys
    | (n, x::ys) -> not_a_loop (x::xs) (n-1, ys)
  not_a_loop [] (n, list)

Respondido 02 Feb 12, 03:02

I dont understand too much your code but it seems that it uses a loop. I was thinking of doing some recurisve calls where i would specify n as the index and having as a base case return when the item in position n is found .. What do you think? - user1072706

That's more or less what this does, accumulating elements prior to n along the way. - Daniel

@user1072706 : No, it uses a recursive function llamado loop. The name is arbitrary, don't let it confuse you. - ildjarn

Thanks, what does the statement | (_,([] as ys)) translate to? - user1072706

There's a certain amount of irony here, considering the inner function is tail-recursive and therefore most likely compile to a loop. - Daniel

New solution - splitAt is now built into List and Array. See commit around 2014 on github. I noticed this today while using F# in VS.2015

Now you can simply do this...

let splitList n list = 
    List.splitAt n list

And as you might expect the signature is...

n: int -> list: 'a list -> 'a list * 'a list

Ejemplo de uso:

let (firstThree, remainder)  = [1;2;3;4;5] |> (splitList 3)
printfn "firstThree %A" firstThree
printfn "remainder %A" remainder

Salida:

firstThree [1; 2; 3]
remainder [4; 5]

Github for those interested: https://github.com/dsyme/visualfsharp/commit/1fc647986f79d20f58978b3980e2da5a1e9b8a7d

Respondido el 01 de Septiembre de 15 a las 05:09

One more way, using fold:

let biApply f (a, b) = (f a, f b)

let splitAt n list = 
  let splitter ((xs, ys), n') c =
    if n' < n then
      ((c :: xs, ys), n' + 1)
    else
      ((xs, c :: ys), n' + 1)
  List.fold splitter (([], []), 0) list 
  |> fst 
  |> biApply List.rev

Aquí is a great series on folds than you can follow to learn more on the topic.

Respondido 04 Feb 12, 22:02

Doesn't this function walk through the todo list, instead of just up to n? - Henrik

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