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.
精彩评论