it-swarm.dev

Wie kann man elegant überprüfen, ob eine Zahl innerhalb eines Bereichs liegt?

Wie kann ich dies elegant mit C # und .NET 3.5/4 machen?

Beispielsweise kann eine Zahl zwischen 1 und 100 liegen.

Ich weiß ein einfaches wenn würde genügen; Aber das Schlüsselwort für diese Frage ist Eleganz. Es ist für mein Spielzeugprojekt nicht für die Produktion.

Bei dieser Frage ging es nicht um Geschwindigkeit, sondern um Code-Schönheit. Hören Sie auf, über Effizienz und dergleichen zu reden. Denken Sie daran, Sie predigen zum Chor.

123
Sergio Tapia

Es gibt viele Möglichkeiten:

int x = 30;
if (Enumerable.Range(1,100).Contains(x))
    //true

if (x >= 1 && x <= 100)
    //true

Schauen Sie sich auch diese SO post für Regex-Optionen an.

108
Dustin Laine

Meinst du?

if(number >= 1 && number <= 100)

oder 

bool TestRange (int numberToCheck, int bottom, int top)
{
  return (numberToCheck >= bottom && numberToCheck <= top);
}
82
kemiller2002

Um das Rauschen noch zu erhöhen, können Sie eine Erweiterungsmethode erstellen:

public static bool IsWithin(this int value, int minimum, int maximum)
{
    return value >= minimum && value <= maximum;
}

Was würde Sie so etwas tun lassen ...

int val = 15;

bool foo = val.IsWithin(5,20);

Davon abgesehen scheint dies eine dumme Sache zu sein, wenn die Prüfung selbst nur eine Zeile enthält.

46
Adam Robinson

Wie andere sagten, benutze ein einfaches if.

Sie sollten über die Bestellung nachdenken. 

z.B

1 <= x && x <= 100

ist leichter zu lesen als

x >= 1 && x <= 100
39

Sie können die Anzahl der Vergleiche mit Hilfe von Mathematik von zwei auf eins reduzieren. Die Idee ist, dass einer der beiden Faktoren negativ wird, wenn die Zahl außerhalb des Bereichs liegt, und null, wenn die Zahl einer der Grenzen entspricht:

Wenn die Grenzen inklusive sind:

(x - 1) * (100 - x) >= 0

oder

(x - min) * (max - x) >= 0

Wenn die Grenzen exklusiv sind:

(x - 1) * (100 - x) > 0

oder

(x - min) * (max - x) > 0

In Produktionscode würde ich jedoch einfach 1 < x && x < 100 schreiben, es ist einfacher zu verstehen. 

Mit etwas Missbrauch der Erweiterungsmethoden können wir die folgende "elegante" Lösung erhalten:

using System;

namespace Elegant {
    public class Range {
        public int Lower { get; set; }
        public int Upper { get; set; }
    }

    public static class Ext {
        public static Range To(this int lower, int upper) {
            return new Range { Lower = lower, Upper = upper };
        }

        public static bool In(this int n, Range r) {
            return n >= r.Lower && n <= r.Upper;
        }
    }

    class Program {
        static void Main() {
            int x = 55;
            if (x.In(1.To(100)))
                Console.WriteLine("it's in range! elegantly!");
        }
    }
}
16
Ferruccio

Ich schlage folgendes vor:

public static bool IsWithin<T>(this T value, T minimum, T maximum) where T : IComparable<T> {
    if (value.CompareTo(minimum) < 0)
       return false;
    if (value.CompareTo(maximum) > 0)
       return false;
    return true;
}

Beispiele:

45.IsWithin(32, 89)
true
87.2.IsWithin(87.1, 87.15)
false
87.2.IsWithin(87.1, 87.25)
true

und natürlich mit Variablen:

myvalue.IsWithin(min, max)

Es ist leicht zu lesen (nahe an der menschlichen Sprache) und funktioniert mit jedem vergleichbaren Typ (Ganzzahl, Doppel, benutzerdefinierte Typen ...). 

Leicht lesbarer Code ist wichtig, weil der Entwickler keine "Gehirnzyklen" verschwenden muss, um ihn zu verstehen. In langen Codierungssitzungen machen vergeudete Gehirnzyklen den Entwickler früher müde und anfällig für Fehler. 

