Come verificare elegantemente se un numero rientra in un intervallo?


157

Come posso farlo elegantemente con C # e .NET 3.5 / 4?

Ad esempio, un numero può essere compreso tra 1 e 100.

Conosco un semplice se sarebbe sufficiente; ma la parola chiave a questa domanda è eleganza. È per il mio progetto giocattolo non per la produzione.

Questa domanda non riguardava la velocità, ma la bellezza del codice. Smetti di parlare di efficienza e simili; ricorda che stai predicando al coro.


23
Ri: La tua "modifica" - semplice è elegante . Personalmente trovo la dichiarazione if più elegante di qualsiasi mezzo non standard per fare questo controllo ...
Reed Copsey,

4
"Tutto dovrebbe essere reso il più semplice possibile, ma non più semplice." - Albert Einstein
corsiKa

3
@Sergio: non mi sento pedante. Sento che le persone spesso abusano dei metodi di estensione e di altri strumenti nella lingua per sostituire cose che sono già semplici. Esistono centinaia di modi per confrontare due valori int, ma usare qualcosa di più ovvio è una scelta sbagliata, IMO.
Reed Copsey,

3
@Sergio: suppongo, quindi, non vedo il punto della domanda;)
Reed Copsey,

6
@Sergio: se ifnon è "barocco" non aggiustarlo.
StriplingWarrior il

Risposte:


154

Ci sono tante opzioni:

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

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

Inoltre, dai un'occhiata a questo post SO per le opzioni regex.


334
Enumerable.Range deve prima generare l'enumerabile di numeri interi, quindi scorrere ogni elemento per trovarlo. È un'idea terribile e le prestazioni rispetto al controllo di un valore sono drasticamente diverse. Penso che dovremmo adottare una moto, solo perché le estensioni LINQ sono fantastiche, non significa che dovrebbero essere utilizzate per tutto.
Matthew Abbott,


15
Sono d'accordo sul fatto che questa è una terribile idea dal punto di vista delle prestazioni, ma l'OP vuole qualcosa di più elegante di una ifdichiarazione. Questo certamente compie questo ...;)
Tim Coker il

10
Vale la pena notare che il secondo parametro non è "stop", ma "count". Ad esempio, Enumerable.Range (150, 300) .Contains (400) restituirà true.
Shathur,

5
Per favore, non usare questa risposta . Avrà prestazioni orrende se le tue gamme sono abbastanza grandi. Si prega di vedere la risposta di @ olivier-jacot-descombes
Aaron Hudon,

95

Vuoi dire?

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

o

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

