Wie kann ich elegant prüfen, ob eine Zahl innerhalb eines Bereichs liegt?


157

Wie kann ich das 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 ausreichen; aber das Schlüsselwort zu dieser Frage ist Eleganz. Es ist für mein Spielzeugprojekt nicht für die Produktion.

Bei diesen Fragen ging es nicht um Geschwindigkeit, sondern um Code-Schönheit. Hör auf über Effizienz und so zu reden; Denken Sie daran, Sie predigen vor dem Chor.


23
Betreff: Ihre "Bearbeitung" - einfach ist elegant . Ich persönlich finde die if-Anweisung eleganter als jedes nicht standardmäßige Mittel, um diese Prüfung
Reed Copsey

4
"Alles sollte so einfach wie möglich gemacht werden, aber nicht einfacher." - Albert Einstein
corsiKa

3
@Sergio: Ich fühle mich nicht pedantisch. Ich habe das Gefühl, dass Menschen häufig Erweiterungsmethoden und andere Tools in der Sprache missbrauchen, um Dinge zu ersetzen, die bereits einfach sind. Es gibt Hunderte von Möglichkeiten, zwei int-Werte zu vergleichen, aber alles andere als die offensichtlichere zu verwenden, ist eine schlechte Wahl, IMO.
Reed Copsey

3
@ Sergio: Ich denke, dann sehe ich den Punkt der Frage nicht;)
Reed Copsey

6
@Sergio: Wenn ifes nicht "Barock" ist, beheben Sie es nicht.
StriplingWarrior

Antworten:


151

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 diesen SO-Beitrag für Regex-Optionen an.


334
Enumerable.Range muss zuerst die Aufzählung von Ganzzahlen generieren und dann jedes Element durchlaufen, um es zu finden. Das ist eine schreckliche Idee, und die Leistung im Vergleich zur Überprüfung eines Werts unterscheidet sich drastisch. Ich denke, wir sollten ein Moto übernehmen, nur weil LINQ Extensions cool sind, heißt das nicht, dass sie für alles verwendet werden sollten.
Matthew Abbott


15
Ich bin damit einverstanden, dass dies in Bezug auf die Leistung eine schreckliche Idee ist, aber das OP möchte etwas ausgefalleneres als eine ifAussage. Dies erreicht sicherlich, dass ...;)
Tim Coker

10
Es ist zu beachten, dass der zweite Parameter nicht "stop", sondern "count" ist. So gibt beispielsweise Enumerable.Range (150, 300) .Contains (400) true zurück.
Shathur

5
Bitte verwenden Sie diese Antwort nicht . Es wird eine schreckliche Leistung haben, wenn Ihre Reichweiten ziemlich groß sind. Bitte beachten Sie die Antwort von @ olivier-Jacot-Descombes
Aaron Hudon

95

Meinst du?

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

oder

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

1
Sie brauchen dort kein "ist" ... Dies wird nicht kompiliert. (Ansonsten stimme ich 100% zu)
Reed Copsey

4
@ Ben, warte nur, bis ich versuche, es auch zu patentieren :)
kemiller2002

Ich denke, dies ist die solide Lösung, aber nicht so elegant, wie es der Fragesteller sucht, oder?
Kevin Simple

Das einzige, was ich ändern würde, ist das Hinzufügen des statischen Schlüsselworts zur Methode. ;-)
Robert S.

Benötigt Grenzflags, dh InRange (Nummer, Untergrenze, LOWER_IS_INCLUSIVE, Obergrenze, UPPER_IS_EXCLUSIVE), um <vs <= zu ermöglichen. Ich habe dies in der Absicht geschrieben, snarky zu sein, aber jetzt, wo ich darüber nachdenke, würden die Flaggen den Anrufer tatsächlich ermutigen, ihre Spezifikation klar zu formulieren.
William T. Mallard

56

Um das Rauschen hier zu verstärken, können Sie eine Erweiterungsmethode erstellen:

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

Welches 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 der Scheck selbst nur eine Zeile ist.


1
@Ben: Ich ging auf das Thema ein, das "innerhalb eines Bereichs" sagt (was ich in dieser Hinsicht nicht für mehrdeutig halte), aber Sie haben Recht damit, dass der Fragentext "zwischen 1 und 100" sagt (was ist) natürlich mehrdeutig).
Adam Robinson

48

Verwenden Sie, wie andere sagten, ein einfaches if.

Sie sollten über die Bestellung nachdenken.