14
Anton M

In diesem Fall reicht eine einfache if aus. Wenn dies an vielen Stellen der Fall ist, sollten Sie die beiden folgenden Punkte berücksichtigen:

  • PostSharp . Dekorieren Sie Methoden mit Attributen, die nach der Kompilierung Code in die Methode einfügen. Ich weiß es nicht genau, aber ich kann mir vorstellen, dass es dafür verwendet werden kann. 

So etwas wie:

[Between("parameter", 0, 100)]
public void Foo(int parameter)
{
}
  • Kodexverträge . Hat den Vorteil, dass die Einschränkungen zur Kompilierzeit durch statische Überprüfung Ihres Codes und der Stellen, an denen Ihr Code verwendet wird, überprüft werden können. 
7
JulianR

Die Verwendung eines &&-Ausdrucks zum Verbinden zweier Vergleiche ist einfach der eleganteste Weg, dies zu tun. Wenn Sie versuchen, ausgefallene Erweiterungsmethoden zu verwenden, stellen Sie die Frage, ob Sie die Obergrenze, die Untergrenze oder beide einschließen möchten. Wenn Sie mit dem Hinzufügen zusätzlicher Variablen beginnen oder die Erweiterungsnamen ändern, um anzugeben, was enthalten ist, wird der Code länger und schwerer zu lesen (für die große Mehrheit der Programmierer). Darüber hinaus werden Sie mit Tools wie Resharper gewarnt, wenn Ihr Vergleich keinen Sinn ergibt (number > 100 && number < 1). Dies ist jedoch nicht der Fall, wenn Sie eine Methode ('i.IsBetween (100, 1)') verwenden.

Der einzige andere Kommentar, den ich anmerken möchte, ist, dass, wenn Sie Eingaben mit der Absicht überprüfen, eine Ausnahme auszulösen, Codeverträge in Betracht ziehen sollten:

Contract.Requires(number > 1 && number < 100)

Dies ist eleganter als if(...) throw new Exception(...) und Sie könnten sogar Warnungen zur Kompilierungszeit erhalten, wenn jemand versucht, Ihre Methode aufzurufen, ohne sicherzustellen, dass die Anzahl zuerst in Grenzen ist.

5
if (value > 1 && value < 100)
{
    // do work
}
else
{
    // handle outside of range logic
}
5
Nick Larsen

Denn alle anderen Antworten werden nicht von mir erfunden, hier nur meine Implementierung:

public enum Range
{
    /// <summary>
    /// A range that contains all values greater than start and less than end.
    /// </summary>
    Open,
    /// <summary>
    /// A range that contains all values greater than or equal to start and less than or equal to end.
    /// </summary>
    Closed,
    /// <summary>
    /// A range that contains all values greater than or equal to start and less than end.
    /// </summary>
    OpenClosed,
    /// <summary>
    /// A range that contains all values greater than start and less than or equal to end.
    /// </summary>
    ClosedOpen
}

public static class RangeExtensions
{
    /// <summary>
    /// Checks if a value is within a range that contains all values greater than start and less than or equal to end.
    /// </summary>
    /// <param name="value">The value that should be checked.</param>
    /// <param name="start">The first value of the range to be checked.</param>
    /// <param name="end">The last value of the range to be checked.</param>
    /// <returns><c>True</c> if the value is greater than start and less than or equal to end, otherwise <c>false</c>.</returns>
    public static bool IsWithin<T>(this T value, T start, T end) where T : IComparable<T>
    {
        return IsWithin(value, start, end, Range.ClosedOpen);
    }

    /// <summary>
    /// Checks if a value is within the given range.
    /// </summary>
    /// <param name="value">The value that should be checked.</param>
    /// <param name="start">The first value of the range to be checked.</param>
    /// <param name="end">The last value of the range to be checked.</param>
    /// <param name="range">The kind of range that should be checked. Depending on the given kind of range the start end end value are either inclusive or exclusive.</param>
    /// <returns><c>True</c> if the value is within the given range, otherwise <c>false</c>.</returns>
    public static bool IsWithin<T>(this T value, T start, T end, Range range) where T : IComparable<T>
    {
        if (value == null)
            throw new ArgumentNullException(nameof(value));

        if (start == null)
            throw new ArgumentNullException(nameof(start));

        if (end == null)
            throw new ArgumentNullException(nameof(end));

        switch (range)
        {
            case Range.Open:
                return value.CompareTo(start) > 0
                       && value.CompareTo(end) < 0;
            case Range.Closed:
                return value.CompareTo(start) >= 0
                       && value.CompareTo(end) <= 0;
            case Range.OpenClosed:
                return value.CompareTo(start) > 0
                       && value.CompareTo(end) <= 0;
            case Range.ClosedOpen:
                return value.CompareTo(start) >= 0
                       && value.CompareTo(end) < 0;
            default:
                throw new ArgumentException($"Unknown parameter value {range}.", nameof(range));
        }
    }
}

