Czy istnieje typ C # do reprezentowania zakresu liczb całkowitych?


105

Muszę przechowywać zakres liczb całkowitych. Czy istnieje istniejący typ w C # 4.0?

Oczywiście, mógłbym napisać własną klasę z int Fromoraz int Towłaściwości i budować w odpowiedniej logiki, aby zapewnić From <= To. Ale jeśli typ już istnieje, oczywiście wolałbym go użyć.


2
Myślę, że zaakceptowana odpowiedź powinna zostać zmieniona. @ rsenna odpowiada Enumerable.Range, z tego, co widziałem, de facto sposobem implementacji zakresów w C # 3.0. A C # 3.0 istnieje od 2007 roku, czyli od 9 lat.
Ehtesh Choudhury

@EhteshChoudhury OP nie wspomina nic o obsłudze wartości dyskretnych, ani nawet o sprawdzaniu tych typów pod kątem min i max, co jest tym, co Enumerable.Rangeosiąga. OP szukał po prostu istniejącej struktury danych, która obsługuje interwały, które mogą mieć właściwości dolnej i górnej granicy, i nic więcej (poza metodami wymuszającymi określone zachowania).
Sunny Patel

2
@EhteshChoudhury Dodam swój głos do refrenu „Nie używaj Enumerable.Range”. To ohydny sposób rozwiązania prostego problemu trzymania się pary wartości min / max. Każde wywołanie Enumerable.Max (IEnumerable <int>) (lub Enumerable.Min) wykonuje iterację w całym zakresie, aby określić granice. Jak powiedzieli inni, może to być wiele iteracji: nie mówimy tutaj o mikro dostrajaniu wydajności, mówimy o paraliżującej powolności. Tego rodzaju programowanie jest powodem, dla którego .Net ma (niesłusznie) złą opinię za wydajność! Przyjęta odpowiedź i podobne odpowiedzi to jedyne praktyczne rozwiązania.
Daniel Scott

1
To uczciwe. Enumerable.Rangezajmie znacznie więcej miejsca dla (1,1000000) w porównaniu z typem danych Range dla (1,1000000). Źle odczytałem pytanie za pierwszym razem i pomyślałem, że to pytanieEnumerable.Range
Ehtesh Choudhury

1
@EhteshChoudhury Enumerable.Range nie zajmie 1,1000000, przynajmniej wywołasz metodę ToList ().
Luis

Odpowiedzi:


136

Uważam, że najlepiej jest toczyć własne. Niektórzy używają Tuples lub Points, ale ostatecznie chcesz, aby Twój Rangeplik był obszerny i udostępnił kilka przydatnych metod związanych z plikiem Range. Najlepiej też, jeśli jest ogólny (a co, jeśli potrzebujesz zakresu Doubles lub zakresu jakiejś niestandardowej klasy?) Na przykład:

/// <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
Wtedy nie byłem pewien, czy IComparableto zagwarantuje przeciążenie operatora. Mam gwarancję CompareTometody, stąd takie użycie.
drharris

14
Okazuje się, że robiłem to dobrze: stackoverflow.com/questions/5101378/ ...
drharris

2
Może być lepiej structzamiast class?
xmedeko

2
@Hej, człowieku, nie podważ mądrości dokumentacji podstawowej. :)
drharris

7
Bardzo pomocne, dzięki! Dlaczego nie dodać konstruktora, który umożliwia natychmiastową inicjalizację? public Range(T min, T max) { Minimum = min; Maximum = max; }
NateJC,

8

Tylko mała klasa, którą napisałem, która może być pomocna dla kogoś:

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

Wtedy wystarczy zadzwonić:

Range.understand("1,5-9,14;16,17;20-24")

A wynik wygląda następująco:

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
Bardzo podoba mi się funkcja zrozumienia
Dragonborn

Ta understandfunkcja jest fajna. Gdybym zobaczył to w recenzji w naszym kodzie, sugerowałbym zmianę nazwy na Set, ponieważ Range(dla mnie) brzmi nieprzerwanie.
dlw

7

Zakresy i indeksy są publikowane w języku C # 8.0.

Teraz możesz to zrobić

