Apakah ada tipe C # untuk mewakili Rentang integer?


105

Saya memiliki kebutuhan untuk menyimpan kisaran integer. Apakah sudah ada tipe untuk itu di C # 4.0?

Tentu saja, aku bisa menulis kelas saya sendiri dengan int Fromdan int Toproperti dan membangun dalam logika yang tepat untuk memastikan bahwa From <= To. Tetapi jika suatu tipe sudah ada, saya tentu saja lebih suka menggunakannya.


2
Saya pikir jawaban yang diterima harus diubah. Jawaban @ rsenna dengan Enumerable.Rangeadalah, dari apa yang saya lihat, cara de facto untuk mengimplementasikan rentang di C # 3.0. Dan C # 3.0 telah ada sejak 2007, jadi 9 tahun pada saat ini.
Ehtesh Choudhury

@EhteshChoudhury OP tidak menyebutkan apa pun tentang menangani nilai-nilai diskrit, atau bahkan memvalidasi tipe tersebut untuk min dan max, itulah yang Enumerable.Rangeberhasil. OP hanya mencari struktur data yang ada yang menangani Interval yang dapat memiliki properti batas bawah dan atas, dan tidak lebih (selain metode yang memberlakukan perilaku tertentu).
Cerah Patel

2
@EhteshChoudhury Saya akan menambahkan suara saya ke paduan suara "jangan gunakan Enumerable.Range". Itu cara yang mengerikan untuk memecahkan masalah sederhana dengan memegang sepasang nilai min / max. Setiap panggilan ke Enumerable.Max (IEnumerable <int>) (atau Enumerable.Min) mengulang seluruh rentang untuk mencari tahu batasannya. Seperti yang dikatakan orang lain, itu bisa jadi banyak iterasi: kita tidak berbicara tentang penyetelan kinerja mikro di sini, kita berbicara tentang kelambatan yang melumpuhkan. Pemrograman semacam itu adalah alasan .Net mendapat nama buruk (tidak adil) untuk kinerja! Jawaban yang diterima dan jawaban serupa adalah satu-satunya solusi praktis.
Daniel Scott

1
Itu adil. Enumerable.Rangeakan mengambil lebih banyak ruang untuk (1.100.000) vs tipe data Rentang untuk (1.100.000). Saya salah membaca pertanyaan saat pertama kali ditanya dan mengira pertanyaan itu memang ditanyakanEnumerable.Range
Ehtesh Choudhury

1
@EhteshChoudhury Enumerable.Range tidak akan membutuhkan 1.100.000 setidaknya Anda memanggil metode ToList ().
Luis

Jawaban:


136

Saya merasa paling baik menggulung sendiri. Beberapa orang menggunakan Tuples atau Points, tetapi pada akhirnya Anda ingin Rangememperluas dan menyediakan beberapa metode praktis yang berhubungan dengan a Range. Ini juga paling baik jika generik (bagaimana jika Anda memerlukan rentang Doubles, atau rentang beberapa kelas khusus?) Misalnya:

/// <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
Pada saat itu, saya tidak yakin itu IComparableakan menjamin operator kelebihan beban. Saya dijamin sebuah CompareTometode, maka penggunaan itu.
drharris

14
Ternyata saya benar dalam melakukan itu: stackoverflow.com/questions/5101378/…
drharris

2
Mungkin lebih baik structdaripada class?
xmedeko

2
@Will hey man jangan mengetuk kebijaksanaan dokumentasi barebones. :)
drharris

7
Sangat membantu, terima kasih! Mengapa tidak menambahkan konstruktor yang memungkinkan Anda untuk memulainya segera? public Range(T min, T max) { Minimum = min; Maximum = max; }
NateJC

8

Hanya kelas kecil yang saya tulis yang dapat membantu seseorang:

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

Kemudian Anda tinggal menelepon:

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

Dan hasilnya terlihat seperti:

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
Saya sangat menyukai fungsi mengerti
Dragonborn

Itu understandfungsi keren. Jika saya melihat ini dalam ulasan di kode kami, saya sarankan untuk mengganti nama ini menjadi Set, karena Range(bagi saya) terdengar terus menerus.
dlw

7

Rentang dan Indeks dirilis dengan C # 8.0.

