Domanda

Ho due frammenti di codice che tenta di convertire un elenco galleggiante a un elenco Vector3 o Vector2. L'idea è quella di prendere 2/3 elementi alla volta dall'elenco e combinarli come vettore. Il risultato finale è una sequenza di vettori.

    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?"
            }

Il codice è molto simile e tuttavia sembra che ci sia alcun modo per estrarre una porzione comune. Tutte le idee?

È stato utile?

Soluzione

Ecco un approccio. Non sono sicuro quanto più semplice questo è davvero, ma lo fa astratto alcuni dei logica ripetuto fuori.

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)

Altri suggerimenti

Come ha commentato Rex, se si desidera che questo solo per due casi, allora probabilmente non hanno alcun problema se si lascia il codice così com'è. Tuttavia, se si desidera estrarre un modello comune, allora si può scrivere una funzione che divide un elenco in sotto-elenco di un determinato periodo (2 o 3 o qualsiasi altro numero). Una volta fatto questo, userete map solo per trasformare ogni lista della lunghezza specificata in Vector.

La funzione per la lista di scissione non è disponibile nella libreria F # (per quanto posso dire), quindi dovrete implementare da soli. Si può fare più o meno in questo modo:

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      

Ora, è possibile utilizzare questa funzione per implementare le due conversioni in questo modo:

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

Questo darà un avvertimento, perché stiamo utilizzando un modello incompleto che si aspetta che le liste restituite saranno di lunghezza 2 o 3 rispettivamente, ma questo è corretta aspettativa, in modo che il codice funzionerà bene. Sto utilizzando anche una breve versione di espressione sequenza il -> fa la stessa cosa di do yield, ma può essere utilizzato solo in casi semplici come questo.

Questo è simular soluzione KVB ma non utilizza un modello attivo parziale.

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?")

Onestamente, quello che hai è più o meno buono come si può ottenere, anche se si potrebbe essere in grado di fare un po 'più compatta che utilizza questo:

// 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))

Ora il processo di rompere le vostre liste viene spostato nelle sue proprie funzioni generiche "prendere" e "split", la sua molto più facile per mappare al vostro tipo desiderato.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top