Existe um tipo C # para representar um intervalo de inteiros?


105

Eu preciso armazenar um intervalo inteiro. Existe um tipo existente para isso no C # 4.0?

Claro, eu poderia escrever minha própria classe com int Frome int Topropriedades e construção na lógica adequada para garantir que From <= To. Mas se já existe um tipo, é claro que prefiro usá-lo.


2
Acho que a resposta aceita deve ser alterada. A resposta de @rsenna com Enumerable.Rangeé, pelo que tenho visto, a maneira de fato de implementar intervalos em C # 3.0. E o C # 3.0 existe desde 2007, então 9 anos neste ponto.
Ehtesh Choudhury

@EhteshChoudhury O OP não menciona nada sobre como lidar com valores discretos, ou mesmo validar esses tipos para um mínimo e máximo, que é o que acontece Enumerable.Range. OP estava simplesmente procurando por uma estrutura de dados existente que lida com intervalos que podem ter propriedades de um limite inferior e superior, e nada mais (além de métodos que impõem certos comportamentos).
Sunny Patel

2
@EhteshChoudhury Vou adicionar minha voz ao refrão "não use Enumerable.Range". Essa é uma maneira horrível de resolver o problema simples de manter um par de valores mínimo / máximo. Cada chamada para Enumerable.Max (IEnumerable <int>) (ou Enumerable.Min) itera em todo o intervalo para descobrir os limites. Como outros já disseram, isso poderia ser um monte de iterações: não estamos falando de ajuste de micro desempenho aqui, estamos falando de uma lentidão paralisante. Esse tipo de programação é o motivo pelo qual .Net recebe uma (injustamente) má fama para desempenho! A resposta aceita e respostas semelhantes são as únicas soluções práticas.
Daniel Scott

1
Isso é justo. Enumerable.Rangeocupará muito mais espaço para (1,1000000) vs um tipo de dados Range para (1,1000000). Eu li a pergunta errado na primeira vez que foi feita e pensei que estava perguntandoEnumerable.Range
Ehtesh Choudhury

1
@EhteshChoudhury Enumerable.Range não levará 1,1000000 pelo menos quando você chamar o método ToList ().
Luis

Respostas:


135

Achei melhor rolar por conta própria. Algumas pessoas usam Tuples ou Points, mas no final você deseja Rangeque seja extenso e forneça alguns métodos úteis relacionados a a Range. Também é melhor se for genérico (e se você precisar de um intervalo de Doubles ou de alguma classe personalizada?) Por exemplo:

/// <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
Na época, eu não tinha certeza se isso IComparablegarantiria a sobrecarga do operador. Tenho a garantia de um CompareTométodo, daí esse uso.
drharris

14
Acontece que eu estava certo ao fazer isso: stackoverflow.com/questions/5101378/…
drharris

2
Pode ser melhor em structvez de class?
xmedeko

2
@ Will, cara, não destrua a sabedoria da documentação básica. :)
drharris

6
Muito útil, obrigado! Por que não adicionar um construtor que permite inicializá-lo imediatamente? public Range(T min, T max) { Minimum = min; Maximum = max; }
NateJC

8

Apenas uma pequena aula que escrevi que pode ser útil para alguém:

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

Então você apenas liga:

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

E o resultado é o seguinte:

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
Eu realmente gosto da função de compreensão
Dragonborn

Essa understandfunção é legal. Se eu vi isso em uma revisão em nosso código, sugeriria renomear para Set, porque Range(para mim) parece contínuo.
dlw

7

Intervalos e índices são lançados com C # 8.0.

Agora você é capaz de fazer

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

Confira https://blogs.msdn.microsoft.com/dotnet/2018/12/05/take-c-8-0-for-a-spin/ para mais detalhes.


Este é um conceito diferente e não de acordo com o OP pretendido.
PepitoSh 01 de

O System.Rangetipo só aceita int, no entanto.
snipsnipsnip

É importante mencionar que ele está disponível apenas a partir do .NET Core 3.0. Não está disponível no .NET Framework.
Sarrus

2

Melhorar a resposta @ andrius-naruševičius muito útil para torná-la mais idiomática e facilmente personalizável

/// <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 os testes:

    [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

Escreva um método de extensão como este

 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 então usá-lo elegantemente

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

1
Por que fazer if (value >= from && value <= to) return true;e não fazer return (value >= from && value <= to)?
sdgfsdh

2

Esta implementação, inspirada na resposta de @drharris, permite definir um intervalo matemático adequado com valores, que podem ser Inclusivos / Exclusivos.

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

Além disso, um pouco de uma tangente diferente aqui, mas às vezes os intervalos são úteis apenas para iterar sobre eles, um pouco como é comum fazer em python. Nesse caso, o System.Linqnamespace define um static IEnumerable<int> Range(Int32, Int32)método, que, como a assinatura sugere,

gera uma sequência de números inteiros dentro de um intervalo especificado

Veja a documentação e exemplos no MSDN


@Seth não deveria, pois o intervalo Enumerable aceita início e contagem.
Rashid

@Rashid é justo, mas não considero isso um problema. Sei que o OP pediu uma "aula com int Frome int To", mas estou bastante confiante de que esse não era um requisito específico, mas sim algo para ilustrar seu ponto. Para qualquer pessoa interessada, é claro, o valor do countparâmetro pode ser facilmente determinado, desde que você já tenha int Frome int Tovariáveis.
Gaboik1

1

Existe um Enumerable.Rangemétodo, mas este aceita starte countcomo parâmetro. Para que funcione como você deseja starte end, a endfórmula seriaend - start + 1

Uso:

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


0

Como também estava faltando intervalos em C #, implementei uma classe Interval totalmente genérica que pode até mesmo cuidar de intervalos com tipos mais complexos, por exemplo, um intervalo entre dois DateTime's, que envolve TimeSpan' s durante os cálculos.

Um exemplo de caso de uso, em que um elemento GUI representa um intervalo de 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 ) );

Para uma representação mais ampla da funcionalidade com suporte, verifique os testes de unidade .

Nos bastidores, ele usa árvores de expressão para compilar operações de operador de tipo em tempo de execução , que são armazenadas em cache para que haja apenas um custo na primeira vez em que o tipo é inicializado.


-2

Que tal uma estrutura ?


2
Eu não usaria uma estrutura a menos que você soubesse que os valores mínimo e máximo nunca mudariam depois de instanciados.
IAbstract

Ele responde à pergunta "já existe". O tipo de estrutura existe especificamente para tipos como o em questão.
James Sumners,

3
Struct não é um tipo em si. Você está basicamente dizendo "não, vá, crie seu próprio tipo" - essa é uma resposta legítima
Robert Levy,

2
As três primeiras palavras na documentação vinculada discordam de você: "O tipo de estrutura ..."
James Sumners

Eu tendo a concordar com jsumners. Uma estrutura é uma maneira fácil e direta de fazer isso, e não produz 2 páginas de códigos que precisam ser mantidas posteriormente para um problema tão simples. Você também pode usar um Par <...> para a mesma coisa. A Microsoft quebrou sua própria "regra" sobre as estruturas serem imutáveis ​​em vários lugares.
Tom
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.