Sie können es dann so verwenden:

var value = 5;
var start = 1;
var end = 10;

var result = value.IsWithin(start, end, Range.Closed);
2
Oliver

Wenn Sie mehr Code als ein einfaches if schreiben möchten, können Sie vielleicht Folgendes tun: Erstellen Sie eine Erweiterungsmethode namens IsBetween

public static class NumberExtensionMethods
{
    public static bool IsBetween(this long value, long Min, long Max)
    {
        // return (value >= Min && value <= Max);
        if (value >= Min && value <= Max) return true;
        else return false;
    }
}

...

// Checks if this number is between 1 and 100.
long MyNumber = 99;
MessageBox.Show(MyNumber.IsBetween(1, 100).ToString());

Nachtrag: Es ist erwähnenswert, dass Sie in der Praxis sehr selten "nur auf Gleichheit prüfen" (oder <,>) in einer Codebase. (Anders als in den trivialsten Situationen.) Alle Spielprogrammierer verwenden rein grundsätzliche Kategorien wie die folgenden in jedem Projekt. Beachten Sie, dass in diesem Beispiel (zufällig) eine in dieser Umgebung integrierte Funktion (Mathf.Approxäher) verwendet wird. In der Praxis müssen Sie in der Regel sorgfältig Ihre eigenen Konzepte entwickeln, was Vergleiche für Computerdarstellungen von reellen Zahlen für die Art der Situation bedeuten, die Sie konstruieren. (Erwähnen Sie nicht einmal, dass, wenn Sie so etwas wie einen Controller, einen PID-Controller oder ähnliches machen, das ganze Thema von zentraler Bedeutung wird und sehr schwierig wird, wird es zum Wesen des Projekts.) Auf keinen Fall ist das OP Frage hier eine triviale oder unwichtige Frage.

private bool FloatLessThan(float a, float b)
    {
    if ( Mathf.Approximately(a,b) ) return false;
    if (a<b) return true;
    return false;
    }

private bool FloatLessThanZero(float a)
    {
    if ( Mathf.Approximately(a,0f) ) return false;
    if (a<0f) return true;
    return false;
    }

private bool FloatLessThanOrEqualToZero(float a)
    {
    if ( Mathf.Approximately(a,0f) ) return true;
    if (a<0f) return true;
    return false;
    }
2
Tony

Wie wäre es mit so etwas?

if (theNumber.isBetween(low, high, IntEx.Bounds.INCLUSIVE_INCLUSIVE))
{
}

mit der Erweiterungsmethode wie folgt (getestet):

public static class IntEx
{
    public enum Bounds 
    {
        INCLUSIVE_INCLUSIVE, 
        INCLUSIVE_EXCLUSIVE, 
        EXCLUSIVE_INCLUSIVE, 
        EXCLUSIVE_EXCLUSIVE
    }

    public static bool isBetween(this int theNumber, int low, int high, Bounds boundDef)
    {
        bool result;
        switch (boundDef)
        {
            case Bounds.INCLUSIVE_INCLUSIVE:
                result = ((low <= theNumber) && (theNumber <= high));
                break;
            case Bounds.INCLUSIVE_EXCLUSIVE:
                result = ((low <= theNumber) && (theNumber < high));
                break;
            case Bounds.EXCLUSIVE_INCLUSIVE:
                result = ((low < theNumber) && (theNumber <= high));
                break;
            case Bounds.EXCLUSIVE_EXCLUSIVE:
                result = ((low < theNumber) && (theNumber < high));
                break;
            default:
                throw new System.ArgumentException("Invalid boundary definition argument");
        }
        return result;
    }
}
1

