2010-05-27 11 views
13

¿Hay alguna forma de convertir entre F # List y F # Tuple?¿Cómo puedo convertir entre F # List y F # Tuple?

Por ejemplo:

[1;2;3] -> (1,2,3)  
(1,2,3,4) -> [1;2;3;4] 

Necesito dos funciones para hacer eso:

let listToTuple list = ... 
let tupleToList tuple = ... 

gracias de antemano.

+4

Esto va a ser un problema difícil precisamente porque las tuplas deben dimensionarse estáticamente mientras que las listas deben dimensionarse dinámicamente. Creo que la respuesta de sepp2k es probablemente la mejor si realmente necesita una solución así ... defina una función para cada tamaño hasta el tamaño de lista más grande que necesite y luego elija la función para llamar en función de la longitud de la lista. –

Respuesta

14

Además listToTuple then pblasucci tiene la respuesta correcta. Pero no estará contento con el resultado a menos que sepa algo sobre los tipos de letra involucrados, o si quiere hacer mucho boxeo y desempaquetar.

let tupleToList t = 
    if Microsoft.FSharp.Reflection.FSharpType.IsTuple(t.GetType()) 
     then Some (Microsoft.FSharp.Reflection.FSharpValue.GetTupleFields t |> Array.toList) 
     else None 

let listToTuple l = 
    let l' = List.toArray l 
    let types = l' |> Array.map (fun o -> o.GetType()) 
    let tupleType = Microsoft.FSharp.Reflection.FSharpType.MakeTupleType types 
    Microsoft.FSharp.Reflection.FSharpValue.MakeTuple (l' , tupleType) 
4

Actualmente necesita 2*n funciones donde n es el mayor tamaño de tupla que desea admitir. Una tupla que contiene tres entradas tiene un tipo completamente diferente que una tupla que contiene cuatro entradas, por lo que debe escribir una función tupleToList y una función listToTuple para cada una por separado.

También tenga en cuenta que para listToTuple necesita saber el tamaño de la tupla que desea obtener en tiempo de compilación. Es decir. no puede crear una función que decida si devolver (int, int, int) o (int, int) dependiendo de la longitud de la lista de entrada (ya que como dije, son tipos totalmente diferentes). Debería tener una función listToNTuple que tome una lista de al menos N elementos y devuelva una N-tupla.

Podría ser posible escribir funciones independientes del tamaño para esto mediante el uso de la reflexión, pero como no se podía saber el tipo de tupla devuelta por dicha función en tiempo de compilación, sería bastante doloroso usar .

0

Bueno, no es bonita, pero:

let tuple_to_array input = 
    let temp_str = input.ToString() 
    temp_str.Substring(1, temp_str.Length - 2) 
    |> Array.map (fun (x:string) -> x.TrimStart(' ')) 

No estoy seguro de cómo te gustaría ir hacia el otro lado sin embargo. Y realmente no estoy seguro de que esto sea sensato, pero si realmente tienes que hacerlo.

+0

Hmmm. Parece que solo funcionaría con contenedores de tuplas serializables por cadenas. No estoy seguro de que me guste esta solución. – Daniel

+0

Sí, es extremadamente hacky, pero solo tuve cinco minutos para probar el concepto. La solución de Huusom es infinitamente mejor. – Massif

10

Como ya se ha señalado, este es un problema complicado, porque tupla no es un solo tipo - es una familia de tipos, tales como int * int * int o int * int y F # no proporciona ninguna manera para tomar la totalidad familia de tipos como argumento Puedes escribir muchas funciones similares (lo cual es muy incómodo) o usar la reflexión (que es un poco lenta y no es segura para el tipo de letra).

Alternativamente, podría limitar la función a tuplas con cierta estructura; por ejemplo, en lugar de trabajar con (1, 2, 3, 4), podría usar tuplas anidadas como (1, (2, (3, 4))). Esto es un poco menos cómodo, pero mantiene el tipo de seguridad y no es tan malo.

A continuación, puede escribir fácilmente combinadores para la construcción de funciones de conversión sobre la marcha:

// creates function for converting tuple (possibly with a nested 
// tuple in the second component to list 
let tl f (a, b) = a::(f b) 
// converts last element of the tuple to singleton list 
let te a = [a] 

A continuación, se puede combinar funciones tl y te para crear una función de tipo seguro que convierte tupla anidada que contiene 4 elementos a una lista de esta manera:

let l = (1, (2, (3, 4))) |> (tl (tl (tl te))) 

del mismo modo, puede crear funciones para convertir la lista de tuplas - en cuenta que esto puede producir una excepción si la lista no coincide con el formato esperado:

let le = function 
    | [x] -> x 
    | _ -> failwith "incompatible" 
let lt f = function 
    | [] -> failwith "incompatible" 
    | x::xs -> (x, f xs) 

// convert list to a tuple of four elements 
let t = [1; 2; 3; 4] |> lt (lt (lt le)) 

Supongo que esto es probablemente lo más parecido a la función segura y reutilizable para convertir entre tuplas y listas, ya que puede obtener. No es perfecto (en absoluto), pero eso se debe al hecho de que está tratando de implementar una operación muy poco utilizada. En F #, la distinción entre tuplas y listas es más clara que, por ejemplo, en Python (que es dinámico, por lo que no tiene que tratar con la seguridad de tipo estático).

+0

+1 por sugerir tuplas anidadas. –

+0

Por supuesto, la otra cosa es que las tuplas no son simplemente '(int * int * int)' sino también '(int * float * string)', que no se combina bien con una lista. – Benjol

+0

Estoy luchando para encontrar cualquier documentación para la siguiente sintaxis. ¿Ayudaría usted por favor? let le = function | [x] -> x | _ -> falla con "incompatible" –

2

Haciendo uso de la estructura PropertyInfo se puede construir una lista recursivamente. Un problema con este enfoque es que la información de tipo se pierde y el resultado se produce como una lista de obj. Sin embargo, esto resuelve la tupla de la porción de la pregunta.

let tupleToList tpl = 
    let rec loop tpl counter acc = 
     let getItemPropertyInfo t n = t.GetType().GetProperty(sprintf "Item%d" n) 
     let getItem t n = (getItemPropertyInfo t n).GetValue(t,null) 
     match counter with 
     | 8 -> 
      match tpl.GetType().GetProperty("Rest") with 
      | null -> acc 
      | _ as r -> 
       let rest = r.GetValue(tpl,null) 
       loop rest 2 ((getItem rest 1) :: acc) 
     | _ as n -> 
      match getItemPropertyInfo tpl n with 
      | null -> acc 
      | _ as item -> loop tpl (counter+1) (item.GetValue(tpl,null) :: acc) 
    loop tpl 1 [] |> List.rev