1
Non hai bisogno di "è" lì dentro ... Questo non verrà compilato. (Altrimenti, sono d'accordo al 100%)
Reed Copsey,

4
@Ben, aspetta solo che provi a brevettare anch'io :)
kemiller2002,

Penso che questa sia la soluzione più solida, ma non così elegantemente l'interrogante che sta cercando, no?
Kevin Simple,

L'unica cosa che vorrei cambiare è aggiungere la parola chiave statica al metodo. ;-)
Robert S.

Richiede flag di confine, ovvero InRange (numero, lowerBound, LOWER_IS_INCLUSIVE, Upperbound, UPPER_IS_EXCLUSIVE) per consentire <vs <=. Ho scritto questo con l'intenzione di essere snarky ma ora che ci penso le bandiere incoraggerebbero davvero il chiamante a ottenere le loro specifiche.
William T. Mallard,

56

Solo per aggiungere al rumore qui, è possibile creare un metodo di estensione:

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

Che ti permetterebbe di fare qualcosa come ...

int val = 15;

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

Detto questo, questa sembra una cosa sciocca da fare quando il controllo stesso è solo una riga.


1
@Ben: sono andato sull'argomento, che dice "entro un raggio" (che non credo sia ambiguo a tale riguardo), ma hai ragione nel dire che il corpo della domanda dice "tra 1 e 100" (che è , ovviamente, ambiguo).
Adam Robinson,

48

Come altri hanno detto, usa un semplice if.

Dovresti pensare all'ordinamento.

per esempio

1 <= x && x <= 100

è più facile da leggere di

x >= 1 && x <= 100

19
"Più facile" è negli occhi di chi guarda. Personalmente preferisco avere la variabile in questione a sinistra e la costante o la variabile non in questione a destra.
Adam Robinson,

15
In Perl 6 , scriveresti 1 <= x <= 100.
Jordão,

2
L'ordine della linea numerica è inizialmente il più chiaro, ma puoi allenare gli occhi / la mente per altri ordini. In particolare, mi piace il trucco di posizionare la costante a sinistra, sempre. Se lo fai, il compilatore ti dirà quando hai digitato =invece di ==. Non aiuta con gli operatori relazionali di non uguaglianza, ma è facile abituarsi ad usarlo in modo coerente.
davidbak,

1
Voglio solo aggiungere che questa soluzione non è utile in nessun caso. Considerare che xè una chiamata di funzione complessa o un'espressione Linq che richiede tempo. In questo caso lo faresti due volte, il che non è una buona cosa. Sicuramente dovresti archiviare il valore in una variabile locale temporanea, ma ci sono alcuni casi (ad es. In istruzioni else-if) in cui vuoi chiamare le funzioni solo dopo che gli altri if o else-if non sono riusciti. Con le variabili temporanee devi chiamarle comunque prima. Un metodo di estensione (menzionato in altre risposte) è la migliore soluzione imho in quei casi.
Robert S.

4
Mi piace anche l'ordine delle linee numeriche e anche per il test del complemento, ad esempio x <10 || 20 <x. Per me grida "x è fuori dall'intervallo 10 - 20".
William T. Mallard,

44

Nel codice di produzione scrivo semplicemente

1 <= x && x <= 100

Questo è facile da capire e molto leggibile.


Ecco un metodo intelligente che riduce il numero di confronti da due a uno usando un po 'di matematica. L'idea è che uno dei due fattori diventa negativo se il numero si trova al di fuori dell'intervallo e zero se il numero è uguale a uno dei limiti:

Se i limiti sono inclusi:

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

o

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

Se i limiti sono esclusivi:

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

o

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

3
Secondo i miei standard questa è di gran lunga la soluzione più elegante, interessante è che per me sembra anche funzionare un po 'più velocemente rispetto al controllo di entrambe le espressioni, che ha detto che sembra anche più incoerente (la velocità sembra variare di più) sarebbe interessante vedere se c'è qualche ricerca fatta su quale è il più veloce.
Thomas Lindvall,

3
Testato la tua soluzione su javascript e la sua precisione con numeri in virgola mobile fino a 14 decimali. È un ottimo frammento di codice. Ti sarei votato tre volte se potessi
gomme gomma

4
Tuttavia, c'è un piccolo problema se sono coinvolti grandi numeri positivi, può traboccare! XD Potresti tenerlo a mente quando scrivi il tuo codice.
BrainStorm.exe

2
La domanda richiede eleganza ed è quindi più accademica che di valore pratico. Personalmente userei un semplice 1 < x && x < 100codice produttivo. È più facile da capire.
Olivier Jacot-Descombes,

1
Per coloro che sono preoccupati per le prestazioni, 1 < x & x < 100(nessun && cortocircuito) indica al compilatore che può sempre valutare x < 100indipendentemente dal risultato 1 < x. Stranamente (a causa della previsione del ramo) è sempre più veloce fare questa semplice operazione che a volte saltarla.
Tom Leys il

23

Propongo questo:

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

Esempi:

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

e ovviamente con variabili:

myvalue.IsWithin(min, max)

È facile da leggere (vicino al linguaggio umano) e funziona con qualsiasi tipo comparabile (intero, doppio, tipi personalizzati ...).

Avere un codice di facile lettura è importante perché lo sviluppatore non sprecherà "cicli cerebrali" per capirlo. Nelle lunghe sessioni di programmazione, i cicli cerebrali sprecati rendono lo sviluppatore stanco prima e soggetto a bug.


3
semplificherei ancora di più usando la parola in mezzo e avendo una bandiera booleana per determinare se inclusivo o no
Ben

Buona. È facile da capire Ho cambiato il nome 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 avrebbe funzionato ma non mi piacciono le condizioni negative
Paulustrious

21

Con un po 'di abuso del metodo di estensione, possiamo ottenere la seguente soluzione "elegante":

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

Ti piace la soluzione! Btw per sostenere inclusiva, creare enum Inclusivecon i valori: Lower, Upper, All. E passare per la Infunzione di un parametro aggiuntivo di tipo enum Inclusivecon il valore predefinito Inclusive.All, aggiornare il Tocorpo della funzione di maniglia All, Lower, Uppervalori :)
Nikita

