1

現在、多次元関数を扱うコードを Java から F# に移植しています。可変次元をサポートしているため、元の実装では各ポイントは double の配列として表されます。コードの重要な機能は、基本的にいくつかの基準に基づいてポイントのシーケンスを生成し、これらのポイントで特定の関数を評価し、最大値を探す最適化ルーチンです。これはどの次元でも機能します。私が必要とする操作は次のとおりです。

  • 点の次元をチェックする
  • 指定された点と同じ次元で新しい点を作成する
  • 点の与えられた座標を (手続き上または機能上の意味で) 設定する

F# では、明らかに同じ方法で配列を使用することもできます。もっと良い方法があれば、私はさまよっていました。次元が事前に固定されている場合、当然の選択はタプルを使用することです。ただし、この動的設定でタプルを使用することは可能ですか?

4

1 に答える 1

3

いいえ、タプルは寸法によって固定されます。また、.NETタプルはボックス化されていることに注意してください。小さな次元のポイントの大規模なコレクション(2Dポイントの配列など)を操作している場合は、構造体を使用すると役立つ場合があります。

本当にJavaよりもF#/。NETの利点をプッシュしたい場合は、ジェネリックスを見てください。ジェネリックを使用してコードを記述すると、任意の次元で機能するコードを記述し、さまざまな次元でさまざまな表現を使用できます(たとえば、1〜3次元の構造体、より大きな次元のベクトル)。

let op<'T where 'T :> IVector> (x: 'T) =
    ...

ただし、これは、絶対的に最高のパフォーマンスと一般性を得るために長い道のりを進んで進んでいる場合にのみ関係します。ほとんどのプロジェクトはそれを必要とせず、機能する最も単純なものに固執します。

それを楽しむために、ジェネリックスとF#インライン化を利用する方法の拡張例を次に示します。

open System.Numerics

type IVector<'T,'V> =
    abstract member Item : int -> 'T with get
    abstract member Length : int
    abstract member Update : int * 'T -> 'V

let lift<'T,'V when 'V :> IVector<'T,'V>> f (v: 'V) : 'V =
    if v.Length = 0 then v else
        let mutable r = v.Update(0, f v.[0])
        for i in 1 .. v.Length - 1 do
            r <- r.Update(i, f v.[i])
        r

let inline norm (v: IVector<_,_>) =
    let sq i =
        let x = v.[i]
        x * x
    Seq.sum (Seq.init v.Length sq)

let inline normalize (v: 'V) : 'V =
    let n = norm v
    lift (fun x -> x / n) v

[<Struct>]
type Vector2D<'T>(x: 'T, y: 'T) =
    member this.X = x
    member this.Y = y

    interface IVector<'T,Vector2D<'T>> with
        member this.Item
            with get (i: int) =
                match i with
                | 0 -> x
                | _ -> y
        member this.Length = 2
        member this.Update(i: int, v: 'T) =
            match i with
            | 0 -> Vector2D(v, y)
            | _ -> Vector2D(x, v)

    override this.ToString() =
        System.String.Format("{0}, {1}", x, y)

[<Sealed>]
type Vector<'T>(x: 'T []) =

    interface IVector<'T,Vector<'T>> with
        member this.Item with get (i: int) = x.[i]
        member this.Length = x.Length
        member this.Update(i: int, v: 'T) =
            let a = Array.copy x
            a.[i] <- v
            Vector(a)

    override this.ToString() =
        x
        |> Seq.map (fun e -> e.ToString())
        |> String.concat ", "

[<Struct>]
type C(c: Complex) =
    member this.Complex = c
    static member Zero = C(Complex(0., 0.))
    static member ( + ) (a: C, b: C) = C(a.Complex + b.Complex)
    static member ( * ) (a: C, b: C) = C(a.Complex * b.Complex)
    static member ( / ) (a: C, b: C) = C(a.Complex / b.Complex)
    override this.ToString() = string c

let v1 = Vector2D(10., 30.)
normalize v1
|> printfn "%O"

let v2 = Vector2D(C(Complex(1.25, 0.8)), C(Complex(0.5, -1.)))
normalize v2
|> printfn "%O"

let v3 = Vector([| 10.; 30.; 50.|])
normalize v3
|> printfn "%O"

normnormalizeはかなり一般的であり、特殊な2Dベクトルと一般化されたN次元ベクトル、および複素数などのさまざまなコンポーネントタイプ(独自に定義できます)を処理することに注意してください。ジェネリックスとF#インライン化を使用すると、一般的ではありますが、これらのアルゴリズムは、コンパクトな表現を使用して、特殊なケースで適切に機能します。これは、適切なパフォーマンスを得るためにコードの特殊なコピーを作成する必要があるJavaと比較して、F#および.NETジェネリックが優れている点です。

于 2012-06-07T14:47:39.107 に答える