Có loại C # nào để biểu diễn một Dải số nguyên không?


105

Tôi có nhu cầu lưu trữ một dãy số nguyên. Có loại nào hiện có cho điều đó trong C # 4.0 không?

Tất nhiên, tôi có thể viết lớp của riêng mình với int Fromint Tocác thuộc tính và xây dựng theo logic thích hợp để đảm bảo điều đó From <= To. Nhưng nếu một kiểu đã tồn tại, tất nhiên tôi muốn sử dụng kiểu đó hơn.


2
Tôi nghĩ câu trả lời được chấp nhận nên được thay đổi. Câu trả lời của @ rsenna Enumerable.Rangelà, từ những gì tôi đã thấy, cách thực tế để triển khai các phạm vi trong C # 3.0. Và C # 3.0 đã xuất hiện từ năm 2007, đến thời điểm này là 9 năm.
Ehtesh Choudhury

@EhteshChoudhury OP không đề cập bất cứ điều gì về việc xử lý các giá trị rời rạc, hoặc thậm chí xác thực các loại đó cho tối thiểu và tối đa, đó là những gì Enumerable.Rangehoàn thành. OP chỉ đơn giản là tìm kiếm một cấu trúc dữ liệu hiện có xử lý các Khoảng có thể có các thuộc tính của giới hạn dưới và giới hạn trên, và không có gì khác (ngoài các phương thức thực thi các hành vi nhất định).
Sunny Patel

2
@EhteshChoudhury Tôi sẽ thêm giọng của mình vào điệp khúc "không sử dụng Enumerable.Range". Đó là một cách ghê tởm để giải quyết vấn đề đơn giản là giữ chặt một cặp giá trị tối thiểu / tối đa. Mọi lệnh gọi tới Enumerable.Max (IEnumerable <int>) (hoặc Enumerable.Min) lặp lại trên toàn bộ phạm vi để tìm ra giới hạn. Như những người khác đã nói, đó có thể là rất nhiều lần lặp lại: chúng ta không nói về điều chỉnh hiệu suất vi mô ở đây, chúng ta đang nói về sự chậm chạp làm tê liệt. Kiểu lập trình đó là lý do khiến .Net bị đặt tên xấu (vô cớ) cho hiệu suất! Câu trả lời được chấp nhận và các câu trả lời tương tự là giải pháp thực tế duy nhất.
Daniel Scott

1
Đó là công bằng. Enumerable.Rangesẽ chiếm nhiều dung lượng hơn cho (1.1000000) so với kiểu dữ liệu Phạm vi cho (1.1000000). Tôi đọc những câu hỏi sai lần đầu tiên nó được yêu cầu và nghĩ rằng nó đã được yêu cầuEnumerable.Range
Ehtesh Choudhury

1
@EhteshChoudhury Enumerable.Range sẽ không mất 1.1000000 ít nhất khi bạn gọi phương thức ToList ().
Luis

Câu trả lời:


135

Tôi thấy tốt nhất là tự mình lăn bánh. Một số người sử dụng Tuples hoặc Points, nhưng cuối cùng bạn muốn của bạn Rangeđược mở rộng và cung cấp một số phương pháp hữu ích liên quan đến a Range. Nó cũng tốt nhất nếu chung chung (điều gì xảy ra nếu bạn cần một phạm vi Doubles hoặc một phạm vi của một số lớp tùy chỉnh?) Ví dụ:

/// <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
Vào thời điểm đó, tôi không chắc rằng điều đó IComparablesẽ đảm bảo quá tải cho nhà điều hành. Tôi được đảm bảo một CompareTophương pháp, do đó cách sử dụng đó.
drharris

14
Hóa ra tôi đã đúng khi làm điều đó: stackoverflow.com/questions/5101378/…
drharris

2
Có thể tốt hơn structthay vì class?
xmedeko

2
@ Này người đàn ông sẽ không đánh gục sự khôn ngoan của tài liệu đầu tiên. :)
drharris

6
Rất hữu ích, cảm ơn! Tại sao không thêm một hàm tạo cho phép bạn khởi tạo nó ngay lập tức? public Range(T min, T max) { Minimum = min; Maximum = max; }
NateJC

8

Chỉ là một lớp học nhỏ tôi đã viết có thể hữu ích cho ai đó:

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

Sau đó, bạn chỉ cần gọi:

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

Và kết quả như sau:

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
Tôi thực sự thích chức năng hiểu
Dragonborn

understandChức năng đó thật tuyệt. Nếu tôi thấy điều này trong một bài đánh giá trong mã của chúng tôi, tôi khuyên bạn nên đổi tên nó thành Set, vì Range(đối với tôi) nghe có vẻ liên tục.
dlw

7