7

Se questo è accidentale, ifè sufficiente un semplice . Se ciò accade in molti luoghi, potresti considerare questi due:

  • PostSharp . Decorare i metodi con attributi che 'iniettano' il codice nel metodo dopo la compilazione. Non lo so per certo, ma posso immaginare che possa essere usato per questo.

Qualcosa di simile a:

[Between("parameter", 0, 100)]
public void Foo(int parameter)
{
}
  • Contratti di codice . Ha il vantaggio che i vincoli possono essere controllati in fase di compilazione, mediante verifica statica del codice e dei luoghi che lo utilizzano.

+1 per i contratti con codice; è specifico per la convalida di un parametro, ma è un caso d'uso frequente e la verifica statica ha il potenziale per essere estremamente utile.
Dan Bryant,

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

5

Usare &&un'espressione per unire due confronti è semplicemente il modo più elegante per farlo. Se si tenta di utilizzare metodi di estensione fantasiosi e simili, si pone la questione se includere il limite superiore, il limite inferiore o entrambi. Una volta che inizi ad aggiungere variabili aggiuntive o a modificare i nomi delle estensioni per indicare cosa è incluso, il tuo codice diventa più lungo e più difficile da leggere (per la stragrande maggioranza dei programmatori). Inoltre, strumenti come Resharper ti avviseranno se il tuo confronto non ha senso (number > 100 && number < 1 ), cosa che non farebbero se usi un metodo ('i.IsB Between (100, 1)').

L'unico altro commento che farei è che se stai controllando gli input con l'intenzione di lanciare un'eccezione, dovresti prendere in considerazione l'uso di contratti di codice:

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

Questo è più elegante di if(...) throw new Exception(...), e potresti anche ricevere avvisi in fase di compilazione se qualcuno prova a chiamare il tuo metodo senza assicurarsi che il numero sia prima nei limiti.


2
Cordiali saluti, l'analizzatore statico dei contratti è più felice quando i vincoli limite inferiore e limite superiore sono suddivisi in istruzioni richieste separate.
Dan Bryant,

Grazie Dan Bryant, è esattamente quello che stavo cercando qui. Impossibile trovare molto materiale sui suggerimenti sullo stile delle condizioni per i Requisiti e altri metodi relativi al Contratto di codice.
jpierson,

2

Se vuoi scrivere più codice di un semplice if, forse puoi: Creare un metodo di estensione chiamato 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());

Addendum:vale la pena notare che in pratica molto raramente "basta verificare l'uguaglianza" (o <,>) in una base di codice. (Tranne che nelle situazioni più banali.) A puro titolo di esempio, ogni programmatore di giochi userebbe categorie simili alle seguenti in ogni progetto, come una questione di base. Si noti che in questo esempio (sembra essere) utilizzando una funzione (Mathf.Approssimativamente) che è integrata in quell'ambiente; in pratica, in genere, è necessario sviluppare attentamente i propri concetti su ciò che il confronto significa per le rappresentazioni al computer di numeri reali, per il tipo di situazione che si sta progettando. (Non menzionare nemmeno che se stai facendo qualcosa di simile, forse un controller, un controller PID o simili, l'intero problema diventa centrale e molto difficile, diventa la natura del progetto.

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
Sostituisci if e else conreturn (value >= Min && value <= Max);
AeroX il

