代码之家  ›  专栏  ›  技术社区  ›  Sergio Tapia

如何优雅地检查一个数字是否在一个范围内?

  •  126
  • Sergio Tapia  · 技术社区  · 14 年前

    如何使用C#和.NET 3.5/4优雅地执行此操作?

    我知道一个简单的假设就足够了;但这个问题的关键词是优雅。是为了我的玩具项目,不是为了生产。

    这些问题不是关于速度,而是关于代码美。别再谈效率之类的了;记住你在向唱诗班讲道。

    22 回复  |  直到 9 年前
        1
  •  177
  •   Community CDub    8 年前

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

    还有,看看这个 SO post

        2
  •  104
  •   Liam Laverty Paolo Bergantino    9 年前

    你的意思是?

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

    bool TestRange (int numberToCheck, int bottom, int top)
    {
      return (numberToCheck >= bottom && numberToCheck <= top);
    }
    
        3
  •  70
  •   Olivier Jacot-Descombes    4 年前

    在生产代码中,我只需编写

    1 <= x && x <= 100
    

    从C#9.0开始,我们可以编写

    x is >= 1 and <= 100   // Note that we must write x only once.
                           // "is" introduces a pattern matching expression.
                           // "and" is part of the pattern matching unlike the logical "&&".
                           // With "&&" we would have to write: x is >= 1 && x is <= 100
    

    这里有一个聪明的方法,通过使用一些数学知识将比较的次数从两个减少到一个。如果数字超出范围,则两个因子中的一个变为负;如果数字等于某个界限,则为零:

    如果边界包含:

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

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

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

    (x - min) * (max - x) > 0
    
        4
  •  65
  •   Adam Robinson    9 年前

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

    尽管如此,当支票本身只有一行时,这样做似乎是一件愚蠢的事情。

        5
  •  50
  •   Marshal    9 年前

    正如其他人所说,使用一个简单的if。

    你应该考虑点餐。

    1 <= x && x <= 100
    

    x >= 1 && x <= 100
    
        6
  •  24
  •   Anton M    8 年前

    我提议:

    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
    

    当然还有变量:

    myvalue.IsWithin(min, max)
    

    让代码易于阅读是很重要的,因为开发人员不会浪费“大脑周期”去理解它。在长时间的编码过程中,浪费的大脑周期会使开发人员更早地感到疲倦,并容易出现bug。

        7
  •  22
  •   Ferruccio    14 年前

    通过一些扩展方法的滥用,我们可以得到以下“优雅”的解决方案:

    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!");
            }
        }
    }
    
        8
  •  7
  •   JulianR    14 年前

    if 这就是你所需要的。如果这种情况在许多地方发生,您可能需要考虑以下两种情况:

    比如:

    [Between("parameter", 0, 100)]
    public void Foo(int parameter)
    {
    }
    
        9
  •  6
  •   Nick Larsen    14 年前
    if (value > 1 && value < 100)
    {
        // do work
    }
    else
    {
        // handle outside of range logic
    }
    
        10
  •  6
  •   StriplingWarrior    14 年前

    使用 && 表达式连接两个比较是最优雅的方法。如果您尝试使用花哨的扩展方法之类的方法,您会遇到是否包括上限、下限或两者的问题。一旦您开始添加额外的变量或更改扩展名以指示所包含的内容,您的代码就会变得更长、更难阅读(对于绝大多数程序员来说)。此外,如果您的比较没有意义,Resharper之类的工具会警告您( number > 100 && number < 1 ),如果您使用一个方法('i.IsBetween(100,1)'),他们不会这样做。

    我唯一要说的是,如果您检查输入的目的是抛出一个异常,那么您应该考虑使用代码契约:

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

    if(...) throw new Exception(...) ,并且如果有人试图调用您的方法而不首先确保数字在边界内,您甚至可以得到编译时警告。

        11
  •  3
  •   C. Sederqvist    5 年前

    编辑:提供新答案。 我刚开始使用C#写这个问题的第一个答案时,事后我意识到我的“解决方案”是幼稚和低效的。

    我最初的回答是: 我会选择更简单的版本:

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

    由于我还没有看到任何其他更有效的解决方案(至少根据我的测试),我将再尝试一次。

    新的更好的方法

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

    这可以用于正范围和负范围,默认范围为

    1..100(含)及用途 x min max .

    增加好的措施的例子

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

    退货:

    True
    True
    True
    False
    True
    

    例2: 使用1到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);
    

    退货:

    66660 ints found in range 1..100
    
    

    执行时间:0.016秒

        12
  •  2
  •   Fattie    10 年前

    如果您想编写比简单If更多的代码,也许您可以: 创建一个名为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());
    

    附录: 值得注意的是,在实践中,您很少“只检查等式”(或<>)在代码库中(纯粹作为一个例子,任何游戏程序员都会在每个项目中使用类似以下的类别作为基本问题。注意,在这个例子中,它(碰巧)使用了一个内置于该环境中的函数(Mathf.approximaty);在实践中,你通常必须仔细地发展你自己的概念,比较对于实数的计算机表示意味着什么,对于你正在设计的情况类型(如果你在做一些事情,比如控制器,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;
        }
    
        13
  •  2
  •   Oliver    8 年前

    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);
    
        14
  •  1
  •   Ben Hoffstein    14 年前

    public bool IsWithinRange(int number, int topOfRange, int bottomOfRange, bool includeBoundaries) {
        if (includeBoundaries)
            return number <= topOfRange && number >= bottomOfRange;
        return number < topOfRange && number > bottomOfRange;
    }
    
        15
  •  1
  •   Sachin Chavan bortzmeyer    11 年前

    在C语言中,如果时间效率非常重要,并且整数溢出将被包装,则可以这样做 if ((unsigned)(value-min) <= (max-min)) ... . 如果'max'和'min'是自变量,(max min)的额外减法将浪费时间,但是如果该表达式可以在编译时预计算,或者如果它可以在运行时计算一次以测试同一范围内的多个数字,即使在值在范围内的情况下,也可以有效地计算上述表达式(如果大部分值将低于有效范围,则使用该表达式可能更快) if ((value >= min) && (value <= max)) ... 提前退出 如果值小于min)。

    不过,在使用这样的实现之前,先对目标机器进行基准测试。在某些处理器上,两部分表达式在所有情况下都可能更快,因为两个比较可以独立完成,而在减法和比较法中,减法必须在执行比较之前完成。

        16
  •  1
  •   William T. Mallard    11 年前

    像这样的怎么样?

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

    扩展方法如下(测试):

    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;
        }
    }
    
        17
  •  1
  •   Peter Mortensen icecrime    9 年前

    我会做一个范围对象,像这样:

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

    这样您就可以将其重新用于其他类型。

        18
  •  1
  •   rahicks    7 年前

    当检查一个“数字”是否在某个范围内时,你必须清楚你的意思,两个数字相等意味着什么?一般来说,你应该把所有的浮点数都包装在所谓的“epsilon ball”中,这是通过选取一些小的值来完成的,如果两个值如此接近,它们就是同一件事。

        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改变。

        19
  •  1
  •   Tom Leys    7 年前

    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 );
    }
    
        20
  •  1
  •   İBRAHİM GAZALOĞLU    6 年前
    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);
        }
    }
    

    待检双数=7;

    var result=numberToBeChecked.IsBetween(100122);

    var结果=5.IsBetween(100120);

    var结果=8.0。介于(1.2,9.6)之间;

        21
  •  1
  •   hanan    5 年前

    下面是一些可以帮助您的扩展方法

      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;
        }
    
        22
  •  1
  •   Hugo Delsing    5 年前

    bool TestRangeDistance (int numberToCheck, int bottom, int distance)
    {
      return (numberToCheck >= bottom && numberToCheck <= bottom+distance);
    }
    
    //var t = TestRangeDistance(10, somelist.Count()-5, 10);
    

    bool TestRangeMargin (int numberToCheck, int target, int margin)
    {
      return (numberToCheck >= target-margin && numberToCheck <= target+margin);
    }
    
    //var t = TestRangeMargin(10, somelist.Count(), 5);
    
        23
  •  1
  •   hector-j-rivas    5 年前

    关于优雅,最接近数学符号( a<=x<=b )略微提高可读性:

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

    进一步说明:

    public static bool IsOutside(this int value, int min, int max)
    {
        return value < min || max < value;
    }
    
        24
  •  0
  •   Kalikovision    8 年前

    这只适用于存在?:的较新版本的C

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

    很明显,你可以为了你的目的改变那里的标志。我也会喜欢打字。我只需要一个边界内的浮点返回(或等于)

        25
  •  0
  •   user8790965    6 年前

    我不知道,但我用这个方法:

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

    我可以这样使用它:

        [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);
        }
    
        26
  •  0
  •   Etienne Charland    5 年前

    public void Start(int pos)
    {
        pos.CheckRange(nameof(pos), min: 0);
    
        if (pos.IsInRange(max: 100, maxInclusive: false))
        {
            // ...
        }
    }
    

    我刚刚写了这些漂亮的函数。它还有一个优点,就是对有效值没有分支(单个if)。最困难的部分是构造适当的异常消息。

    /// <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);
    
        27
  •  -2
  •   Polluks    6 年前

    in [1..100]