Browse Source

io: ported delimited reader. made it generic, added support for different cultures, and fixed regex bug

la-knuth
Marcus Cuda 16 years ago
parent
commit
fbb3f2a716
  1. 196
      src/Numerics/LinearAlgebra/Double/IO/DelimitedReader.cs
  2. 83
      src/Numerics/LinearAlgebra/Double/IO/MatrixReader.cs
  3. 6
      src/Numerics/Numerics.csproj
  4. 9
      src/Numerics/Properties/Resources.Designer.cs
  5. 3
      src/Numerics/Properties/Resources.resx
  6. 10
      src/Silverlight/Silverlight.csproj
  7. 121
      src/UnitTests/LinearAlgebraTests/Double/IO/DelimitedReaderTests.cs
  8. 1
      src/UnitTests/UnitTests.csproj

196
src/Numerics/LinearAlgebra/Double/IO/DelimitedReader.cs

@ -0,0 +1,196 @@
// <copyright file="DelimitedReader.cs" company="Math.NET">
// Math.NET Numerics, part of the Math.NET Project
// http://numerics.mathdotnet.com
// http://github.com/mathnet/mathnet-numerics
// http://mathnetnumerics.codeplex.com
// Copyright (c) 2009-2010 Math.NET
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
// </copyright>
namespace MathNet.Numerics.LinearAlgebra.Double.IO
{
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Text.RegularExpressions;
/// <summary>
/// Creates a <see cref="Matrix"/> from a delimited text file. If the user does not
/// specify a delimiter, then any whitespace is used.
/// </summary>
/// <typeparam name="TMatrix">The type of the matrix to return.</typeparam>
public class DelimitedReader<TMatrix> : MatrixReader<TMatrix>
where TMatrix : Matrix
{
/// <summary>
/// Constructor to create matrix instance.
/// </summary>
private static readonly ConstructorInfo Constructor = typeof(TMatrix).GetConstructor(new[] { typeof(int), typeof(int) });
/// <summary>
/// Whitespace regular expression.
/// </summary>
private static readonly Regex WhiteSpace = new Regex(@"[\s]+");
/// <summary>
/// The delimiter to use.
/// </summary>
private readonly Regex _delimiter;
/// <summary>
/// The <see cref="CultureInfo"/> to use.
/// </summary>
private CultureInfo _cultureInfo = CultureInfo.CurrentCulture;
/// <summary>
/// Initializes a new instance of the <see cref="DelimitedReader{TMatrix}"/> class using
/// any whitespace as the delimiter.
/// </summary>
public DelimitedReader()
{
_delimiter = WhiteSpace;
}
/// <summary>
/// Initializes a new instance of the <see cref="DelimitedReader{TMatrix}"/> class.
/// </summary>
/// <param name="delimiter">The delimiter to use.</param>
public DelimitedReader(char delimiter)
{
_delimiter = new Regex("[" + new string(delimiter, 1) + "]+");
}
/// <summary>
/// Initializes a new instance of the <see cref="DelimitedReader{TMatrix}"/> class.
/// </summary>
/// <param name="delimiter">
/// The delimiter to use.
/// </param>
/// <exception cref="ArgumentNullException">
/// If <paramref name="delimiter"/> is <see langword="null"/>.
/// </exception>
public DelimitedReader(string delimiter)
{
if (delimiter == null)
{
throw new ArgumentNullException("delimiter");
}
_delimiter = new Regex("[" + delimiter + "]+");
}
/// <summary>
/// Gets or sets the <see cref="CultureInfo"/> to use when parsing the numbers.
/// </summary>
/// <value>The culture info.</value>
/// <remarks>Defaults to <c>CultureInfo.CurrentCulture</c>.</remarks>
public CultureInfo CultureInfo
{
get
{
return _cultureInfo;
}
set
{
if (value != null)
{
_cultureInfo = value;
}
}
}
/// <summary>
/// Gets or sets a value indicating whether the files has a header row.
/// </summary>
/// <value>
/// <c>true</c> if this instance has a header row; otherwise, <c>false</c>.
/// </value>
/// <remarks>Defaults to <see langword="false"/>.</remarks>
public bool HasHeaderRow
{
get;
set;
}
/// <summary>
/// Performs the actual reading.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to read the matrix from.</param>
/// <returns>
/// A matrix containing the data from the <see cref="Stream"/>. <see langword="null"/> is returned if the <see cref="Stream"/> is empty.
/// </returns>
protected override TMatrix DoReadMatrix(Stream stream)
{
var data = new List<string[]>();
// max is used to supports files like:
// 1,2
// 3,4,5,6
// 7
// this creates a 3x4 matrix:
// 1, 2, 0 ,0
// 3, 4, 5, 6
// 7, 0, 0, 0
var max = -1;
var reader = new StreamReader(stream);
var line = reader.ReadLine();
if (HasHeaderRow)
{
line = reader.ReadLine();
}
while (line != null)
{
line = line.Trim();
if (line.Length > 0)
{
var row = _delimiter.Split(line);
max = Math.Max(max, row.Length);
data.Add(row);
}
line = reader.ReadLine();
}
var ret = (TMatrix)Constructor.Invoke(new object[] { data.Count, max });
if (data.Count != 0)
{
for (var i = 0; i < data.Count; i++)
{
var row = data[i];
for (var j = 0; j < row.Length; j++)
{
// strip off quotes
var value = row[j].Replace("'", string.Empty).Replace("\"", string.Empty);
ret[i, j] = double.Parse(value, NumberStyles.Any, _cultureInfo);
}
}
}
reader.Close();
reader.Dispose();
return ret;
}
}
}

