Jak elegancko sprawdzić, czy liczba mieści się w zakresie?


157

Jak mogę to zrobić elegancko w C # i .NET 3.5 / 4?

Na przykład liczba może wynosić od 1 do 100.

Wiem, że wystarczyłoby proste; ale słowem kluczowym w tym pytaniu jest elegancja. To dla mojego projektu zabawki, a nie do produkcji.

To pytanie nie dotyczyło szybkości, ale piękna kodu. Przestań mówić o wydajności i tym podobnych; pamiętaj, że głosisz w chórze.


23
Odp .: Twoja „edycja” - prosta jest elegancka . Osobiście uważam, że stwierdzenie if jest bardziej eleganckie niż jakikolwiek niestandardowy sposób wykonania tego sprawdzenia ...
Reed Copsey,

4
„Wszystko powinno być tak proste, jak to tylko możliwe, ale nie prostsze”. - Albert Einstein
corsiKa

3
@Sergio: Nie czuję, że jestem pedantyczny. Czuję, że ludzie często nadużywają metod rozszerzania i innych narzędzi w języku, aby zastąpić rzeczy, które już są proste. Istnieją setki sposobów porównywania dwóch wartości int, ale użycie czegoś innego niż bardziej oczywiste jest kiepskim wyborem, IMO.
Reed Copsey

3
@Sergio: Myślę więc, że nie widzę sensu pytania;)
Reed Copsey

6
@Sergio: jeśli ifnie jest „barokowy”, nie naprawiaj tego.
StriplingWarrior

Odpowiedzi:


154

Istnieje wiele opcji:

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

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

Sprawdź również ten post SO dla opcji regex.


334
Enumerable.Range musi najpierw wygenerować wyliczalne liczby całkowite, a następnie zapętlić każdy element, aby go znaleźć. To okropny pomysł, a wydajność w porównaniu do sprawdzania wartości jest drastycznie inna. Myślę, że powinniśmy przyjąć moto, tylko dlatego, że rozszerzenia LINQ są fajne, nie oznacza, że ​​powinny być używane do wszystkiego.
Matthew Abbott,


15
Zgadzam się, że to okropny pomysł, jeśli chodzi o wydajność, ale OP chce czegoś bardziej wymyślnego niż ifoświadczenia. To z pewnością powoduje, że ...;)
Tim Coker,

10
Warto zauważyć, że drugim parametrem nie jest „stop”, ale „liczba”. Na przykład Enumerable.Range (150, 300) .Contains (400) zwróci wartość true.
Shathur

5
Nie używaj tej odpowiedzi . Będzie miał horrendalną wydajność, jeśli twoje zasięgi są dość duże. Zobacz odpowiedź @ olivier-jacot-descombes
Aaron Hudon

95

Czy masz na myśli?

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

lub

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

1
Nie potrzebujesz tam "jest" ... To się nie skompiluje. (W przeciwnym razie zgadzam się w 100%)
Reed Copsey,

4
@Ben, po prostu poczekaj, aż spróbuję to również opatentować :)
kemiller2002

Myślę, że jest to najbardziej solidne rozwiązanie, ale nie tak elegancko, którego szuka pytający, prawda?
Kevin Simple

Jedyne, co bym zmienił, to dodanie słowa kluczowego static do metody. ;-)
Robert S.

Wymaga flag granic, tj. InRange (number, lowerBound, LOWER_IS_INCLUSIVE, Upperbound, UPPER_IS_EXCLUSIVE), aby umożliwić <vs <=. Napisałem to z zamiarem złośliwości, ale teraz, kiedy o tym myślę, flagi faktycznie zachęcałyby dzwoniącego do prostego wyjaśnienia specyfikacji.
William T. Mallard

56

Aby dodać do szumu tutaj, możesz utworzyć metodę rozszerzenia:

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

Co pozwoliłoby ci zrobić coś takiego ...

int val = 15;

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

Biorąc to pod uwagę, wydaje się to głupią rzeczą do zrobienia, gdy sam czek jest tylko jedną linią.


1
@Ben: Poszedłem do tematu, który mówi „w pewnym zakresie” (co nie wydaje mi się niejednoznaczne w tym względzie), ale masz rację, że treść pytania mówi „między 1 a 100” (czyli oczywiście niejednoznaczne).
Adam Robinson

48

Jak powiedzieli inni, użyj prostego if.

Powinieneś pomyśleć o zamówieniu.

na przykład

1 <= x && x <= 100

jest łatwiejszy do odczytania niż