il modo elegante di scrivere il confronto è "in ordine logico ..." se (Min <= valore && valore <= Max). È molto più bello.
Fattie

2
Oltre a questa domanda, è così sorprendente che nessuno abbia menzionato il problema centrale in qualsiasi progetto del mondo reale (in particolare se sei un ingegnere di gioco) è che devi affrontare il problema dell'approssimazione . In qualsiasi software del mondo reale in pratica non si "fa semplicemente un confronto" (che sia uguaglianza o <,>) che si deve considerare e affrontare il problema dell'errore, a seconda della situazione attuale. Ho modificato un addendum a questa risposta (l'unica risposta corretta qui!) Poiché non sono consentite più risposte.
Fattie,

Grazie per questa osservazione e l'addendum.
Tony,

2

Perché tutte le altre risposte non sono state inventate da me, qui solo la mia implementazione:

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

Puoi quindi usarlo in questo modo:

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

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

2

EDIT: Nuova risposta fornita. Stavo iniziando a usare C # quando ho scritto la prima risposta a questa domanda, e col senno di poi ora mi rendo conto che la mia "soluzione" era / è ingenua e inefficiente.

La mia risposta originale: andrei con la versione più semplice:

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

Un modo migliore

Dato che non ho visto altre soluzioni più efficienti (almeno secondo i miei test), ci proverò ancora.

Modo nuovo e migliore che funziona anche con intervalli negativi :

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

Questo può essere utilizzato con intervalli sia positivi che negativi e per impostazione predefinita su un intervallo di

1..100 (incluso) e utilizza xcome numero da controllare seguito da un intervallo opzionale definito da mine max.

Aggiunta di esempi per una buona misura

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

Ritorna:

True
True
True
False
True

Esempio 2: utilizzo di un elenco di input casuali tra 1 e 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);

Ritorna:

66660 ints found in range 1..100

Tempo di esecuzione: 0,016 secondo (s)


Sì, sto commentando un commento alla mia risposta del 2013 :) @RyanTheLeach: In che modo la mia risposta a questa domanda è diversa dalla risposta ora "accettata"? Mi rendo conto che non è l'attraversamento più efficace, ma "terribile"? Quanto può essere grave l'allocazione e il looping tra 100 ints? Nel 1950 probabilmente non fu accettato socialmente, ma ...
ceder

@RyanTheLeach Non ti biasimo ... Ho aggiornato la mia risposta, quindi, se conosci una soluzione ancora più efficiente, ti preghiamo di elaborare!
sabato

1
Ho eliminato i miei commenti perché non sono più validi. Grazie per la correzione, sembra ok.
Ryan The Leach,

1

Una nuova svolta su un vecchio preferito:

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

3
In realtà ci sono quattro casi, inclusi / inclusi, inclusi / esclusivi, esclusivi / inclusi ed esclusivi / esclusivi.
William T. Mallard

1

