2010-02-14 20 views
11

Tengo dos fragmentos de código que intentan convertir una lista flotante en una lista Vector3 o Vector2. La idea es tomar 2/3 elementos a la vez de la lista y combinarlos como un vector. El resultado final es una secuencia de vectores.Evitar la duplicación de código en F #

let rec vec3Seq floatList = 
     seq { 
      match floatList with 
      | x::y::z::tail -> yield Vector3(x,y,z) 
           yield! vec3Seq tail 
      | [] ->() 
      | _ -> failwith "float array not multiple of 3?" 
      } 

    let rec vec2Seq floatList = 
     seq { 
      match floatList with 
      | x::y::tail -> yield Vector2(x,y) 
          yield! vec2Seq tail 
      | [] ->() 
      | _ -> failwith "float array not multiple of 2?" 
      } 

El código parece muy similar y, sin embargo, parece que no hay forma de extraer una parte común. ¿Algunas ideas?

+0

se ve bastante limpio para mí, no creo que se encontrará con un problema de mantenimiento –

+1

Se puede escribir código genérico que podría agarrar N elementos y luego simplemente usar partido de seleccionar '' Vector3' o Vector2 '(según corresponda), pero ¿por qué? La sobrecarga sería más complicada de lo que tienes aquí. Ahora, si subes hasta 12, esa es otra historia ... –

Respuesta

13

Aquí hay un enfoque. No estoy seguro de que sea mucho más simple, pero abstrae algo de la lógica repetitiva.

let rec mkSeq (|P|_|) x = 
    seq { 
    match x with 
    | P(p,tail) -> 
     yield p 
     yield! mkSeq (|P|_|) tail 
    | [] ->() 
    | _ -> failwith "List length mismatch" } 

let vec3Seq = 
    mkSeq (function 
    | x::y::z::tail -> Some(Vector3(x,y,z), tail) 
    | _ -> None) 
+3

Cuanto más miro esto, más me gusta. – Brian

+3

Es ... hermoso. – cfern

+3

Buen uso de un patrón activo parcial. – gradbot

2

Como comentó Rex, si desea esto solo para dos casos, entonces probablemente no tendrá ningún problema si deja el código tal como está. Sin embargo, si desea extraer un patrón común, puede escribir una función que divida una lista en una sublista de una longitud especificada (2 o 3 o cualquier otro número). Una vez que lo haga, solo usará map para convertir cada lista de la longitud especificada en Vector.

La función para dividir la lista no está disponible en la biblioteca F # (hasta donde puedo decir), por lo que tendrá que implementarla usted mismo. Se puede hacer más o menos así:

let divideList n list = 
    // 'acc' - accumulates the resulting sub-lists (reversed order) 
    // 'tmp' - stores values of the current sub-list (reversed order) 
    // 'c' - the length of 'tmp' so far 
    // 'list' - the remaining elements to process 
    let rec divideListAux acc tmp c list = 
    match list with 
    | x::xs when c = n - 1 -> 
     // we're adding last element to 'tmp', 
     // so we reverse it and add it to accumulator 
     divideListAux ((List.rev (x::tmp))::acc) [] 0 xs 
    | x::xs -> 
     // add one more value to 'tmp' 
     divideListAux acc (x::tmp) (c+1) xs 
    | [] when c = 0 -> List.rev acc // no more elements and empty 'tmp' 
    | _ -> failwithf "not multiple of %d" n // non-empty 'tmp' 
    divideListAux [] [] 0 list  

Ahora, puede utilizar esta función para poner en práctica sus dos conversiones de esta manera:

seq { for [x; y] in floatList |> divideList 2 -> Vector2(x,y) } 
seq { for [x; y; z] in floatList |> divideList 3 -> Vector3(x,y,z) } 

Esto le dará una advertencia, porque estamos usando un incompleto patrón que espera que las listas devueltas sean de longitud 2 o 3 respectivamente, pero esa es la expectativa correcta, por lo que el código funcionará bien. También estoy usando una versión breve de expresión de secuencia-> hace lo mismo que do yield, pero solo se puede usar en casos simples como este.

0

Honestamente, lo que tienes es casi tan bueno como se puede conseguir, aunque es posible que pueda hacer un poco más compacto que utiliza este:

// take 3 [1 .. 5] returns ([1; 2; 3], [4; 5]) 
let rec take count l = 
    match count, l with 
    | 0, xs -> [], xs 
    | n, x::xs -> let res, xs' = take (count - 1) xs in x::res, xs' 
    | n, [] -> failwith "Index out of range" 

// split 3 [1 .. 6] returns [[1;2;3]; [4;5;6]] 
let rec split count l = 
    seq { match take count l with 
      | xs, ys -> yield xs; if ys <> [] then yield! split count ys } 

let vec3Seq l = split 3 l |> Seq.map (fun [x;y;z] -> Vector3(x, y, z)) 
let vec2Seq l = split 2 l |> Seq.map (fun [x;y] -> Vector2(x, y)) 

Ahora el proceso de dividir su listas se mueve en sus propias funciones genéricas de "tomar" y "dividir", es mucho más fácil asignarlo al tipo deseado.

2

Esto es similar a la solución de kvb pero no usa un patrón activo parcial.

let rec listToSeq convert (list:list<_>) = 
    seq { 
     if not(List.isEmpty list) then 
      let list, vec = convert list 
      yield vec 
      yield! listToSeq convert list 
     } 

let vec2Seq = listToSeq (function 
    | x::y::tail -> tail, Vector2(x,y) 
    | _ -> failwith "float array not multiple of 2?") 

let vec3Seq = listToSeq (function 
    | x::y::z::tail -> tail, Vector3(x,y,z) 
    | _ -> failwith "float array not multiple of 3?") 
Cuestiones relacionadas