z.B

1 <= x && x <= 100

ist leichter zu lesen als

x >= 1 && x <= 100

19
"Einfacher" liegt im Auge des Betrachters. Ich persönlich bevorzuge es, die betreffende Variable links und die nicht fragliche Konstante oder Variable rechts zu haben.
Adam Robinson

15
In Perl 6 würden Sie schreiben 1 <= x <= 100.
Jordão

2
Die Reihenfolge der Zahlenreihen ist anfangs am klarsten - aber Sie können Ihre Augen / Ihren Verstand für andere Bestellungen trainieren. Insbesondere - ich mag den Trick, die Konstante immer links zu platzieren. Wenn Sie dies tun, teilt Ihnen der Compiler mit, wann Sie =statt eingegeben haben ==. Es hilft nicht bei relationalen Operatoren, die nicht gleich sind - aber es ist leicht, sich daran zu gewöhnen, es konsequent zu verwenden.
Davidbak

1
Ich möchte nur hinzufügen, dass diese Lösung auf keinen Fall nützlich ist. Betrachten Sie xeinen komplexen Funktionsaufruf oder einen zeitaufwändigen Linq-Ausdruck. In diesem Fall würden Sie dies zweimal tun, was keine gute Sache ist. Sicher, Sie sollten den Wert in einer temporären lokalen Variablen speichern, aber es gibt einige Fälle (z. B. in else-if-Anweisungen), in denen Sie die Funktionen nur nach anderen if- oder else-if-Fehlern aufrufen möchten. Bei temporären Variablen müssen Sie diese trotzdem vorher aufrufen. Eine Erweiterungsmethode (in anderen Antworten erwähnt) ist in diesen Fällen die beste Lösung.
Robert S.

4
Ich mag auch die Reihenfolge der Zahlenzeilen und auch für den Komplement-Test, zB x <10 || 20 <x. Für mich ruft es "x liegt außerhalb des Bereichs von 10 - 20".
William T. Mallard

44

Im Produktionscode würde ich einfach schreiben

1 <= x && x <= 100

Dies ist leicht zu verstehen und sehr gut lesbar.


Hier ist eine clevere Methode, mit der Sie die Anzahl der Vergleiche mithilfe von Mathematik von zwei auf eins reduzieren können. Die Idee ist, dass einer der beiden Faktoren negativ wird, wenn die Zahl außerhalb des Bereichs liegt, und Null, wenn die Zahl gleich einer der Grenzen ist:

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

3
Nach meinen Maßstäben ist dies bei weitem die eleganteste Lösung. Interessant ist, dass es für mich auch etwas schneller zu laufen scheint als das Überprüfen der beiden Ausdrücke. Das heißt, es scheint auch inkonsistenter zu sein (Geschwindigkeit scheint mehr zu variieren) wenn es irgendwelche Forschungen gibt, bei denen man am schnellsten ist.
Thomas Lindvall

3
Testen Sie Ihre Lösung auf Javascript und ihre Genauigkeit mit Gleitkommazahlen bis zu 14 Dezimalstellen. Es ist ein sehr gutes Code-Snippet. Es würde dich dreimal positiv bewerten, wenn ich könnte
Rubbyrubber

4
Obwohl es ein kleines Problem gibt, wenn große positive Zahlen beteiligt sind, kann es überlaufen! XD Möglicherweise möchten Sie dies beim Schreiben Ihres Codes berücksichtigen.
BrainStorm.exe

2
Die Frage verlangt nach Eleganz und ist daher eher akademischer als praktischer Bedeutung. Persönlich würde ich nur einen einfachen 1 < x && x < 100in produktiven Code verwenden. Es ist leichter zu verstehen.
Olivier Jacot-Descombes

1
Für diejenigen, die sich Gedanken über die Leistung machen, weist 1 < x & x < 100(kein && Kurzschluss) den Compiler an, dass er immer auswerten kann, x < 100unabhängig vom Ergebnis von 1 < x. Seltsamerweise ist es (aufgrund der Verzweigungsvorhersage) schneller, diese einfache Operation immer auszuführen, als sie manchmal zu überspringen.
Tom Leys

23

Ich schlage 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 (ganzzahlige, doppelte, benutzerdefinierte Typen ...).

Es ist wichtig, dass der Code einfach zu lesen ist, da der Entwickler keine "Gehirnzyklen" verschwendet, um ihn zu verstehen. In langen Codierungssitzungen machen verschwendete Gehirnzyklen Entwickler früher müde und anfällig für Fehler.