x >= 1 && x <= 100

19
„Łatwiej” jest w oczach patrzącego. Ja osobiście wolę mieć zmienną w pytaniu po lewej i stałej lub zmiennej nie w pytaniu po prawej stronie.
Adam Robinson

15
W Perlu 6 napisałbyś 1 <= x <= 100.
Jordão,

2
Porządek liczbowy jest początkowo najwyraźniejszy - ale możesz ćwiczyć swoje oczy / umysł do innych poleceń. A konkretnie - podoba mi się sztuczka polegająca na umieszczaniu stałej zawsze po lewej stronie. Jeśli to zrobisz, kompilator poinformuje Cię, kiedy =zamiast ==. Nie pomaga to w przypadku operatorów relacji opartych na nierówności - ale łatwo jest przyzwyczaić się do konsekwentnego korzystania z niego.
davidbak

1
Dodam tylko, że to rozwiązanie w żadnym wypadku nie jest przydatne. Rozważ xto złożone wywołanie funkcji lub czasochłonne wyrażenie Linq. W takim przypadku zrobiłbyś to dwa razy, co nie jest dobre. Oczywiście, powinieneś przechowywać tę wartość w tymczasowej zmiennej lokalnej, ale są pewne przypadki (np. W instrukcjach else-if), w których chcesz wywoływać funkcje tylko po innych funkcjach if lub else-if. W przypadku zmiennych tymczasowych i tak musisz je wcześniej wywoływać. W takich przypadkach najlepszym rozwiązaniem jest metoda rozszerzenia (wspomniana w innych odpowiedziach).
Robert S.

4
Lubię też porządek liczbowy, a także test uzupełnienia, np. X <10 || 20 <x. Do mnie krzyczy "x jest poza zakresem 10 - 20".
William T. Mallard

44

W kodzie produkcyjnym po prostu napisałbym

1 <= x && x <= 100

Jest to łatwe do zrozumienia i bardzo czytelne.


Oto sprytna metoda, która zmniejsza liczbę porównań z dwóch do jednego dzięki zastosowaniu matematyki. Chodzi o to, że jeden z dwóch czynników staje się ujemny, jeśli liczba leży poza zakresem, a zero, jeśli liczba jest równa jednej z granic:

Jeśli granice są włącznie:

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

lub

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

Jeśli granice są wyłączne:

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

lub

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

3
Według moich standardów jest to zdecydowanie najbardziej eleganckie rozwiązanie, interesujące jest to, że dla mnie wydaje się również działać nieco szybciej niż sprawdzenie obu wyrażeń, co oznacza, że ​​wydaje się również bardziej niespójne (prędkość wydaje się być bardziej zróżnicowana) byłoby interesujące zobaczyć jeśli są jakieś badania, które z nich są szybsze.
Thomas Lindvall

3
Przetestowałem Twoje rozwiązanie na javascript i jego dokładność z liczbami zmiennoprzecinkowymi do 14 miejsc po przecinku. To bardzo dobry fragment kodu. Głosowałbym trzykrotnie, gdybym mógł
gumowata

4
Chociaż istnieje drobny problem, jeśli chodzi o duże liczby dodatnie, może to spowodować przepełnienie! XD Warto o tym pamiętać podczas pisania kodu.
BrainStorm.exe,

2
Pytanie to wymaga elegancji i dlatego ma raczej wartość akademicką niż praktyczną. Osobiście użyłbym prostego 1 < x && x < 100w produktywnym kodzie. Łatwiej to zrozumieć.
Olivier Jacot-Descombes

1
Dla tych, którzy martwią się wydajnością, 1 < x & x < 100(no && short circuit) instruuje kompilator, że zawsze może ocenić x < 100bez względu na wynik 1 < x. O dziwo (ze względu na przewidywanie gałęzi) zawsze szybciej wykonuje się tę prostą operację, niż czasami ją pomija.
Tom Leys

23

Proponuję to:

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

Przykłady:

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

i oczywiście ze zmiennymi:

myvalue.IsWithin(min, max)

Jest łatwy do odczytania (zbliżony do ludzkiego języka) i działa z każdym porównywalnym typem (liczby całkowite, podwójne, typy niestandardowe ...).

Posiadanie łatwego do odczytania kodu jest ważne, ponieważ programista nie marnuje „cykli mózgowych”, aby go zrozumieć. Podczas długich sesji kodowania zmarnowane cykle mózgowe powodują, że programista jest wcześniej zmęczony i podatny na błędy.


