2010-02-14 37 views
11

Tôi có hai đoạn mã cố gắng chuyển đổi danh sách thả nổi thành danh sách Vector3 hoặc Vector2. Ý tưởng là lấy 2/3 phần tử tại một thời điểm từ danh sách và kết hợp chúng dưới dạng vectơ. Kết quả cuối cùng là một chuỗi các vectơ.Tránh sao chép mã trong 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?" 
      } 

Mã có vẻ rất giống nhau nhưng dường như không có cách nào để trích xuất một phần chung. Bất kỳ ý tưởng?

+0

trông khá sạch sẽ với tôi, tôi không nghĩ rằng bạn sẽ gặp phải vấn đề về bảo trì –

+1

Bạn có thể viết mã chung có thể lấy N mục và sau đó chỉ cần sử dụng kết hợp để chọn 'Vector3' hoặc 'Vector2 '(thích hợp), nhưng tại sao? Chi phí sẽ phức tạp hơn những gì bạn có ở đây. Bây giờ, nếu bạn đi tất cả các con đường lên đến 12, đó là một câu chuyện .... –

Trả lời

13

Đây là một cách tiếp cận. Tôi không chắc điều này thực sự đơn giản hơn bao nhiêu, nhưng nó trừu tượng hóa một số logic lặp đi lặp lại.

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

Càng nhìn vào điều này, tôi càng thích nó. – Brian

+3

Đó là ... đẹp. – cfern

+3

Sử dụng tốt một mẫu hoạt động một phần. – gradbot

2

Như Rex nhận xét, nếu bạn muốn điều này chỉ cho hai trường hợp, sau đó bạn có thể sẽ không có bất kỳ vấn đề nếu bạn để lại mã như nó được. Tuy nhiên, nếu bạn muốn trích xuất một mẫu chung, thì bạn có thể viết một hàm chia danh sách thành danh sách phụ có độ dài được chỉ định (2 hoặc 3 hoặc bất kỳ số nào khác). Khi bạn làm điều đó, bạn sẽ chỉ sử dụng map để biến từng danh sách có độ dài được chỉ định thành Vector.

Chức năng tách danh sách không khả dụng trong thư viện F # (theo như tôi có thể biết), vì vậy bạn sẽ phải tự thực hiện nó. Nó có thể được thực hiện gần như thế này:

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  

Bây giờ, bạn có thể sử dụng chức năng này để thực hiện hai chuyển đổi của bạn như thế này:

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

này sẽ cung cấp một cảnh báo, bởi vì chúng tôi đang sử dụng không đầy đủ mô hình mong rằng các danh sách được trả về sẽ có độ dài 2 hoặc 3 tương ứng, nhưng đó là kỳ vọng chính xác, vì vậy mã sẽ hoạt động tốt. Tôi cũng đang sử dụng phiên bản ngắn gọn của biểu thức trình tự-> thực hiện tương tự như do yield, nhưng nó chỉ có thể được sử dụng trong các trường hợp đơn giản như thế này.

0

Thành thực mà nói, những gì bạn có là khá nhiều càng tốt vì nó có thể nhận được, mặc dù bạn có thể có thể làm cho một chút nhỏ gọn hơn sử dụng này:

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

Bây giờ quá trình tan rã của bạn danh sách được chuyển thành các hàm "take" và "split" chung của nó, dễ dàng hơn nhiều trong việc ánh xạ nó tới loại mong muốn của bạn.

2

Đây là mô hình giải pháp của kvb nhưng không sử dụng mẫu hoạt động từng phần.

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?") 
Các vấn đề liên quan