string[] names =
{
    "Archimedes", "Pythagoras", "Euclid", "Socrates", "Plato"
};
foreach (var name in names[1..4])
{
    yield return name;
}

Sprawdź https://blogs.msdn.microsoft.com/dotnet/2018/12/05/take-c-8-0-for-a-spin/ więcej szczegółów.


To jest inna koncepcja i nie jest to zgodne z oczekiwaniami PO.
PepitoSh

Jednak System.Rangetyp akceptuje tylko int.
snipsnipsnip

Należy wspomnieć, że jest dostępny tylko od wersji .NET Core 3,0. Jest niedostępny w .NET Framework.
Sarrus

2

Ulepszenie @ andrius-naruševičius bardzo pomocnej odpowiedzi, aby uczynić ją bardziej idiomatyczną i łatwiejszą do dostosowania

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

oraz testy:

    [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

Napisz taką metodę rozszerzenia

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

a następnie używaj go elegancko

if (age.InRange(18, 39))
{ 
//Logic
}

1
Dlaczego if (value >= from && value <= to) return true;i nie return (value >= from && value <= to)?
sdgfsdh

2

Ta implementacja, zainspirowana odpowiedzią @drharris, pozwala zdefiniować odpowiedni matematyczny przedział z wartościami, które mogą być włączające / wyłączające.

/// <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

Również tutaj jest trochę inna styczna, ale czasami zakresy są przydatne tylko do iteracji po nich, trochę tak, jak jest to zwykle robione w Pythonie. W takim przypadku System.Linqprzestrzeń nazw definiuje static IEnumerable<int> Range(Int32, Int32)metodę, która, jak sugeruje sygnatura,

generuje sekwencję liczb całkowitych w określonym zakresie

Zobacz dokumentację i przykłady w witrynie MSDN


@Seth, nie powinno, ponieważ Enumerable range akceptuje start i count.
Rashid

@Rashid w porządku, ale tak naprawdę nie uważam tego za problem. Wiem, że OP poprosił o „zajęcia z int Fromi int To”, ale jestem przekonany, że nie był to konkretny wymóg, ale raczej coś, co ilustruje ich punkt widzenia. Dla wszystkich zainteresowanych oczywiście wartość countparametru można bardzo łatwo określić, zakładając, że już masz int Fromi int Tozmienne.
Gaboik 1

1

Jest Enumerable.Rangemetoda, ale ta przyjmuje starti countjako parametr. W celu użycia go do pracy, co chcesz starti endThe endformuła byłabyend - start + 1

Stosowanie:

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


0

Ponieważ brakowało mi również interwałów w C #, zaimplementowałem w pełni ogólną klasę Interval, która może zająć się nawet interwałami o bardziej skomplikowanych typach, np. Interwał między dwoma DateTime, który występuje TimeSpanpodczas obliczeń.

Przykładowy przypadek użycia, w którym element GUI reprezentuje przedział czasu:

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

Aby uzyskać bardziej szczegółową reprezentację obsługiwanych funkcji, zapoznaj się z testami jednostkowymi .

Pod osłonami używa drzew wyrażeń do kompilowania operacji operatora typu w czasie wykonywania , które są buforowane, więc koszt jest tylko przy pierwszej inicjalizacji typu.


-2

A co ze strukturą ?


2
Nie użyłbym struktury, gdybyś nie wiedział, że wartości min i max nigdy nie zmienią się po utworzeniu instancji.
IAbstract

Odpowiada na pytanie „już istnieje”. Typ struktury istnieje specjalnie dla typów, takich jak ten, o którym mowa.
James Sumners,

3
Struct nie jest typem samym w sobie. Zasadniczo mówisz „nie idź, stwórz swój własny typ” - to poprawna odpowiedź
Robert Levy

2
Te trzy pierwsze słowa w powiązanej dokumentacji nie zgadzają się ze sobą: „Ten typ struct ...”
James Sumners

Zwykle zgadzam się z jsumners. Struct jest łatwym i prostym sposobem na zrobienie tego i nie tworzy 2 stron kodów, które muszą być później utrzymywane dla tak prostego problemu. Możesz również użyć pary <...> do tego samego celu. Microsoft łamie własną „zasadę” dotyczącą niezmienności struktur w różnych miejscach.
Tom
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.