3
uprościłbym jeszcze bardziej, używając słowa między i mając flagę logiczną, aby określić, czy obejmuje, czy nie
Ben

Dobry. Łatwo to zrozumieć. Zmieniłem nazwę 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, ale nie lubię negatywnych warunków
Paulustrious

21

Przy odrobinie nadużycia metody rozszerzenia możemy uzyskać następujące „eleganckie” rozwiązanie:

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!");
        }
    }
}

Podoba mi się rozwiązanie! Btw wspierać włącznie, tworzyć enum Inclusivez wartości: Lower, Upper, All. I przekazać do Infunkcji jednym dodatkowym parametrem typu enum Inclusiveo wartości domyślnej Inclusive.All, zaktualizuj Tociało funkcji do uchwytu All, Lower, Upperwartości :)
Nikita

7

Jeśli to przypadek, wystarczy prosty if. Jeśli zdarza się to w wielu miejscach, możesz rozważyć te dwa:

  • PostSharp . Udekoruj metody atrybutami, które „wstrzykują” kod do metody po kompilacji. Nie wiem na pewno, ale wyobrażam sobie, że można to do tego wykorzystać.

Coś jak:

[Between("parameter", 0, 100)]
public void Foo(int parameter)
{
}
  • Kod kontraktów . Ma tę zaletę, że ograniczenia można sprawdzić w czasie kompilacji, poprzez statyczną weryfikację kodu i miejsc, które go używają.

+1 dla kontraktów kodowych; jest specyficzny dla walidacji parametru, ale jest częstym przypadkiem użycia, a weryfikacja statyczna może być niezwykle przydatna.
Dan Bryant

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

5

Użycie &&wyrażenia do połączenia dwóch porównań jest po prostu najelegantszym sposobem na zrobienie tego. Jeśli spróbujesz użyć wymyślnych metod rozszerzania i tym podobnych, napotkasz pytanie, czy uwzględnić górną granicę, dolną granicę, czy obie. Gdy zaczniesz dodawać dodatkowe zmienne lub zmieniasz nazwy rozszerzeń, aby wskazać, co jest zawarte, kod staje się dłuższy i trudniejszy do odczytania (dla większości programistów). Ponadto narzędzia takie jak Resharper ostrzegają cię, jeśli twoje porównanie nie ma sensu ( number > 100 && number < 1), czego nie zrobią, jeśli użyjesz metody (`` i.IsBetween (100, 1) '').

Jedynym innym komentarzem, jaki mógłbym zrobić, jest to, że jeśli sprawdzasz dane wejściowe z zamiarem zgłoszenia wyjątku, powinieneś rozważyć użycie kontraktów kodu:

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

Jest to bardziej eleganckie niż if(...) throw new Exception(...)i możesz nawet otrzymać ostrzeżenia w czasie kompilacji, jeśli ktoś spróbuje wywołać Twoją metodę bez upewnienia się, że numer znajduje się w granicach.


2
Do Twojej wiadomości, analizator statyczny kontraktów jest szczęśliwszy, gdy ograniczenia dolnej i górnej granicy są podzielone na oddzielne instrukcje Require.
Dan Bryant

Dzięki, Dan Bryant, właśnie tego tutaj szukałem. Nie można znaleźć zbyt wielu materiałów na temat sugestii dotyczących stylu warunków dla wymagań i innych powiązanych metod Code Contract.
jpierson,

2

Jeśli chcesz napisać więcej kodu niż proste if, być może możesz: Utworzyć metodę rozszerzenia o nazwie 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());

