X Tutup
using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; // ReSharper disable once CheckNamespace namespace NpgsqlTypes; /// /// Represents a PostgreSQL cube data type. /// /// /// See https://www.postgresql.org/docs/current/cube.html /// public readonly struct NpgsqlCube : IEquatable { // Store the coordinates as a value tuple array readonly double[] _lowerLeft; readonly double[] _upperRight; /// /// The lower left coordinates of the cube. /// public IReadOnlyList LowerLeft => _lowerLeft; /// /// The upper right coordinates of the cube. /// public IReadOnlyList UpperRight => _upperRight; /// /// The number of dimensions of the cube. /// public int Dimensions => _lowerLeft.Length; /// /// True if the cube is a point, that is, the two defining corners are the same. /// public bool IsPoint { get; } /// /// Makes a cube with upper right and lower left coordinates as defined by the two arrays, which must be of the same length. /// /// This is an internal constructor to optimize the number of allocations. /// The lower left values. /// The upper right values. /// /// Thrown if the number of dimensions in the upper left and lower right values do not match. /// internal NpgsqlCube(double[] lowerLeft, double[] upperRight) { if (lowerLeft.Length != upperRight.Length) throw new ArgumentException($"Not a valid cube: Different point dimensions in {lowerLeft} and {upperRight}."); IsPoint = lowerLeft.SequenceEqual(upperRight); _lowerLeft = lowerLeft; _upperRight = upperRight; } /// /// Makes a one dimensional cube with both coordinates the same. /// /// The point coordinate. public NpgsqlCube(double coord) { IsPoint = true; _lowerLeft = [coord]; _upperRight = _lowerLeft; } /// /// Makes a one dimensional cube. /// /// The lower left value. /// The upper right value. public NpgsqlCube(double lowerLeft, double upperRight) { IsPoint = lowerLeft.CompareTo(upperRight) == 0; _lowerLeft = [lowerLeft]; _upperRight = IsPoint ? _lowerLeft : [upperRight]; } /// /// Makes a zero-volume cube using the coordinates defined by the array. /// /// The coordinates. public NpgsqlCube(IEnumerable coords) { // Always create a defensive copy to prevent external mutation _lowerLeft = coords.ToArray(); IsPoint = true; _upperRight = _lowerLeft; } /// /// Makes a cube with upper right and lower left coordinates as defined by the two arrays, which must be of the same length. /// /// The lower left values. /// The upper right values. /// /// Thrown if the number of dimensions in the upper left and lower right values do not match /// or if the cube exceeds the maximum dimensions (100). /// public NpgsqlCube(IEnumerable lowerLeft, IEnumerable upperRight) : this(lowerLeft.ToArray(), upperRight.ToArray()) { } /// /// Makes a new cube by adding a dimension on to an existing cube, with the same values for both endpoints of the new coordinate. /// This is useful for building cubes piece by piece from calculated values. /// /// The existing cube. /// The coordinate to add. public NpgsqlCube(NpgsqlCube cube, double coord) { IsPoint = cube.IsPoint; if (IsPoint) { _lowerLeft = cube._lowerLeft.Append(coord).ToArray(); _upperRight = _lowerLeft; } else { _lowerLeft = cube._lowerLeft.Append(coord).ToArray(); _upperRight = cube._upperRight.Append(coord).ToArray(); } } /// /// Makes a new cube by adding a dimension on to an existing cube. /// This is useful for building cubes piece by piece from calculated values. /// /// The existing cube. /// The lower left value. /// The upper right value. public NpgsqlCube(NpgsqlCube cube, double lowerLeft, double upperRight) { IsPoint = cube.IsPoint && lowerLeft.CompareTo(upperRight) == 0; if (IsPoint) { _lowerLeft = cube._lowerLeft.Append(lowerLeft).ToArray(); _upperRight = _lowerLeft; } else { _lowerLeft = cube._lowerLeft.Append(lowerLeft).ToArray(); _upperRight = cube._upperRight.Append(upperRight).ToArray(); } } /// /// Makes a new cube from an existing cube, using a list of dimension indexes from an array. /// Can be used to extract the endpoints of a single dimension, or to drop dimensions, or to reorder them as desired. /// /// The list of dimension indexes. /// A new cube. /// /// /// var cube = new NpgsqlCube(new[] { 1, 3, 5 }, new[] { 6, 7, 8 }); // '(1,3,5),(6,7,8)' /// cube.ToSubset(1); // '(3),(7)' /// cube.ToSubset(2, 1, 0, 0); // '(5,3,1,1),(8,7,6,6)' /// /// public NpgsqlCube ToSubset(params int[] indexes) { var lowerLeft = new double[indexes.Length]; var upperRight = new double[indexes.Length]; for (var i = 0; i < indexes.Length; i++) { lowerLeft[i] = _lowerLeft[indexes[i]]; upperRight[i] = _upperRight[indexes[i]]; } return new NpgsqlCube(lowerLeft, upperRight); } /// public bool Equals(NpgsqlCube other) => Dimensions == other.Dimensions && _lowerLeft.SequenceEqual(other._lowerLeft) && _upperRight.SequenceEqual(other._upperRight); /// public override bool Equals(object? obj) => obj is NpgsqlCube other && Equals(other); /// public static bool operator ==(NpgsqlCube x, NpgsqlCube y) => x.Equals(y); /// public static bool operator !=(NpgsqlCube x, NpgsqlCube y) => !(x == y); /// public override int GetHashCode() { var hashCode = new HashCode(); for (var i = 0; i < Dimensions; i++) { hashCode.Add(_lowerLeft[i]); hashCode.Add(_upperRight[i]); } return hashCode.ToHashCode(); } /// /// Writes the cube in PostgreSQL's text format. /// void Write(StringBuilder stringBuilder) { var leftBuilder = new StringBuilder(); var rightBuilder = new StringBuilder(); leftBuilder.Append('('); rightBuilder.Append('('); for (var i = 0; i < Dimensions; i++) { leftBuilder.Append(CultureInfo.InvariantCulture, $"{_lowerLeft[i]:G17}"); rightBuilder.Append(CultureInfo.InvariantCulture, $"{_upperRight[i]:G17}"); if (i >= Dimensions - 1) continue; leftBuilder.Append(", "); rightBuilder.Append(", "); } leftBuilder.Append(')'); rightBuilder.Append(')'); if (IsPoint) { stringBuilder.Append(leftBuilder); } else { stringBuilder.Append(leftBuilder); stringBuilder.Append(','); stringBuilder.Append(rightBuilder); } } /// /// Writes the cube in PostgreSQL's text format. /// public override string ToString() { var sb = new StringBuilder(); Write(sb); return sb.ToString(); } }
X Tutup