如何优雅地检查一个数字是否在一个范围内?
我怎样才能用C#和.NET 3.5 / 4优雅地做到这一点?
例如,一个数字可以在1到100之间。
我知道一个简单的就足够了; 但这个问题的关键字是高雅。 这是我的玩具项目不是为了生产。
这个问题不是速度,而是代码美。 停止谈论效率等等。 记得你正在向合唱团传道。
有很多select:
int x = 30; if (Enumerable.Range(1,100).Contains(x)) //true if (x >= 1 && x <= 100) //true
另外,看看这个SOpost的正则expression式选项。
你的意思是?
if(number >= 1 && number <= 100)
要么
bool TestRange (int numberToCheck, int bottom, int top) { return (numberToCheck >= bottom && numberToCheck <= top); }
只是为了增加噪音,你可以创build一个扩展方法:
public static bool IsWithin(this int value, int minimum, int maximum) { return value >= minimum && value <= maximum; }
这会让你做一些像…
int val = 15; bool foo = val.IsWithin(5,20);
话虽如此,当检查本身只有一行时,这似乎是一件愚蠢的事情。
正如别人所说,使用简单的if。
你应该考虑订购。
例如
1 <= x && x <= 100
比比较容易阅读
x >= 1 && x <= 100
通过使用一些mathalgorithm,可以将比较次数从两个减less到一个。 这个想法是,如果数字在范围之外,则两个因素中的一个变成否定的,如果数字等于边界之一,则两个因素中的一个变为零:
如果界限包含在内:
(x - 1) * (100 - x) >= 0
要么
(x - min) * (max - x) >= 0
如果界限是独占的:
(x - 1) * (100 - x) > 0
要么
(x - min) * (max - x) > 0
但是,在生产代码中,我会简单地使用1 < x && x < 100
,这更容易理解。
用一些扩展方法滥用,我们可以得到以下“优雅”的解决scheme:
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!"); } } }
我build议:
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; }
例子:
45.IsWithin(32, 89) true 87.2.IsWithin(87.1, 87.15) false 87.2.IsWithin(87.1, 87.25) true
当然还有variables:
myvalue.IsWithin(min, max)
这很容易阅读(接近人类的语言),并与任何可比较的types(整数,双倍,自定义types…)。
让代码易于阅读非常重要,因为开发人员不会浪费“大脑循环”来理解它。 长时间的编程会浪费大脑循环,使得开发人员更容易疲劳,容易出错。
如果这是偶然的,一个简单的if
是你所需要的。 如果在很多地方发生这种情况,你可能要考虑这两个:
- PostSharp 。 在编译之后,用具有“注入”代码的属性来装饰方法。 我不知道,但我可以想象它可以用于此。
就像是:
[Between("parameter", 0, 100)] public void Foo(int parameter) { }
- 代码合同 。 具有的优点是可以在编译时通过静态validation代码和使用代码的位置来检查约束。
if (value > 1 && value < 100) { // do work } else { // handle outside of range logic }
使用&&
expression式来join两个比较只是最简单的方法。 如果您尝试使用奇特的扩展方法等,则会遇到是包含上限还是下限的问题。 一旦你开始添加额外的variables或者改变扩展名来指示包含的内容,你的代码就会变得越来越难以阅读(绝大多数程序员)。 此外,像Resharper这样的工具会警告你,如果你的比较没有意义( number > 100 && number < 1
),如果你使用方法('i.IsBetween(100,1)'),他们不会这样做。
我唯一要做的其他评论是,如果你正在检查意图抛出exception的input,你应该考虑使用代码合同:
Contract.Requires(number > 1 && number < 100)
这是比if(...) throw new Exception(...)
更优雅,甚至可以得到编译时的警告,如果有人试图调用你的方法,而不是确保数字是在第一个界限。
在C中,如果时间效率是至关重要的,并且整数溢出将会包装, if ((unsigned)(value-min) <= (max-min)) ...
。 如果'max'和'min'是独立variables,额外的(max-min)减法将浪费时间,但是如果这个expression式可以在编译时进行预计算,或者在运行时可以计算一次,即使在值在范围内的情况下,也可以有效地计算上述expression式(如果大部分值将低于有效范围,则使用if ((value >= min) && (value <= max)) ...
因为如果value小于min,它会提前退出 )。
然而,在使用这样的实现之前,要testing一个人的目标机器。 在一些处理器上,两部分expression式在所有情况下都可能更快,因为两个比较可以独立完成,而在减法和比较方法中,减法必须在比较可以执行之前完成。
我会去更简单的版本:
if(Enumerable.Range(1,100).Contains(intInQuestion)) { ...DoStuff; }
这样的事情呢?
if (theNumber.isBetween(low, high, IntEx.Bounds.INCLUSIVE_INCLUSIVE)) { }
用下面的扩展方法(testing):
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; } }
如果要编写比简单的代码更多的代码,也许可以:创build名为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());
附录:值得注意的是,在实践中,你很less在代码库中“仅仅检查平等”(或<,>)。 (除了最微不足道的情况之外)。纯粹作为一个例子,任何游戏程序员都会在每个项目中使用类似下面的类别作为基本的事情。 请注意,在这个例子中,它恰好是使用内置于该环境中的函数(Mathf.Approximately) 在实践中,你通常必须认真地发展自己的概念,比较什么样的比较对于计算机表示实数的意义,对于你正在devise的情况types。 (甚至不要提到,如果你正在做某种事情,或许是一个控制器,一个PID控制器或类似的东西,那么整个问题变得很重要,而且非常困难,这就成了项目的本质。)绝不是OP在这里提出一个微不足道或不重要的问题。
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; }
我会做一个Range对象,像这样:
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; } }
然后你用这个方法:
Range<int> myRange = new Range<int>(1,999); bool isWithinRange = myRange.IsWithinBoundaries(3);
这样你可以重用它的另一种types。
旧的最爱的一个新的转折:
public bool IsWithinRange(int number, int topOfRange, int bottomOfRange, bool includeBoundaries) { if (includeBoundaries) return number <= topOfRange && number >= bottomOfRange; return number < topOfRange && number > bottomOfRange; }
因为所有其他的答案不是由我发明的,这里只是我的实现:
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)); } } }
你可以像这样使用它:
var value = 5; var start = 1; var end = 10; var result = value.IsWithin(start, end, Range.Closed);
我正在寻找一个优雅的方式来做边界可能会被切换(即不知道这些值的顺序)。
这只适用于?:存在的C#的新版本
bool ValueWithinBounds(float val, float bounds1, float bounds2) { return bounds1 >= bounds2 ? val <= bounds1 && val >= bounds2 : val <= bounds2 && val >= bounds1; }
很明显,你可以改变里面的=号。 也可以喜欢types铸造。 我只需要一个范围内的浮动返回(或等于)
当检查一个“数字”是否在一个范围内,你必须清楚你的意思,两个数字是什么意思呢? 一般来说,你应该把所有的浮点数都包含在所谓的“ε球”中,这是通过select一个小值来完成的,并且说两个值是否接近,它们是相同的。
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);
有了这两个帮手,并假设如果任何数字可以作为一个双精度没有要求的准确性。 现在您只需要一个枚举和另一个方法
public enum BoundType { Open, Closed, OpenClosed, ClosedOpen }
另一种方法如下:
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"); } }
现在这可能比你想要的要多得多,但是它不会让你一直处理四舍五入的问题,并试图记住一个值是否已经四舍五入到什么地方。 如果你需要,你可以很容易地扩大这与任何epsilon工作,并允许你的epsilon改变。