มีประเภท C # สำหรับแสดงช่วงจำนวนเต็มหรือไม่?


105

ฉันมีความต้องการที่จะจัดเก็บช่วงจำนวนเต็ม มีประเภทที่มีอยู่สำหรับสิ่งนั้นใน C # 4.0 หรือไม่?

แน่นอนฉันจะเขียนชั้นเรียนของฉันเองด้วยint Fromและคุณสมบัติและสร้างในตรรกะที่เหมาะสมเพื่อให้มั่นใจว่าint To From <= Toแต่ถ้ามีประเภทอยู่แล้วแน่นอนว่าฉันควรใช้แบบนั้น


2
ฉันคิดว่าคำตอบที่ได้รับการยอมรับควรมีการเปลี่ยนแปลง คำตอบของ @ rsenna Enumerable.Rangeคือจากสิ่งที่ฉันได้เห็นวิธีการใช้งานช่วงใน C # 3.0 โดยพฤตินัย และ C # 3.0 มีมาตั้งแต่ปี 2550 ดังนั้น 9 ปีที่มาถึงจุดนี้
Ehtesh Choudhury

@EhteshChoudhury OP ไม่ได้กล่าวถึงอะไรเกี่ยวกับการจัดการค่าที่ไม่ต่อเนื่องหรือแม้กระทั่งการตรวจสอบความถูกต้องของประเภทเหล่านั้นสำหรับค่าต่ำสุดและสูงสุดซึ่งเป็นสิ่งที่Enumerable.Rangeบรรลุผล OP กำลังมองหาโครงสร้างข้อมูลที่มีอยู่ซึ่งจัดการกับช่วงเวลาที่สามารถมีคุณสมบัติของขอบเขตล่างและบนและไม่มีอะไรเพิ่มเติม (นอกจากวิธีการที่บังคับใช้พฤติกรรมบางอย่าง)
Sunny Patel

2
@EhteshChoudhury ฉันจะเพิ่มเสียงของฉันลงในคอรัส "ไม่ใช้ Enumerable.Range" นั่นเป็นวิธีที่น่ากลัวในการแก้ปัญหาง่ายๆในการจับคู่ของค่าต่ำสุด / สูงสุด ทุกครั้งที่เรียก Enumerable.Max (IEnumerable <int>) (หรือ Enumerable.Min) จะวนซ้ำในช่วงทั้งหมดเพื่อหาขอบเขต อย่างที่คนอื่น ๆ พูดนั่นอาจเป็นการทำซ้ำหลายครั้ง: เราไม่ได้พูดถึงการปรับแต่งประสิทธิภาพขนาดเล็กที่นี่เรากำลังพูดถึงความช้าที่ทำให้พิการ การเขียนโปรแกรมแบบนั้นเป็นสาเหตุที่ทำให้ Net ได้รับชื่อเสีย (อย่างไม่ยุติธรรม) สำหรับประสิทธิภาพ! คำตอบที่ได้รับการยอมรับและคำตอบที่คล้ายกันนี้เป็นคำตอบที่ใช้ได้จริง
Daniel Scott

1
นั่นยุติธรรมแล้ว Enumerable.Rangeจะใช้พื้นที่มากขึ้นสำหรับ (1,1000000) เทียบกับประเภทข้อมูลช่วงสำหรับ (1,1000000) ฉันอ่านคำถามผิดในครั้งแรกที่ถูกถามและคิดว่ามันกำลังถามEnumerable.Range
Ehtesh Choudhury

1
@EhteshChoudhury Enumerable ช่วงจะไม่ใช้ 1,1000000 อย่างน้อยคุณเรียก ToList () method
Luis

คำตอบ:


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

14
ปรากฎว่าฉันทำถูกแล้ว: stackoverflow.com/questions/5101378/…
drharris

2
อาจจะดีกว่าstructแทนclass?
xmedeko

2
@ เฮ้ผู้ชายจะไม่เคาะภูมิปัญญาของเอกสารแบร์โบน :)
drharris

7
มีประโยชน์มากขอบคุณ! ทำไมไม่เพิ่มตัวสร้างที่ช่วยให้คุณเริ่มต้นได้ทันที? public Range(T min, T max) { Minimum = min; Maximum = max; }
NateJC

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 และ. NET Core

ตอนนี้คุณสามารถทำได้แล้ว

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

1
System.Rangeประเภทยอมรับเฉพาะintแต่
snipsnipsnip

1
สิ่งสำคัญคือต้องระบุว่ามีให้ใช้งานตั้งแต่. 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 ไม่ควรเนื่องจากช่วงที่นับได้ยอมรับการเริ่มต้นและนับ
Rashid

@Rashid ยุติธรรมพอ แต่ฉันไม่คิดว่ามันเป็นปัญหาจริงๆ ฉันรู้ว่า OP ขอ "คลาสที่มีint Fromและint To" แต่ฉันค่อนข้างมั่นใจว่านี่ไม่ใช่ข้อกำหนดเฉพาะ แต่เป็นสิ่งที่แสดงถึงประเด็นของพวกเขา สำหรับทุกคนที่สนใจแน่นอนว่าค่าของcountพารามิเตอร์สามารถกำหนดได้ง่ายมากเนื่องจากคุณมีint Fromและint Toตัวแปรอยู่แล้ว
Gaboik1

1

มีEnumerable.Rangeวิธีการหนึ่ง แต่วิธีนี้ยอมรับstartและcountเป็นพารามิเตอร์ ในการสั่งซื้อเพื่อให้การทำงานเป็นสิ่งที่คุณต้องการstartและendที่endสูตรจะเป็นend - start + 1

การใช้งาน:

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


0

เนื่องจากฉันขาดช่วงเวลาใน C # ฉันจึงใช้คลาส Interval ทั่วไปเต็มรูปแบบซึ่งสามารถดูแลช่วงเวลาที่มีประเภทที่ซับซ้อนมากขึ้นเช่นช่วงเวลาระหว่างสองDateTimeซึ่งเกี่ยวข้องกับTimeSpanระหว่างการคำนวณ

ตัวอย่างการใช้งานโดยที่องค์ประกอบ 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

วิธีการเกี่ยวกับstruct ?


2
ฉันจะไม่ใช้โครงสร้างเว้นแต่คุณจะรู้ว่าค่าต่ำสุด & สูงสุดจะไม่เปลี่ยนแปลงทันทีที่สร้างอินสแตนซ์
IAbstract

มันตอบคำถาม "มีอยู่แล้ว" ประเภทของโครงสร้างมีอยู่โดยเฉพาะสำหรับประเภทเช่นเดียวกับที่มีปัญหา
James Sumners

3
โครงสร้างไม่ใช่ประเภทของตัวมันเอง คุณกำลังพูดว่า "ไม่ไปสร้างประเภทของคุณเอง" นั่นคือคำตอบที่ถูกต้อง
Robert Levy

2
แรกสามคำในเอกสารที่เชื่อมโยงไม่เห็นด้วยกับคุณ: "ประเภท struct ..."
เจมส์ Sumners

ฉันมักจะเห็นด้วยกับ jsumners โครงสร้างเป็นวิธีที่ง่ายและตรงไปตรงมาในการทำเช่นนี้และไม่ได้สร้างโค้ด 2 หน้าซึ่งต้องได้รับการดูแลในภายหลังสำหรับปัญหาง่ายๆเช่นนี้ คุณยังสามารถใช้คู่ <... > สำหรับสิ่งเดียวกัน Microsoft ทำลาย "กฎ" ของตัวเองเกี่ยวกับโครงสร้างที่ไม่เปลี่ยนรูปในที่ต่างๆ
ทอม
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.