Bir tamsayı Aralığı temsil eden bir C # türü var mı?


105

Bir tamsayı aralığı kaydetmem gerekiyor. C # 4.0'da bunun için mevcut bir tür var mı?

Tabii ki, kendi sınıfımı int Fromve int Toözellikleriyle yazabilir ve bunu sağlamak için uygun bir mantık oluşturabilirim From <= To. Ama zaten bir tür varsa, elbette bunu kullanmayı tercih ederim.


2
Kabul edilen cevabın değiştirilmesi gerektiğini düşünüyorum. @ rsenna'nın cevabı Enumerable.Range, gördüğüm kadarıyla, C # 3.0'da aralıkları uygulamanın fiili yoludur. Ve C # 3.0 2007'den beri var, yani bu noktada 9 yıldır.
Ehtesh Choudhury

@EhteshChoudhury OP, ayrık değerlerin işlenmesi ve hatta bu türlerin bir min ve maks için doğrulanması hakkında hiçbir şeyden bahsetmez, ki bunu Enumerable.Rangebaşarır. OP, sadece bir alt ve üst sınırın özelliklerine sahip olabilen Aralıkları işleyen mevcut bir veri yapısını arıyordu ve daha fazlasını değil (belirli davranışları zorlayan yöntemlerin yanı sıra).
Sunny Patel

2
@EhteshChoudhury "Enumerable.Range kullanma" korosuna sesimi ekleyeceğim. Bu, bir çift min / maks değerine bağlı kalma sorununu çözmenin çirkin bir yolu. Enumerable.Max (IEnumerable <int>) (veya Enumerable.Min) 'e yapılan her çağrı, sınırları bulmak için tüm aralık boyunca yinelenir. Başkalarının da söylediği gibi, bu pek çok yineleme olabilir: burada mikro performans ayarından bahsetmiyoruz, sakat bırakan yavaşlıktan bahsediyoruz. Bu tür bir programlama .Net'in performans için (haksız olarak) kötü bir isim almasının nedenidir! Kabul edilen cevap ve benzer cevaplar tek pratik çözümdür.
Daniel Scott

1
Bu adil. Enumerable.Range(1,1000000) için bir Aralık veri türü için (1,1000000) için çok daha fazla yer kaplar. Soruyu ilk sorulduğunda yanlış okudum ve sorduğunu düşündümEnumerable.Range
Ehtesh Choudhury

1
@EhteshChoudhury Enumerable.Range 1,1000000 almayacaktır en azından ToList () yöntemini çağırırsınız.
Luis

Yanıtlar:


136

En iyisi kendiminkini atmayı buldum. Bazı insanlar Tuples veya Points kullanır, ancak sonunda Rangekapsamlı olmanızı ve a ile ilgili bazı kullanışlı yöntemler sağlamanızı istersiniz Range. Genel ise en iyisidir (bir dizi Doublee veya özel bir sınıf aralığına ihtiyacınız varsa ?) Örneğin:

/// <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
O zamanlar IComparablebunun operatörün aşırı yüklenmesini garanti edeceğinden emin değildim . Bir CompareToyöntem garanti ediyorum , dolayısıyla bu kullanım.
drharris

14
Bunu yaparken haklı olduğum ortaya çıktı: stackoverflow.com/questions/5101378/…
drharris

2
Daha iyi olabilir structyerine class?
xmedeko

2
@ Hey adam, barebones belgelerinin bilgeliğini çalmaz. :)
drharris

7
Çok yardımcı oldu, teşekkürler! Neden onu hemen başlatmanıza izin veren bir kurucu eklemiyorsunuz? public Range(T min, T max) { Minimum = min; Maximum = max; }
NateJC

8

Birisi için yararlı olabilecek, yazdığım küçük bir sınıf:

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

Sonra ara:

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

Ve sonuç şöyle görünür:

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
Anlama işlevini gerçekten seviyorum
Dragonborn

Bu understandişlev harika. Bunu kodumuzdaki bir incelemede görmüş olsaydım, bunu olarak yeniden adlandırmayı öneririm Set, çünkü Range(benim için) sürekli geliyor.
dlw

7

Aralıklar ve Endeksler C # 8.0 ile yayınlandı.

Şimdi yapabilirsin

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

Check out https://blogs.msdn.microsoft.com/dotnet/2018/12/05/take-c-8-0-for-a-spin/ Daha fazla ayrıntı için.


