Существует ли тип С# для представления целочисленного диапазона?
Мне нужно хранить целочисленный диапазон. Есть ли существующий тип для С# 4.0?
Конечно, я мог бы написать свой собственный класс с int From
и int To
свойствами и построить правильную логику, чтобы обеспечить From <= To
. Но если тип уже существует, я бы, конечно, использовал его.
Ответы
Ответ 1
Я нашел, что лучше всего сворачивать. Некоторые люди используют Tuple
или Point
s, но в конце вы хотите, чтобы ваш Range
был обширным и предоставлял некоторые удобные методы, относящиеся к Range
. Это также лучше всего, если общий (что, если вам нужен диапазон Double
s или диапазон некоторых пользовательских классов?) Например:
/// <summary>The Range class.</summary>
/// <typeparam name="T">Generic parameter.</typeparam>
public class Range<T> where T : IComparable<T>
{
/// <summary>Minimum value of the range.</summary>
public T Minimum { get; set; }
/// <summary>Maximum value of the range.</summary>
public T Maximum { get; set; }
/// <summary>Presents the Range in readable format.</summary>
/// <returns>String representation of the Range</returns>
public override string ToString()
{
return string.Format("[{0} - {1}]", this.Minimum, this.Maximum);
}
/// <summary>Determines if the range is valid.</summary>
/// <returns>True if range is valid, else false</returns>
public bool IsValid()
{
return this.Minimum.CompareTo(this.Maximum) <= 0;
}
/// <summary>Determines if the provided value is inside the range.</summary>
/// <param name="value">The value to test</param>
/// <returns>True if the value is inside Range, else false</returns>
public bool ContainsValue(T value)
{
return (this.Minimum.CompareTo(value) <= 0) && (value.CompareTo(this.Maximum) <= 0);
}
/// <summary>Determines if this Range is inside the bounds of another range.</summary>
/// <param name="Range">The parent range to test on</param>
/// <returns>True if range is inclusive, else false</returns>
public bool IsInsideRange(Range<T> range)
{
return this.IsValid() && range.IsValid() && range.ContainsValue(this.Minimum) && range.ContainsValue(this.Maximum);
}
/// <summary>Determines if another range is inside the bounds of this range.</summary>
/// <param name="Range">The child range to test</param>
/// <returns>True if range is inside, else false</returns>
public bool ContainsRange(Range<T> range)
{
return this.IsValid() && range.IsValid() && this.ContainsValue(range.Minimum) && this.ContainsValue(range.Maximum);
}
}
Ответ 2
Работает на С# 3.0:
IEnumerable<int> myRange = Enumerable.Range(1, 10);
Изменить Пожалуйста, имейте в виду, что:
- Нет (быстрого) повышения производительности при использовании этого - это Linq, поэтому выполнение отложено.
- Существуют уже такие методы, как
Min
и Max
.
-
IEnumerable
также предоставляет методы типа Contains
, Intersect
и Union
- поэтому диапазоны могут быть запрошены не только, они также могут быть объединены.
- Есть люди, которые уже используют это для представления диапазонов (в отличие от простого перечисления по набору целых чисел).
Конечно, если "store" OP беспокоится только о сохранении данных (в отличие от представления данных), он может использовать простой DTO с двумя целыми полями.
Тем не менее, он спрашивает: "Существует ли существующий тип для С# 4.0?" - и я считаю, что ответ да, есть и он называется IEnumerable<int>
.
Изменить 2
Также имейте в виду, что Enumerable.Range
может иметь очень плохую производительность.
Вероятно, что беспокоит большинство из нижеприведенных ответов: есть случаи, когда Enumerable.Range
не является практическим вариантом.
Тем не менее, я считаю, что следует также учитывать следующее:
- Это семантически звуковой способ представления целочисленного диапазона.
- Что он может использоваться для небольших диапазонов без заметного снижения производительности.
- Проблема с производительностью является случайной, то есть вполне возможно вернуть
IEnumerable<int>
, который может быть запрошен против, без необходимости итерации по всему диапазону.
Что касается третьего элемента, эта библиотека реализует метод расширения Sequence.Create
. Он работает аналогично Enumerable.Range
, а также возвращает a IEnumerable<int>
, но он должен быть более эффективным.
Ответ 3
Просто небольшой класс, который я написал, который может быть полезен для кого-то:
public class Range
{
public static List<int> range(int a, int b)
{
List<int> result = new List<int>();
for(int i = a; i <= b; i++)
{
result.Add(i);
}
return result;
}
public static int[] Understand(string input)
{
return understand(input).ToArray();
}
public static List<int> understand(string input)
{
List<int> result = new List<int>();
string[] lines = input.Split(new char[] {';', ','});
foreach (string line in lines)
{
try
{
int temp = Int32.Parse(line);
result.Add(temp);
}
catch
{
string[] temp = line.Split(new char[] { '-' });
int a = Int32.Parse(temp[0]);
int b = Int32.Parse(temp[1]);
result.AddRange(range(a, b).AsEnumerable());
}
}
return result;
}
}
Затем вы просто вызываете:
Range.understand("1,5-9,14;16,17;20-24")
И результат выглядит так:
List<int>
[0]: 1
[1]: 5
[2]: 6
[3]: 7
[4]: 8
[5]: 9
[6]: 14
[7]: 16
[8]: 17
[9]: 20
[10]: 21
[11]: 22
[12]: 23
[13]: 24
Ответ 4
Напишите способ расширения, подобный этому
public static class NumericExtentions
{
public static bool InRange(this int value, int from, int to)
{
if (value >= from && value <= to)
return true;
return false;
}
public static bool InRange(this double value, double from, double to)
{
if (value >= from && value <= to)
return true;
return false;
}
}
а затем используйте его элегантно
if (age.InRange(18, 39))
{
//Logic
}
Ответ 5
Эта реализация, вдохновленная ответом @drharris, позволяет вам определить правильный математический интервал со значениями, которые могут быть включающими/исключающими.
/// <summary>The Interval class.</summary>
/// <typeparam name="T">Generic parameter.</typeparam>
public class Interval<T> : IEquatable<Interval<T>>
where T : IComparable<T>, IEquatable<T>
{
public Interval()
{ }
public Interval(IntervalValue<T> minimum, IntervalValue<T> maximum)
{
this.Minimum = minimum;
this.Maximum = maximum;
}
/// <summary>Minimum value of the interval.</summary>
public IntervalValue<T>? Minimum { get; set; }
/// <summary>Maximum value of the interval.</summary>
public IntervalValue<T>? Maximum { get; set; }
/// <summary>Presents the Interval in readable format.</summary>
/// <returns>String representation of the Interval</returns>
public override string ToString()
{
var min = this.Minimum;
var max = this.Maximum;
var sb = new StringBuilder();
if (min.HasValue)
sb.AppendFormat(min.Value.ToString(IntervalNotationPosition.Left));
else
sb.Append("(-∞");
sb.Append(',');
if (max.HasValue)
sb.AppendFormat(max.Value.ToString(IntervalNotationPosition.Right));
else
sb.Append("∞)");
var result = sb.ToString();
return result;
}
/// <summary>Determines if the interval is valid.</summary>
/// <returns>True if interval is valid, else false</returns>
public bool IsValid()
{
var min = this.Minimum;
var max = this.Maximum;
if (min.HasValue && max.HasValue)
return min.Value.Value.CompareTo(max.Value.Value) <= 0;
return true;
}
/// <summary>Determines if the provided value is inside the interval.</summary>
/// <param name="x">The value to test</param>
/// <returns>True if the value is inside Interval, else false</returns>
public bool ContainsValue(T x)
{
if (x == null)
throw new ArgumentNullException(nameof(x));
var min = this.Minimum;
var max = this.Maximum;
var isValid = this.IsValid();
if (!isValid)
throw new InvalidOperationException("Interval is not valid.");
bool result = true; // (-∞,∞)
if (min.HasValue)
{
if (min.Value.Type == IntervalValueType.Exclusive)
result &= min.Value.Value.CompareTo(x) < 0;
else if (min.Value.Type == IntervalValueType.Inclusive)
result &= min.Value.Value.CompareTo(x) <= 0;
else
throw new NotSupportedException();
}
if (max.HasValue)
{
if (max.Value.Type == IntervalValueType.Exclusive)
result &= max.Value.Value.CompareTo(x) > 0;
else if (max.Value.Type == IntervalValueType.Inclusive)
result &= max.Value.Value.CompareTo(x) >= 0;
else
throw new NotSupportedException();
}
return result;
}
public bool Equals(Interval<T> other)
{
if (other == null)
return false;
if (ReferenceEquals(this, other))
return true;
return this.Minimum?.Equals(other.Minimum) == true
&& this.Maximum?.Equals(other.Maximum) == true;
}
public override bool Equals(object obj)
{
return this.Equals(obj as Interval<T>);
}
public override int GetHashCode()
{
unchecked
{
int hash = (int)2166136261;
hash = hash * 16777619 ^ this.Minimum?.GetHashCode() ?? 0;
hash = hash * 16777619 ^ this.Maximum?.GetHashCode() ?? 0;
return hash;
}
}
}
public struct IntervalValue<T> : IEquatable<IntervalValue<T>>
where T : IComparable<T>, IEquatable<T> //, IFormattable
{
private readonly T value;
private readonly IntervalValueType type;
public IntervalValue(T value, IntervalValueType type)
{
if (value == null)
throw new ArgumentNullException(nameof(value));
this.value = value;
this.type = type;
}
public T Value
{
get { return this.value; }
}
public IntervalValueType Type
{
get { return this.type; }
}
public bool Equals(IntervalValue<T> other)
{
return this.value.Equals(other.value)
&& this.type == other.type;
}
public override bool Equals(object obj)
{
return obj is IntervalValue<T> && this.Equals((IntervalValue<T>)obj);
}
public override int GetHashCode()
{
unchecked
{
int hash = (int)2166136261;
hash = hash * 16777619 ^ this.value.GetHashCode();
hash = hash * 16777619 ^ this.type.GetHashCode();
return hash;
}
}
internal string ToString(IntervalNotationPosition position)
{
var notation = this.Type.ToString(position);
switch (position)
{
case IntervalNotationPosition.Left:
return string.Format("{0}{1}", notation, this.Value);
case IntervalNotationPosition.Right:
return string.Format("{0}{1}", this.Value, notation);
default:
throw new NotSupportedException();
}
}
}
internal static class IntervalValueTypeExtensions
{
public static string ToString(this IntervalValueType type, IntervalNotationPosition position)
{
switch (position)
{
case IntervalNotationPosition.Left:
switch (type)
{
case IntervalValueType.Inclusive: return "[";
case IntervalValueType.Exclusive: return "(";
default:
throw new NotSupportedException();
}
case IntervalNotationPosition.Right:
switch (type)
{
case IntervalValueType.Inclusive: return "]";
case IntervalValueType.Exclusive: return ")";
default:
throw new NotSupportedException();
}
break;
default:
throw new NotSupportedException();
}
}
}
public enum IntervalValueType
{
Inclusive,
Exclusive
}
public enum IntervalNotationPosition
{
Left,
Right
}
Ответ 6
Так как у меня также отсутствовали интервалы в С#, я реализовал полностью общий класс Interval, который может даже ухаживать за интервалами с более сложными типами, например интервал между двумя DateTime
's, который во время вычислений включает TimeSpan
.
Пример использования, где элемент GUI представляет временной интервал:
// Mockup of a GUI element and mouse position.
var timeBar = new { X = 100, Width = 200 };
int mouseX = 180;
// Find out which date on the time bar the mouse is positioned on,
// assuming it represents whole of 2014.
var timeRepresentation = new Interval<int>( timeBar.X, timeBar.X + timeBar.Width );
DateTime start = new DateTime( 2014, 1, 1 );
DateTime end = new DateTime( 2014, 12, 31 );
var thisYear = new Interval<DateTime, TimeSpan>( start, end );
DateTime hoverOver = timeRepresentation.Map( mouseX, thisYear );
// If the user clicks, zoom in to this position.
double zoomLevel = 0.5;
double zoomInAt = thisYear.GetPercentageFor( hoverOver );
Interval<DateTime, TimeSpan> zoomed = thisYear.Scale( zoomLevel, zoomInAt );
// Iterate over the interval, e.g. draw labels.
zoomed.EveryStepOf( TimeSpan.FromDays( 1 ), d => DrawLabel( d ) );
Для более подробного представления поддерживаемых функциональных возможностей проверить модульные тесты.
Под обложками использует деревья выражений для компиляции операций оператора типа во время выполнения, которые кэшируются, поэтому в первый раз тип инициализируется.
Ответ 7
Улучшение на @andrius-naruševičius очень полезного ответа, чтобы сделать его более идиоматичным и легко настраиваемым
/// <summary>
/// http://stackoverflow.com/questions/5343006/is-there-a-c-sharp-type-for-representing-an-integer-range
/// </summary>
public class Range
{
readonly static char[] Separators = {','};
public static List<int> Explode(int from, int to)
{
return Enumerable.Range(from, (to-from)+1).ToList();
}
public static List<int> Interpret(string input)
{
var result = new List<int>();
var values = input.Split(Separators);
string rangePattern = @"(?<range>(?<from>\d+)-(?<to>\d+))";
var regex = new Regex(rangePattern);
foreach (string value in values)
{
var match = regex.Match(value);
if (match.Success)
{
var from = Parse(match.Groups["from"].Value);
var to = Parse(match.Groups["to"].Value);
result.AddRange(Explode(from, to));
}
else
{
result.Add(Parse(value));
}
}
return result;
}
/// <summary>
/// Split this out to allow custom throw etc
/// </summary>
private static int Parse(string value)
{
int output;
var ok = int.TryParse(value, out output);
if (!ok) throw new FormatException($"Failed to parse '{value}' as an integer");
return output;
}
}
и тесты:
[Test]
public void ExplodeRange()
{
var output = Range.Explode(5, 9);
Assert.AreEqual(5, output.Count);
Assert.AreEqual(5, output[0]);
Assert.AreEqual(6, output[1]);
Assert.AreEqual(7, output[2]);
Assert.AreEqual(8, output[3]);
Assert.AreEqual(9, output[4]);
}
[Test]
public void ExplodeSingle()
{
var output = Range.Explode(1, 1);
Assert.AreEqual(1, output.Count);
Assert.AreEqual(1, output[0]);
}
[Test]
public void InterpretSimple()
{
var output = Range.Interpret("50");
Assert.AreEqual(1, output.Count);
Assert.AreEqual(50, output[0]);
}
[Test]
public void InterpretComplex()
{
var output = Range.Interpret("1,5-9,14,16,17,20-24");
Assert.AreEqual(14, output.Count);
Assert.AreEqual(1, output[0]);
Assert.AreEqual(5, output[1]);
Assert.AreEqual(6, output[2]);
Assert.AreEqual(7, output[3]);
Assert.AreEqual(8, output[4]);
Assert.AreEqual(9, output[5]);
Assert.AreEqual(14, output[6]);
Assert.AreEqual(16, output[7]);
Assert.AreEqual(17, output[8]);
Assert.AreEqual(20, output[9]);
Assert.AreEqual(21, output[10]);
Assert.AreEqual(22, output[11]);
Assert.AreEqual(23, output[12]);
Assert.AreEqual(24, output[13]);
}
[ExpectedException(typeof (FormatException))]
[Test]
public void InterpretBad()
{
Range.Interpret("powdered toast man");
}
Ответ 8
Диапазоны и индексы выпускаются с С# 8.0.
Теперь вы можете сделать
string[] names =
{
"Archimedes", "Pythagoras", "Euclid", "Socrates", "Plato"
};
foreach (var name in names[1..4])
{
yield return name;
}
Проверьте https://blogs.msdn.microsoft.com/dotnet/2018/12/05/take-c-8-0-for-a-spin/ для получения более подробной информации.
Ответ 9
Как насчет struct?