是否有C#类型表示整数Range?


105

我需要存储一个整数范围。C#4.0中是否存在该类型?

当然,我可以使用int Fromint To属性编写自己的类,并构建适当的逻辑以确保From <= To。但是,如果类型已经存在,我当然会使用它。


2
我认为应该更改接受的答案。@rsenna的答案Enumerable.Range是,据我所知,实际上是在C#3.0中实现范围的方法。C#3.0自2007年问世以来,已经有9年了。
Ehtesh Choudhury

@EhteshChoudhury OP没有提及任何有关处理离散值,甚至没有验证最小和最大类型的事情,这就是Enumerable.Range完成的事情。OP只是在寻找处理间隔的现有数据结构,该间隔可以具有上下限的属性,仅此而已(除了强制执行某些行为的方法外)。
阳光明媚的帕特尔

2
@EhteshChoudhury我会将自己的声音添加到“不要使用Enumerable.Range”合唱中。这是解决保留一对最小/最大值的简单问题的可怕方法。每次对Enumerable.Max(IEnumerable <int>)(或Enumerable.Min)的调用都会在整个范围内进行迭代以找出界限。正如其他人所说,这可能是很多迭代:我们这里不是在谈论微性能调整,我们是在讨论缓慢的性能。这类编程就是.Net为性能而(不恰当地)取坏名字的原因!公认的答案和类似答案是唯一可行的解​​决方案。
丹尼尔·斯科特

1
这还算公平。Enumerable.Range与(1,1000000)的Range数据类型相比,将为(1,1000000)占用更多的空间。我第一次问这个问题时就读错了,并以为是问Enumerable.Range
Ehtesh Choudhury

1
@EhteshChoudhury Enumerable.Range不会占用1,1000000,至少您调用ToList()方法。
路易斯

Answers:


136

我发现最好自己动手。有些人使用Tuples或Points,但是最后您希望您使用或,Range并且提供一些与有关的便捷方法Range。最好是通用的(如果需要一定范围的Doubles或一定范围的某些自定义类,该怎么办?)例如:

/// <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
当时,我不确定这IComparable是否会保证操作员过载。我保证有一种CompareTo方法,因此可以使用。
drharris 2011年

14
事实证明我是正确的这样做:stackoverflow.com/questions/5101378/...
drharris

2
可能会更好struct而不是class
xmedeko

2
@嘿男人不会敲打准系统文档的智慧。:)
drharris '16

7
非常有帮助,谢谢!为什么不添加允许您立即对其进行初始化的构造函数? public Range(T min, T max) { Minimum = min; Maximum = max; }
NateJC'1

8

我写的一小节课可能对某人有帮助:

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

然后,您只需致电:

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

结果看起来像:

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
我真的很喜欢理解功能
Dragonborn

understand功能很酷。如果我在代码中的评论中看到了此内容,建议将其重命名为Set,因为Range(对我而言)听起来是连续的。
dlw

7

范围和索引随C#8.0一起发布。

您现在可以做

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

查看https://blogs.msdn.microsoft.com/dotnet/2018/12/05/take-c-8-0-for-a-spin/了解更多信息。


这是一个不同的概念,而不是所需的OP。
PepitoSh

System.Range类型仅接受int
snipsnipsnip

重要的是要提到它仅从.NET Core 3.0开始可用。在.NET Framework中不可用。
Sarrus

2

改进@andrius-naruševičius很有帮助的答案,使其更加惯用且易于自定义

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

和测试:

    [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

编写这样的扩展方法

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

然后优雅地使用它

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

1
为什么if (value >= from && value <= to) return true;return (value >= from && value <= to)
sdgfsdh

2

此实现受@drharris答案的启发,允许您使用值(包含/不包含)定义适当的数学区间。

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

另外,这里的切线有些不同,但有时范围仅对迭代它们有用,就像在python中习惯那样。在这种情况下,System.Linq名称空间定义了一个static IEnumerable<int> Range(Int32, Int32)方法,如签名所示,

生成指定范围内的整数序列

请参阅MSDN上的文档和示例


@Seth不应这样做,因为Enumerable范围接受开始和计数。
拉希德

@Rashid足够公平,但是我真的不认为这是一个问题。我知道OP要求使用“ int From和”类,int To但是我非常有信心,这不是特定的要求,而是可以说明他们观点的东西。对于任何感兴趣的人,当然,count只要您已经拥有int Fromint To变量,就可以非常容易地确定参数的值。
Gaboik 1

1

有一种Enumerable.Range方法,但是这个方法接受startcount作为其参数。为了让它工作,你想要什么start,并end,该end公式将是end - start + 1

用法:

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


0

由于我也缺少C#中的间隔,因此我实现了一个完全通用的Interval类,它甚至可以处理具有更复杂类型的间隔,例如DateTimeTimeSpan在计算过程中涉及到的两个之间的间隔。

一个示例用例,其中GUI元素表示时间间隔:

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

要获得所支持功能的更广泛表示,请查看单元测试

在幕后,它使用表达式树在运行时编译类型运算符操作,这些操作被缓存,因此第一次初始化类型仅花费了一笔。


-2

一个结构怎么样?


2
除非您知道最小值和最大值一旦实例化就不会改变,否则我不会使用结构。
IAbstract

它确实回答了“已经存在”的问题。struct类型专门用于所讨论的类型。
James Sumners 2011年

3
结构本身不是一种类型。您基本上是在说“不要去创建自己的类型”-这是一个合法的答案
Robert Levy

2
链接文档中的前三个词与您不同意:“结构类型...”
James Sumners 2011年

我倾向于同意jsumners。struct是一种简单而直接的方法,它不会产生2页的代码,以后针对此类简单问题必须对其进行维护。您也可以将Pair <...>用于同一件事。微软打破了自己的关于结构在各个地方不变的“规则”。
汤姆(Tom)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.