3
Ich würde noch mehr vereinfachen, indem ich das Wort zwischen verwende und eine boolesche Flagge habe, um festzustellen, ob inklusive oder nicht
Ben

Gut. Es ist leicht zu verstehen. Ich habe den Namen IsInRange. I'm not that keen on Ben's inclusive boolean as that requires a few more brain cycles. It has the advantage that it can be used in any class that that implements IComparer. This is in my Extensions now along with LiesWithin / LiesInside. Just can't decide which. NotOutside geändert, aber ich mag keine negativen Bedingungen
Paulustrious

21

Mit ein wenig Missbrauch der Erweiterungsmethode 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!");
        }
    }
}

Wie die Lösung! Btw zu unterstützen inklusive, erstellen enum Inclusivemit Werten: Lower, Upper, All. Und Pass für die InFunktion einen zusätzlichen Parameter des Typs enum Inclusivemit Standardwert Inclusive.All, aktualisieren Sie die ToFunktion Körper Griff All, Lower, UpperWerte :)
Nikita

7

Wenn dies zufällig ist, ist ein einfaches ifalles, was Sie brauchen. Wenn dies an vielen Stellen passiert, sollten Sie diese beiden 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.

Etwas wie:

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

+1 für Codeverträge; Es ist spezifisch für die Validierung eines Parameters, aber es ist ein häufiger Anwendungsfall, und die statische Überprüfung kann äußerst nützlich sein.
Dan Bryant

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

5

Die Verwendung eines &&Ausdrucks zum Verbinden zweier Vergleiche ist einfach die eleganteste Methode, um dies zu tun. Wenn Sie versuchen, ausgefallene Erweiterungsmethoden und dergleichen zu verwenden, stellen Sie sich die Frage, ob die Obergrenze, die Untergrenze oder beides eingeschlossen werden soll. Sobald Sie zusätzliche Variablen hinzufügen oder die Erweiterungsnamen ändern, um anzugeben, was enthalten ist, wird Ihr Code länger und schwerer zu lesen (für die überwiegende Mehrheit der Programmierer). Darüber hinaus werden Sie von Tools wie Resharper gewarnt, wenn Ihr Vergleich keinen Sinn ergibt (number > 100 && number < 1 ), was sie nicht tun, wenn Sie eine Methode verwenden ('i.IsBetween (100, 1)').

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

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

Das 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 Nummer zuerst in Grenzen liegt.


2
Zu Ihrer Information, der statische Vertragsanalysator ist zufriedener, wenn die Einschränkungen für die untere und obere Grenze in separate Requires-Anweisungen aufgeteilt werden.
Dan Bryant

Danke Dan Bryant, genau das habe ich hier gesucht. Es kann nicht viel Material zu Vorschlägen zum Stil der Bedingungen für die Anforderungen und andere verwandte Code-Vertragsmethoden gefunden werden.
Jpierson

2

