private static IEnumerable <BigIntRange> EnumerateIncludedRanges(IEnumerable <BigIntRangeBound> sortedBounds)
        {
            BigInteger?prevBound = null;              //only non-null when the previous bound is incomplete and needs the next bound before anything can be returned

            foreach (var bound in sortedBounds)
            {
                if (bound.Direction == Direction.Nowhere)
                {
                    if (prevBound != null)
                    {
                        throw new InvalidOperationException("Underlying sorted set of range bounds is corrupt - previous bound (at value " + prevBound.Value + ") expected a matching closing bound next but current bound (at value " + bound.Bound + ") represents a single point.");
                    }
                    yield return(BigIntRange.Single(bound.Bound));
                }
                else if (bound.Direction == Direction.Up)
                {
                    if (prevBound != null)
                    {
                        throw new InvalidOperationException("Underlying sorted set of range bounds is corrupt - previous bound (at value " + prevBound.Value + ") expected a matching closing bound next but current bound (at value " + bound.Bound + ") represents the start of a new range.");
                    }
                    prevBound = bound.Bound;
                }
                else
                {
                    if (prevBound == null)
                    {
                        throw new InvalidOperationException("Underlying sorted set of range bounds is corrupt - current bound (at value " + bound.Bound + ") represents the end of a range, but the previous bound was a complete range or there was no previous bound");
                    }
                    yield return(BigIntRange.CreateStartEnd(prevBound.Value, bound.Bound));

                    prevBound = null;
                }
            }
        }
 public IEnumerable <BigInteger> EnumerateIncludedValues(bool reverse = false)
 {
     if (_sortedSet.Count == 0)
     {
         return(Enumerable.Empty <BigInteger>());
     }
     else
     {
         return(EnumerateIncludedValues(BigIntRange.CreateStartEnd(this.Min.Value, this.Max.Value), reverse));
     }
 }
        public void RemoveRange(BigIntRange range)
        {
            SortedSet <BigIntRangeBound> nearbyBounds = GetOverlappingOrAdjacentBounds(range.Start, range.End);

            if (nearbyBounds.Count == 0)
            {
                //There's no bounds nearby, but there might still be some larger range
                //that includes the range to be removed. To check for this, check if
                //some value in the new range is already included in the RangeSet.
                if (this.Includes(range.Start))
                {
                    AddBound(range.Start - 1, Direction.Down);
                    AddBound(range.End + 1, Direction.Up);
                }
            }
            else
            {
                var minNearby = nearbyBounds.Min;
                var maxNearby = nearbyBounds.Max;

                RemoveSubset(nearbyBounds);

                if (minNearby.Direction == Direction.Down)
                {
                    //If lowest existing pointed down (ie. joins onto a lower range),
                    //then add a new bound outside the removed range, pointing down
                    AddBound(range.Start - 1, Direction.Down);
                }
                else if (minNearby.Bound == range.Start - 1)
                {
                    //If the lowest existing bound is exactly one below the removed range (and doesn't point down),
                    //then removing the range cuts its range down to a single point, so add that point
                    AddBound(range.Start - 1, Direction.Nowhere);
                }

                //Vice versa for maximum
                if (maxNearby.Direction == Direction.Up)
                {
                    AddBound(range.End + 1, Direction.Up);
                }
                else if (maxNearby.Bound == range.End + 1)
                {
                    AddBound(range.End + 1, Direction.Nowhere);
                }
            }
        }
 public IEnumerable <BigInteger> EnumerateExcludedValues(BigIntRange searchRange, bool reverse = false)
 {
     if (reverse)
     {
         //If reverse is true, reverse the order of the provided bounds. However, they are expected to be in
         //ascending order, so to make this true, multiply them by negative 1 (and reverse their directions,
         //as well as taking the negative of the search range's limits). Then, take the negative of the results,
         //to get the correct output, still in reverse order.
         return(RangeSet.EnumerateExcludedValues(
                    sortedBounds: _sortedSet.Reverse().Select(x => x.GetNegative()),
                    searchRange: searchRange.GetNegative()
                    ).Select(x => - x));
     }
     else
     {
         return(RangeSet.EnumerateExcludedValues(
                    sortedBounds: _sortedSet,
                    searchRange: searchRange
                    ));
     }
 }
        public bool TryGetSurroundingIncludedRange(BigInteger x, out BigIntRange range)
        {
            bool rangeIsIncluded;

            if (TryGetSurroundingRange(x, out range, out rangeIsIncluded))
            {
                if (rangeIsIncluded)
                {
                    return(true);
                }
                else
                {
                    range = default(BigIntRange);
                    return(false);
                }
            }
            else
            {
                range = default(BigIntRange);
                return(false);
            }
        }
        public bool TryGetSurroundingGap(BigInteger x, out BigIntRange gap)
        {
            bool rangeIsIncluded;

            if (TryGetSurroundingRange(x, out gap, out rangeIsIncluded))
            {
                if (rangeIsIncluded)
                {
                    gap = default(BigIntRange);
                    return(false);
                }
                else
                {
                    return(true);
                }
            }
            else
            {
                gap = default(BigIntRange);
                return(false);
            }
        }
        public void AddRange(BigIntRange range)
        {
            //	Console.WriteLine("51: " + range);

            SortedSet <BigIntRangeBound> nearbyBounds = GetOverlappingOrAdjacentBounds(range.Start, range.End);

            if (nearbyBounds.Count == 0)
            {
                //No existing ranges are adjacent or *partially* overlapping, however there could
                //be an existing range that entirely includes the new range. To check for that,
                //check if some value in the new range is already included in the RangeSet.
                if (this.Includes(range.Start))
                {
                    //Don't need to do anything - the new range is already included.
                }
                else
                {
                    //Need to add the range, and there's no bounds nearby to affect doing this, so its easy
                    if (range.Start == range.End)
                    {
                        AddBound(range.Start, Direction.Nowhere);
                    }
                    else
                    {
                        AddBound(range.Start, Direction.Up);
                        AddBound(range.End, Direction.Down);
                    }
                }
            }
            else
            {
                var minNearby = nearbyBounds.Min;
                var maxNearby = nearbyBounds.Max;

                RemoveSubset(nearbyBounds);                 //All contents & ends are unneeded (both ends are to be either deleted or modified/re-added)

                bool resultingRangeIsPoint = (
                    //If the new min and max are different, the resulting range will include at least 2 points
                    range.Start == range.End
                    //If there's more than one existing bound, they have to point either be at different points,
                    //or point different ways, so the resulting range won't be a single point
                    && nearbyBounds.Count == 1
                    //If the new point and the existing bound are different, the resulting range will include at least 2 points
                    && range.Start == minNearby.Bound
                    //If the existing bound points up or down, the resulting range will be more than a single point
                    && minNearby.Direction == Direction.Nowhere
                    );

                if (resultingRangeIsPoint)
                {
                    AddBound(range.Start, Direction.Nowhere);                     //minimum == maximum == minInExpandedRange == maxInExpandedRange
                }
                else
                {
                    //If the minimum existing bound doesn't point down (ie. join onto a lower range), provide a new lower bound
                    if (minNearby.Direction != Direction.Down)
                    {
                        AddBound(BigInteger.Min(minNearby.Bound, range.Start), Direction.Up);
                    }

                    //If the maximum existing bound doesn't point up (ie. join onto a higher range), provide a new upper bound
                    if (maxNearby.Direction != Direction.Up)
                    {
                        AddBound(BigInteger.Max(maxNearby.Bound, range.End), Direction.Down);
                    }
                }
            }
        }
        public bool TryGetSurroundingRange(BigInteger x, out BigIntRange range, out bool rangeIsIncluded)
        {
            BigIntRangeBound below;
            BigIntRangeBound above;

            if (TryGetSurroundingBounds(x, out below, out above))
            {
                switch (below.Direction)
                {
                case Direction.Nowhere:
                    range           = BigIntRange.Single(below.Bound);                   //below == above
                    rangeIsIncluded = true;
                    return(true);

                case Direction.Down:
                    switch (above.Direction)
                    {
                    case Direction.Down: throw new InvalidOperationException(
                                  "Underlying sorted set of range bounds is corrupt - the bound below or equal to " + nameof(x) + "=" + x + " "
                                  + "(at value " + below.Bound + ") represents the end of a range, but the bound above or equal "
                                  + "to " + nameof(x) + "=" + x + " (at value " + above.Bound + ") also represents the end of a range, meaning "
                                  + "this second range would have no starting value."
                                  );

                    case Direction.Nowhere:                                     //Same behaviour as for Direction.Up
                    case Direction.Up:
                        if (BigInteger.Abs(below.Bound - above.Bound) >= 2)     //If there is a gap between the bounds
                        {
                            range           = BigIntRange.CreateStartEnd(below.Bound + 1, above.Bound - 1);
                            rangeIsIncluded = false;
                            return(true);
                        }
                        else
                        {
                            range           = default(BigIntRange);
                            rangeIsIncluded = default(bool);
                            return(false);
                        }

                    default: break;
                    }
                    break;

                case Direction.Up:
                    switch (above.Direction)
                    {
                    case Direction.Nowhere: throw new InvalidOperationException(
                                  "Underlying sorted set of range bounds is corrupt - the bound below or equal to " + nameof(x) + "=" + x + " "
                                  + "(at value " + below.Bound + ") represents the start of a range, so and end to this range is expected next, "
                                  + "but the bound above or equal to " + nameof(x) + "=" + x + " (at value " + above.Bound + ") represents a range "
                                  + "containing only single value."
                                  );

                    case Direction.Up: throw new InvalidOperationException(
                                  "Underlying sorted set of range bounds is corrupt - the bound below or equal to " + nameof(x) + "=" + x + " "
                                  + "(at value " + below.Bound + ") represents the end of a range, so and end to this range is expected next, "
                                  + "but the bound above or equal to " + nameof(x) + "=" + x + " (at value " + above.Bound + ") represents "
                                  + "the start of another range."
                                  );

                    case Direction.Down:
                        range           = BigIntRange.CreateStartEnd(below.Bound, above.Bound);
                        rangeIsIncluded = true;
                        return(true);

                    default: break;
                    }
                    break;

                default: break;
                }
            }

            range           = default(BigIntRange);
            rangeIsIncluded = default(bool);
            return(false);
        }
        public bool InclduesInEntirety(BigIntRange range)
        {
            BigIntRange foundRange;

            return(TryGetSurroundingIncludedRange(range.Start, out foundRange) && foundRange.Start <= range.Start && range.End <= foundRange.End);
        }
 public bool OverlapsWith(BigIntRange other) => other.Start <= this.End && other.End >= this.Start;
 /// <summary>Gets a range from (-End) to (-Start)</summary>
 public BigIntRange GetNegative() => BigIntRange.CreateStartEnd(start: -this.End, end: -this.Start);
 //Not a clear name & difficult to define (it's not a union - what is it)
 //	public static BigIntRange Combine(BigIntRange a, BigIntRange b) => BigIntRange.CreateStartEnd(
 //		start: BigInteger.Min(a.Start, b.Start),
 //		end:   BigInteger.Max(a.End  , b.End  )
 //	);
 //Instead do:
 public BigIntRange ExpandToInclude(BigIntRange other) => BigIntRange.CreateStartEnd(
     start: BigInteger.Min(this.Start, other.Start),
     end:   BigInteger.Max(this.End, other.End)
     );
 public BigIntRange ExpandToInclude(BigInteger x) => BigIntRange.CreateStartEnd(
     start: BigInteger.Min(this.Start, x),
     end:   BigInteger.Max(this.End, x)
     );
 public BigIntRange Expand(BigInteger downBy, BigInteger upBy) => BigIntRange.CreateStartEnd(this.Start - downBy, this.End + upBy);
 public bool IncludesInEntirety(BigIntRange other) => this.Start <= other.Start && other.End <= this.End;
 public bool IsAdjacentOrOverlaps(BigIntRange other) => other.Start <= this.End + 1 && other.End >= this.Start - 1;
        private static IEnumerable <BigInteger> EnumerateIncludedValues(IEnumerable <BigIntRangeBound> sortedBounds, BigIntRange searchRange)
        {
            BigInteger i = searchRange.Start;

            foreach (var bound in sortedBounds)
            {
                while (i <= searchRange.End)
                {
                    if (bound.Direction == Direction.Up)
                    {
                        //Move i to the start of the range, but only if that means moving forwards,
                        //and then continue to the next bound
                        i = BigInteger.Max(i, bound.Bound);
                        break;
                    }
                    else if (bound.Direction == Direction.Down)
                    {
                        //Keep returning sequential values until i passes the bound (or the end of the search range)
                        if (i > bound.Bound)
                        {
                            break;
                        }
                        yield return(i);

                        i++;
                    }
                    else
                    {
                        if (searchRange.Includes(bound.Bound))
                        {
                            yield return(bound.Bound);

                            i = bound.Bound + 1;
                        }
                        break;
                    }
                }
            }
        }
        private static IEnumerable <BigInteger> EnumerateExcludedValues(IEnumerable <BigIntRangeBound> sortedBounds, BigIntRange searchRange)
        {
            //	Console.WriteLine("40.0");

            var includedValues = EnumerateIncludedValues(sortedBounds, searchRange);

            BigInteger prev = searchRange.Start - 1;

            foreach (BigInteger included in includedValues)
            {
                //	Console.WriteLine("40.1: " + included + ", " + prev);

                //If there's been a jump of at least 2 (ie 1 number in gap),
                //loop until all numbers in the gap have been yielded
                while (prev + 1 < included)
                {
                    prev++;
                    yield return(prev);
                }

                prev = included;
            }
            //	Console.WriteLine("40.2: searchRange.End: " + searchRange.End + ", prev: " + prev);

            //Yield all final values (that are in a gap) until the search range is exited
            while (prev < searchRange.End)
            {
                prev++;
                yield return(prev);
            }
        }
        public static IEnumerable <BigInteger> IterateNonTrivialZeros(BigInteger maxZ, Action <ExpansionTerm> currentPathStartingPointPrinter = null)
        {
            var termOptions = new TermOptionsMatrix(maxZ);

            //	var sortedEliminatedRanges = new List<BigIntRange>();
            var eliminatedRanges = new RangeSet();

            BigInteger prevStartingPointEval = BigInteger.Zero;             //Starts lower than any evaluated starting point

            foreach (ExpansionTerm pathStartingPoint in EnumerateSortedTermOptions())
            {
                currentPathStartingPointPrinter?.Invoke(pathStartingPoint);
                //	Console.WriteLine("#25 current starting point: " + pathStartingPoint);

                //	//Debugging:
                //	eliminatedRanges.DebugBounds();
                //	foreach (var range in eliminatedRanges.IncludedRanges) {
                //		Console.WriteLine("#40: " + range.Start + " to " + range.End);
                //	}
                //	Console.WriteLine("#41: " + eliminatedRanges.Count);

                BigInteger startingPointEval = pathStartingPoint.Evaluate();

                if (startingPointEval > maxZ)
                {
                    //If gone past end of requested range, return all remaining stored values
                    //up to end of requested range, then stop enumerating.
                    var gaps = eliminatedRanges.EnumerateExcludedValues(
                        searchRange: BigIntRange.CreateStartEnd(
                            start: prevStartingPointEval + 1,
                            end: maxZ
                            )
                        );
                    foreach (BigInteger gap in gaps)
                    {
                        yield return(gap);
                    }
                    //	foreach (var range in eliminatedRanges.IncludedRanges) {
                    //		Console.WriteLine("#42: " + range.Start + " to " + range.End);
                    //	}
                    //	Console.WriteLine("#43: " + eliminatedRanges.Count);
                    yield break;
                }

                foreach (BigInteger summedPath in termOptions.EnumerateSummedExpansionPaths(pathStartingPoint, capToMaximum: true))
                {
                    //	Console.WriteLine("#44: " + summedPath + ", " + expansionPath.ToString(includeSpaces: false));

                    BigInteger newRangeMax = summedPath;
                    BigInteger newRangeMin = summedPath;

                    //	Console.WriteLine("#44.1: " + newRangeMin + ", " + newRangeMax);

                    //Exclude adjacent trivial zeros (multiples of 3) as well
                    if ((newRangeMax + 1) % 3 == 0)
                    {
                        newRangeMax += 1;
                    }
                    if ((newRangeMin - 1) % 3 == 0)
                    {
                        newRangeMin -= 1;
                    }

                    //	Console.WriteLine("#44.2: " + newRangeMin + ", " + newRangeMax);

                    //	Console.WriteLine("#45: " + eliminatedRanges.Count);
                    eliminatedRanges.AddRange(BigIntRange.CreateStartEnd(newRangeMin, newRangeMax));
                    //	Console.WriteLine("#46: " + eliminatedRanges.Count);
                }

                {
                    //	Console.WriteLine("#47: " + eliminatedRanges.Count);
                    var gaps = eliminatedRanges.EnumerateExcludedValues(
                        searchRange: BigIntRange.CreateStartEnd(
                            start: prevStartingPointEval + 1,
                            end: startingPointEval
                            )
                        );
                    foreach (BigInteger gap in gaps)
                    {
                        yield return(gap);
                    }
                    //	Console.WriteLine("#48");
                }

                prevStartingPointEval = startingPointEval;
            }
        }
 public static bool Equals(BigIntRange a, BigIntRange b) => a.Start == b.Start && a.End == b.End;