83
src/Numerics/LinearAlgebra/Double/IO/MatrixReader.cs

@ -0,0 +1,83 @@
// <copyright file="MatrixReader.cs" company="Math.NET">
// Math.NET Numerics, part of the Math.NET Project
// http://numerics.mathdotnet.com
// http://github.com/mathnet/mathnet-numerics
// http://mathnetnumerics.codeplex.com
// Copyright (c) 2009-2010 Math.NET
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
// </copyright>
namespace MathNet.Numerics.LinearAlgebra.Double.IO
{
using System;
using System.IO;
using Properties;
/// <summary>
/// Base class to read a single <see cref="Matrix"/> from a file or stream.
/// </summary>
/// <typeparam name="TMatrix">The type of Matrix to return.</typeparam>
public abstract class MatrixReader<TMatrix> where TMatrix : Matrix
{
/// <summary>
/// Reads a <see cref="Matrix"/> from a file.
/// </summary>
/// <param name="file">The file to read the matrix from.</param>
/// <returns>A <see cref="Matrix"/> containing the data from the file. <see langword="null" /> is returned if the file is empty.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="file"/> is <see langword="null" />.</exception>
/// <exception cref="IOException">If the file doesn't exist.</exception>
/// <exception cref="FormatException">If a value is not a number or not in a valid format.</exception>
/// <exception cref="OverflowException">If a value represents a number less than <see cref="Double.MinValue"/> or greater than <see cref="Double.MaxValue"/>.</exception>
public Matrix ReadMatrix(string file)
{
if (file == null)
{
throw new ArgumentNullException("file", Resources.StringNullOrEmpty);
}
return ReadMatrix(File.OpenRead(file));
}
/// <summary>
/// Reads a <see cref="Matrix"/> from a <see cref="Stream"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to read the matrix from.</param>
/// <returns>A matrix containing the data from the <see cref="Stream"/>. <see langword="null" /> is returned if the <see cref="Stream"/> is empty.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="stream"/> is <see langword="null" />.</exception>
/// <exception cref="FormatException">If a value is not a number or not in a valid format.</exception>
/// <exception cref="OverflowException">If a value represents a number less than <see cref="Double.MinValue"/> or greater than <see cref="Double.MaxValue"/>.</exception>
public TMatrix ReadMatrix(Stream stream)
{
if (stream == null)
{
throw new ArgumentNullException("stream");
}
return DoReadMatrix(stream);
}
/// <summary>
/// Subclasses override this method to do the actual reading.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to read the matrix from.</param>
/// <returns>A matrix containing the data from the <see cref="Stream"/>. <see langword="null" /> is returned if the <see cref="Stream"/> is empty.</returns>
protected abstract TMatrix DoReadMatrix(Stream stream);
}
}

