/* * NumSharp * Copyright (C) 2018 Haiping Chen * * This program is free software: you can redistribute it and/or modify * it under the terms of the Apache License 2.0 as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the Apache License 2.0 * along with this program. If not, see . */ using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Text; using System.Globalization; namespace NumSharp { /// /// A powerful N-dimensional array object /// Inspired from https://www.numpy.org/devdocs/user/quickstart.html /// public partial class NDArray { /// /// 1 dim array data storage /// public T[] Data { get; set; } /// /// Data length of every dimension /// public Shape Shape { get; set; } /// /// Dimension count /// public int NDim => Shape.Length; /// /// Total of elements /// public int Size => Data.Length; public NDArray() { // set default shape as 1 dim and 0 elements. Shape = new Shape(new int[] { 0 }); Data = new T[] { }; } /// /// Index accessor /// /// /// public T this[params int[] select] { get { return Data[GetIndexInShape(select)]; } set { Data[GetIndexInShape(select)] = value; } } public NDArray Vector(params int[] select) { if (select.Length == NDim) { throw new Exception("Please use NDArray[m, n] to access element."); } else { int start = GetIndexInShape(select); int length = Shape.DimOffset[select.Length - 1]; var n = new NDArray(); //n.Shape = shape.Skip(select.Length).ToList(); Span data = Data; n.Data = data.Slice(start, length).ToArray(); // Since n.Shape is a IList it cannot be converted to Span // This is a lot of hoops to jump throught to get it into a span // shape.Skip(select.Length).ToList() may be more efficient - not sure n.Shape = new Shape(Shape.Shapes.ToArray().AsSpan().Slice(select.Length).ToArray()); return n; } } public void Vector(Shape shape, T value) { if (shape.Length == NDim) { throw new Exception("Please use NDArray[m, n] to access element."); } else { int start = GetIndexInShape(shape.Shapes.ToArray()); int length = Shape.DimOffset[shape.Length - 1]; Span data = Data; var elements = data.Slice(start, length); for (int i = 0; i < elements.Length; i++) { elements[i] = value; } } } /// /// Filter specific elements through select. /// /// /// Return a new NDArray with filterd elements. public NDArray this[IList select] { get { var n = new NDArray(); if (NDim == 1) { n.Data = new T[select.Count()]; n.Shape = new Shape(select.Count()); for (int i = 0; i < select.Count(); i++) { n[i] = this[select[i]]; } } else if (NDim == 2) { n.Data = new T[select.Count() * Shape[1]]; n.Shape = new Shape(select.Count(), Shape[1]); for (int i = 0; i < select.Count(); i++) { for (int j = 0; j < Shape[1]; j++) { n[i, j] = this[select[i], j]; } } } else { throw new NotImplementedException(); } return n; } } /// /// Overload /// /// /// public NDArray this[NDArray select] => this[select.Data.ToList()]; private int GetIndexInShape(params int[] select) { int idx = 0; for (int i = 0; i < select.Length; i++) { idx += Shape.DimOffset[i] * select[i]; } return idx; } public override string ToString() { string output = ""; if (this.NDim == 2) { output = this._ToMatrixString(); } else { output = "array(["; // loop for (int r = 0; r < Data.Length; r++) { output += (r == 0) ? Data[r] + "" : ", " + Data[r]; } output += "])"; } return output; } public override bool Equals(object obj) { return Data[0].Equals(obj); } public static bool operator ==(NDArray np, object obj) { return np.Data[0].Equals(obj); } public static bool operator !=(NDArray np, object obj) { return np.Data[0].Equals(obj); } public override int GetHashCode() { unchecked { var result = 1337; result = (result * 397) ^ this.NDim; result = (result * 397) ^ this.Size; return result; } } public TCast ToDotNetArray() { dynamic dotNetArray = null; switch (this.NDim) { case 1 : dotNetArray = new T[this.Shape.Shapes[0]].ToArray();break; case 2 : dotNetArray = new T[this.Shape.Shapes[0]][].Select(x => new T[this.Shape.Shapes[1]].ToArray()).ToArray();break; case 3 : dotNetArray = new T[this.Shape.Shapes[0]][][].Select(x => new T[this.Shape.Shapes[1]][].Select(y => new T[this.Shape.Shapes[2]].ToArray().ToArray()).ToArray()).ToArray();break; } switch (this.NDim) { case 1 : { dotNetArray = this.Data.ToArray(); break; } case 2 : { for(int idx = 0; idx < this.Shape.Shapes[0];idx++) { for(int jdx = 0; jdx < this.Shape.Shapes[1];jdx++) { dotNetArray[idx][jdx] = this[idx,jdx]; } } break; } case 3 : { for(int idx = 0; idx < this.Shape.Shapes[0];idx++) { for(int jdx = 0; jdx < this.Shape.Shapes[1];jdx++) { for(int kdx = 0; kdx < this.Shape.Shapes[2];kdx++) { dotNetArray[idx][jdx][kdx] = this[idx,jdx,kdx]; } } } break; } } TCast castedDotNetArray = (TCast)dotNetArray; return castedDotNetArray; } protected string _ToMatrixString() { string returnValue = "array([["; int digitBefore = 0; int digitAfter = 0; var dataParsed = Data.Select(x => _ParseNumber(x,ref digitBefore,ref digitAfter)).ToArray(); string elementFormatStart = "{0:"; string elementFormatEnd = ""; for(int idx = 0; idx < digitAfter;idx++) elementFormatEnd += "0"; elementFormatEnd += "}"; int missingDigits; string elementFormat; for (int idx = 0; idx < (Data.Length-1);idx++) { missingDigits = digitBefore - dataParsed[idx].Replace(" ","").Split('.')[0].Length; elementFormat = elementFormatStart + new string(Enumerable.Repeat(' ',missingDigits).ToArray()) + "0." + elementFormatEnd; if( ((idx+1) % Shape.Shapes[1] ) == 0 ) { returnValue += (String.Format(new CultureInfo("en-us"),elementFormat, Data[idx]) + "], \n ["); } else { returnValue += (String.Format(new CultureInfo("en-us"),elementFormat, Data[idx]) + ", "); } } missingDigits = digitBefore - dataParsed.Last().Replace(" ","").Split('.')[0].Length; elementFormat = elementFormatStart + new string(Enumerable.Repeat(' ',missingDigits).ToArray()) + "." + elementFormatEnd; returnValue += (String.Format(new CultureInfo("en-us"),elementFormat, Data.Last()) + "]])"); return returnValue; } protected string _ParseNumber(T number, ref int noBefore,ref int noAfter) { string parsed = string.Format(new CultureInfo("en-us"),"{0:0.00000000}",number); parsed = (parsed.StartsWith("-")) ? parsed : (" " + parsed); int noBefore_local = parsed.Split('.')[0].Length; int noAfter_local = parsed.Split('.')[1].ToCharArray().Reverse().SkipWhile(x => x == '0').ToArray().Length; noBefore = (noBefore_local > noBefore) ? noBefore_local : noBefore; noAfter = (noAfter_local > noAfter ) ? noAfter_local : noAfter; return parsed; } } }