In C, se l'efficienza temporale è cruciale e gli overflow di interi si concluderanno, si potrebbe fare if ((unsigned)(value-min) <= (max-min)) .... Se 'max' e 'min' sono variabili indipendenti, la sottrazione aggiuntiva per (max-min) farà perdere tempo, ma se quell'espressione può essere precalcolata in fase di compilazione, o se può essere calcolata una volta in fase di esecuzione per testarne molte numeri sullo stesso intervallo, l'espressione sopra può essere calcolata in modo efficiente anche nel caso in cui il valore sia compreso nell'intervallo (se una grande frazione di valori sarà inferiore all'intervallo valido, potrebbe essere più veloce da usare if ((value >= min) && (value <= max)) ...perché uscirà presto se il valore è inferiore a min).

Prima di utilizzare un'implementazione del genere, tuttavia, confrontare la propria macchina target. Su alcuni processori, l'espressione in due parti può essere più veloce in tutti i casi poiché i due confronti possono essere eseguiti in modo indipendente mentre nel metodo di sottrazione e confronto la sottrazione deve essere completata prima che il confronto possa essere eseguito.


1

Che ne dici di questo?

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

con il metodo di estensione come segue (testato):

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

Vorrei fare un oggetto Range, qualcosa del genere:

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

Quindi lo usi in questo modo:

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

In questo modo puoi riutilizzarlo per un altro tipo.


L' Rangeoggetto deve utilizzare il CompareTometodo per confrontare gli elementi, non l' <operatore.
Servito il

Hai ragione, anche se implementando IComparable dovresti anche sovrascrivere gli operatori (almeno è quello che dice la mia analisi del codice VS), il che significa che <funzionerebbe. Anche se potrei sbagliarmi, non ho molta esperienza e questa è la mia prima risposta su SO
IEatBagels

No, il tuo compilatore non dirà che funziona. Questo non verrà compilato. È del tutto ragionevole per un oggetto implementare IComparablee non sovraccaricare l' <operatore.
Servi

1

Quando controlli se un "Numero" è compreso in un intervallo, devi essere chiaro in cosa intendi e cosa significano due numeri uguali? In generale dovresti racchiudere tutti i numeri in virgola mobile in quella che viene chiamata una "palla epsilon", facendo un piccolo valore e dicendo che se due valori sono così vicini sono la stessa cosa.

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

Con questi due aiutanti in atto e supponendo che qualsiasi numero possa essere lanciato come doppio senza la precisione richiesta. Tutto ciò di cui hai bisogno ora è un enum e un altro metodo

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

L'altro metodo segue:

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

Ora questo potrebbe essere molto più di quello che volevi, ma ti impedisce di occuparti di arrotondare continuamente e di cercare di ricordare se un valore è stato arrotondato e in quale luogo. Se necessario, puoi estenderlo facilmente per lavorare con qualsiasi epsilon e consentire al tuo epsilon di cambiare.


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

uso

doppio numeroToBeChecked = 7;

var result = numberToBeChecked.IsB Between (100.122);

var var = 5.IsB Between (100.120);

var var = 8.0.IsB Between (1.2,9.6);


1

Se sei interessato al commento di @Daap sulla risposta accettata e puoi passare il valore solo una volta, puoi provare una delle seguenti

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

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

o

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

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

1

Per quanto riguarda l'eleganza, la cosa più vicina alla notazione matematica ( a <= x <= b ) migliora leggermente la leggibilità:

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

Per ulteriore illustrazione:

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

0

Stavo cercando un modo elegante per farlo dove i limiti potrebbero essere cambiati (cioè non sono sicuro in quale ordine siano i valori).

Funzionerà solo su versioni più recenti di C # in cui esiste?:

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

Ovviamente potresti cambiare i segni = lì dentro per i tuoi scopi. Potrebbe fare fantasia anche con il casting del tipo. Avevo solo bisogno di un ritorno a virgola mobile entro i limiti (o uguale a)


0

Elegante perché non richiede di determinare quale dei due valori limite è maggiore per primo. Inoltre non contiene rami.

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

& + | sono operatori bit per bit
nelsontruran

0

Non lo so ma utilizzo questo metodo:

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

E questo è il modo in cui posso usarlo:

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

Fornisci un esempio di utilizzo sotto il blocco di codice, questo aiuterà OP a sapere se si adatta al suo scopo
Gabriel Balsa Cantú

0

Questi sono alcuni metodi di estensione che possono aiutare

  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

Se si tratta di validare i parametri del metodo, nessuna delle soluzioni genera ArgumentOutOfRangeException e consente una configurazione semplice / corretta di valori min / max inclusivi / esclusivi.

Usa così

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

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

Ho appena scritto queste meravigliose funzioni. Ha anche il vantaggio di non avere ramificazioni (un singolo if) per valori validi. La parte più difficile è creare i messaggi di eccezione appropriati.

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

Stai cercando in [1..100]? Questo è solo Pascal.


2
Non è vero, non è solo Pascal. Molte lingue moderne hanno caratteristiche come questa. In Kotlin, ad esempio, si chiama "Pattern Matching". Esempio when (number) { in 0..9 -> println("1 digit") in 10..99 -> println("2 digits") in 100..999 -> println("3 digits") }
stesso
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.