Gibt es einen C # -Typ zur Darstellung eines ganzzahligen Bereichs?


105

Ich muss einen ganzzahligen Bereich speichern. Gibt es dafür in C # 4.0 einen Typ?

Natürlich könnte ich meine eigene Klasse mit int Fromund int ToEigenschaften schreiben und die richtige Logik einbauen, um dies sicherzustellen From <= To. Aber wenn es bereits einen Typ gibt, würde ich das natürlich lieber verwenden.


2
Ich denke, die akzeptierte Antwort sollte geändert werden. @ rsennas Antwort mit Enumerable.Rangeist, wie ich gesehen habe, de facto die Möglichkeit, Bereiche in C # 3.0 zu implementieren. Und C # 3.0 gibt es seit 2007, also 9 Jahre zu diesem Zeitpunkt.
Ehtesh Choudhury

@EhteshChoudhury Das OP erwähnt nichts über den Umgang mit diskreten Werten oder die Validierung dieser Typen für min und max, was Enumerable.Rangeerreicht wird. OP suchte einfach nach einer vorhandenen Datenstruktur, die Intervalle verarbeitet, die Eigenschaften einer Unter- und Obergrenze haben können, und nicht mehr (außer Methoden, die bestimmte Verhaltensweisen erzwingen).
Sunny Patel

2
@EhteshChoudhury Ich werde meine Stimme dem Refrain "Enumerable.Range nicht verwenden" hinzufügen. Das ist ein schrecklicher Weg, um das einfache Problem zu lösen, ein Paar von Min / Max-Werten festzuhalten. Jeder Aufruf von Enumerable.Max (IEnumerable <int>) (oder Enumerable.Min) durchläuft den gesamten Bereich, um die Grenzen herauszufinden. Wie andere gesagt haben, könnte dies eine Menge Iterationen sein: Wir sprechen hier nicht von einer Optimierung der Mikroleistung , sondern von einer lähmenden Langsamkeit. Diese Art der Programmierung ist der Grund. Net bekommt einen (zu Unrecht) schlechten Ruf für Leistung! Die akzeptierte Antwort und ähnliche Antworten sind die einzigen praktischen Lösungen.
Daniel Scott

1
Das ist fair. Enumerable.Rangenimmt viel mehr Platz für (1.1000000) ein als ein Range-Datentyp für (1.1000000). Ich habe die Frage falsch gelesen, als sie zum ersten Mal gestellt wurde, und dachte, sie würde gefragtEnumerable.Range
Ehtesh Choudhury

1
@EhteshChoudhury Enumerable.Range benötigt nicht mindestens 1.1000000, wenn Sie die ToList () -Methode aufrufen.
Luis

Antworten:


135

Ich fand es am besten, meine eigenen zu rollen. Einige Leute verwenden Tuples oder Points, aber am Ende möchten Sie Range, dass Sie umfangreich sind und einige praktische Methoden bereitstellen, die sich auf a beziehen Range. Es ist auch am besten, wenn generisch (was ist, wenn Sie einen Bereich von Doubles oder einen Bereich einer benutzerdefinierten Klasse benötigen ?) Zum Beispiel:

/// <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
Zu diesem Zeitpunkt war ich mir nicht sicher, ob IComparabledies eine Überlastung des Bedieners garantieren würde. Mir ist eine CompareToMethode garantiert , daher diese Verwendung.
Drharris

14
Es stellte sich heraus, dass ich damit Recht hatte: stackoverflow.com/questions/5101378/…
drharris

2
Kann besser sein structals class?
Xmedeko

2
@ Will hey man klopft nicht an die Weisheit der Barebone-Dokumentation. :)
Drharris

6
Sehr hilfreich, danke! Warum nicht einen Konstruktor hinzufügen, mit dem Sie ihn sofort initialisieren können? public Range(T min, T max) { Minimum = min; Maximum = max; }
NateJC

8

Nur eine kleine Klasse, die ich geschrieben habe und die für jemanden hilfreich sein könnte:

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

Dann rufen Sie einfach an:

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

Und das Ergebnis sieht aus wie:

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
Ich mag die Funktion zum Verstehen
Dragonborn

Diese understandFunktion ist cool. Wenn ich dies in einer Überprüfung in unserem Code sehen würde, würde ich vorschlagen, dies umzubenennen Set, da Range(für mich) kontinuierlich klingt.
30.

7

