2010-02-14 10 views
11

Ich habe zwei Code-Schnipsel, die versucht, eine Float-Liste in eine Vector3 oder Vector2-Liste zu konvertieren. Die Idee ist, 2/3 Elemente gleichzeitig aus der Liste zu nehmen und sie als Vektor zu kombinieren. Das Endergebnis ist eine Folge von Vektoren.Vermeidung von Code-Duplizierung in 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?" 
      } 

Der Code sieht sehr ähnlich aus und dennoch scheint es keine Möglichkeit zu geben, einen gemeinsamen Teil zu extrahieren. Irgendwelche Ideen?

+0

sieht ziemlich sauber zu mir, ich glaube nicht, dass Sie in ein Wartbarkeit Problem führen werde –

+1

Sie generischen Code schreiben könnte, die N Elemente greifen konnte und dann nur Spiel benutzen, um 'Vector3' auszusuchen oder' Vector2 '(wie passend), aber warum? Der Aufwand wäre komplizierter als das, was Sie hier haben. Nun, wenn du den ganzen Weg bis zu 12 gehst, ist das eine andere Geschichte .... –

Antwort

13

Hier ist ein Ansatz. Ich bin nicht sicher, wie viel einfacher das wirklich ist, aber es abstrahiert einige der wiederholten Logik heraus.

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

Je mehr ich mir das anschaue, desto mehr mag ich es. – Brian

+3

Es ist ... wunderschön. – cfern

+3

Nette Verwendung eines partiellen aktiven Musters. – gradbot

2

Wie Rex kommentierte, wenn Sie dies nur für zwei Fälle wollen, dann haben Sie wahrscheinlich kein Problem, wenn Sie den Code so lassen wie er ist. Wenn Sie jedoch ein gemeinsames Muster extrahieren möchten, können Sie eine Funktion schreiben, die eine Liste in eine Unterliste einer bestimmten Länge aufteilt (2 oder 3 oder eine andere Zahl). Sobald Sie dies getan haben, verwenden Sie nur map, um jede Liste der angegebenen Länge in Vector umzuwandeln.

Die Funktion zum Aufteilen der Liste ist in der F # -Bibliothek nicht verfügbar (soweit ich das beurteilen kann), Sie müssen sie also selbst implementieren.

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  

Jetzt, können Sie diese Funktion verwenden können, um zwei Conversions wie dies zu realisieren: Es lassen sich grob wie folgt durchgeführt werden

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

Dies gibt eine Warnung aus, weil wir ein unvollständiges verwenden Muster, das erwartet, dass die zurückgegebenen Listen die Länge 2 bzw. 3 haben, aber das ist richtige Erwartung, so wird der Code gut funktionieren. Ich verwende auch eine kurze Version von Sequenzausdruck die -> tut das gleiche wie do yield, aber es kann nur in einfachen Fällen wie diesem verwendet werden.

0

Ehrlich gesagt, was Sie haben, ist ziemlich so gut wie es bekommen kann, obwohl Sie in der Lage sein könnte, ein wenig kompakter mit diesem zu machen:

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

nun der Prozess der Zerschlagung Ihre Listen wird in seine eigenen generischen "Take" - und "Split" -Funktionen verschoben, es ist viel einfacher, sie auf den gewünschten Typ zu mappen.

2

Dies entspricht der kvb-Lösung, verwendet jedoch kein partielles aktives Muster.

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