using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace Tensorflow
{
///
/// NDArray can be indexed using slicing
/// A slice is constructed by start:stop:step notation
///
/// Examples:
///
/// a[start:stop] # items start through stop-1
/// a[start:] # items start through the rest of the array
/// a[:stop] # items from the beginning through stop-1
///
/// The key point to remember is that the :stop value represents the first value that is not
/// in the selected slice. So, the difference between stop and start is the number of elements
/// selected (if step is 1, the default).
///
/// There is also the step value, which can be used with any of the above:
/// a[:] # a copy of the whole array
/// a[start:stop:step] # start through not past stop, by step
///
/// The other feature is that start or stop may be a negative number, which means it counts
/// from the end of the array instead of the beginning. So:
/// a[-1] # last item in the array
/// a[-2:] # last two items in the array
/// a[:-2] # everything except the last two items
/// Similarly, step may be a negative number:
///
/// a[::- 1] # all items in the array, reversed
/// a[1::- 1] # the first two items, reversed
/// a[:-3:-1] # the last two items, reversed
/// a[-3::- 1] # everything except the last two items, reversed
///
/// NumSharp is kind to the programmer if there are fewer items than
/// you ask for. For example, if you ask for a[:-2] and a only contains one element, you get an
/// empty list instead of an error.Sometimes you would prefer the error, so you have to be aware
/// that this may happen.
///
/// Adapted from Greg Hewgill's answer on Stackoverflow: https://stackoverflow.com/questions/509211/understanding-slice-notation
///
/// Note: special IsIndex == true
/// It will pick only a single value at Start in this dimension effectively reducing the Shape of the sliced matrix by 1 dimension.
/// It can be used to reduce an N-dimensional array/matrix to a (N-1)-dimensional array/matrix
///
/// Example:
/// a=[[1, 2], [3, 4]]
/// a[:, 1] returns the second column of that 2x2 matrix as a 1-D vector
///
public class Slice
{
///
/// return : for this dimension
///
public static readonly Slice All = new Slice(null, null);
///
/// return 0:0 for this dimension
///
public static readonly Slice None = new Slice(0, 0, 1);
///
/// fill up the missing dimensions with : at this point, corresponds to ...
///
public static readonly Slice Ellipsis = new Slice(0, 0, 1) { IsEllipsis = true };
///
/// insert a new dimension at this point
///
public static readonly Slice NewAxis = new Slice(0, 0, 1) { IsNewAxis = true };
///
/// return exactly one element at this dimension and reduce the shape from n-dim to (n-1)-dim
///
///
///
public static Slice Index(int index) => new Slice(index, index + 1) { IsIndex = true };
/////
///// return multiple elements for this dimension specified by the given index array (or boolean mask array)
/////
/////
/////
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
//public static Slice Select(NDArray index_array_or_mask) => new Slice(null, null) { Selection=index_array_or_mask };
public int? Start;
public int? Stop;
public int Step;
public bool IsIndex;
public bool IsEllipsis;
public bool IsNewAxis;
/////
///// Array of integer indices to select elements by index extraction or boolean values to select by masking the elements of the given dimension.
/////
//public NDArray Selection = null;
///
/// Length of the slice.
///
/// The length is not guaranteed to be known for i.e. a slice like ":". Make sure to check Start and Stop
/// for null before using it
///
public int? Length => Stop - Start;
///
/// ndarray can be indexed using slicing
/// slice is constructed by start:stop:step notation
///
/// Start index of the slice, null means from the start of the array
/// Stop index (first index after end of slice), null means to the end of the array
/// Optional step to select every n-th element, defaults to 1
public Slice(int? start = null, int? stop = null, int step = 1, bool isIndex = false)
{
Start = start;
Stop = stop;
Step = step;
IsIndex = isIndex;
}
public Slice(string slice_notation)
{
Parse(slice_notation);
}
///
/// Parses Python array slice notation and returns an array of Slice objects
///
public static Slice[] ParseSlices(string multi_slice_notation)
{
return Regex.Split(multi_slice_notation, @",\s*").Where(s => !string.IsNullOrWhiteSpace(s)).Select(token => new Slice(token)).ToArray();
}
///
/// Creates Python array slice notation out of an array of Slice objects (mainly used for tests)
///
public static string FormatSlices(params Slice[] slices)
{
return string.Join(",", slices.Select(s => s.ToString()));
}
private void Parse(string slice_notation)
{
if (string.IsNullOrEmpty(slice_notation))
throw new ArgumentException("Slice notation expected, got empty string or null");
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*$");
if (!match.Success)
throw new ArgumentException($"Invalid slice notation: '{slice_notation}'");
if (match.Groups["ellipsis"].Success)
{
Start = 0;
Stop = 0;
Step = 1;
IsEllipsis = true;
return;
}
if (match.Groups["newaxis"].Success)
{
Start = 0;
Stop = 0;
Step = 1;
IsNewAxis = true;
return;
}
if (match.Groups["index"].Success)
{
if (!int.TryParse(Regex.Replace(match.Groups["index"].Value ?? "", @"\s+", ""), out var start))
throw new ArgumentException($"Invalid value for index: '{match.Groups["index"].Value}'");
Start = start;
Stop = start + 1;
Step = 1; // special case for dimensionality reduction by picking a single element
IsIndex = true;
return;
}
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";
var stop_string = Regex.Replace(match.Groups["stop"].Value ?? "", @"\s+", "");
var step_string = Regex.Replace(match.Groups["step"].Value ?? "", @"\s+", "");
if (string.IsNullOrWhiteSpace(start_string))
Start = null;
else
{
if (!int.TryParse(start_string, out var start))
throw new ArgumentException($"Invalid value for start: {start_string}");
Start = start;
}
if (string.IsNullOrWhiteSpace(stop_string))
Stop = null;
else
{
if (!int.TryParse(stop_string, out var stop))
throw new ArgumentException($"Invalid value for start: {stop_string}");
Stop = stop;
}
if (string.IsNullOrWhiteSpace(step_string))
Step = 1;
else
{
if (!int.TryParse(step_string, out var step))
throw new ArgumentException($"Invalid value for start: {step_string}");
Step = step;
}
}
#region Equality comparison
public static bool operator ==(Slice a, Slice b)
{
if (ReferenceEquals(a, b))
return true;
if (a is null || b is null)
return false;
return a.Start == b.Start && a.Stop == b.Stop && a.Step == b.Step;
}
public static bool operator !=(Slice a, Slice b)
{
return !(a == b);
}
public override bool Equals(object obj)
{
if (obj == null)
return false;
if (obj.GetType() != typeof(Slice))
return false;
var b = (Slice)obj;
return Start == b.Start && Stop == b.Stop && Step == b.Step;
}
public override int GetHashCode()
{
return ToString().GetHashCode();
}
#endregion
public override string ToString()
{
if (IsIndex)
return $"{Start ?? 0}";
else if (IsNewAxis)
return "np.newaxis";
else if (IsEllipsis)
return "...";
var optional_step = Step == 1 ? "" : $":{Step}";
return $"{(Start == 0 ? "" : Start.ToString())}:{(Stop == null ? "" : Stop.ToString())}{optional_step}";
}
// return the size of the slice, given the data dimension on this axis
// note: this works only with sanitized shapes!
public int GetSize()
{
var astep = Math.Abs(Step);
return (Math.Abs(Start.Value - Stop.Value) + (astep - 1)) / astep;
}
#region Operators
public static Slice operator ++(Slice a)
{
if (a.Start.HasValue)
a.Start++;
if (a.Stop.HasValue)
a.Stop++;
return a;
}
public static Slice operator --(Slice a)
{
if (a.Start.HasValue)
a.Start--;
if (a.Stop.HasValue)
a.Stop--;
return a;
}
public static implicit operator Slice(int index) => Slice.Index(index);
public static implicit operator Slice(string slice) => new Slice(slice);
//public static implicit operator Slice(NDArray selection) => Slice.Select(selection);
#endregion
}
}