Phạm vi và Chỉ số được phát hành với C # 8.0.

Bây giờ bạn có thể làm

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

Hãy xem https://blogs.msdn.microsoft.com/dotnet/2018/12/05/take-c-8-0-for-a-spin/ để biết thêm chi tiết.


Đây là một khái niệm khác và không theo ý muốn của OP.
PepitoSh

Các System.Rangeloại chỉ chấp nhận int, mặc dù.
snipsnipsnip

Điều quan trọng cần đề cập là nó chỉ khả dụng kể từ .NET Core 3.0. Nó không khả dụng trong .NET Framework.
Sarrus

2

Cải thiện câu trả lời rất hữu ích trên @ andrius-naruševičius để làm cho câu trả lời dễ thành ngữ hơn và dễ dàng tùy chỉnh

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

và các bài kiểm tra:

    [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

Viết một phương thức mở rộng như thế này

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

và sau đó sử dụng nó một cách thanh lịch

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

1
Tại sao làm if (value >= from && value <= to) return true;và không return (value >= from && value <= to)?
sdgfsdh

2

Việc triển khai này, lấy cảm hứng từ câu trả lời của @drharris, cho phép bạn xác định khoảng toán học thích hợp với các giá trị, có thể là Bao gồm / Loại trừ.

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

Ngoài ra, một chút khác biệt ở đây nhưng đôi khi các phạm vi chỉ hữu ích để lặp lại chúng, giống như một thói quen thường làm trong python. Trong trường hợp đó, System.Linqkhông gian tên xác định một static IEnumerable<int> Range(Int32, Int32)phương thức, như chữ ký gợi ý,

tạo ra một chuỗi các số tích phân trong một phạm vi xác định

Xem tài liệu và ví dụ trên MSDN


@Seth nó không nên vì phạm vi Enumerable chấp nhận bắt đầu và đếm.
Rashid

@Rashid đủ công bằng nhưng tôi không thực sự coi đó là một vấn đề. Tôi biết OP đã yêu cầu một "lớp với int Fromint To" nhưng tôi khá tự tin rằng đây không phải là một yêu cầu cụ thể mà là một thứ để minh họa cho quan điểm của họ. Tất nhiên, đối với bất kỳ ai quan tâm, giá trị của counttham số có thể được xác định rất dễ dàng, cho rằng bạn đã có int Fromint Tocác biến.
Gaboik1

1

Có một Enumerable.Rangephương thức nhưng phương thức này chấp nhận startcountlàm tham số của nó. Để cho nó hoạt động như những gì bạn muốn startend, các endcông thức sẽ làend - start + 1

Sử dụng:

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


0

Vì tôi cũng bị thiếu các khoảng trong C #, nên tôi đã triển khai một lớp Khoảng thời gian chung chung đầy đủ mà thậm chí có thể quan tâm đến các khoảng với các kiểu phức tạp hơn, ví dụ khoảng giữa hai DateTime', liên quan đến TimeSpan' trong khi tính toán.

Một trường hợp sử dụng ví dụ, trong đó phần tử GUI đại diện cho một khoảng thời gian:

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

Để có bản trình bày rộng rãi hơn về chức năng được hỗ trợ, hãy kiểm tra các bài kiểm tra đơn vị .

Dưới vỏ bọc, nó sử dụng cây biểu thức để biên dịch các hoạt động của toán tử kiểu trong thời gian chạy , được lưu trong bộ nhớ cache để chỉ có chi phí khi kiểu đầu tiên được khởi tạo.


-2

Làm thế nào về một cấu trúc ?


2
Tôi sẽ không sử dụng cấu trúc trừ khi bạn biết rằng giá trị tối thiểu & tối đa sẽ không bao giờ thay đổi sau khi được khởi tạo.
IAbstract

Nó trả lời câu hỏi "đã tồn tại". Loại cấu trúc tồn tại đặc biệt cho các loại như loại được đề cập.
James Sumners

3
Struct không phải là một loại trong và của chính nó. Về cơ bản bạn đang nói "không đi tạo kiểu riêng của bạn" - đó là một câu trả lời hợp pháp
Robert Levy

2
Các ba chữ đầu tiên trong tài liệu liên quan không đồng ý với bạn: "Các cấu trúc kiểu ..."
James Sumners

Tôi có xu hướng đồng ý với jsumners. Một cấu trúc là một cách dễ dàng và đơn giản để làm điều này và nó không tạo ra 2 trang mã phải được duy trì sau này cho một vấn đề đơn giản như vậy. Bạn cũng có thể sử dụng Cặp <...> cho cùng một thứ. Microsoft đã phá vỡ "quy tắc" của riêng họ về việc cấu trúc là bất biến ở nhiều nơi khác nhau.
Tom
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.