Esiste un tipo C # per rappresentare un intervallo intero?


105

Ho bisogno di memorizzare un intervallo intero. Esiste un tipo esistente per quello in C # 4.0?

Certo, avrei potuto scrivere la mia classe con int Frome int Toproprietà e costruire in logica corretta per garantire che From <= To. Ma se esiste già un tipo, ovviamente preferisco usarlo.


2
Penso che la risposta accettata dovrebbe essere cambiata. La risposta di @ rsenna Enumerable.Rangeè, da quello che ho visto, il modo de facto per implementare gli intervalli in C # 3.0. E C # 3.0 esiste dal 2007, quindi 9 anni a questo punto.
Ehtesh Choudhury

@EhteshChoudhury L'OP non menziona nulla sulla gestione di valori discreti, o anche sulla convalida di quei tipi per un minimo e un massimo, che è ciò che Enumerable.Rangerealizza. OP stava semplicemente cercando una struttura dati esistente che gestisse gli intervalli che possono avere proprietà di un limite inferiore e superiore e nient'altro (oltre a metodi che impongono determinati comportamenti).
Sunny Patel

2
@EhteshChoudhury Aggiungerò la mia voce al ritornello "don't use Enumerable.Range". Questo è un modo orribile per risolvere il semplice problema di mantenere una coppia di valori min / max. Ogni chiamata a Enumerable.Max (IEnumerable <int>) (o Enumerable.Min) esegue un'iterazione sull'intero intervallo per calcolare i limiti. Come altri hanno già detto, potrebbero essere molte iterazioni: non stiamo parlando di micro ottimizzazione delle prestazioni qui, stiamo parlando di una lentezza paralizzante. Questo tipo di programmazione è la ragione per cui .Net riceve una cattiva reputazione (ingiustamente) per le sue prestazioni! La risposta accettata e risposte simili sono le uniche soluzioni pratiche.
Daniel Scott

1
È giusto. Enumerable.Rangeoccuperà molto più spazio per (1,1000000) rispetto a un tipo di dati Range per (1,1000000). Ho letto la domanda sbagliata la prima volta che è stata posta e ho pensato che fosse richiestaEnumerable.Range
Ehtesh Choudhury,

1
@EhteshChoudhury Enumerable.Range non accetta 1,1000000 almeno si chiama il metodo ToList ().
Luis

Risposte:


136

Ho trovato il modo migliore per rotolare il mio. Alcune persone usano Tuples o Points, ma alla fine vuoi che il tuo Rangesia ampio e fornisca alcuni metodi utili che si riferiscono a un file Range. È anche meglio se generico (cosa succede se hai bisogno di un intervallo di Doubles o di un intervallo di qualche classe personalizzata?) Ad esempio:

/// <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
A quel tempo, non ero sicuro che IComparableavrebbe garantito il sovraccarico dell'operatore. Mi è garantito un CompareTometodo, da qui tale utilizzo.
drharris

14
Si scopre che aveva ragione a farlo: stackoverflow.com/questions/5101378/...
drharris

2
Può essere migliore structinvece di class?
xmedeko

2
@ Will hey amico non bussare alla saggezza della documentazione barebone. :)
drharris

7
Molto utile, grazie! Perché non aggiungere un costruttore che ti permetta di inizializzarlo immediatamente? public Range(T min, T max) { Minimum = min; Maximum = max; }
NateJC

8

Solo una piccola classe che ho scritto che potrebbe essere utile per qualcuno:

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

Quindi chiama semplicemente:

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

E il risultato è simile a:

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
Mi piace molto la funzione di comprensione
Dragonborn

Quella understandfunzione è fantastica. Se l'ho visto in una recensione nel nostro codice, suggerirei di rinominarlo in Set, perché Range(per me) suona continuo.
dlw

7

Gli intervalli e gli indici vengono rilasciati con C # 8.0.

Ora sei in grado di farlo

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

Visita https://blogs.msdn.microsoft.com/dotnet/2018/12/05/take-c-8-0-for-a-spin/ per maggiori dettagli.


Questo è un concetto diverso e non come da OP voluto.
PepitoSh

Il System.Rangetipo accetta solo int, però.
snipsnipsnip

È importante ricordare che è disponibile solo a partire da .NET Core 3.0. Non è disponibile in .NET Framework.
Sarrus

2

Migliorare su @ andrius-naruševičius una risposta molto utile per renderlo più idiomatico e facilmente personalizzabile

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

e le prove:

    [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

Scrivi un metodo di estensione come questo

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

e poi usalo elegantemente

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

1
Perché if (value >= from && value <= to) return true;e no return (value >= from && value <= to)?
sdgfsdh

2

Questa implementazione, ispirata alla risposta di @drharris, consente di definire un intervallo matematico appropriato con valori, che possono essere inclusivi / esclusivi.

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

Inoltre, una tangente un po 'diversa qui, ma a volte gli intervalli sono utili solo per iterarli su di essi, un po' come è consuetudine fare in Python. In tal caso, lo System.Linqspazio dei nomi definisce un static IEnumerable<int> Range(Int32, Int32)metodo che, come suggerisce la firma,

genera una sequenza di numeri interi all'interno di un intervallo specificato

Consulta la documentazione e gli esempi su MSDN


@Seth non dovrebbe poiché l'intervallo Enumerable accetta inizio e conteggio.
Rashid

@ Rashid è abbastanza giusto, ma non lo considero davvero un problema. So che OP ha chiesto una "lezione con int Frome int To" ma sono abbastanza fiducioso che questo non fosse un requisito specifico, ma piuttosto qualcosa per illustrare il loro punto. Per chiunque sia interessato, ovviamente, il valore del countparametro può essere determinato molto facilmente, ammesso che tu abbia già int Frome int Tovariabili.
Gaboik1

1

C'è un Enumerable.Rangemetodo ma questo accetta starte countcome suo parametro. Affinché funzioni come vuoi starte end, la endformula sarebbeend - start + 1

Uso:

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


0

Dato che mancavano anche gli intervalli in C #, ho implementato una classe Interval completamente generica che può persino occuparsi di intervalli con tipi più complessi, ad esempio un intervallo tra due DateTime, che coinvolge gli intervalli TimeSpandurante i calcoli.

Un caso d'uso di esempio, in cui un elemento della GUI rappresenta un intervallo di tempo:

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

Per una rappresentazione più ampia delle funzionalità supportate controlla gli unit test .

Sotto le coperte utilizza alberi delle espressioni per compilare le operazioni degli operatori di tipo in fase di esecuzione , che vengono memorizzate nella cache, quindi c'è solo un costo la prima volta che il tipo viene inizializzato.


-2

Che ne dici di una struttura ?


2
Non userei una struttura a meno che tu non sapessi che i valori min e max non cambierebbero mai una volta istanziati.
IAbstract

Risponde alla domanda "già esistente". Il tipo struct esiste specificamente per tipi come quello in questione.
James Sumners

3
Struct non è un tipo in sé e per sé. Stai praticamente dicendo "no, crea il tuo tipo" - questa è una risposta legittima
Robert Levy

2
Le prime tre parole nella documentazione collegata non sono d'accordo con te: "Il tipo di struttura ..."
James Sumners

Tendo ad essere d'accordo con jsumners. Una struttura è un modo semplice e diretto per farlo, e non produce 2 pagine di codici che devono essere mantenute in seguito per un problema così semplice. Puoi anche usare una coppia <...> per la stessa cosa. Microsoft infrange la propria "regola" sull'immutabilità delle strutture in vari punti.
Tom
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.