如何优雅地检查一个数字是否在一个范围内?
我怎样才能用 C# 优雅地做到这一点?
例如,数字可以在 1 到 100 之间。
我知道一个简单的 if (x >= 1 && x <= 100)
就足够了;但是随着 C#/.Net 不断添加大量语法糖和新功能,这个问题是关于更惯用的(可以很优雅)的编写方式。
性能不是问题,但请为非 O(1) 的解决方案添加性能说明,因为人们可能会复制粘贴建议。
How can I do this elegantly with C#?
For example, a number can be between 1 and 100.
I know a simple if (x >= 1 && x <= 100)
would suffice; but with a lot of syntax sugar and new features constantly added to C#/.Net this question is about more idiomatic (one can all it elegance) ways to write that.
Performance is not a concern, but please add performance note to solutions that are not O(1) as people may copy-paste the suggestions.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(30)
有很多选项:
实际上,可以在第一次检查中用相反的顺序更优雅地编写基本的
if
:另外,请查看此 SO post 用于正则表达式选项。
注意:
LINQ 解决方案严格适用于样式点 - 因为 Contains 迭代所有项目,其复杂度为 O(range_size),而不是范围检查通常预期的 O(1)。
其他范围的更通用版本(请注意第二个参数是计数,而不是结束):
有人试图编写没有
&&
的if
解决方案,如1 <= x <= 100
- 看起来非常优雅,但在 C# 中会导致语法错误“运算符 '<=' 无法应用于类型为 'bool' 和 'int' 的操作数”There are a lot of options:
And indeed basic
if
more elegantly can be written with reversing order in the first check:Also, check out this SO post for regex options.
Notes:
LINQ solution is strictly for style points - since Contains iterates over all items its complexity is O(range_size) and not O(1) normally expected from a range check.
More generic version for other ranges (notice that second argument is count, not end):
There is temptation to write
if
solution without&&
like1 <= x <= 100
- that look really elegant, but in C# leads to a syntax error "Operator '<=' cannot be applied to operands of type 'bool' and 'int'"在生产代码中我只需编写
<代码>1 <= x && x <= 100
这很容易理解并且非常可读。
从 C#9.0 开始我们可以编写
请注意,我们只能编写一次
x
。is
引入了一个模式匹配表达式,其中and
是模式的一部分。&&
需要我们重复x is
,如x is >= 1 && x 是 <= 100
<前><代码>
这是一种巧妙的方法,通过使用一些数学方法将比较次数从两次减少到一次。这样做不一定有性能优势,但它很优雅。这个想法是,如果数字超出范围,则两个因素之一变为负值;如果数字等于边界之一,则两个因素之一变为零:
如果边界包含在内:
<前><代码>(x - 1) * (100 - x) >= 0
或
<前><代码>(x - 最小值) * (最大值 - x) >= 0
<小时>
如果边界是互斥的:
<前><代码> (x - 1) * (100 - x) > 0
或
In production code I would simply write
1 <= x && x <= 100
This is easy to understand and very readable.
Starting with C#9.0 we can write
Note that we must write
x
only once.is
introduces a pattern matching expression whereand
is part of the pattern.&&
would require us to repeatx is
as inx is >= 1 && x is <= 100
Here is a clever method that reduces the number of comparisons from two to one by using some math. There is not necessarily a performance advantage in doing so, but it is elegant. The idea is that one of the two factors becomes negative if the number lies outside of the range and zero if the number is equal to one of the bounds:
If the bounds are inclusive:
or
If the bounds are exclusive:
or
你的意思是?
或者
Do you mean?
or
只是为了增加这里的噪音,您可以创建一个扩展方法:
这会让您做类似的事情...
话虽这么说,当检查本身只有一行时,这似乎是一件愚蠢的事情。
Just to add to the noise here, you could create an extension method:
Which would let you do something like...
That being said, this seems like a silly thing to do when the check itself is only one line.
正如其他人所说,使用简单的 if。
你应该考虑一下顺序。
例如
比
As others said, use a simple if.
You should think about the ordering.
e.g
is easier to read than
我建议这样:
示例:
当然还有变量:
它很容易阅读(接近人类语言)并且适用于任何类似的类型(整数、双精度、自定义类型...)。
使代码易于阅读很重要,因为开发人员不会浪费“大脑周期”来理解它。在长时间的编码过程中,浪费的大脑周期会让开发人员更早感到疲劳并容易出现错误。
I propose this:
Examples:
and of course with variables:
It's easy to read (close to human language) and works with any comparable type (integer, double, custom types...).
Having code easy to read is important because the developer will not waste "brain cycles" to understand it. In long coding sessions wasted brain cycles make developer tired earlier and prone to bug.
通过滥用一些扩展方法,我们可以获得以下“优雅”的解决方案:
With a bit of extension method abuse, we can get the following "elegant" solution:
如果这是偶然的,那么您只需要一个简单的
if
即可。如果这种情况在很多地方发生,您可能需要考虑以下两个:类似于:
If this is incidental, a simple
if
is all you need. If this happens in many places, you might want to consider these two:Something like:
编辑:提供了新答案。
当我写下这个问题的第一个答案时,我刚刚开始使用 C#,事后看来,我现在意识到我的“解决方案”是幼稚且低效的。
我原来的答案:
我会选择更简单的版本:
`if(Enumerable.Range(1,100).Contains(intInQuestion)) { ...DoStuff; }`
更好的方法
由于我没有看到任何其他更有效的解决方案(至少根据我的测试),我会再试一次。
也适用于负范围的新的更好的方法:
这可以用于正范围和负范围,默认范围为
1..100(含)并使用
x
作为要检查的数字,后跟由min
和max
定义的可选范围。添加良好措施的示例
示例 1:
返回:
示例 2:
使用 1 到 150 之间的 100000 个随机整数的列表
返回:
EDIT: New Answer provided.
I was just starting out using C# when I wrote the first answer to this question, and in hindsight I now realize that my "solution" was / is naive and inefficient.
My original answer:
I'd go with the more simple version:
`if(Enumerable.Range(1,100).Contains(intInQuestion)) { ...DoStuff; }`
A Better Way
As I haven't seen any other solution that is more efficient (according to my tests at least), I'll give it another go.
New and better way that also works with negative ranges:
This can be used with both positive and negative ranges and defaults to a range of
1..100 (inclusive) and uses
x
as the number to check followed by an optional range defined bymin
andmax
.Adding Examples For Good Measure
Example 1:
Returns:
Example 2:
Using a list of 100000 random ints between 1 and 150
Returns:
使用
&&
表达式连接两个比较是执行此操作的最优雅的方法。如果您尝试使用奇特的扩展方法等,您会遇到是否包含上限、下限或两者的问题。一旦您开始添加其他变量或更改扩展名称以指示所包含的内容,您的代码就会变得更长且更难以阅读(对于绝大多数程序员而言)。此外,如果您的比较没有意义(number > 100 && number < 1
),Resharper 等工具会向您发出警告,但如果您使用方法 ( 'i.IsBetween(100, 1)')。我要说的唯一的其他评论是,如果您检查输入的目的是抛出异常,则应该考虑使用代码契约:
这比
if(...) throw new Exception(. ..)
,如果有人尝试调用您的方法而不首先确保数字在范围内,您甚至可能会收到编译时警告。Using an
&&
expression to join two comparisons is simply the most elegant way to do this. If you try using fancy extension methods and such, you run into the question of whether to include the upper bound, the lower bound, or both. Once you start adding additional variables or changing the extension names to indicate what is included, your code becomes longer and harder to read (for the vast majority of programmers). Furthermore, tools like Resharper will warn you if your comparison doesn't make sense (number > 100 && number < 1
), which they won't do if you use a method ('i.IsBetween(100, 1)').The only other comment I'd make is that if you're checking inputs with the intention to throw an exception, you should consider using code contracts:
This is more elegant than
if(...) throw new Exception(...)
, and you could even get compile-time warnings if someone tries to call your method without ensuring that the number is in bounds first.好的,我也一起玩。已经有很多答案了,但也许还有一些其他新奇的空间:
(显然实际上并没有使用这些)
或者
或者
好吧,也许你可以使用最后一个。
好的,再来一张
Ok I'll play along. So many answers already but maybe still room for some other novelties:
(obviously don't actually use these)
Or
Or
OK maybe you could use that last one.
OK one more
在 C 语言中,如果时间效率至关重要并且整数溢出会换行,则可以执行 if ((unsigned)(value-min) <= (max-min)) ...。如果“max”和“min”是自变量,则 (max-min) 的额外减法会浪费时间,但如果该表达式可以在编译时预先计算,或者可以在运行时计算一次以测试许多对于相同范围内的数字,即使值在范围内,上述表达式也可以有效地计算(如果大部分值低于有效范围,则使用
if ((value >= min) && (value <= max)) ...
因为如果 value 小于 min),它将提前退出。不过,在使用这样的实现之前,请先对目标机器进行基准测试。在某些处理器上,两部分表达式在所有情况下都可能更快,因为两个比较可以独立完成,而在减法和比较方法中,减法必须在比较可以执行之前完成。
In C, if time efficiency is crucial and integer overflows will wrap, one could do
if ((unsigned)(value-min) <= (max-min)) ...
. If 'max' and 'min' are independent variables, the extra subtraction for (max-min) will waste time, but if that expression can be precomputed at compile time, or if it can be computed once at run-time to test many numbers against the same range, the above expression may be computed efficiently even in the case where the value is within range (if a large fraction of values will be below the valid range, it may be faster to useif ((value >= min) && (value <= max)) ...
because it will exit early if value is less than min).Before using an implementation like that, though, benchmark one one's target machine. On some processors, the two-part expression may be faster in all cases since the two comparisons may be done independently whereas in the subtract-and-compare method the subtraction has to complete before the compare can execute.
用法
double numberToBeChecked = 7;
var 结果 = numberToBeChecked.IsBetween(100,122);
var 结果 = 5.IsBetween(100,120);
var 结果 = 8.0.IsBetween(1.2,9.6);
Usage
double numberToBeChecked = 7;
var result = numberToBeChecked.IsBetween(100,122);
var result = 5.IsBetween(100,120);
var result = 8.0.IsBetween(1.2,9.6);
这些是一些可以帮助的扩展方法
These are some Extension methods that can help
您可以使用模式匹配以最优雅的方式实现此目的:
You can use pattern matching to achieve this in the most elegant way:
使用内置的 Range 结构(C# 8+),我们可以创建一个扩展方法来检查
Index
是否在原始范围内。由于
Index
覆盖了隐式运算符,因此我们可以传递int
而不是Index
结构。Using the built in Range struct (C# 8+), we can create an extension method to check if an
Index
is within the original range.Since
Index
overrides the implicit operator, we can pass anint
instead of anIndex
struct.如果您想编写比简单的 if 更多的代码,也许您可以:
创建一个名为 IsBetween
...
附录的扩展方法,值得注意的是,在实践中,您很少在代码库中“仅检查相等性”(或 <、>)。 (除了在最琐碎的情况下。)纯粹作为一个例子,任何游戏程序员都会在每个项目中使用类似于以下内容的类别,作为基本问题。请注意,在这个示例中,它(碰巧)使用了内置于该环境中的函数(Mathf.Approximately);在实践中,您通常必须仔细发展自己的概念,了解比较对于实数的计算机表示以及您正在设计的情况类型意味着什么。 (更不用说,如果你正在做类似控制器、PID 控制器之类的东西,整个问题就变得中心且非常困难,它成为项目的本质。) OP 绝不是这里提出一个微不足道或不重要的问题。
If you want to write more code than a simple if, maybe you can:
Create a Extension Method called IsBetween
...
Addendum: it's worth noting that in practice you very rarely "just check for equality" (or <, >) in a codebase. (Other than in the most trivial situations.) Purely as an example, any game programmer would use categories something like the following in every project, as a basic matter. Note that in this example it (happens to be) using a function (Mathf.Approximately) which is built in to that environment; in practice you typically have to carefully develop your own concepts of what comparisons means for computer representations of real numbers, for the type of situation you are engineering. (Don't even mention that if you're doing something like, perhaps a controller, a PID controller or the like, the whole issue becomes central and very difficult, it becomes the nature of the project.) BY no means is the OP question here a trivial or unimportant question.
因为所有其他答案都不是我发明的,这里只是我的实现:
然后你可以像这样使用它:
Cause all the other answer are not invented by me, here just my implementation:
You can then use it like this:
旧的最爱的新变化:
A new twist on an old favorite:
像这样的事情怎么样?
扩展方法如下(已测试):
How about something like this?
with the extension method as follows (tested):
我会做一个 Range 对象,像这样:
然后你可以这样使用它:
这样你就可以将它重用于另一种类型。
I would do a Range object, something like this:
Then you use it this way:
That way you can reuse it for another type.
当检查“数字”是否在某个范围内时,您必须清楚自己的意思,以及两个数字相等意味着什么?一般来说,您应该将所有浮点数包装在所谓的“epsilon ball”中,这是通过选择一些小值并说如果两个值如此接近它们是同一件事来完成的。
有了这两个助手,并假设任何数字都可以在没有所需精度的情况下转换为双精度数。现在您需要的只是一个枚举和另一个方法
另一个方法如下:
现在这可能远远超出您想要的,但它使您不必一直处理舍入并尝试记住值是否已舍入并什么地方。如果您需要,您可以轻松扩展它以适用于任何 epsilon 并允许您的 epsilon 更改。
When checking if a "Number" is in a range you have to be clear in what you mean, and what does two numbers are equal mean? In general you should wrap all floating point numbers in what is called a 'epsilon ball' this is done by picking some small value and saying if two values are this close they are the same thing.
With these two helpers in place and assuming that if any number can be cast as a double without the required accuracy. All you will need now is an enum and another method
The other method follows:
Now this may be far more than what you wanted, but it keeps you from dealing with rounding all the time and trying to remember if a value has been rounded and to what place. If you need to you can easily extend this to work with any epsilon and to allow your epsilon to change.
优雅是因为它不需要您首先确定两个边界值中哪一个更大。它也不包含任何分支。
Elegant because it doesn't require you to determine which of the two boundary values is greater first. It also contains no branches.
如果您担心 @Daap 对已接受答案的评论并且只能传递该值一次,您可以尝试以下方法之一
或
If you are concerned with the comment by @Daap on the accepted answer and can only pass the value once, you could try one of the following
or
关于优雅,最接近数学符号 (a <= x <= b) 稍微提高了可读性:
为了进一步说明:
Regarding elegance, the closest thing to the mathematical notation (a <= x <= b) slightly improves readability:
For further illustration:
使用 C# 8 和新的
System.Range struct
还有一个新选项,它利用新的..
运算符,但尚未提及。首先为其创建一个扩展:
然后您可以将其应用到
Range
本身:With C# 8 and the new
System.Range struct
there is one more new option that utilizes the new..
operator and hasn't been mentioned yet.First create an extension for it:
Then you can apply it to the
Range
itself:我一直在寻找一种优雅的方法来实现可以切换边界的方式(即不确定值的顺序)。
这仅适用于存在 ?: 的较新版本的 C#
显然,您可以根据您的目的更改其中的 = 符号。也可以喜欢类型转换。我只需要一个在范围内(或等于)的浮点返回
I was looking for an elegant way to do it where the bounds might be switched (ie. not sure which order the values are in).
This will only work on newer versions of C# where the ?: exists
Obviously you could change the = signs in there for your purposes. Could get fancy with type casting too. I just needed a float return within bounds (or equal to)
我不知道,但我使用这种方法:
这就是我可以使用它的方式:
I don't know but i use this method:
And this is the way I can use it:
如果要验证方法参数,则没有任何解决方案会抛出 ArgumentOutOfRangeException 并允许轻松/正确配置包含/排除最小/最大值。
像这样使用
我刚刚编写了这些漂亮的函数。它还具有有效值没有分支(单个 if)的优点。最困难的部分是制作正确的异常消息。
If it's to validate method parameters, none of the solutions throw ArgumentOutOfRangeException and allow easy/proper configuration of inclusive/exclusive min/max values.
Use like this
I just wrote these beautiful functions. It also has the advantage of having no branching (a single if) for valid values. The hardest part is to craft the proper exception messages.