Bu farklı bir kavramdır ve OP'nin istediği gibi değildir.
PepitoSh

System.RangeTürü, yalnızca kabul intolsa da,.
snipsnipsnip

Yalnızca .NET Core 3.0'dan beri mevcut olduğunu belirtmek önemlidir. .NET Framework'te kullanılamaz.
Sarrus

2

@ Andrius-naruševičius'u daha deyimsel ve kolayca özelleştirilebilir hale getirmek için çok yararlı bir yanıt geliştirmek

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

ve testler:

    [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

Bunun gibi bir uzatma yöntemi yazın

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

ve sonra onu zarif bir şekilde kullanın

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

1
Neden yapıyorsun if (value >= from && value <= to) return true;ve etmiyorsun return (value >= from && value <= to)?
sdgfsdh

2

@Drharris'in cevabından esinlenilen bu uygulama, Kapsayıcı / Münhasır olabilen değerlerle uygun matematiksel aralığı tanımlamanıza olanak tanır.

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

Ayrıca, burada biraz farklı bir teğet, ancak bazen aralıklar, biraz python'da yapılması alışılmış gibi, yalnızca bunların üzerinde yineleme yapmak için yararlıdır. Bu durumda System.Linqad alanı static IEnumerable<int> Range(Int32, Int32), imzadan da anlaşılacağı gibi, bir yöntemi tanımlar.

belirli bir aralıkta bir dizi integral numarası üretir

MSDN'deki belgelere ve örneklere bakın


@Seth, Numaralandırılabilir aralık başlangıç ​​ve sayımı kabul ettiğinden olmamalıdır.
Rashid

@Rashid yeterince adil ama bunu bir sorun olarak görmüyorum. OP'nin " int Fromve ile sınıf" istediğini biliyorum int To, ancak bunun belirli bir gereklilik olmadığından çok, onların görüşlerini açıklayan bir şey olduğundan oldukça eminim. İlgilenen herkes için, elbette, countparametrenin değeri, sahip olduğunuz int Fromve int Tozaten değişkenler olduğu varsayıldığında, çok kolay bir şekilde belirlenebilir .
Gaboik1

1

Orada bir olduğunu Enumerable.Rangeyöntem ama bu bir kabul startve countonun parametre olarak. Amacıyla senin ne istediğini olarak çalışması için startve end, endformülü olurduend - start + 1

Kullanım:

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


0

C # 'da aralıkları da kaçırdığım için , daha karmaşık türlere sahip aralıklarla bile ilgilenebilen, tam bir genel Interval sınıfı uyguladım , örneğin DateTime, TimeSpanhesaplamalar sırasında' s'ları içeren iki 's arasındaki bir aralık .

Bir GUI öğesinin bir zaman aralığını temsil ettiği örnek bir kullanım durumu:

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

Desteklenen işlevselliğin daha kapsamlı bir gösterimi için birim testlerine bakın .

Kapakların altında, çalışma zamanında tür işleci işlemlerini derlemek için ifade ağaçlarını kullanır , bunlar önbelleğe alınır, bu nedenle yalnızca tür ilk başlatıldığında bir maliyet olur.


-2

Bir yapıya ne dersiniz ?


2
Min ve maks değerlerinin somutlaştırıldıktan sonra asla değişmeyeceğini bilmediğiniz sürece bir yapı kullanmam .
IAbstract

"Zaten var" sorusuna cevap veriyor. Yapı türü, özellikle söz konusu olana benzer türler için mevcuttur.
James Sumners

3
Struct başlı başına bir tür değildir. Temelde "hayır kendi tipinizi yaratmayın" diyorsunuz - bu yasal bir cevap
Robert Levy

2
İlk üç kelime bağlantılı belgelerde seninle aynı fikirde: "yapı türü ..."
James Sumners

Gazetecilerle aynı fikirde olma eğilimindeyim. Yapı, bunu yapmanın kolay ve anlaşılır bir yoludur ve bu kadar basit bir problem için daha sonra korunması gereken 2 sayfalık kodlar üretmez. Aynı şey için bir Çift <...> de kullanabilirsiniz. Microsoft, yapıların çeşitli yerlerde değişmez olmasıyla ilgili kendi "kurallarını" çiğniyor.
Tom
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.