You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

Slice.cs 16 kB

4 years ago
4 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Text.RegularExpressions;
  6. namespace Tensorflow
  7. {
  8. /// <summary> <br></br>
  9. /// NDArray can be indexed using slicing <br></br>
  10. /// A slice is constructed by start:stop:step notation <br></br>
  11. /// <br></br>
  12. /// Examples: <br></br>
  13. /// <br></br>
  14. /// a[start:stop] # items start through stop-1 <br></br>
  15. /// a[start:] # items start through the rest of the array <br></br>
  16. /// a[:stop] # items from the beginning through stop-1 <br></br>
  17. /// <br></br>
  18. /// The key point to remember is that the :stop value represents the first value that is not <br></br>
  19. /// in the selected slice. So, the difference between stop and start is the number of elements <br></br>
  20. /// selected (if step is 1, the default). <br></br>
  21. /// <br></br>
  22. /// There is also the step value, which can be used with any of the above: <br></br>
  23. /// a[:] # a copy of the whole array <br></br>
  24. /// a[start:stop:step] # start through not past stop, by step <br></br>
  25. /// <br></br>
  26. /// The other feature is that start or stop may be a negative number, which means it counts <br></br>
  27. /// from the end of the array instead of the beginning. So: <br></br>
  28. /// a[-1] # last item in the array <br></br>
  29. /// a[-2:] # last two items in the array <br></br>
  30. /// a[:-2] # everything except the last two items <br></br>
  31. /// Similarly, step may be a negative number: <br></br>
  32. /// <br></br>
  33. /// a[::- 1] # all items in the array, reversed <br></br>
  34. /// a[1::- 1] # the first two items, reversed <br></br>
  35. /// a[:-3:-1] # the last two items, reversed <br></br>
  36. /// a[-3::- 1] # everything except the last two items, reversed <br></br>
  37. /// <br></br>
  38. /// NumSharp is kind to the programmer if there are fewer items than <br></br>
  39. /// you ask for. For example, if you ask for a[:-2] and a only contains one element, you get an <br></br>
  40. /// empty list instead of an error.Sometimes you would prefer the error, so you have to be aware <br></br>
  41. /// that this may happen. <br></br>
  42. /// <br></br>
  43. /// Adapted from Greg Hewgill's answer on Stackoverflow: https://stackoverflow.com/questions/509211/understanding-slice-notation <br></br>
  44. /// <br></br>
  45. /// Note: special IsIndex == true <br></br>
  46. /// It will pick only a single value at Start in this dimension effectively reducing the Shape of the sliced matrix by 1 dimension. <br></br>
  47. /// It can be used to reduce an N-dimensional array/matrix to a (N-1)-dimensional array/matrix <br></br>
  48. /// <br></br>
  49. /// Example: <br></br>
  50. /// a=[[1, 2], [3, 4]] <br></br>
  51. /// a[:, 1] returns the second column of that 2x2 matrix as a 1-D vector <br></br>
  52. /// </summary>
  53. public class Slice
  54. {
  55. /// <summary>
  56. /// return : for this dimension
  57. /// </summary>
  58. public static readonly Slice All = new Slice(null, null);
  59. /// <summary>
  60. /// return 0:0 for this dimension
  61. /// </summary>
  62. public static readonly Slice None = new Slice(0, 0, 1);
  63. /// <summary>
  64. /// fill up the missing dimensions with : at this point, corresponds to ...
  65. /// </summary>
  66. public static readonly Slice Ellipsis = new Slice(0, 0, 1) { IsEllipsis = true };
  67. /// <summary>
  68. /// insert a new dimension at this point
  69. /// </summary>
  70. public static readonly Slice NewAxis = new Slice(0, 0, 1) { IsNewAxis = true };
  71. /// <summary>
  72. /// return exactly one element at this dimension and reduce the shape from n-dim to (n-1)-dim
  73. /// </summary>
  74. /// <param name="index"></param>
  75. /// <returns></returns>
  76. public static Slice Index(int index) => new Slice(index, index + 1) { IsIndex = true };
  77. ///// <summary>
  78. ///// return multiple elements for this dimension specified by the given index array (or boolean mask array)
  79. ///// </summary>
  80. ///// <param name="index_array_or_mask"></param>
  81. ///// <returns></returns>
  82. //[MethodImpl(MethodImplOptions.AggressiveInlining)]
  83. //public static Slice Select(NDArray index_array_or_mask) => new Slice(null, null) { Selection=index_array_or_mask };
  84. public int? Start;
  85. public int? Stop;
  86. public int Step;
  87. public bool IsIndex;
  88. public bool IsEllipsis;
  89. public bool IsNewAxis;
  90. ///// <summary>
  91. ///// Array of integer indices to select elements by index extraction or boolean values to select by masking the elements of the given dimension.
  92. ///// </summary>
  93. //public NDArray Selection = null;
  94. /// <summary>
  95. /// Length of the slice.
  96. /// <remarks>
  97. /// The length is not guaranteed to be known for i.e. a slice like ":". Make sure to check Start and Stop
  98. /// for null before using it</remarks>
  99. /// </summary>
  100. public int? Length => Stop - Start;
  101. /// <summary>
  102. /// ndarray can be indexed using slicing
  103. /// slice is constructed by start:stop:step notation
  104. /// </summary>
  105. /// <param name="start">Start index of the slice, null means from the start of the array</param>
  106. /// <param name="stop">Stop index (first index after end of slice), null means to the end of the array</param>
  107. /// <param name="step">Optional step to select every n-th element, defaults to 1</param>
  108. public Slice(int? start = null, int? stop = null, int step = 1, bool isIndex = false)
  109. {
  110. Start = start;
  111. Stop = stop;
  112. Step = step;
  113. IsIndex = isIndex;
  114. }
  115. public Slice(string slice_notation)
  116. {
  117. Parse(slice_notation);
  118. }
  119. /// <summary>
  120. /// Parses Python array slice notation and returns an array of Slice objects
  121. /// </summary>
  122. public static Slice[] ParseSlices(string multi_slice_notation)
  123. {
  124. return Regex.Split(multi_slice_notation, @",\s*").Where(s => !string.IsNullOrWhiteSpace(s)).Select(token => new Slice(token)).ToArray();
  125. }
  126. /// <summary>
  127. /// Creates Python array slice notation out of an array of Slice objects (mainly used for tests)
  128. /// </summary>
  129. public static string FormatSlices(params Slice[] slices)
  130. {
  131. return string.Join(",", slices.Select(s => s.ToString()));
  132. }
  133. private void Parse(string slice_notation)
  134. {
  135. if (string.IsNullOrEmpty(slice_notation))
  136. throw new ArgumentException("Slice notation expected, got empty string or null");
  137. var match = Regex.Match(slice_notation, @"^\s*((?'start'[+-]?\s*\d+)?\s*:\s*(?'stop'[+-]?\s*\d+)?\s*(:\s*(?'step'[+-]?\s*\d+)?)?|(?'index'[+-]?\s*\d+)|(?'ellipsis'\.\.\.)|(?'newaxis'(np\.)?newaxis))\s*$");
  138. if (!match.Success)
  139. throw new ArgumentException($"Invalid slice notation: '{slice_notation}'");
  140. if (match.Groups["ellipsis"].Success)
  141. {
  142. Start = 0;
  143. Stop = 0;
  144. Step = 1;
  145. IsEllipsis = true;
  146. return;
  147. }
  148. if (match.Groups["newaxis"].Success)
  149. {
  150. Start = 0;
  151. Stop = 0;
  152. Step = 1;
  153. IsNewAxis = true;
  154. return;
  155. }
  156. if (match.Groups["index"].Success)
  157. {
  158. if (!int.TryParse(Regex.Replace(match.Groups["index"].Value ?? "", @"\s+", ""), out var start))
  159. throw new ArgumentException($"Invalid value for index: '{match.Groups["index"].Value}'");
  160. Start = start;
  161. Stop = start + 1;
  162. Step = 1; // special case for dimensionality reduction by picking a single element
  163. IsIndex = true;
  164. return;
  165. }
  166. var start_string = Regex.Replace(match.Groups["start"].Value ?? "", @"\s+", ""); // removing spaces from match to be able to parse what python allows, like: "+ 1" or "- 9";
  167. var stop_string = Regex.Replace(match.Groups["stop"].Value ?? "", @"\s+", "");
  168. var step_string = Regex.Replace(match.Groups["step"].Value ?? "", @"\s+", "");
  169. if (string.IsNullOrWhiteSpace(start_string))
  170. Start = null;
  171. else
  172. {
  173. if (!int.TryParse(start_string, out var start))
  174. throw new ArgumentException($"Invalid value for start: {start_string}");
  175. Start = start;
  176. }
  177. if (string.IsNullOrWhiteSpace(stop_string))
  178. Stop = null;
  179. else
  180. {
  181. if (!int.TryParse(stop_string, out var stop))
  182. throw new ArgumentException($"Invalid value for start: {stop_string}");
  183. Stop = stop;
  184. }
  185. if (string.IsNullOrWhiteSpace(step_string))
  186. Step = 1;
  187. else
  188. {
  189. if (!int.TryParse(step_string, out var step))
  190. throw new ArgumentException($"Invalid value for start: {step_string}");
  191. Step = step;
  192. }
  193. }
  194. #region Equality comparison
  195. public static bool operator ==(Slice a, Slice b)
  196. {
  197. if (ReferenceEquals(a, b))
  198. return true;
  199. if (a is null || b is null)
  200. return false;
  201. return a.Start == b.Start && a.Stop == b.Stop && a.Step == b.Step;
  202. }
  203. public static bool operator !=(Slice a, Slice b)
  204. {
  205. return !(a == b);
  206. }
  207. public override bool Equals(object obj)
  208. {
  209. if (obj == null)
  210. return false;
  211. if (obj.GetType() != typeof(Slice))
  212. return false;
  213. var b = (Slice)obj;
  214. return Start == b.Start && Stop == b.Stop && Step == b.Step;
  215. }
  216. public override int GetHashCode()
  217. {
  218. return ToString().GetHashCode();
  219. }
  220. #endregion
  221. public override string ToString()
  222. {
  223. if (IsIndex)
  224. return $"{Start ?? 0}";
  225. else if (IsNewAxis)
  226. return "np.newaxis";
  227. else if (IsEllipsis)
  228. return "...";
  229. var optional_step = Step == 1 ? "" : $":{Step}";
  230. return $"{(Start == 0 ? "" : Start.ToString())}:{(Stop == null ? "" : Stop.ToString())}{optional_step}";
  231. }
  232. // return the size of the slice, given the data dimension on this axis
  233. // note: this works only with sanitized shapes!
  234. public int GetSize()
  235. {
  236. var astep = Math.Abs(Step);
  237. return (Math.Abs(Start.Value - Stop.Value) + (astep - 1)) / astep;
  238. }
  239. #region Operators
  240. public static Slice operator ++(Slice a)
  241. {
  242. if (a.Start.HasValue)
  243. a.Start++;
  244. if (a.Stop.HasValue)
  245. a.Stop++;
  246. return a;
  247. }
  248. public static Slice operator --(Slice a)
  249. {
  250. if (a.Start.HasValue)
  251. a.Start--;
  252. if (a.Stop.HasValue)
  253. a.Stop--;
  254. return a;
  255. }
  256. public static implicit operator Slice(int index) => Slice.Index(index);
  257. public static implicit operator Slice(string slice) => new Slice(slice);
  258. //public static implicit operator Slice(NDArray selection) => Slice.Select(selection);
  259. #endregion
  260. }
  261. }