Anda sekarang bisa melakukannya

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

Lihat https://blogs.msdn.microsoft.com/dotnet/2018/12/05/take-c-8-0-for-a-spin/ untuk detail lebih lanjut.


Ini adalah konsep yang berbeda dan tidak sesuai keinginan OP.
PepitoSh

The System.Rangetipe hanya menerima int, meskipun.
snipsnipsnip

Penting untuk disebutkan bahwa ini hanya tersedia sejak .NET Core 3.0. Tidak tersedia di .NET Framework.
Sarrus

2

Memperbaiki @ andrius-naruševičius, jawaban yang sangat membantu agar lebih idiomatis dan mudah disesuaikan

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

dan tesnya:

    [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

Tulis metode ekstensi seperti ini

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

dan kemudian menggunakannya dengan elegan

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

1
Mengapa if (value >= from && value <= to) return true;dan tidak return (value >= from && value <= to)?
sdgfsdh

2

Implementasi ini, terinspirasi oleh jawaban @drharris, memungkinkan Anda untuk menentukan interval matematika yang tepat dengan nilai, yang bisa Inklusif / Eksklusif.

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

Juga, sedikit garis singgung yang berbeda di sini tetapi terkadang rentang hanya berguna untuk mengulanginya, seperti yang biasa dilakukan dengan python. Dalam hal ini, System.Linqnamespace mendefinisikan static IEnumerable<int> Range(Int32, Int32)metode, yang, seperti yang disarankan oleh tanda tangan,

menghasilkan urutan bilangan integral dalam rentang tertentu

Lihat dokumentasi dan contoh di MSDN


@Seth seharusnya tidak karena Enumerable range menerima start dan count.
Rashid

@Rashid cukup adil tetapi saya tidak menganggapnya sebagai masalah. Saya tahu OP meminta "kelas dengan int Fromdan int To" tetapi saya cukup yakin bahwa ini bukanlah persyaratan khusus, melainkan sesuatu untuk menggambarkan maksud mereka. Bagi siapa pun yang tertarik, tentu saja nilai countparameter dapat dengan mudah ditentukan, asalkan Anda sudah memiliki int Fromdan int Tovariabel.
Gaboik1

1

Ada Enumerable.Rangemetode tetapi yang ini menerima startdan countsebagai parameternya. Agar itu berfungsi seperti yang Anda inginkan startdan end, endrumusnya akan menjadiend - start + 1

Pemakaian:

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


0

Karena saya juga kehilangan interval dalam C #, saya menerapkan kelas Interval yang sepenuhnya generik yang bahkan dapat menangani interval dengan jenis yang lebih kompleks, misalnya interval antara dua DateTime, yang melibatkan TimeSpanselama perhitungan.

Contoh kasus penggunaan, di mana elemen GUI mewakili interval waktu:

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

Untuk representasi yang lebih luas dari fungsionalitas yang didukung, periksa pengujian unit .

Di bawah sampulnya, ia menggunakan pohon ekspresi untuk mengompilasi operasi operator tipe pada waktu proses , yang di-cache sehingga hanya ada biaya saat pertama kali jenis diinisialisasi.


-2

Bagaimana dengan struct ?


2
Saya tidak akan menggunakan struct kecuali Anda tahu bahwa nilai min & max tidak akan pernah berubah setelah dipakai.
IAbstract

Itu menjawab pertanyaan "sudah ada". Jenis struct ada khusus untuk jenis seperti yang dimaksud.
James Sumners

3
Struct bukanlah tipe dalam dan dari dirinya sendiri. Pada dasarnya Anda mengatakan "jangan buat tipe Anda sendiri" - itu jawaban yang sah
Robert Levy

2
The tiga kata pertama dalam dokumentasi terkait tidak setuju dengan Anda: "Jenis struct ..."
James Sumners

Saya cenderung setuju dengan pelawak. Sebuah struct adalah cara yang mudah dan langsung untuk melakukan ini, dan tidak menghasilkan 2 halaman kode yang harus dipertahankan nanti untuk masalah sederhana seperti itu. Anda juga bisa menggunakan Pair <...> untuk hal yang sama. Microsoft melanggar "aturan" mereka sendiri tentang struct yang tidak dapat diubah di berbagai tempat.
Tom
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.