Wenn Sie mehr Code als ein einfaches if schreiben möchten, können Sie möglicherweise: Eine Erweiterungsmethode namens IsBetween erstellen

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 Codebasis. (Anders als in den trivialsten Situationen.) Als Beispiel würde jeder Spielprogrammierer in jedem Projekt grundsätzlich Kategorien wie die folgenden verwenden. Beachten Sie, dass in diesem Beispiel eine Funktion (Mathf.Approximate) verwendet wird, die in diese Umgebung integriert ist. In der Praxis müssen Sie in der Regel sorgfältig Ihre eigenen Konzepte entwickeln, was Vergleiche für Computerdarstellungen von reellen Zahlen bedeuten, für die Art der Situation, in der Sie sich befinden. (Erwähnen Sie nicht einmal, dass, wenn Sie so etwas wie einen Regler, einen PID-Regler oder ähnliches tun, das ganze Problem zentral und sehr schwierig wird, es wird zur Natur des Projekts.

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

1
Ersetzen Sie das if und else durchreturn (value >= Min && value <= Max);
AeroX

Die elegante Art, den Vergleich zu schreiben, ist "in logischer Reihenfolge ..." if (Min <= Wert && Wert <= Max). Das ist viel schöner.
Fattie

2
Bei dieser Frage ist es so überraschend, dass niemand das zentrale Problem in einem realen Projekt erwähnt hat (insbesondere wenn Sie ein Spieleingenieur sind), dass Sie sich mit dem Approximationsproblem befassen müssen . In jeder realen Software müssen Sie im Wesentlichen nie "nur einen Vergleich durchführen" (ob Gleichheit oder <,>), sondern das Fehlerproblem je nach Situation berücksichtigen und behandeln. Ich habe in einem Nachtrag zu dieser Antwort (die einzig richtige Antwort hier!) Bearbeitet, da keine Antworten mehr erlaubt sind.
Fattie

Vielen Dank für diese Beobachtung und den Nachtrag.
Tony

2

Weil alle anderen Antworten nicht von mir erfunden wurden, 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 folgendermaßen verwenden:

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

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

2

BEARBEITEN: Neue Antwort bereitgestellt. Ich habe gerade mit C # angefangen, als ich die erste Antwort auf diese Frage schrieb, und im Nachhinein stelle ich jetzt fest, dass meine "Lösung" naiv und ineffizient war / ist.

Meine ursprüngliche Antwort: Ich würde mit der einfacheren Version gehen:

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

Ein besserer Weg

Da ich keine andere Lösung gesehen habe, die effizienter ist (zumindest nach meinen Tests), werde ich es noch einmal versuchen.

Neuer und besserer Weg, der auch mit negativen Bereichen funktioniert :

// Returns true if x is in range [min..max], else false 
bool inRange(int x, int min=1, int max=100) => ((x - max)*(x - min) <= 0);

Dies kann sowohl mit positiven als auch mit negativen Bereichen verwendet werden und ist standardmäßig auf einen Bereich von eingestellt

1..100 (einschließlich) und wird xals zu prüfende Nummer verwendet, gefolgt von einem optionalen Bereich, der durch minund definiert ist max.

Hinzufügen von Beispielen für eine gute Maßnahme

Beispiel 1:

// Returns true if x is in range [min..max], else false 
bool inRange(int x, int min=1, int max=100) => ((x - max)*(x - min) <= 0);

Console.WriteLine(inRange(25));
Console.WriteLine(inRange(1));
Console.WriteLine(inRange(100));
Console.WriteLine(inRange(25, 30, 150));
Console.WriteLine(inRange(-25, -50, 0));

Kehrt zurück:

True
True
True
False
True

Beispiel 2: Verwenden einer Liste zufälliger Ints zwischen 1 und 150

// Returns true if x is in range [min..max], else false 
bool inRange(int x, int min=1, int max=100) => ((x - max)*(x - min) <= 0);

// Generate 100000 ints between 1 and 150
var intsToCheck = new List<int>();
var randGen = new Random();
for(int i = 0; i < 100000; ++i){
    intsToCheck.Add(randGen.Next(150) + 1);
}

var counter = 0;
foreach(int n in intsToCheck) {
    if(inRange(n)) ++counter;
}

Console.WriteLine("{0} ints found in range 1..100", counter);

Kehrt zurück:

66660 ints found in range 1..100

Ausführungszeit: 0,016 Sekunde (n)


Ja, ich kommentiere einen Kommentar zu meiner Antwort von 2013 :) @RyanTheLeach: Wie unterscheidet sich meine Antwort auf diese Frage von der jetzt "akzeptierten" Antwort? Mir ist klar, dass es nicht die effektivste Durchquerung ist, sondern „schrecklich“? Wie schlecht kann das Zuweisen und Durchlaufen von 100 Zoll sein? 1950 wurde es wahrscheinlich nicht sozial akzeptiert, aber ...
cseder

@RyanTheLeach Ich beschuldige Sie nicht ... Ich habe meine Antwort aktualisiert. Wenn Sie also eine noch effizientere Lösung kennen, gehen Sie bitte näher darauf ein!
cseder

1
Ich habe meine Kommentare gelöscht, da sie nicht mehr stehen. Danke für das Update, es scheint in Ordnung zu sein.
Ryan The Leach

1

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

3
Es gibt tatsächlich vier Fälle, inklusive / inklusive, inklusive / exklusiv, exklusiv / inklusive und exklusiv / exklusiv.
William T. Mallard

1

In C könnte man dies tun, wenn die Zeiteffizienz entscheidend ist und ganzzahlige Überläufe umlaufen if ((unsigned)(value-min) <= (max-min)) .... Wenn 'max' und 'min' unabhängige Variablen sind, verschwendet die zusätzliche Subtraktion für (max-min) Zeit, aber wenn dieser Ausdruck zur Kompilierungszeit vorberechnet werden kann oder wenn er zur Laufzeit einmal berechnet werden kann, um viele zu testen Bei Zahlen für denselben Bereich kann der obige Ausdruck auch dann effizient berechnet werden, wenn der Wert innerhalb des Bereichs liegt (wenn ein großer Teil der Werte unter dem gültigen Bereich liegt, ist die Verwendung möglicherweise schneller, if ((value >= min) && (value <= max)) ...da er bei einem Wert vorzeitig beendet wird ist kleiner als min).

