// // 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); } } }