Existe-t-il un type C # pour représenter une plage entière?


105

J'ai besoin de stocker une plage entière. Existe-t-il un type pour cela dans C # 4.0?

Bien sûr, je pourrais écrire ma propre classe avec int Fromet int Toproperties et intégrer une logique appropriée pour m'en assurer From <= To. Mais si un type existe déjà, je préfère bien sûr l'utiliser.


2
Je pense que la réponse acceptée devrait être modifiée. La réponse de @ rsenna Enumerable.Rangeest, d'après ce que j'ai vu, la manière de facto d'implémenter des plages en C # 3.0. Et C # 3.0 existe depuis 2007, donc 9 ans à ce stade.
Ehtesh Choudhury

@EhteshChoudhury L'OP ne mentionne rien sur la gestion des valeurs discrètes, ni même la validation de ces types pour un min et un max, ce qui Enumerable.Rangeaccomplit. OP cherchait simplement une structure de données existante qui gère les intervalles qui peuvent avoir des propriétés d'une limite inférieure et supérieure, et rien de plus (à part des méthodes qui imposent certains comportements).
Sunny Patel

2
@EhteshChoudhury J'ajouterai ma voix au refrain "don't use Enumerable.Range". C'est une manière hideuse de résoudre le simple problème de conserver une paire de valeurs min / max. Chaque appel à Enumerable.Max (IEnumerable <int>) (ou Enumerable.Min) itère sur toute la plage pour déterminer les limites. Comme d'autres l'ont dit, cela pourrait être beaucoup d'itérations: nous ne parlons pas ici de micro-réglage des performances, nous parlons de lenteur paralysante. Ce type de programmation est la raison pour laquelle .Net reçoit un mauvais nom (injustement) pour la performance! La réponse acceptée et les réponses similaires sont les seules solutions pratiques.
Daniel Scott

1
C'est juste. Enumerable.Rangeprendra beaucoup plus d'espace pour (1,1000000) que pour un type de données Range pour (1,1000000). J'ai mal lu la question la première fois qu'elle a été posée et j'ai pensé qu'elle demandaitEnumerable.Range
Ehtesh Choudhury

1
@EhteshChoudhury Enumerable.Range ne prendra pas 1,1000000 au moins vous appelez la méthode ToList ().
Luis

Réponses:


136

J'ai trouvé qu'il valait mieux rouler le mien. Certaines personnes utilisent Tuples ou Points, mais à la fin, vous voulez que vous Rangesoyez étendu et fournissez des méthodes pratiques liées à un fichier Range. Il est également préférable d'être générique (que faire si vous avez besoin d'une plage de Doubles ou d'une plage d'une classe personnalisée?) Par exemple:

/// <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
À l'époque, je n'étais pas sûr que IComparablecela garantirait la surcharge de l'opérateur. J'ai la garantie d'une CompareTométhode, d'où cet usage.
drharris

14
Il s'avère que j'avais raison de faire ça: stackoverflow.com/questions/5101378/…
drharris

2
Peut-être mieux structau lieu de class?
xmedeko

2
@Will hey man ne frappe pas la sagesse de la documentation barebones. :)
drharris

7
Très utile, merci! Pourquoi ne pas ajouter un constructeur qui vous permet de l'initialiser immédiatement? public Range(T min, T max) { Minimum = min; Maximum = max; }
NateJC

8

Juste un petit cours que j'ai écrit qui pourrait être utile à quelqu'un:

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

Ensuite, vous appelez simplement:

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

Et le résultat ressemble à:

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
J'aime vraiment la fonction Comprendre
Dragonborn

Cette understandfonction est cool. Si je voyais cela dans un examen de notre code, je suggérerais de le renommer Set, car Range(pour moi) cela semble continu.
dlw

7

Les plages et les indices sont publiés avec C # 8.0.

Vous êtes maintenant capable de faire

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

Consultez https://blogs.msdn.microsoft.com/dotnet/2018/12/05/take-c-8-0-for-a-spin/ pour plus de détails.


Il s'agit d'un concept différent et non conforme au PO souhaité.
PepitoSh

Le System.Rangetype n'accepte que int, cependant.
snipsnipsnip

Il est important de mentionner qu'il n'est disponible que depuis .NET Core 3.0. Il n'est pas disponible dans .NET Framework.
Sarrus

2

Amélioration de la réponse très utile de @ andrius-naruševičius pour la rendre plus idiomatique et facilement personnalisable

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

et les 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

Écrivez une méthode d'extension comme celle-ci

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

puis utilisez-le avec élégance

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

1
Pourquoi faire if (value >= from && value <= to) return true;et pas return (value >= from && value <= to)?
sdgfsdh

2

Cette implémentation, inspirée de la réponse de @drharris, vous permet de définir un intervalle mathématique approprié avec des valeurs, qui peuvent être inclusives / exclusives.

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

De plus, une tangente un peu différente ici, mais parfois les plages ne sont utiles que pour les parcourir, un peu comme il est habituel de le faire en python. Dans ce cas, l' System.Linqespace de noms définit une static IEnumerable<int> Range(Int32, Int32)méthode qui, comme le suggère la signature,

génère une séquence de nombres entiers dans une plage spécifiée

Voir la documentation et les exemples sur MSDN


@Seth il ne devrait pas car la plage Enumerable accepte le début et le nombre.
Rashid

@Rashid assez juste mais je ne considère pas vraiment cela comme un problème. Je sais qu'OP a demandé un «cours avec int Fromet int To», mais je suis assez convaincu que ce n'était pas une exigence spécifique mais plutôt quelque chose pour illustrer leur propos. Pour toute personne intéressée, bien sûr, la valeur du countparamètre peut être très facilement déterminé, acquis que vous avez int Fromet int Tovariables déjà.
Gaboik1

1

Il existe une Enumerable.Rangeméthode mais celle-ci accepte startet countcomme paramètre. Pour que cela fonctionne comme vous le souhaitez startet end, la endformule seraitend - start + 1

Usage:

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


0

Comme il me manquait également des intervalles en C #, j'ai implémenté une classe Interval entièrement générique qui peut même prendre en charge les intervalles avec des types plus complexes, par exemple un intervalle entre deux DateTime, ce qui impliqueTimeSpan des s pendant les calculs.

Un exemple de cas d'utilisation, où un élément GUI représente un intervalle de temps:

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

Pour une représentation plus complète des fonctionnalités prises en charge, consultez les tests unitaires .

Sous les couvertures, il utilise des arborescences d'expression pour compiler les opérations d'opérateur de type au moment de l'exécution , qui sont mises en cache de sorte qu'il n'y a qu'un coût la première fois que le type est initialisé.


-2

Que diriez-vous d'une structure ?


2
Je n'utiliserais pas de structure à moins que vous ne sachiez que les valeurs min et max ne changeraient jamais une fois instanciées.
IAbstract

Il répond à la question «déjà existent». Le type struct existe spécifiquement pour les types comme celui en question.
James Sumners

3
Struct n'est pas un type en soi. Vous dites essentiellement "non, créez votre propre type" - c'est une réponse légitime
Robert Levy

2
Les trois premiers mots de la documentation liée ne sont pas d'accord avec vous: "Le type struct ..."
James Sumners

J'ai tendance à être d'accord avec jsumners. Une structure est une manière simple et directe de le faire, et elle ne produit pas 2 pages de codes qui doivent être maintenues plus tard pour un problème aussi simple. Vous pouvez également utiliser une paire <...> pour la même chose. Microsoft enfreint sa propre «règle» à propos des structures immuables à divers endroits.
Tom
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.