Bereiche und Indizes werden mit C # 8.0 veröffentlicht.

Sie können jetzt tun

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

Weitere Informationen finden Sie unter https://blogs.msdn.microsoft.com/dotnet/2018/12/05/take-c-8-0-for-a-spin/ .


Dies ist ein anderes Konzept und nicht wie vom OP gewünscht.
PepitoSh

Der System.RangeTyp akzeptiert intjedoch nur.
Snipsnipsnip

Es ist wichtig zu erwähnen, dass es erst seit .NET Core 3.0 verfügbar ist. Es ist in .NET Framework nicht verfügbar.
Sarrus

2

Verbesserung von @ andrius-naruševičius sehr hilfreiche Antwort, um es idiomatischer und leicht anpassbar zu machen

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

und die Tests:

    [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

Schreiben Sie eine solche Erweiterungsmethode

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

und dann elegant verwenden

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

1
Warum if (value >= from && value <= to) return true;und nicht return (value >= from && value <= to)?
SDGFSDH

2

Diese Implementierung, inspiriert von der Antwort von @drharris, ermöglicht es Ihnen, das richtige mathematische Intervall mit Werten zu definieren, die inklusiv / exklusiv sein können.

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

Auch ein bisschen eine andere Tangente hier, aber manchmal sind Bereiche nur nützlich, um sie zu durchlaufen, ein bisschen wie es in Python üblich ist. In diesem Fall System.Linqdefiniert der Namespace eine static IEnumerable<int> Range(Int32, Int32)Methode, die, wie die Signatur andeutet,

generiert eine Folge von Integralzahlen innerhalb eines bestimmten Bereichs

Siehe die Dokumentation und Beispiele zu MSDN


@ Seth sollte es nicht, da Enumerable Range Start und Count akzeptiert.
Rashid

@ Rashid fair genug, aber ich halte es nicht wirklich für ein Problem. Ich weiß, dass OP um eine "Klasse mit int Fromund int To" gebeten hat, aber ich bin ziemlich zuversichtlich, dass dies keine spezifische Anforderung war, sondern etwas, um ihren Standpunkt zu veranschaulichen. Für alle Interessierten, natürlich, der Wert der countkann Parameter sehr leicht werden, bestimmt gewährt , dass Sie int Fromund int ToVariablen bereits.
Gaboik1

1

Es gibt eine Enumerable.RangeMethode, aber diese akzeptiert startund countals Parameter. Damit es zu arbeiten , wie das, was Sie wollen , startund enddie endwürden Formelend - start + 1

Verwendung:

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


0

Da mir auch Intervalle in C # fehlten, habe ich eine vollständig generische Intervallklasse implementiert, die sogar Intervalle mit komplexeren Typen behandeln kann, z. B. ein Intervall zwischen zwei DateTime, das TimeSpanbei Berechnungen berücksichtigt wird.

Ein Anwendungsbeispiel, bei dem ein GUI-Element ein Zeitintervall darstellt:

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

Eine ausführlichere Darstellung der unterstützten Funktionen finden Sie in den Komponententests .

Unter dem Deckmantel werden Ausdrucksbäume zum Kompilieren von Typoperatoroperationen zur Laufzeit verwendet , die zwischengespeichert werden, sodass beim ersten Initialisieren des Typs nur Kosten anfallen.


-2

Wie wäre es mit einer Struktur ?


2
Ich würde keine Struktur verwenden, wenn Sie nicht wüssten, dass sich die Min- und Max-Werte nach der Instanziierung niemals ändern würden .
IAbstract

Es beantwortet die Frage "bereits vorhanden". Der Strukturtyp existiert speziell für Typen wie den fraglichen.
James Sumners

3
Struct ist kein Typ an und für sich. Sie sagen im Grunde "no go create your own type" - das ist eine legitime Antwort
Robert Levy

2
Die ersten drei Wörter in der verknüpften Dokumentation stimmen nicht mit Ihnen überein: "Der Strukturtyp ..."
James Sumners

Ich stimme eher jsumners zu. Eine Struktur ist eine einfache und unkomplizierte Möglichkeit, dies zu tun, und es werden keine 2 Seiten mit Codes erzeugt, die später für ein so einfaches Problem gepflegt werden müssen. Sie können auch ein Paar <...> für dieselbe Sache verwenden. Microsoft bricht seine eigene "Regel", wonach Strukturen an verschiedenen Stellen unveränderlich sind.
Tom
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.