Ich würde ein Range-Objekt machen, etwa so:

public class Range<T> where T : IComparable
{
    public T InferiorBoundary{get;private set;}
    public T SuperiorBoundary{get;private set;}

    public Range(T inferiorBoundary, T superiorBoundary)
    {
        InferiorBoundary = inferiorBoundary;
        SuperiorBoundary = superiorBoundary;
    }

    public bool IsWithinBoundaries(T value){
        return InferiorBoundary.CompareTo(value) > 0 && SuperiorBoundary.CompareTo(value) < 0;
    }
}

Dann benutzen Sie es so:

Range<int> myRange = new Range<int>(1,999);
bool isWithinRange = myRange.IsWithinBoundaries(3);

Auf diese Weise können Sie es für einen anderen Typ wiederverwenden.

1
IEatBagels
static class ExtensionMethods
{
    internal static bool IsBetween(this double number,double bound1, double bound2)
    {
        return Math.Min(bound1, bound2) <= number && number <= Math.Max(bound2, bound1);
    }

    internal static bool IsBetween(this int number, double bound1, double bound2)
    {
        return Math.Min(bound1, bound2) <= number && number <= Math.Max(bound2, bound1);
    }
}

Verwendungszweck

double numberToBeChecked = 7;

var result = numberToBeChecked.IsBetween (100,122);

var result = 5.IsBetween (100, 120);

var result = 8.0.IsBetween (1.2,9.6);

Wenn in C die Zeiteffizienz entscheidend ist und Integer-Überläufe umlaufen, könnte if ((unsigned)(value-min) <= (max-min)) ... ausgeführt werden. Wenn 'max' und 'min' unabhängige Variablen sind, verschwendet die zusätzliche Subtraktion für (max-min) Zeit, aber wenn dieser Ausdruck zur Kompilierzeit vorberechnet werden kann oder wenn er zur Laufzeit einmal berechnet werden kann, um viele zu testen Wenn der Wert innerhalb des Bereichs liegt (wenn ein großer Bruchteil der Werte unter dem gültigen Bereich liegt, kann die Verwendung von if ((value >= min) && (value <= max)) ... schneller sein, da er exit beendet.) früh wenn Wert kleiner als min ist).

Bevor Sie jedoch eine solche Implementierung einsetzen, setzen Sie einen Benchmark-Computer an. Bei einigen Prozessoren kann der zweiteilige Ausdruck in allen Fällen schneller sein, da die beiden Vergleiche unabhängig voneinander durchgeführt werden können, wohingegen beim Subtrahieren und Vergleichen die Subtraktion abgeschlossen sein muss, bevor der Vergleich ausgeführt werden kann.

1
supercat

Elegant, weil Sie nicht feststellen müssen, welcher der beiden Grenzwerte zuerst größer ist. Es enthält auch keine Zweige.

public static bool InRange(float val, float a, float b)
{
    // Determine if val lies between a and b without first asking which is larger (a or b)
    return ( a <= val & val < b ) | ( b <= val & val < a );
}
1
Tom Leys

Eine neue Variante eines alten Favoriten:

public bool IsWithinRange(int number, int topOfRange, int bottomOfRange, bool includeBoundaries) {
    if (includeBoundaries)
        return number <= topOfRange && number >= bottomOfRange;
    return number < topOfRange && number > bottomOfRange;
}
1
Ben Hoffstein

Ich würde mit der einfacheren Version gehen:

if(Enumerable.Range(1,100).Contains(intInQuestion)) { ...DoStuff; }
0
cseder

Ich weiß es nicht, aber ich verwende diese Methode:

    public static Boolean isInRange(this Decimal dec, Decimal min, Decimal max, bool includesMin = true, bool includesMax = true ) {

    return (includesMin ? (dec >= min) : (dec > min)) && (includesMax ? (dec <= max) : (dec < max));
}

Und so kann ich es verwenden:

    [TestMethod]
    public void IsIntoTheRange()
    {
        decimal dec = 54;

        Boolean result = false;

        result = dec.isInRange(50, 60); //result = True
        Assert.IsTrue(result);

        result = dec.isInRange(55, 60); //result = False
        Assert.IsFalse(result);

        result = dec.isInRange(54, 60); //result = True
        Assert.IsTrue(result);

        result = dec.isInRange(54, 60, false); //result = False
        Assert.IsFalse(result);

        result = dec.isInRange(32, 54, false, false);//result = False
        Assert.IsFalse(result);

        result = dec.isInRange(32, 54, false);//result = True
        Assert.IsTrue(result);
    }