Bevor Sie jedoch eine solche Implementierung verwenden, sollten Sie einen Benchmark für den eigenen Zielcomputer durchführen. Auf einigen Prozessoren kann der zweiteilige Ausdruck in allen Fällen schneller sein, da die beiden Vergleiche unabhängig voneinander durchgeführt werden können, während bei der Subtraktions- und Vergleichsmethode die Subtraktion abgeschlossen sein muss, bevor der Vergleich ausgeführt werden kann.


1

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 erstellen, ungefähr 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 verwenden Sie es folgendermaßen:

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.


Ihr RangeObjekt muss die CompareToMethode zum Vergleichen von Elementen verwenden, nicht den <Operator.
Servy

Sie haben Recht, aber wenn Sie IComparable implementieren, sollten Sie auch Operatoren überschreiben (zumindest sagt das meine VS-Code-Analyse), was bedeutet, dass <funktionieren würde. Obwohl ich mich irren könnte, habe ich nicht viel Erfahrung und dies ist meine erste Antwort auf SO
IEatBagels

Nein, Ihr Compiler wird nicht sagen, dass dies funktioniert. Dies wird nicht kompiliert. Es ist völlig vernünftig, dass ein Objekt IComparableden <Bediener implementiert und nicht überlastet .
Servy

1

Wenn Sie prüfen, ob sich eine "Zahl" in einem Bereich befindet, müssen Sie sich darüber im Klaren sein, was Sie meinen, und was bedeuten zwei Zahlen gleich viel? Im Allgemeinen sollten Sie alle Gleitkommazahlen in eine sogenannte "Epsilon-Kugel" einschließen. Dies geschieht, indem Sie einen kleinen Wert auswählen und sagen, wenn zwei Werte so nahe beieinander liegen, 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 an Ort und Stelle und unter der Annahme, dass eine beliebige Zahl ohne die erforderliche Genauigkeit als Doppel gewirkt werden kann. Jetzt brauchen Sie nur noch 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");
        }
    }

Das ist vielleicht weit mehr als das, was Sie wollten, aber es hält Sie davon ab, sich ständig mit Rundungen zu befassen und sich zu erinnern, ob und an welcher Stelle ein Wert gerundet wurde. Wenn nötig, können Sie dies problemlos erweitern, um mit jedem Epsilon zu arbeiten und um zu ermöglichen, dass sich Ihr Epsilon ändert.


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

Verwendung

double numberToBeChecked = 7;

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

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

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


1

Wenn Sie sich mit dem Kommentar von @Daap zur akzeptierten Antwort befassen und den Wert nur einmal übergeben können, können Sie eine der folgenden Möglichkeiten ausprobieren

bool TestRangeDistance (int numberToCheck, int bottom, int distance)
{
  return (numberToCheck >= bottom && numberToCheck <= bottom+distance);
}

//var t = TestRangeDistance(10, somelist.Count()-5, 10);

oder

bool TestRangeMargin (int numberToCheck, int target, int margin)
{
  return (numberToCheck >= target-margin && numberToCheck <= target+margin);
}

//var t = TestRangeMargin(10, somelist.Count(), 5);

1

In Bezug auf Eleganz verbessert das, was der mathematischen Notation am nächsten kommt ( a <= x <= b ), die Lesbarkeit geringfügig:

public static bool IsBetween(this int value, int min, int max)
{
    return min <= value && value <= max;
}

Zur weiteren Veranschaulichung:

public static bool IsOutside(this int value, int min, int max)
{
    return value < min || max < value;
}

0

Ich suchte nach einer eleganten Möglichkeit, um die Grenzen zu ändern (dh nicht sicher, in welcher Reihenfolge sich die Werte befinden).

Dies funktioniert nur bei neueren Versionen von C #, bei denen 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 Typguss ausgefallen sein. Ich brauchte nur einen Float Return innerhalb von Grenzen (oder gleich)


0

Elegant, da Sie nicht zuerst bestimmen müssen, welcher der beiden Grenzwerte 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 );
}