6
src/Numerics/Numerics.csproj

@ -107,6 +107,8 @@
<Compile Include="LinearAlgebra\Double\Factorization\UserLU.cs" />
<Compile Include="LinearAlgebra\Double\Factorization\UserQR.cs" />
<Compile Include="LinearAlgebra\Double\Factorization\UserSvd.cs" />
<Compile Include="LinearAlgebra\Double\IO\DelimitedReader.cs" />
<Compile Include="LinearAlgebra\Double\IO\MatrixReader.cs" />
<Compile Include="LinearAlgebra\Double\ISolver.cs" />
<Compile Include="LinearAlgebra\Double\Factorization\QR.cs" />
<Compile Include="LinearAlgebra\Double\Factorization\Svd.cs" />
@ -264,9 +266,7 @@
<Install>true</Install>
</BootstrapperPackage>
</ItemGroup>
<ItemGroup>
<Folder Include="LinearAlgebra\Double\IO\" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.

9
src/Numerics/Properties/Resources.Designer.cs

@ -618,6 +618,15 @@ namespace MathNet.Numerics.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to String parameter cannot be empty or null..
/// </summary>
internal static string StringNullOrEmpty {
get {
return ResourceManager.GetString("StringNullOrEmpty", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The moment of the distribution is undefined..
/// </summary>

3
src/Numerics/Properties/Resources.resx

@ -312,4 +312,7 @@
<data name="WorkArrayTooSmall" xml:space="preserve">
<value>The given work array is too small. Check work[0] for the corret size.</value>
</data>
<data name="StringNullOrEmpty" xml:space="preserve">
<value>String parameter cannot be empty or null.</value>
</data>
</root>

10
src/Silverlight/Silverlight.csproj

@ -260,6 +260,12 @@
<Compile Include="..\Numerics\LinearAlgebra\Double\Factorization\UserSvd.cs">
<Link>LinearAlgebra\Double\Factorization\UserSvd.cs</Link>
</Compile>
<Compile Include="..\Numerics\LinearAlgebra\Double\IO\DelimitedReader.cs">
<Link>LinearAlgebra\Double\IO\DelimitedReader.cs</Link>
</Compile>
<Compile Include="..\Numerics\LinearAlgebra\Double\IO\MatrixReader.cs">
<Link>LinearAlgebra\Double\IO\MatrixReader.cs</Link>
</Compile>
<Compile Include="..\Numerics\LinearAlgebra\Double\ISolver.cs">
<Link>LinearAlgebra\Double\ISolver.cs</Link>
</Compile>
@ -408,9 +414,7 @@
<LastGenOutput>Version.cs</LastGenOutput>
</None>
</ItemGroup>
<ItemGroup>
<Folder Include="LinearAlgebra\Double\IO\" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Silverlight\$(SilverlightVersion)\Microsoft.Silverlight.CSharp.targets" />
<ProjectExtensions>
<VisualStudio>

121
src/UnitTests/LinearAlgebraTests/Double/IO/DelimitedReaderTests.cs

@ -0,0 +1,121 @@
namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.IO
{
using System;
using System.Globalization;
using System.IO;
using System.Text;
using LinearAlgebra.Double;
using LinearAlgebra.Double.IO;
using MbUnit.Framework;
[TestFixture]
public class DelimitedReaderTests
{
[Test]
[MultipleAsserts]
public void CanParseCommaDelimitedData()
{
const string data =
@"a,b,c
1
""2.2"",0.3e1
'4',5,6
";
var reader = new DelimitedReader<DenseMatrix>(',')
{
HasHeaderRow = true
};
var matrix = reader.ReadMatrix(new MemoryStream(Encoding.UTF8.GetBytes(data)));
Assert.AreEqual(3, matrix.RowCount);
Assert.AreEqual(3, matrix.ColumnCount);
Assert.AreEqual(1.0, matrix[0, 0]);
Assert.AreEqual(0.0, matrix[0, 1]);
Assert.AreEqual(0.0, matrix[0, 2]);
Assert.AreEqual(2.2, matrix[1, 0]);
Assert.AreEqual(3.0, matrix[1, 1]);
Assert.AreEqual(0.0, matrix[1, 2]);
Assert.AreEqual(4.0, matrix[2, 0]);
Assert.AreEqual(5.0, matrix[2, 1]);
Assert.AreEqual(6.0, matrix[2, 2]);
}
[Test]
[MultipleAsserts]
public void CanParseTabDelimtedData()
{
var data = "1"
+ Environment.NewLine
+ "\"2.2\"\t\t0.3e1"
+ Environment.NewLine
+ "'4'\t5\t6";
var reader = new DelimitedReader<DenseMatrix>('\t');
var matrix = reader.ReadMatrix(new MemoryStream(Encoding.UTF8.GetBytes(data)));
Assert.AreEqual(3, matrix.RowCount);
Assert.AreEqual(3, matrix.ColumnCount);
Assert.AreEqual(1.0, matrix[0, 0]);
Assert.AreEqual(0.0, matrix[0, 1]);
Assert.AreEqual(0.0, matrix[0, 2]);
Assert.AreEqual(2.2, matrix[1, 0]);
Assert.AreEqual(3.0, matrix[1, 1]);
Assert.AreEqual(0.0, matrix[1, 2]);
Assert.AreEqual(4.0, matrix[2, 0]);
Assert.AreEqual(5.0, matrix[2, 1]);
Assert.AreEqual(6.0, matrix[2, 2]);
}
[Test]
[MultipleAsserts]
public void CanParseWhiteSpaceDelimitedData()
{
const string data =
@"1
""2.2"" 0.3e1
'4' 5 6
";
var reader = new DelimitedReader<DenseMatrix>();
var matrix = reader.ReadMatrix(new MemoryStream(Encoding.UTF8.GetBytes(data)));
Assert.AreEqual(3, matrix.RowCount);
Assert.AreEqual(3, matrix.ColumnCount);
Assert.AreEqual(1.0, matrix[0, 0]);
Assert.AreEqual(0.0, matrix[0, 1]);
Assert.AreEqual(0.0, matrix[0, 2]);
Assert.AreEqual(2.2, matrix[1, 0]);
Assert.AreEqual(3.0, matrix[1, 1]);
Assert.AreEqual(0.0, matrix[1, 2]);
Assert.AreEqual(4.0, matrix[2, 0]);
Assert.AreEqual(5.0, matrix[2, 1]);
Assert.AreEqual(6.0, matrix[2, 2]);
}
[Test]
[MultipleAsserts]
public void CanParsePeriodDelimitedData()
{
const string data =
@"a.b.c
1
""2,2"".0,3e1
'4,0'.5,0.6,0
";
var reader = new DelimitedReader<DenseMatrix>('.')
{
HasHeaderRow = true,
CultureInfo = new CultureInfo("tr-TR")
};
var matrix = reader.ReadMatrix(new MemoryStream(Encoding.UTF8.GetBytes(data)));
Assert.AreEqual(3, matrix.RowCount);
Assert.AreEqual(3, matrix.ColumnCount);
Assert.AreEqual(1.0, matrix[0, 0]);
Assert.AreEqual(0.0, matrix[0, 1]);
Assert.AreEqual(0.0, matrix[0, 2]);
Assert.AreEqual(2.2, matrix[1, 0]);
Assert.AreEqual(3.0, matrix[1, 1]);
Assert.AreEqual(0.0, matrix[1, 2]);
Assert.AreEqual(4.0, matrix[2, 0]);
Assert.AreEqual(5.0, matrix[2, 1]);
Assert.AreEqual(6.0, matrix[2, 2]);
}
}
}

1
src/UnitTests/UnitTests.csproj

@ -96,6 +96,7 @@
<Compile Include="LinearAlgebraTests\Double\Factorization\UserSvdTests.cs" />
<Compile Include="LinearAlgebraTests\Double\Factorization\SvdTests.cs" />
<Compile Include="LinearAlgebraTests\Double\Factorization\QRTests.cs" />
<Compile Include="LinearAlgebraTests\Double\IO\DelimitedReaderTests.cs" />
<Compile Include="LinearAlgebraTests\Double\SparseMatrixTests.cs" />
<Compile Include="PermutationTest.cs" />
<Compile Include="DistributionTests\CommonDistributionTests.cs" />

Loading…
Cancel
Save