//
// 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-2015 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.
//
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Text.RegularExpressions;
using MathNet.Numerics.LinearAlgebra;
namespace MathNet.Numerics.Data.Text
{
///
/// Creates a from a delimited text file. If the user does not
/// specify a delimiter, then any whitespace is used.
///
public static class DelimitedReader
{
///
/// The base regular expression.
///
const string RegexTemplate = "\\([^\\)]*\\)|'[^']*'|\"[^\"]*\"|^{0}{{1,}}|{0}$|{0}{{2,}}|[^{0}]*";
///
/// Regular expression to match whitespace.
///
const string WhiteSpaceRegexTemplate = "\\([^\\)]*\\)|'[^']*'|\"[^\"]*\"|[^\\s]*";
///
/// Cached compiled regular expressions for various delimiters, as needed.
///
static readonly ConcurrentDictionary RegexCache = new ConcurrentDictionary();
///
/// Reads a from the given .
///
/// The to read the matrix from.
/// Whether the returned matrix should be constructed as sparse (true) or dense (false). Default: false.
/// Number delimiter between numbers of the same line. Supports Regex groups. Default: "\s" (white space).
/// Whether the first row contains column headers or not. Default: false.
/// The culture to use. Default: null.
/// The value to represent missing values. Default: NaN.
/// A matrix containing the data from the .
/// The data type of the Matrix. It can be either: double, float, Complex, or Complex32.
public static Matrix Read(TextReader reader, bool sparse = false, string delimiter = @"\s", bool hasHeaders = false, IFormatProvider formatProvider = null, T? missingValue = null)
where T : struct, IEquatable, IFormattable
{
delimiter = CleanDelimiter(delimiter);
var mv = missingValue ?? (sparse ? Zero() : NaN());
var mvStr = mv.ToString("G", formatProvider);
var regex = delimiter == @"\s" ?
RegexCache.GetOrAdd(delimiter, d => new Regex(WhiteSpaceRegexTemplate, RegexOptions.Compiled)) :
RegexCache.GetOrAdd(delimiter, d => new Regex(string.Format(RegexTemplate, d), RegexOptions.Compiled));
var parse = CreateParser(formatProvider);
var data = new List();
// max is used to supports files like:
// 1,2
// 3,4,5,6
// 7
// this creates a 3x4 matrix:
// 1, 2, MISSING, MISSING
// 3, 4, 5, 6
// 7, MISSING, MISSING, MISSING
var max = -1;
var line = reader.ReadLine();
if (hasHeaders)
{
line = reader.ReadLine();
}
while (line != null)
{
line = line.Trim();
if (line.Length > 0)
{
var matches = regex.Matches(line);
if (delimiter == @"\s")
{
var row = (from Match match in matches where match.Length > 0 select parse(match.Value.StripOffQuotes())).ToArray();
max = Math.Max(max, row.Length);
data.Add(row);
}
else
{
var offset = 0;
var row = new List();
foreach (var value in (from Match match in matches where match.Length > 0 select match.Value))
{
var delimterCount = value.StartsWith(delimiter) ? value.CountDelimiters(delimiter) : 0;
if (delimterCount > 0)
{
for (var i = offset; i < delimterCount; i++)
{
row.Add(parse(mvStr.StripOffQuotes()));
}
}
else
{
row.Add(parse((string.IsNullOrWhiteSpace(value) ? mvStr : value).StripOffQuotes()));
}
offset = 1;
}
max = Math.Max(max, row.Count);
data.Add(row.ToArray());
}
}
line = reader.ReadLine();
}
var matrix = sparse ? Matrix.Build.Sparse(data.Count, max, mv) : Matrix.Build.Dense(data.Count, max, mv);
var storage = matrix.Storage;
for (var i = 0; i < data.Count; i++)
{
var row = data[i];
for (var j = 0; j < row.Length; j++)
{
var value = row[j];
storage.At(i, j, value);
}
}
#if !NETSTANDARD1_3
reader.Close();
#endif
reader.Dispose();
return matrix;
}
///
/// Reads a from the given file.
///
/// The path and name of the file to read the matrix from.
/// Whether the returned matrix should be constructed as sparse (true) or dense (false). Default: false.
/// Number delimiter between numbers of the same line. Supports Regex groups. Default: "\s" (white space).
/// Whether the first row contains column headers or not. Default: false.
/// The culture to use. Default: null.
/// The value to represent missing values. Default: NaN.
/// A matrix containing the data from the .
/// The data type of the Matrix. It can be either: double, float, Complex, or Complex32.
public static Matrix Read(string filePath, bool sparse = false, string delimiter = @"\s", bool hasHeaders = false, IFormatProvider formatProvider = null, T? missingValue = null)
where T : struct, IEquatable, IFormattable
{
using (var stream = File.OpenRead(filePath))
using (var reader = new StreamReader(stream))
{
return Read(reader, sparse, delimiter, hasHeaders, formatProvider, missingValue);
}
}
///
/// Reads a from the given .
///
/// The to read the matrix from.
/// Whether the returned matrix should be constructed as sparse (true) or dense (false). Default: false.
/// Number delimiter between numbers of the same line. Supports Regex groups. Default: "\s" (white space).
/// Whether the first row contains column headers or not. Default: false.
/// The culture to use. Default: null.
/// The value to represent missing values. Default: NaN.
/// A matrix containing the data from the .
/// The data type of the Matrix. It can be either: double, float, Complex, or Complex32.
public static Matrix Read(Stream stream, bool sparse = false, string delimiter = @"\s", bool hasHeaders = false, IFormatProvider formatProvider = null, T? missingValue = null)
where T : struct, IEquatable, IFormattable
{
using (var reader = new StreamReader(stream))
{
return Read(reader, sparse, delimiter, hasHeaders, formatProvider, missingValue);
}
}
///
/// NaN for the given numeric type.
///
static T NaN()
{
if (typeof (T) == typeof (double))
{
return (T)(object)double.NaN;
}
if (typeof (T) == typeof (float))
{
return (T)(object)float.NaN;
}
if (typeof (T) == typeof (Complex))
{
return (T)(object)new Complex(double.NaN, double.NaN);
}
if (typeof (T) == typeof (Complex32))
{
return (T)(object)new Complex32(float.NaN, float.NaN);
}
throw new NotSupportedException();
}
///
/// Zero for the given numeric type.
///
static T Zero() where T : struct
{
return default(T);
}
static Func CreateParser(IFormatProvider formatProvider)
{
if (typeof (T) == typeof (double))
{
return number => (T)(object)double.Parse(number, NumberStyles.Any, formatProvider);
}
if (typeof (T) == typeof (float))
{
return number => (T)(object)float.Parse(number, NumberStyles.Any, formatProvider);
}
if (typeof (T) == typeof (Complex))
{
return number => (T)(object)number.ToComplex(formatProvider);
}
if (typeof (T) == typeof (Complex32))
{
return number => (T)(object)number.ToComplex32(formatProvider);
}
throw new NotSupportedException();
}
///
/// Counts the number of delimiters in a row.
///
static int CountDelimiters(this string obj, string demlimiter)
{
var pos = 0;
var count = 0;
while ((pos = obj.IndexOf(demlimiter, pos, StringComparison.OrdinalIgnoreCase)) != -1)
{
count++;
pos++;
}
return count;
}
///
/// Returns a regex friendly delimiter.
///
static string CleanDelimiter(string delimiter)
{
if (string.IsNullOrEmpty(delimiter))
{
return @"\s";
}
switch (delimiter)
{
case ".":
return "\\.";
case "\\":
return "\\\\";
default:
return delimiter;
}
}
///
/// strip off quotes (TODO: can we replace this with trimming?)
///
static string StripOffQuotes(this string obj)
{
return obj.Replace("'", string.Empty).Replace("\"", string.Empty);
}
}
}