& + | sind bitweise Operatoren
Nelsontruran

0

Ich weiß es nicht, aber ich benutze 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 benutzen:

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

Bitte geben Sie ein Verwendungsbeispiel unter dem Codeblock an, damit OP weiß, ob es seinem Zweck entspricht
Gabriel Balsa Cantú

0

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

Wenn Methodenparameter validiert werden sollen, löst keine der Lösungen ArgumentOutOfRangeException aus und ermöglicht eine einfache / ordnungsgemäße Konfiguration von Inklusiv- / Exklusiv-Min / Max-Werten.

Verwenden Sie so

public void Start(int pos)
{
    pos.CheckRange(nameof(pos), min: 0);

    if (pos.IsInRange(max: 100, maxInclusive: false))
    {
        // ...
    }
}

Ich habe gerade diese schönen Funktionen geschrieben. Es hat auch den Vorteil, dass keine Verzweigung (ein einzelnes Wenn) für gültige Werte vorliegt. Am schwierigsten ist es, die richtigen Ausnahmemeldungen zu erstellen.

/// <summary>
/// Returns whether specified value is in valid range.
/// </summary>
/// <typeparam name="T">The type of data to validate.</typeparam>
/// <param name="value">The value to validate.</param>
/// <param name="min">The minimum valid value.</param>
/// <param name="minInclusive">Whether the minimum value is valid.</param>
/// <param name="max">The maximum valid value.</param>
/// <param name="maxInclusive">Whether the maximum value is valid.</param>
/// <returns>Whether the value is within range.</returns>
public static bool IsInRange<T>(this T value, T? min = null, bool minInclusive = true, T? max = null, bool maxInclusive = true)
    where T : struct, IComparable<T>
{
    var minValid = min == null || (minInclusive && value.CompareTo(min.Value) >= 0) || (!minInclusive && value.CompareTo(min.Value) > 0);
    var maxValid = max == null || (maxInclusive && value.CompareTo(max.Value) <= 0) || (!maxInclusive && value.CompareTo(max.Value) < 0);
    return minValid && maxValid;
}

/// <summary>
/// Validates whether specified value is in valid range, and throws an exception if out of range.
/// </summary>
/// <typeparam name="T">The type of data to validate.</typeparam>
/// <param name="value">The value to validate.</param>
/// <param name="name">The name of the parameter.</param>
/// <param name="min">The minimum valid value.</param>
/// <param name="minInclusive">Whether the minimum value is valid.</param>
/// <param name="max">The maximum valid value.</param>
/// <param name="maxInclusive">Whether the maximum value is valid.</param>
/// <returns>The value if valid.</returns>
public static T CheckRange<T>(this T value, string name, T? min = null, bool minInclusive = true, T? max = null, bool maxInclusive = true)
where T : struct, IComparable<T>
{
    if (!value.IsInRange(min, minInclusive, max, maxInclusive))
    {
        if (min.HasValue && minInclusive && max.HasValue && maxInclusive)
        {
            var message = "{0} must be between {1} and {2}.";
            throw new ArgumentOutOfRangeException(name, value, message.FormatInvariant(name, min, max));
        }
        else
        {
            var messageMin = min.HasValue ? GetOpText(true, minInclusive).FormatInvariant(min) : null;
            var messageMax = max.HasValue ? GetOpText(false, maxInclusive).FormatInvariant(max) : null;
            var message = (messageMin != null && messageMax != null) ?
                "{0} must be {1} and {2}." :
                "{0} must be {1}.";
            throw new ArgumentOutOfRangeException(name, value, message.FormatInvariant(name, messageMin ?? messageMax, messageMax));
        }
    }
    return value;
}

private static string GetOpText(bool greaterThan, bool inclusive)
{
    return (greaterThan && inclusive) ? "greater than or equal to {0}" :
        greaterThan ? "greater than {0}" :
        inclusive ? "less than or equal to {0}" :
        "less than {0}";
}

public static string FormatInvariant(this string format, params object?[] args) => string.Format(CultureInfo.InvariantCulture, format, args);

-2

Sie suchen in [1..100]? Das ist nur Pascal.


2
Nicht wahr, es ist nicht nur Pascal. Viele moderne Sprachen haben solche Funktionen. In Kotlin heißt es beispielsweise "Pattern Matching". Beispiel when (number) { in 0..9 -> println("1 digit") in 10..99 -> println("2 digits") in 100..999 -> println("3 digits") }
this.myself
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.