¿Cómo verificar elegantemente si un número está dentro de un rango?

¿Cómo puedo hacer esto elegantemente con C # y .NET 3.5 / 4?

Por ejemplo, un número puede estar entre 1 y 100.

Sé que un simple si sería suficiente; pero la palabra clave para esta pregunta es elegancia. Es para mi proyecto de juguete, no para producción.

Esta pregunta no era sobre la velocidad, sino sobre la belleza del código. Deja de hablar sobre eficiencia y tal; recuerda que estás predicando al coro.

Hay muchas opciones:

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

Además, consulte esta publicación SO para las opciones de expresiones regulares.

¿Quieres decir?

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

o

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

Solo para boost el ruido aquí, puedes crear un método de extensión:

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

Lo cual te permitiría hacer algo como ...

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

Dicho eso, parece una tontería hacer cuando el cheque en sí es solo una línea.

Como otros dijeron, use un simple if.

Deberías pensar sobre el orden.

p.ej

 1 < = x && x <= 100 

es más fácil de leer que

 x >= 1 && x < = 100 

Puede reducir el número de comparaciones de dos a uno mediante el uso de algunas matemáticas. La idea es que uno de los dos factores se vuelve negativo si el número se encuentra fuera del rango y cero si el número es igual a uno de los límites:

Si los límites son inclusivos:

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

o

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

Si los límites son exclusivos:

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

o

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

Sin embargo, en el código de producción simplemente escribiría 1 < x && x < 100 , es más fácil de entender.

Con un poco de abuso del método de extensión, podemos obtener la siguiente solución “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!"); } } } 

Propongo esto:

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

Ejemplos:

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

y por supuesto con variables:

 myvalue.IsWithin(min, max) 

Es fácil de leer (cerca del lenguaje humano) y funciona con cualquier tipo comparable (entero, doble, tipos personalizados …).

Tener un código fácil de leer es importante porque el desarrollador no desperdiciará “ciclos cerebrales” para entenderlo. En largas sesiones de encoding, los ciclos cerebrales desperdiciados hacen que el desarrollador se canse antes y sea propenso a errores.

Si esto es incidental, un simple if es todo lo que necesita. Si esto sucede en muchos lugares, es posible que desee considerar estos dos:

  • PostSharp . Decore los métodos con atributos que ‘inyectan’ código en el método después de la comstackción. No lo sé con certeza, pero puedo imaginar que se puede usar para esto.

Algo como:

 [Between("parameter", 0, 100)] public void Foo(int parameter) { } 
  • Contratos de código . Tiene la ventaja de que las restricciones se pueden verificar en tiempo de comstackción, mediante la verificación estática de su código y los lugares que usan su código.

Usar una expresión && para unir dos comparaciones es simplemente la manera más elegante de hacer esto. Si intenta utilizar métodos de extensión sofisticados, se encontrará con la cuestión de si incluir el límite superior, el límite inferior o ambos. Una vez que comienza a agregar variables adicionales o cambia los nombres de las extensiones para indicar qué se incluye, su código se vuelve más largo y más difícil de leer (para la gran mayoría de los progtwigdores). Además, herramientas como Resharper te advertirán si tu comparación no tiene sentido ( number > 100 && number < 1 ), lo cual no harán si usas un método ('i.IsBetween (100, 1)').

El único otro comentario que haré es que si estás revisando entradas con la intención de lanzar una excepción, debes considerar el uso de contratos de código:

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

Esto es más elegante que if(...) throw new Exception(...) , e incluso podría recibir advertencias en tiempo de comstackción si alguien intenta llamar a su método sin asegurarse primero de que el número esté dentro de los límites.

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

Si desea escribir más código que un simple si, tal vez pueda: Crear un método de extensión llamado 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()); 

Adición: vale la pena señalar que, en la práctica, muy rara vez "simplemente verifica la igualdad" (o < ,>) en una base de código. (Aparte de las situaciones más triviales.) Puramente, como ejemplo, cualquier progtwigdor de juegos usaría categorías como las siguientes en cada proyecto, como una cuestión básica. Tenga en cuenta que en este ejemplo (pasa a ser) utilizando una función (Mathf.Approximately) que está incorporada en ese entorno; en la práctica, normalmente tiene que desarrollar cuidadosamente sus propios conceptos de lo que significan las comparaciones para las representaciones de números reales en la computadora, para el tipo de situación que está diseñando. (Ni siquiera mencione que si está haciendo algo como, tal vez un controlador, un controlador PID o similar, todo el problema se vuelve central y muy difícil, se convierte en la naturaleza del proyecto). De ninguna manera es el OP pregunta aquí una pregunta trivial o sin importancia.

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