0
user8790965

Dies sind einige Erweiterungsmethoden, die helfen können

  public static bool IsInRange<T>(this T value, T min, T max)
where T : System.IComparable<T>
    {
        return value.IsGreaterThenOrEqualTo(min) && value.IsLessThenOrEqualTo(max);
    }


    public static bool IsLessThenOrEqualTo<T>(this T value, T other)
         where T : System.IComparable<T>
    {
        var result = value.CompareTo(other);
        return result == -1 || result == 0;
    }


    public static bool IsGreaterThenOrEqualTo<T>(this T value, T other)
         where T : System.IComparable<T>
    {
        var result = value.CompareTo(other);
        return result == 1 || result == 0;
    }
0
hanan

Bei der Prüfung, ob eine "Zahl" in einem Bereich liegt, muss klar sein, was Sie meinen, und was bedeuten zwei Zahlen gleich? Im Allgemeinen sollten Sie alle Gleitkommazahlen in einen sogenannten "Epsilon-Ball" einschließen, indem Sie einen kleinen Wert auswählen und sagen, wenn zwei Werte so nahe sind, sind sie dasselbe.

    private double _epsilon = 10E-9;
    /// <summary>
    /// Checks if the distance between two doubles is within an epsilon.
    /// In general this should be used for determining equality between doubles.
    /// </summary>
    /// <param name="x0">The orgin of intrest</param>
    /// <param name="x"> The point of intrest</param>
    /// <param name="epsilon">The minimum distance between the points</param>
    /// <returns>Returns true iff x  in (x0-epsilon, x0+epsilon)</returns>
    public static bool IsInNeghborhood(double x0, double x, double epsilon) => Abs(x0 - x) < epsilon;

    public static bool AreEqual(double v0, double v1) => IsInNeghborhood(v0, v1, _epsilon);

Mit diesen beiden Helfern und der Annahme, dass eine beliebige Zahl ohne die erforderliche Genauigkeit als Doppelwurf gegossen werden kann. Alles, was Sie jetzt brauchen, ist eine Aufzählung und eine andere Methode

    public enum BoundType
    {
        Open,
        Closed,
        OpenClosed,
        ClosedOpen
    }

Die andere Methode folgt:

    public static bool InRange(double value, double upperBound, double lowerBound, BoundType bound = BoundType.Open)
    {
        bool inside = value < upperBound && value > lowerBound;
        switch (bound)
        {
            case BoundType.Open:
                return inside;
            case BoundType.Closed:
                return inside || AreEqual(value, upperBound) || AreEqual(value, lowerBound); 
            case BoundType.OpenClosed:
                return inside || AreEqual(value, upperBound);
            case BoundType.ClosedOpen:
                return inside || AreEqual(value, lowerBound);
            default:
                throw new System.NotImplementedException("You forgot to do something");
        }
    }

Dies mag zwar weit mehr sein als das, was Sie wollten, aber es hindert Sie daran, die ganze Zeit mit Runden umzugehen und sich zu erinnern, ob ein Wert gerundet wurde und an welchen Ort. Wenn Sie möchten, können Sie dies einfach erweitern, um mit jedem Epsilon zu arbeiten und zu ermöglichen, dass sich Ihr Epsilon ändert.

0
rahicks

Ich suchte nach einer eleganten Methode, bei der die Grenzen geändert werden könnten (dh nicht sicher, in welcher Reihenfolge die Werte liegen).

Dies funktioniert nur bei neueren Versionen von C #, wenn das?: Vorhanden ist

bool ValueWithinBounds(float val, float bounds1, float bounds2)
{
    return bounds1 >= bounds2 ?
      val <= bounds1 && val >= bounds2 : 
      val <= bounds2 && val >= bounds1;
}

Natürlich können Sie die Zeichen dort für Ihre Zwecke ändern. Könnte auch beim Typgießen fancy werden. Ich brauchte nur einen Float Return innerhalb der Grenzen (oder gleich)

0
Kalikovision