Чи існує тип C # для представлення цілого діапазону?


105

У мене є необхідність зберігати цілий діапазон. Чи існує такий тип для цього в C # 4.0?

Звичайно, я міг написати власний клас з int Fromі int Toвластивостями та побудувати в належній логіці, щоб забезпечити це From <= To. Але якщо тип уже існує, я, звичайно, скоріше скористаюся цим.


2
Я думаю, що прийняту відповідь слід змінити. @ rsenna відповідь Enumerable.Range- з того, що я бачив, де-факто спосіб реалізувати діапазони в C # 3.0. А C # 3.0 існує приблизно з 2007 року, тож уже 9 років.
Ehtesh Choudhury

@EhteshChoudhury OP не згадує нічого про обробку дискретних значень або навіть перевірку цих типів для min та max, що і Enumerable.Rangeдосягає. OP просто шукала існуючу структуру даних, яка обробляє інтервали, які можуть мати властивості нижньої та верхньої межі, і більше нічого (крім методів, що застосовують певну поведінку).
Сонячний Патель

2
@EhteshChoudhury Я додаю свій голос до хору "не використовуй Enumerable.Range". Це огидний спосіб вирішити просту проблему утримування пари значень min / max. Кожен дзвінок до Enumerable.Max (IEnumerable <int>) (або Enumerable.Min) повторює весь діапазон, щоб визначити межі. Як говорили інші, це може бути багато повторень: ми не говоримо про налаштування мікроефективності, ми говоримо про покарання повільності. Таке програмування є причиною. Net отримує (несправедливо) погану назву для продуктивності! Прийнята відповідь та подібні відповіді - єдине практичне рішення.
Даніель Скотт

1
Це справедливо. Enumerable.Rangeзайме набагато більше місця для (1,1000000) проти типу даних діапазону для (1,1000000). Я неправильно прочитав запитання, коли вперше його задали і подумав, що його просятьEnumerable.Range
Ехтеш Чудхурі

1
@EhteshChoudhury Enumerable.Range не займе 1,1000000, принаймні, ви викликаєте метод ToList ().
Луїс

Відповіді:


136

Я вважав, що найкраще катати своє. Деякі люди використовують Tuples або Points, але, нарешті, ви хочете, щоб ваш Rangeобширний і надали деякі зручні методи, пов’язані з a Range. Краще також, якщо є загальним (що робити, якщо вам потрібен діапазон Doubles, або діапазон якогось спеціального класу?) Наприклад:

/// <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);
    }
}

13
У той час я не був впевнений, що це IComparableможе гарантувати перевантаження оператора. Я гарантований CompareToметод, отже, використання.
drharris

14
Виявляється, я мав рацію в цьому , що: stackoverflow.com/questions/5101378 / ...
drharris

2
Може бути краще structзамість class?
xmedeko

2
@Будеш, людина не поб'є мудрість документації без кісток. :)
drharris

7
Дуже корисно, дякую! Чому б не додати конструктор, який дозволяє негайно його ініціалізувати? public Range(T min, T max) { Minimum = min; Maximum = max; }
NateJC

8

Лише невеликий клас, який я написав, що може комусь бути корисним:

    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
Мені дуже подобається функція розуміння
Dragonborn

Ця understandфункція класна. Якби я побачив це в огляді в нашому коді, я б запропонував перейменувати це Set, оскільки Range(для мене) це звучить безперервно.
dlw

7

Діапазони та індекси випускаються за допомогою C # 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/ для більш детальної інформації.


Це інша концепція, а не відповідно до того, як хотіла ОП.
PepitoSh

Однак System.Rangeтип приймає лише int.
snipsnipsnip

Важливо зазначити, що він доступний лише з .NET Core 3.0. Він недоступний у .NET Framework.
Саррус

2

Покращення на @ 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");
    }

2

Напишіть такий спосіб розширення

 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
}

1
Чому if (value >= from && value <= to) return true;і ні return (value >= from && value <= to)?
sdgfsdh

2

Ця реалізація, натхненна відповіддю @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
}

1

Крім того, трохи іншої дотичної тут, але іноді діапазони корисні лише для перегляду над ними, трохи як це прийнято робити в python. У цьому випадку System.Linqпростір імен визначає static IEnumerable<int> Range(Int32, Int32)метод, який, як підказує підпис,

створює послідовність інтегральних чисел у визначеному діапазоні

Дивіться документацію та приклади на MSDN


@ Зробити це не слід, оскільки Численні діапазони приймають старт та кількість.
Рашид

@Rashid досить справедливо, але я насправді не вважаю це проблемою. Я знаю, що ОП попросив "клас з" int Fromта int To", але я впевнений, що це була не конкретна вимога, а скоріше щось, щоб проілюструвати їхню думку. Для всіх, хто цікавиться, звичайно, значення countпараметра можна легко визначити, якщо ви вже маєте int Fromта int Toзмінні.
Gaboik1

1

Існує Enumerable.Rangeметод, але цей приймає startі countяк його параметр. Для того , щоб працювати , як то , що ви хочете startі end, то endформула будеend - start + 1

Використання:

Enumerable.Range(start, end - start + 1).ToList()


0

Оскільки мені також не вистачало інтервалів у C #, я реалізував повністю загальний клас Interval, який навіть може піклуватися про інтервали з більш складними типами, наприклад, інтервал між двома DateTime, що включає в себе 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 ) );

Для більш широкого представлення підтримуваної функціональності ознайомтеся з тестами пристрою .

Під обкладинками він використовує дерева виразів для компіляції операторів типу під час виконання , які кешуються, тому існує лише вартість при першому ініціалізації типу.


-2

Як щодо структури ?


2
Я б не використовував структуру, якщо ви не знали, що значення min і max ніколи не змінюватимуться одноразово.
IАнотація

Це відповідає на питання "вже існує". Тип структури існує спеціально для таких типів, як розгляд.
Джеймс Самнерс

3
Структура - це не сам по собі тип. Ви в основному говорите "не йдіть, створюйте власний тип" - це законна відповідь
Роберт Леві

2
У перші три слова в пов'язаному документації НЕ згоден з вами: «Тип структури ...»
James Sumners

Я схильний погоджуватись із дружинами. Структура - це простий і простий спосіб зробити це, і він не створює 2 сторінки кодів, які потрібно буде підтримувати надалі для такої простої проблеми. Ви також можете використовувати пару <...> для того ж самого. Microsoft порушує своє "правило" щодо незмінності конструкцій у різних місцях.
Том
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.