1

我目前正在将一些处理多维函数的代码从 Java 移植到 F#。它支持可变维度,因此在原始实现中,每个点都表示为一个双精度数组。代码的关键功能是一个优化例程,它基本上根据一些标准生成一系列点,在这些点评估给定函数并寻找最大值。这适用于任何维度。我需要的操作是:

  • 检查点的尺寸
  • 创建一个与给定点具有相同尺寸的新点
  • 设置(在程序或功能意义上)点的给定坐标

在 F# 中,我显然也可以以相同的方式使用数组。如果有更好的方法,我正在徘徊。如果维度是预先固定的,那么明显的选择是使用元组。是否可以在此动态设置中使用元组?

4

1 回答 1

3

不,元组将按维度固定。另请注意,.NET 元组是装箱的。如果您正在处理具有小维度的大量点集合(例如二维点数组),则使用结构可能会有所帮助。

如果您真的想将 F#/.NET 的优势推向 Java,请查看泛型。使用泛型编写代码允许编写适用于任何维度的代码,并为不同的维度使用不同的表示形式(比如 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"

请注意,norm它们normalize相当通用,它们处理专门的 2D 向量和广义 N 维向量,以及不同的组件类型,例如复数(您可以定义自己的)。泛型和 F# 内联的使用确保了这些算法虽然通用,但使用紧凑的表示在特殊情况下表现良好。这就是 F# 和 .NET 泛型与 Java 相比的优势所在,在 Java 中,您必须创建代码的专用副本才能获得良好的性能。

于 2012-06-07T14:47:39.107 回答