F#でコードの重複の回避
-
20-09-2019 - |
質問
私がするVector3またはベクトル2リストにフロートリストを変換しようとする2つのコードスニペットを持っています。アイデアは、リストから一度に2/3の要素を取り、ベクトルとしてそれらを組み合わせることです。最終結果は、ベクトルのシーケンスである。
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?"
}
コードは非常に似見て、まだ共通部分を抽出する方法はないように思えます。任意のアイデア?
解決
ここに1つのアプローチです。私はこれが本当にどのくらいに簡単わからないんだけど、それが繰り返されるロジックの抽象いくつかを行います。
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)
他のヒント
は、あなたはおそらく問題ありません。あなたが一般的なパターンを抽出したい場合は、あなたが指定した長さ(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
さて、あなたはこのようなあなたの2つの変換を実装するために、この機能を使用することができます:
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
と同じことを行い、それだけで、このような単純な例で使用することができます。のシーケンス式のの簡単なバージョンを使用しています。
このは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?")
正直なところ、何を持っていることは、かなりそれが得ることができるように良いです。
// 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))
さて、あなたのリストを分割する処理は、一般的なご希望のタイプにマッピングするために「取る」と「分割」機能、その方がはるかに簡単、独自に移動されます。