开发者

F# Comparing Two Arrays for Equality to a Specified Level of Precision

Here is the C# code I am trying to translate:

public bool equals(Matrix matrix, int precision)
    {

        if (precision < 0)
        {
            throw new MatrixError("Precision can't be a negative number.");
        }

        double test = Math.Pow(10.0, precision);
        if (double.IsInfinity(test) || (test > long.MaxValue))
        {
            throw new MatrixError("Precision of " + precision
                    + " decimal places is not supported.");
        }

        precision = (int)Math.Pow(10, precision);

        for (int r = 0; r < this.Rows; r++)
        {
            for (int c = 0; c < this.Cols; c++)
            {
                if ((long)(this[r, c] * precision) != (long)(matrix[r, c] * precision))
                {
                    return false;
                }
            }
        }

        return true;
    }

Here is what I have so far:

type Matrix(sourceMatrix:double[,]) =
     let rows = sourceMatrix.GetUpperBound(0) + 1
     let cols = sourceMatrix.GetUpperBound(1) + 1
     let matrix = Array2D.zeroCreate<double> rows cols
     do
      for i in 0 .. rows - 1 do
      for j in 0 .. cols - 1 do
        matrix.[i,j] <- sourceMatrix.[i,j]
    ///The number of Rows in this Matrix.
    member this.Rows = rows

    ///The number of Columns in this Matrix.
    member this.Cols = cols

    member this.Equals(matrix:Matrix, precision:int) =
     if(precision < 0) then raise (new ArgumentOutOfRangeException("Precision can't be a negative number."))
     let (test:double) = Math.Pow(10.0, double(precision))
     if(System.Double.IsInfinity(test) || (test > double(System.Int32.MaxValue))) then raise (new ArgumentOutOfRangeException("Precision of " + precision.ToS开发者_如何学运维tring() + " decimal places is not supported."))
     let precision = int(Math.Pow(10.0, double(precision)))

As you can see what I have written so far is loaded with type casts which probably means my code is not written the way it should be. The unfinished part needs the method to return on the first element which returns false when evaluated to a certain precision. I'm sure there must be some elegant F# code to achieve this and clearly I am nowhere near it. I was trying to figure out if the Array2D class had some method on it that would allow me to do this, but I wasn't able to find it if there is. I am aware of the PowerPack Matrix class and will use it eventually, but for now I'm trying to learn F# by translating C# code I understand into F#. Easier said than done apparently. :) I believe I've added all the relevant F# code in the type I'm creating. Let me know if I'm missing something.


An elegant and high-level way to write this that probably won't be extremely efficient is to use lazy sequence expressions:

seq { for r in 0 .. this.Rows - 1 do
        for c in 0 .. this.Cols - 1 do
          if <your condition goes here> then
            yield false}
|> Seq.forall id

The idea is that the sequence will generate false as soon as the first element in the matrix matches the condition. Then the Seq.forall function immediately returns false (and stops iterating over the sequence).

In practice, you'll probably need to implement this using recursive function to make it efficient. This is not particularly nice (because breaking out of loops cannot be done in F#), but you shouldn't need code like this too often:

let rec loopRows r = 
  let rec loopCols c = 
    if c = this.Cols then true
    elif <your condition goes here> then false
    else loopCols (c + 1)
  if r = this.Rows then true       // Processed all rows
  elif not (loopCols 0) then false // Nonequal element in this row
  else loopRows (r + 1)            // Continue looping

loopRows 0


Here's a liberal translation of your code:

type Matrix(sourceMatrix) =
    let rows = Array2D.length1 sourceMatrix
    let cols = Array2D.length2 sourceMatrix
    let matrix = Array2D.copy sourceMatrix

    ///The number of Rows in this Matrix.
    member this.Rows = rows

    ///The number of Columns in this Matrix.
    member this.Cols = cols

    ///Retrieve data from this Matrix
    member this.Item(x,y) = matrix.[x,y]

    member this.Equals(matrix:Matrix, precision) =
        if precision < 0 then failwith "Precision can't be a negative number."
        let precision = 10.0 ** double(precision)
        if Double.IsInfinity(precision) then failwith ("Precision of " + string(precision) + " decimal places is not supported.")
        seq {
            for r in 0 .. rows - 1 do
                for c in 0 .. cols - 1 do
                    if floor(matrix.[r, c] * precision) <>
                       floor(this.[r, c] * precision) then
                        yield false
        } |> Seq.forall id

Notice how I changed the array method calls to Array2D function calls, which cleaned things up a bit. Then I added an indexer (this.Item) so that you can actually read the data from the matrix.

You will note that I changed the use of conversion to integers with calls to floor, which eliminates much of the need to convert between ints and floats.

The comparison is done inside a lazy sequence as Thomas suggested.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