整数の範囲を表すためのC#タイプはありますか?


105

整数の範囲を保存する必要があります。C#4.0でそのための既存のタイプはありますか?

もちろん、私は自分のクラスを書くことができますint Fromし、int Toそれを確実にするために、適切なロジックのプロパティとビルドFrom <= To。ただし、タイプがすでに存在する場合は、もちろんそれを使用します。


2
受け入れられた答えは変更されるべきだと思います。@rsennaの答えEnumerable.Rangeは、私が見たことから、C#3.0で範囲を実装するための事実上の方法です。また、C#3.0は2007年から存在しており、この時点で9年になります。
Ehtesh Choudhury

@EhteshChoudhury OPは、離散値の処理、またはこれらの型の最小値と最大値の検証については何も言及していませんEnumerable.Range。OPは単に、下限と上限のプロパティを持つことができるIntervalsを処理する既存のデータ構造を探しているだけで、(特定の動作を強制するメソッド以外には)何もありませんでした。
Sunny Patel

2
@EhteshChoudhury「Enumerable.Rangeを使用しない」コーラスに声を加えます。これは、最小/最大値のペアを保持するという単純な問題を解決するための恐ろしい方法です。Enumerable.Max(IEnumerable <int>)(またはEnumerable.Min)へのすべての呼び出しは、範囲全体を反復処理して境界を計算します。他の人が言ったように、それは多くの反復になる可能性があります。ここでは、マイクロパフォーマンスの調整について話しているのではなく、速度の低下について話しています。この種のプログラミングは、.Netが(不当に)パフォーマンスの悪い名前を取得する理由です!受け入れられた回答と同様の回答が唯一の実用的なソリューションです。
ダニエルスコット

1
それは公正です。Enumerable.Range(1,1000000)の範囲データ型と(1,1000000)の範囲データ型では、はるかに多くのスペースが必要になります。質問が初めて尋ねられたときに間違って読み、それが求めていると思ったEnumerable.Range
Ehtesh Choudhury 2017

1
@EhteshChoudhury Enumerable.Rangeは、少なくともToList()メソッドを呼び出すと1,1000000はかかりません。
Luis

回答:


135

自分で巻いたほうがいいと思いました。一部の人々は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 2015年

2
@やあ男はベアボーンのドキュメントの知恵を叩かないでください。:)
drharris

6
とても助かります、ありがとう!すぐに初期化できるコンストラクターを追加してみませんか? public Range(T min, T max) { Minimum = min; Maximum = max; }
NateJC 2017年

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
私は本当に理解して機能のような
ドラゴンボーン

その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 2017年

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のドキュメントと例を参照してください


Enumerableの範囲は開始とカウントを受け入れるため、@ Sethはできません。
Rashid

@Rashidは十分に公正ですが、私はそれを問題とは考えていません。私はOPはを求め知っている「とクラスint Fromint To」私はこれは、特定の要件ではなく、彼らのポイントを説明するためのものではなかったことをかなり確信しています。興味のある人のために、当然のことながら、の値countパラメータが非常に簡単にあなたが持っていることを許可され、判断することができるint Fromint To、すでに変数。
Gaboik1

1

Enumerable.Rangeメソッドはありますが、これはパラメータとしてstartおよびcountを受け入れます。それはあなたが望むものとして動作させるためにstartendend式は次のようになりますend - start + 1

使用法:

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


0

私はまた、C#での間隔がありませんでしたので、私は完全に汎用的な間隔クラス実装例2の間隔、より複雑な種類の間隔の世話をすることができますDateTime「関係の、TimeSpan計算中にs」を。

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、2011年

3
Struct自体は型ではありません。合法的な答えだ-あなたは基本的に「ノー独自のタイプを作成行く」と言っている
ロバート・レビー

2
リンクされたドキュメントの最初の3つの単語があなたに同意しません: "struct type ..."
James Sumners

私はjsumnersに同意する傾向があります。構造体はこれを行うための簡単で直接的な方法であり、そのような単純な問題のために後で維持する必要がある2ページのコードを生成しません。同じものにPair <...>を使用することもできます。マイクロソフトは、構造体がさまざまな場所で不変であるという独自の「ルール」を破っています。
トム・
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.