2010-02-14 8 views
11

У меня есть два фрагмента кода, который пытается преобразовать список с плавающей точкой в ​​список Vector3 или Vector2. Идея состоит в том, чтобы взять 2/3 элемента за раз из списка и объединить их как вектор. Конечным результатом является последовательность векторов.Избегайте дублирования кода в 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?" 
      } 

Код выглядит очень похожим, но, похоже, нет возможности извлечь общую порцию. Есть идеи?

+0

выглядит довольно чисто для меня, я не думаю, что вы будете работать в вопросе ремонтопригодности – 2010-02-14 02:11:19

+1

Вы могли бы написать общий код, который может захватить N элементов, а затем просто использовать спичку, чтобы выбрать '' Vector3' или Vector2 '(по мере необходимости), но почему? Накладные расходы будут сложнее, чем у вас здесь. Теперь, если вы дойдете до 12, это еще одна история .... –

ответ

13

Вот один из подходов. Я не уверен, насколько проще это на самом деле, но это абстрагирует некоторые из повторяющейся логики.

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

Чем больше я смотрю на это, тем больше мне это нравится. – Brian

+3

Это ... красивый. – cfern

+3

Хорошее использование частичного активного шаблона. – gradbot

2

Как заметил Рекс, если вы хотите это только для двух случаев, то у вас, вероятно, не будет проблем, если вы оставите код таким, какой он есть. Однако, если вы хотите извлечь общий шаблон, вы можете написать функцию, которая разбивает список на под-список указанной длины (2 или 3 или любой другой номер). Как только вы это сделаете, вы будете использовать только map, чтобы перевернуть каждый список указанной длины в Vector.

Функция для разбиения списка недоступна в библиотеке F # (насколько я могу судить), поэтому вам придется ее реализовать самостоятельно. Это может быть сделано примерно так:

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  

Теперь вы можете использовать эту функцию, чтобы реализовать свои два преобразования, как это:

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

Это даст предупреждение, потому что мы используем неполный который ожидает, что возвращенные списки будут иметь длину 2 или 3 соответственно, но это правильное ожидание, поэтому код будет работать нормально. Я также использую краткую версию выражение последовательности-> делает то же самое, что и do yield, но его можно использовать только в простых случаях, подобных этому.

0

Честно говоря, что у вас есть довольно много, как хорошо, как он может получить, хотя вы могли бы сделать немного более компактным, используя это:

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

Теперь процесс разрушения вашего списки перемещаются в свои собственные общие функции «взять» и «разделять», а гораздо проще сопоставить их с желаемым типом.

2

Это симулятор решения kvb, но не использует частичный активный шаблон.

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?") 
Смежные вопросы