Porque todas las otras respuestas no son inventadas por mí, aquí solo mi implementación:

 public enum Range { ///  /// A range that contains all values greater than start and less than end. ///  Open, ///  /// A range that contains all values greater than or equal to start and less than or equal to end. ///  Closed, ///  /// A range that contains all values greater than or equal to start and less than end. ///  OpenClosed, ///  /// A range that contains all values greater than start and less than or equal to end. ///  ClosedOpen } public static class RangeExtensions { ///  /// Checks if a value is within a range that contains all values greater than start and less than or equal to end. ///  /// The value that should be checked. /// The first value of the range to be checked. /// The last value of the range to be checked. /// True if the value is greater than start and less than or equal to end, otherwise false. public static bool IsWithin(this T value, T start, T end) where T : IComparable { return IsWithin(value, start, end, Range.ClosedOpen); } ///  /// Checks if a value is within the given range. ///  /// The value that should be checked. /// The first value of the range to be checked. /// The last value of the range to be checked. /// 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. /// True if the value is within the given range, otherwise false. public static bool IsWithin(this T value, T start, T end, Range range) where T : IComparable { 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)); } } } 

Puede usarlo así:

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

Un nuevo giro en un viejo favorito:

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

En C, si la eficiencia del tiempo es crucial y los desbordamientos de enteros se ajustarán, uno podría hacer if ((unsigned)(value-min) < = (max-min)) ... Si 'max' y 'min' son variables independientes, la resta adicional para (max-min) perderá tiempo, pero si esa expresión se puede precalcular en tiempo de comstackción, o si se puede calcular una vez en tiempo de ejecución para probar muchos números en el mismo rango, la expresión anterior se puede calcular de manera eficiente incluso si el valor está dentro del rango (si una gran fracción de valores estará por debajo del rango válido, puede ser más rápido if ((value >= min) && (value < = max)) ... porque saldrá temprano si el valor es menor que min).

Sin embargo, antes de utilizar una implementación como esa, comparta la máquina objective de uno. En algunos procesadores, la expresión de dos partes puede ser más rápida en todos los casos, ya que las dos comparaciones se pueden realizar de forma independiente, mientras que en el método de resta y comparación, la resta debe completarse antes de que se pueda ejecutar la comparación.

¿Qué tal algo como esto?

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

con el método de extensión de la siguiente manera (probado):

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

Haría un objeto Range, algo como esto:

 public class Range 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; } } 

Entonces lo usa de esta manera:

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

De esa forma puedes reutilizarlo para otro tipo.

Elegante porque no requiere que determine cuál de los dos valores límite es mayor primero. También no contiene twigs.

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

Me gustaría ir con la versión más simple:

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

Estaba buscando una forma elegante de hacerlo donde los límites podrían cambiarse (es decir, no estoy seguro de en qué orden están los valores).

Esto solo funcionará en las versiones más nuevas de C # donde el?: Existe

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

Obviamente, podría cambiar los signos = allí para sus propósitos. Podría ser elegante con el tipo de fundición también. Solo necesitaba un retorno flotante dentro de los límites (o igual a)

Al verificar si un “Número” está en un rango, debe tener claro lo que quiere decir, y ¿qué significan dos números iguales? En general, debe envolver todos los números de punto flotante en lo que se denomina una “bola épsilon”, esto se hace eligiendo un valor pequeño y diciendo que si dos valores son tan cercanos son la misma cosa.

  private double _epsilon = 10E-9; ///  /// Checks if the distance between two doubles is within an epsilon. /// In general this should be used for determining equality between doubles. ///  /// The orgin of intrest ///  The point of intrest /// The minimum distance between the points /// Returns true iff x in (x0-epsilon, x0+epsilon) 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 estos dos ayudantes en su lugar y asumiendo que si cualquier número puede ser lanzado como un doble sin la precisión requerida. Todo lo que necesitarás ahora es una enumeración y otro método

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

El otro método sigue:

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

Ahora bien, esto puede ser mucho más de lo que usted quería, pero le impide lidiar con el redondeo todo el tiempo y tratar de recordar si un valor se ha redondeado y en qué lugar. Si lo necesita, puede extender esto fácilmente para trabajar con cualquier épsilon y permitir que su épsilon cambie.