Uzupełnienie:Warto zauważyć, że w praktyce bardzo rzadko „po prostu sprawdzasz równość” (lub <,>) w bazie kodu. (Poza najbardziej trywialnymi sytuacjami.) Jako przykład, każdy programista gier użyłby w każdym projekcie kategorii podobnych do poniższych. Zauważ, że w tym przykładzie jest to (tak się składa), że używa funkcji (Mathf.Approxately), która jest wbudowana w to środowisko; w praktyce zazwyczaj musisz starannie opracować własne koncepcje tego, co oznaczają porównania dla komputerowych reprezentacji liczb rzeczywistych, w zależności od sytuacji, w której się znajdujesz. (Nie wspominaj nawet o tym, że jeśli robisz coś w rodzaju regulatora, regulatora PID lub podobnego, cała sprawa staje się centralna i bardzo trudna, staje się naturą projektu.

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
Zamień if i else nareturn (value >= Min && value <= Max);
AeroX

elegancki sposób zapisu porównania to „w logicznej kolejności ...” if (Min <= wartość && wartość <= Max). To jest o wiele ładniejsze.
Fattie

2
Wracając do tego pytania, jest to tak zaskakujące, że nikt nie wspomniał o centralnej kwestii w żadnym projekcie w świecie rzeczywistym (szczególnie jeśli jesteś inżynierem gier), że musisz poradzić sobie z problemem przybliżenia . W żadnym prawdziwym oprogramowaniu w zasadzie nigdy nie „po prostu dokonujesz porównania” (czy to równości, czy <,>), musisz wziąć pod uwagę i rozwiązać problem błędu, w zależności od sytuacji. Zredagowałem dodatek do tej odpowiedzi (jedyna poprawna odpowiedź tutaj!), Ponieważ więcej odpowiedzi nie jest dozwolone.
Fattie

Dziękuję za tę obserwację i uzupełnienie.
Tony

2

Ponieważ wszystkie inne odpowiedzi nie są wymyślone przeze mnie, tutaj tylko moja implementacja:

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

Następnie możesz go używać w następujący sposób:

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

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

2

EDYCJA: Podano nową odpowiedź. Zacząłem używać C #, kiedy napisałem pierwszą odpowiedź na to pytanie, iz perspektywy czasu zdaję sobie sprawę, że moje „rozwiązanie” było / jest naiwne i nieefektywne.

Moja pierwotna odpowiedź: wybrałbym prostszą wersję:

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

Lepszy sposób

Ponieważ nie widziałem innego rozwiązania, które byłoby bardziej wydajne (przynajmniej według moich testów), spróbuję jeszcze raz.

Nowy i lepszy sposób, który działa również z zakresami ujemnymi :

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

Można tego używać zarówno z dodatnimi, jak i ujemnymi zakresami, a domyślnie jest to zakres

1..100 (włącznie) i używa xjako liczby do sprawdzenia, po której następuje opcjonalny zakres zdefiniowany przez mini max.

Dodawanie przykładów dobrej miary

Przykład 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));

Zwroty:

True
True
True
False
True

Przykład 2: użycie listy losowych liczb całkowitych od 1 do 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);

Zwroty:

66660 ints found in range 1..100

Czas wykonania: 0,016 sekund (s)


Tak, komentuję komentarz do mojej odpowiedzi z 2013 roku :) @RyanTheLeach: Czym moja odpowiedź na to pytanie różni się od odpowiedzi „zaakceptowanej”? Zdaję sobie sprawę, że nie jest to najskuteczniejsza wędrówka, ale „straszna”? Jak złe może być przydzielanie i zapętlanie 100 int? W 1950 roku prawdopodobnie nie został zaakceptowany społecznie, ale ...
cseder

@RyanTheLeach Nie winię cię ... Zaktualizowałem moją odpowiedź, więc jeśli znasz rozwiązanie, które jest jeszcze bardziej wydajne, opowiedz o nim!
cseder

1
Usunąłem moje komentarze, ponieważ nie są już aktualne. Dzięki za poprawkę, wydaje się w porządku.
Ryan The Leach

1

Nowa wersja starego ulubionego:

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

3
W rzeczywistości istnieją cztery przypadki: włączający / włączający, włączający / wyłączny, wyłączny / włączający i wyłączny / wyłączny.
William T. Mallard

1

W C, jeśli efektywność czasowa jest kluczowa i przepełnienia całkowite zawiną, można to zrobić if ((unsigned)(value-min) <= (max-min)) .... Jeśli „max” i „min” są zmiennymi niezależnymi, dodatkowe odejmowanie dla (max-min) będzie stratą czasu, ale jeśli to wyrażenie można wstępnie obliczyć w czasie kompilacji lub jeśli można je obliczyć raz w czasie wykonywania, aby przetestować wiele liczb z tego samego zakresu, powyższe wyrażenie można obliczyć wydajnie nawet w przypadku, gdy wartość mieści się w zakresie (jeśli duża część wartości będzie poniżej prawidłowego zakresu, może być szybsze w użyciuif ((value >= min) && (value <= max)) ... ponieważ zakończy się wcześniej, jeśli wartość jest mniejsza niż min).

Zanim jednak skorzystasz z takiej implementacji, wykonaj test porównawczy swojej maszyny docelowej. Na niektórych procesorach wyrażenie dwuczęściowe może być szybsze we wszystkich przypadkach, ponieważ dwa porównania można wykonać niezależnie, podczas gdy w metodzie odejmowania i porównywania odejmowanie musi zostać zakończone, zanim będzie można wykonać porównanie.


