确定一个数字是否在指定的一组范围内

18

我正在寻找一种流畅的方法来确定一个数字是否落在指定的一组范围内。目前我的代码大致如下:

int x = 500; // Could be any number

if ( ( x > 4199 && x < 6800 ) ||
     ( x > 6999 && x < 8200 ) ||
     ( x > 9999 && x < 10100 ) ||
     ( x > 10999 && x < 11100 ) ||
     ( x > 11999 && x < 12100 ) )
{
    // More awesome code
}

有没有更好的方法来完成这个任务?


2
更好在哪方面?这看起来相当合理。 - Michael Donohue
3
我认为Steve希望用更优美的方式表达。 - Simon Wilson
1
我认为你提供的例子是最好的(看起来和易于理解)说实话! - Lloyd Powell
我喜欢这段代码,去掉每个 && 语句周围的括号 - 没有必要增加混乱。 - Adam Luter
为了更接近于常见的写法“x在0到10之间” **( 0 < x < 10)**,我会使用 if ( 0 < x && x < 10) - Dick de Reus
显示剩余2条评论
7个回答

36

扩展方法是什么?

bool Between(this int value, int left, int right)
{ 
   return value > left && value < right; 
}

if(x.Between(4199, 6800) || x.Between(6999, 8200) || ...)
你也可以使用这种可怕的hack方法:
bool Between(this int value, params int[] values)
{
    // Should be even number of items
    Debug.Assert(values.Length % 2 == 0); 

    for(int i = 0; i < values.Length; i += 2)
        if(!value.Between(values[i], values[i + 1])
            return false;

    return true;
}

if(x.Between(4199, 6800, 6999, 8200, ...)

可怕的 hack,改良版:

class Range
{
    int Left { get; set; }
    int Right { get; set; }

    // Constructors, etc.
}

Range R(int left, int right)
{
    return new Range(left, right)
}

bool Between(this int value, params Range[] ranges)
{
    for(int i = 0; i < ranges.Length; ++i)
        if(value > ranges[i].Left && value < ranges[i].Right)
            return true;

    return false;
}

if(x.Between(R(4199, 6800), R(6999, 8200), ...))

或者,更好的方法是(这不允许重复的下限):

bool Between(this int value, Dictionary<int, int> ranges)
{
    // Basically iterate over Key-Value pairs and check if value falls within that range
}

if(x.Between({ { 4199, 6800 }, { 6999, 8200 }, ... }

4
我一定会使 Range 不可变。对于像这样的东西,可变性很糟糕... - Jon Skeet
哇!在注释中使用_格式化_!@Jon 是的,你说得对。 - Anton Gogolev

14

定义一个范围类型,然后创建一组范围和一个扩展方法来查看值是否在任何范围内。然后,您可以创建一组范围和一些单独的范围,给它们有用的名称来解释您为什么对它们感兴趣,而不是硬编码这些值:

static readonly Range InvalidUser = new Range(100, 200);
static readonly Range MilkTooHot = new Range (300, 400);

static readonly IEnumerable<Range> Errors =
    new List<Range> { InvalidUser, MilkTooHot };

...

// Normal LINQ (where Range defines a Contains method)
if (Errors.Any(range => range.Contains(statusCode))
// or (extension method on int)
if (statusCode.InAny(Errors))
// or (extension methods on IEnumerable<Range>)
if (Errors.Any(statusCode))

您可能有兴趣了解通用的Range类型,它是MiscUtil的一部分。它还可以简单地进行迭代:

foreach (DateTime date in 19.June(1976).To(25.December(2005)).Step(1.Days()))
{
    // etc
}

(显然这也使用了一些DateTime/TimeSpan相关的扩展方法,但你明白我的意思。)


8

我个人更喜欢@Anton提出的扩展方法 - 但如果您无法这样做,并且要坚持使用当前的代码,我认为您可以通过以下方式反转每行的第一组条件来使其更易读...

int x = 500; // Could be any number
if ( ( 4199 < x && x < 6800 ) ||
     ( 6999 < x && x < 8200 ) ||
     ( 9999 < x && x < 10100 ) ||
     ( 10999 < x && x < 11100 ) ||
     ( 11999 < x && x < 12100 ) )
{
    // More awesome code
}

这使得代码更加易读。在《代码大全》中指出了这一点,自从那时以来我就一直使用这种风格。 - Carra

3
LINQ方法:
添加引用:
using System.Linq;

        /// <summary>
        /// Test to see if value is in specified range.
        /// </summary>
        /// <param name="aStart">int</param>
        /// <param name="aEnd">int</param>
        /// <param name="aValueToTest">int</param>
        /// <returns>bool</returns>
        public static bool CheckValueInRange(int aStart, int aEnd, int aValueToTest)
        {
            // check value in range...
            bool ValueInRange = Enumerable.Range(aStart, aEnd).Contains(aValueToTest);
            // return value...
            return ValueInRange;
        }

2
CheckValueInRange 只在范围的下限有效。由于 Enumerable.Range 的第二个参数是要添加到序列中的值的数量(而不是范围的结束),因此比上限值更高的数仍将被报告为在范围内。例如:CheckValueInRange(4199,6800,6801) 将返回 True,尽管 6801 超出了所需的上限,因为 Enumerable.Range(4199,6800) 返回从 4199 到 10998 的数字范围。 - Scott Lawrence
这种方法对性能非常不利,因为数组将被分配为范围的长度,并且LINQ将尝试迭代它以查找您的测试值。 - Denis Babarykin

1
class Range { 

    public Range(int x, int y) {
        X = x;
        Y = y;
    }

    public int X { get; set; }
    public int Y { get; set; }
}

var ranges = new List<Range>();
ranges.Add(new Range(4199,6800));
ranges.Add(new Range(6999,8200));
ranges.Add(new Range(9999,10100));
ranges.Add(new Range(10999,11100));
ranges.Add(new Range(11999,12100));

bool inRange = ranges.Count(r => x >= r.X && x <= r.Y) > 0;
//or -- Based on Jons recommendation
bool inRange = ranges.Any(r => x >= r.X && x <= r.Y);

1
通常最好使用Any()而不是Count(...) > 0,因为它可以在找到匹配项后立即停止。 - Jon Skeet
感谢您的建议,我会将其添加到帖子中。 - Bob

0
尝试类似以下的代码:
struct Range
{
   public readonly int LowerBound;
   public readonly int UpperBound; 

   public Range( int lower, int upper )
   { LowerBound = lower; UpperBound = upper; }

   public bool IsBetween( int value )
   { return value >= LowerBound && value <= UpperBound; }
}

public void YourMethod( int someValue )
{
   List<Range> ranges = {new Range(4199,6800),new Range(6999,8200),
                         new Range(9999,10100),new Range(10999,11100),
                         new Range(11999,12100)};

   if( ranges.Any( x => x.IsBetween( someValue ) )
   {
      // your awesome code...
   }
}

0
如果您需要在某个时候迭代值对,我建议您将最大下限值和最小上限值捕获到变量中,并执行以下操作:
if ( x>max_lower && x <min_upper)
{
    // More awesome code

}

如果那不是不可能的呢? - Arsen Mkrtchyan
@ArsenMkrt 说:“如果那不是不可能的呢?”“不不可能”=“可能”?我不确定你在问什么。 - KM.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接