1

A może coś takiego?

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

z następującą metodą rozszerzenia (przetestowano):

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

Zrobiłbym obiekt Range, coś takiego:

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

Następnie używasz tego w ten sposób:

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

W ten sposób możesz użyć go ponownie do innego typu.


Twój Rangeobiekt musi korzystać z CompareTometody porównywania elementów, a nie <operatora.
Servy

Masz rację, ale jeśli implementujesz IComparable, powinieneś także przesłonić operatory (przynajmniej tak mówi moja analiza kodu VS), co oznacza, że ​​<zadziała. Chociaż mogę się mylić, nie mam dużego doświadczenia i to jest moja pierwsza odpowiedź na SO
IEatBagels

Nie, Twój kompilator nie powie, że to działa. To się nie skompiluje. Jest całkowicie rozsądne, aby obiekt mógł zaimplementować IComparablei nie przeciążać <operatora.
Servy

1

Sprawdzając, czy „liczba” mieści się w zakresie, musisz jasno określić, co masz na myśli, a co oznacza, że ​​dwie liczby są równe? Ogólnie rzecz biorąc, powinieneś zawijać wszystkie liczby zmiennoprzecinkowe w tak zwaną „kulkę epsilon”. Robi się to przez wybranie małej wartości i stwierdzenie, że jeśli dwie wartości są tak blisko, są tym samym.

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

Z tymi dwoma pomocnikami na miejscu i założeniem, że jeśli jakakolwiek liczba może zostać rzucona jako podwójna bez wymaganej dokładności. Teraz będziesz potrzebować tylko wyliczenia i innej metody

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

Druga metoda jest następująca:

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

To może być o wiele więcej niż to, czego chciałeś, ale powstrzymuje Cię to przed ciągłym zaokrąglaniem i próbą zapamiętania, czy wartość została zaokrąglona i do jakiego miejsca. Jeśli potrzebujesz, możesz łatwo rozszerzyć to, aby działało z dowolnym epsilonem i pozwolić na zmianę epsilon.


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

Stosowanie

double numberToBeChecked = 7;

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

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

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


1

Jeśli obawiasz się komentarza @Daap dotyczącego zaakceptowanej odpowiedzi i możesz przekazać wartość tylko raz, możesz wypróbować jedną z następujących czynności

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

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

lub

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

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

1

Jeśli chodzi o elegancję, to, co najbliższe zapisowi matematycznemu ( a <= x <= b ), nieznacznie poprawia czytelność:

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

Dla dalszych ilustracji:

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

0

Szukałem eleganckiego sposobu na zrobienie tego, w którym granice mogłyby zostać przełączone (tj. Nie byłem pewien, w jakiej kolejności znajdują się wartości).

Będzie to działać tylko w nowszych wersjach języka C #, w których istnieje znak?:

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

Oczywiście możesz zmienić tam znaki = do swoich celów. Można też polubić rzucanie typów. Potrzebowałem tylko powrotu zmiennoprzecinkowego w granicach (lub równych)


0

Elegancki, ponieważ nie wymaga określenia, która z dwóch wartości granicznych jest najpierw większa. Nie zawiera również gałęzi.

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

& + | są operatorami
bitowymi

0

Nie wiem, ale używam tej metody:

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

I tak mogę to wykorzystać:

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

Proszę podać przykład użycia poniżej bloku kodu, pomoże to OP wiedzieć, czy pasuje do jego celu
Gabriel Balsa Cantú

0

Oto kilka metod rozszerzeń, które mogą pomóc

  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

Jeśli ma na celu zweryfikowanie parametrów metody, żadne z rozwiązań nie zgłasza ArgumentOutOfRangeException i nie umożliwia łatwej / prawidłowej konfiguracji włączających / wyłącznych wartości min / max.

Użyj w ten sposób

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

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

Właśnie napisałem te piękne funkcje. Ma również tę zaletę, że nie ma rozgałęzień (pojedynczego if) dla prawidłowych wartości. Najtrudniejsze jest stworzenie odpowiednich komunikatów o wyjątkach.

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

Szukasz in [1..100]? To tylko Pascal.


2
Nieprawda, to nie tylko Pascal. Wiele współczesnych języków ma takie cechy. Na przykład w Kotlinie nazywa się to „dopasowywaniem wzorców”. Przykład when (number) { in 0..9 -> println("1 digit") in 10..99 -> println("2 digits") in 100..999 -> println("3 digits") }
this.myself
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.