Як елегантно перевірити, чи число в межах діапазону?


157

Як я можу це зробити елегантно за допомогою C # і .NET 3.5 / 4?

Наприклад, число може бути від 1 до 100.

Я знаю просте, якби це було достатньо; але ключовим словом цього питання є елегантність. Це для мого іграшкового проекту не для виробництва.

Ці питання стосувалися не швидкості, а краси краси. Перестаньте говорити про ефективність та таке; пам'ятайте, що ви проповідували хору.


23
Re: Ваша "редагування" - проста - це елегантно . Мені особисто здається, що вислів if є більш елегантним, ніж будь-який нестандартний засіб проведення цієї перевірки ...
Reed Copsey

4
"Все має бути зроблено максимально просто, але не простіше". - Альберт Ейнштейн
corsiKa

3
@Sergio: Я не відчуваю, що я буду педантичним. Я відчуваю, що люди часто зловживають методами розширення та іншими інструментами мовою, щоб замінити речі, які вже є простими. Існує сотні способів порівняння двох значень int, але використовуючи що-небудь, окрім очевидного - це поганий вибір, IMO.
Рід Копсей

3
@Sergio: Я думаю, тоді я не бачу сенсу питання;)
Рід Копсей

6
@Sergio: якщо ifце не "бароко", не виправте це.
StriplingWarrior

Відповіді:


154

Варіантів дуже багато:

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

if (x >= 1 && x <= 100)
    //true

Крім того, ознайомтеся з цією публікацією SO щодо варіантів регулярних виразів.


334
Enumerable.Range спочатку повинен створити безліч цілих чисел, а потім перевести цикл на кожен елемент, щоб знайти його. Це жахлива ідея та ефективність у порівнянні з перевіркою значення кардинально відрізняється. Я думаю, що ми повинні прийняти мото, тільки тому, що розширення LINQ круті, не означає, що їх слід використовувати для всього.
Меттью Еббот


15
Я погоджуюсь, що це дуже страшна ідея, але ОП хоче щось більш фантазійне, ніж ifтвердження. Це, безумовно, досягає цього ...;)
Тім Кокер

10
Варто зазначити, що другий параметр - це не «стоп», а «підрахунок». Так, наприклад, Enumerable.Range (150, 300) .Contains (400) повернеться true.
Шатхур

5
Будь ласка, не використовуйте цю відповідь . Це буде мати приголомшливі показники, якщо ваші діапазони досить великі. Будь ласка, дивіться відповідь @ olivier-jacot-descombes
Аарон Хадон

95

Ти маєш на увазі?

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

або

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

1
Вам не потрібно "є" там ... Це не компілюється. (В іншому випадку я згоден на 100%)
Рід Копсей

4
@Ben, просто зачекай, поки я теж спробую патентувати його :)
kemiller2002

Я думаю, що це найбільш тверде рішення, але не те, що елегантно шукає запитувач, чи не так?
Кевін Простий

Єдине, що я хотів би змінити - це додати статичне ключове слово до методу. ;-)
Роберт С.

Потрібні граничні прапори, тобто InRange (кількість, нижня межа, LOWER_IS_INCLUSIVE, верхня межа, UPPER_IS_EXCLUSIVE), щоб мати можливість <vs <= Я написав це, маючи намір бути химерним, але тепер, коли я думаю про це, прапори насправді спонукають абонента зрозуміти їх специфікацію.
Вільям Т. Маллард

56

Просто для додання тут шуму ви можете створити метод розширення:

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

Це, мабуть, здається дурною справою, коли перевірка є лише одним рядком.


1
@Ben: Я перейшов до теми, яка говорить "в межах діапазону" (що, на мою думку, не є неоднозначним у цьому відношенні), але ви маєте рацію в тому, що орган запитання говорить "від 1 до 100" (що , звичайно, неоднозначно).
Адам Робінсон

48

Як говорили інші, використовуйте простий, якщо.

Вам варто подумати про замовлення.

напр

1 <= x && x <= 100

легше читати, ніж

x >= 1 && x <= 100

19
"Легше" - це в очах глядача. Я особисто вважаю за краще, щоб змінна, про яку йдеться, ліворуч і константа чи змінна не викликала сумніву справа.
Адам Робінсон

15
У Perl 6 ви б написали 1 <= x <= 100.
Йордао

2
Порядок чисельних рядків є найяснішим спочатку - але ви можете навчити погляд / розум для інших замовлень. Конкретно - мені завжди подобається фокус розміщення константи зліва. Якщо ви зробите це, компілятор скаже вам, коли ви ввели =замість ==. Це не допомагає реляційним операторам нерівності - але легко звикнути до цього послідовно.
davidbak

1
Я просто хочу додати, що це рішення не корисне ні в якому разі. Розглянемо xце складний виклик функції або трудомісткий вираз Linq. У цьому випадку ви зробите це двічі, що не дуже добре. Звичайно, ви повинні зберегти це значення у тимчасовій локальній змінній, але є деякі випадки (наприклад, у операторах else-if-statement), коли ви хочете викликати функції лише після інших, якщо це чи ще, якщо не вдалося. Із тимчасовими змінними вам доведеться їх зателефонувати раніше. Метод розширення (згаданий в інших відповідях) є найкращим рішенням у цих випадках.
Роберт С.

4
Мені подобається також замовлення рядка чисел, а також тест на доповнення, наприклад, x <10 || 20 <х. На мене він кричить "х - поза діапазоном 10 - 20".
Вільям Т. Маллард

44

У виробничому коді я просто напишу

1 <= x && x <= 100

Це легко зрозуміти і дуже легко читати.


Ось розумний метод, який зменшує кількість порівнянь з двох до одного, використовуючи деяку математику. Ідея полягає в тому, що один з двох факторів стає негативним, якщо число лежить поза діапазоном і дорівнює нулю, якщо число дорівнює одному з меж:

Якщо межі включені:

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

або

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

Якщо межі є ексклюзивними:

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

або

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

3
За моїми мірками це, безумовно, найелегантніше рішення, цікаве те, що для мене це також здається трохи швидше, ніж перевірка обох виразів, що сказане, що воно також здається непослідовнішим (швидкість, здається, змінюється більше) було б цікаво побачити якщо проводиться якесь дослідження, на якому з них швидше.
Томас Ліндвалл

3
Випробували ваше рішення на JavaScript та його точність із числами з плаваючою комою до 14 десяткових знаків. Це дуже хороший фрагмент коду. Якщо б я міг, ти тричі підкреслив би тебе
рубрюбер

4
Хоча є незначна проблема, якщо задіяні великі позитивні цифри, вона може переповнитися! XD Ви можете захотіти це пам’ятати, коли пишете свій код.
BrainStorm.exe

2
Питання вимагає елегантності і тому має більше академічну, ніж практичну цінність. Особисто я б просто скористався простим 1 < x && x < 100продуктивним кодом. Його легше зрозуміти.
Олів’є Якот-Дескомбс

1
Для тих, хто стурбований продуктивністю, 1 < x & x < 100(без && короткого замикання) вказує компілятору, що він завжди може оцінити x < 100незалежно від результату 1 < x. Як не дивно (через передбачення галузей) швидше завжди робити цю просту операцію, ніж іноді її пропускати.
Том Лейс

23

Я пропоную це:

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)

Це легко читати (близьке до людської мови) і працює з будь-яким порівнянним типом (цілі, подвійні, спеціальні типи ...).

Важливим є читання коду, оскільки розробник не витрачає "мозкові цикли" на його розуміння. Під час тривалих сеансів кодування витрачені мозкові цикли змушують розробника втомитися раніше і схильні до помилок.


3
я би спростив ще більше, використовуючи слово між, та маючи булевий прапор, щоб визначити, включено чи ні
Бен

Добре. Це легко зрозуміти. Я змінив ім'я IsInRange. I'm not that keen on Ben's inclusive boolean as that requires a few more brain cycles. It has the advantage that it can be used in any class that that implements IComparer. This is in my Extensions now along with LiesWithin / LiesInside. Just can't decide which. NotOutside буде працювати, але мені не подобаються негативні умови
Paulustrious

21

Дещо зловживаючи методом розширення, ми можемо отримати таке «елегантне» рішення:

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

Як рішення! Btw для підтримки включно, створити enum Inclusiveзі значеннями: Lower, Upper, All. І передати для Inфункції одного додаткового параметра типу enum Inclusiveз Значним по замовчуванням Inclusive.All, оновлення Toтіла функції ручки All, Lower, Upperзначення :)
Микита

7

Якщо це випадково, ifвсе, що вам потрібно , є простим . Якщо це трапляється в багатьох місцях, ви можете розглянути ці два:

  • PostSharp . Прикрасьте методи атрибутами, які 'вводять' код у метод після компіляції. Я точно не знаю, але можу уявити, що це можна використовувати для цього.

Щось на зразок:

[Between("parameter", 0, 100)]
public void Foo(int parameter)
{
}
  • Код контрактів . Перевагою є те, що обмеження можна перевірити під час компіляції за допомогою статичної перевірки коду та місць, у яких використовується ваш код.

+1 для кодових контрактів; Це специфічно для перевірки параметра, але це часті випадки використання і статична перевірка може бути надзвичайно корисною.
Дан Брайант


5

Використовувати &&вираз для з'єднання двох порівнянь - це просто найелегантніший спосіб зробити це. Якщо ви спробуєте скористатися фантазійними методами розширення і подібними, ви стикаєтеся з питанням, чи включати верхню межу, нижню межу чи обидва. Після того, як ви почнете додавати додаткові змінні або змінювати імена розширень, щоб вказати, що включено, ваш код стає довшим і важчим для читання (для переважної більшості програмістів). Крім того, такі інструменти, як Resharper, попередить вас, якщо порівняння не має сенсу (number > 100 && number < 1 ), чого вони не зроблять, якщо ви використовуєте метод ('i.IsBet Between (100, 1)').

Єдиний інший коментар, який я хочу зробити, - це те, що якщо ви перевіряєте матеріали з наміром скинути виняток, вам слід розглянути можливість використання кодових контрактів

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

Це більш елегантно, ніж if(...) throw new Exception(...)ви навіть можете отримати попередження за час компіляції, якщо хтось спробує зателефонувати за вашим методом, не впевнившись, що число в першу чергу знаходиться в межах.


2
FYI, статичний аналізатор контрактів є щасливішим, коли нижня межа та обмеження верхньої межі розділені на окремі Потрібні твердження.
Ден Брайант

Спасибі Ден Брайант, саме це я шукав тут. Не вдається знайти багато матеріалів щодо пропозицій щодо стилю умов для Потрібних та інших відповідних методів Код контракту.
jpierson

2

Якщо ви хочете написати більше коду, ніж простий, якщо, можливо, ви можете: Створіть метод розширення під назвою 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.Approximately), яка вбудована в це середовище; на практиці, як правило, ви повинні ретельно розробити власні уявлення про те, що означає порівняння для комп’ютерних уявлень реальних чисел, для типу ситуації, яку ви розробляєте. (Навіть не згадуйте, що якщо ви робите щось на кшталт, можливо, контролер, PID-контролер тощо), вся проблема стає центральною і дуже складною, вона набуває характер проекту.

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

1
Замініть тему if if else наreturn (value >= Min && value <= Max);
AeroX

елегантний спосіб написати порівняння "в логічному порядку ..." if (Min <= value && value <= Max). Це набагато красивіше.
Fattie

2
Далі в цьому питанні настільки дивно, що ніхто не згадував про центральну проблему в жодному реальному проекті (особливо, якщо ви ігровий інженер) - це те, що вам доведеться вирішити проблему наближення . У будь-якому програмному забезпеченні реального світу ви, по суті, ніколи не «просто робите порівняння» (незалежно від того, рівність чи <,>), вам потрібно враховувати та вирішувати проблеми помилок, залежно від ситуації, що склалася. Я відредагував доповнення до цієї відповіді (єдина правильна відповідь тут!), Оскільки більше відповідей не дозволено.
Fattie

Дякую за зауваження та додаток.
Тоні

2

Тому що вся інша відповідь не придумана мною, ось лише моя реалізація:

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

2

EDIT: Новий відповідь надано. Я тільки починав використовувати 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 секунд


Так, я коментую коментар до моєї відповіді від 2013 року :) @RyanTheLeach: Чим моя відповідь на це питання відрізняється від прийнятої зараз відповіді? Я усвідомлюю, що це не найефективніший обхід, а «жахливий»? Наскільки погано може бути виділення та циклічне проходження через 100 ints? У 1950 році це, мабуть, не було прийнято соціально, але ...
cseder

@RyanTheLeach Я не звинувачую вас ... Я оновив свою відповідь, тому, якщо ви знаєте про рішення, яке є ще більш ефективним, будь ласка, докладно!
cseder

1
Я видалив свої коментарі, оскільки вони більше не стоять. Дякую за виправлення, здається, добре.
Райан Ліч

1

Новий поворот старого улюбленого:

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

3
Насправді є чотири випадки, включно / включно, включно / ексклюзивно, ексклюзивно / включно та ексклюзивно / ексклюзивно.
Вільям Т. Маллард

1

У C, якщо ефективність часу є вирішальним, а цілі переливи завершаться, можна було б зробити if ((unsigned)(value-min) <= (max-min)) .... Якщо 'max' і 'min' є незалежними змінними, додаткове віднімання для (max-min) буде витрачати час, але якщо це вираження може бути попередньо обчислено під час компіляції, або якщо його можна обчислити один раз під час виконання для тестування багатьох Чисел проти того ж діапазону, вищевказаний вираз може бути обчислений ефективно навіть у тому випадку, коли значення знаходиться в межах діапазону (якщо велика частка значень буде нижче допустимого діапазону, його можна використовувати швидше, if ((value >= min) && (value <= max)) ...оскільки воно вийде рано, якщо значення менше, ніж хв).

Перш ніж використовувати таку реалізацію, проте, орієнтир орієнтовної машини. У деяких процесорах вираз з двох частин може бути швидшим у всіх випадках, оскільки два порівняння можуть бути виконані незалежно, тоді як у методі віднімання та порівняння віднімання має завершитися, перш ніж порівняння може виконатись.


1

Як щодо чогось подібного?

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

1

Я б зробив об'єкт 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);

Таким чином ви можете повторно використовувати його для іншого типу.


Ваш Rangeоб'єкт повинен використовувати CompareToметод порівняння елементів, а не <оператор.
Сервіс

Ви маєте рацію, хоча якщо реалізувати IComparable, ви також повинні перекрити операторів (принаймні, так говорить мій аналіз коду VS), тобто <буде працювати. Хоча я можу помилятися, я не маю великого досвіду, і це моя перша відповідь на ТАК
IEatBagels

Ні, ваш компілятор не скаже, що це працює. Це не компілюється. Цілком розумно об'єкт реалізувати IComparableта не перевантажувати <оператора.
Сервіс

1

Перевіряючи, чи "Число" знаходиться в діапазоні, вам повинно бути зрозуміло, що ви маєте на увазі, а що означають два числа, рівні? Загалом, ви повинні зафіксувати всі числа з плаваючою комою у тому, що називається "куля-епсілон", це робиться шляхом вибору деякого невеликого значення і кажучи, якщо два значення є такими близькими, то це одне і те ж.

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

Тепер це може бути набагато більше того, що ви хотіли, але це перешкоджає вам постійно займатися округленням і намагатися запам'ятати, чи було значення округлене і до якого місця. Якщо вам потрібно, ви можете легко розширити це для роботи з будь-яким епсилоном і дозволити вашому епсилону змінюватися.


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

Використання

подвійне числоToBeChecked = 7;

var результат = числоToBeChecked.IsBet Between (100,122);

var результат = 5.IsBet between (100,120);

var результат = 8.0.IsBet between (1.2,9.6);


1

Якщо ви стурбовані коментарем @Daap щодо прийнятої відповіді і можете передати значення лише один раз, ви можете спробувати одне з наступних

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

1

Щодо елегантності, то найбільш близьке до математичного позначення ( 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;
}

0

Я шукав елегантний спосіб зробити це там, де перемикаються межі (тобто не знаю, в якому порядку знаходяться значення).

Це буде працювати лише на новіших версіях C #, де існує:?:

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

Очевидно, ви можете змінити знаки = там для своїх цілей. Можливо, захоплюєтесь і типом лиття. Мені просто потрібно було повернути поплавок в межах (або дорівнює)


0

Елегантний, тому що він не вимагає від вас визначати, яке з двох граничних значень перше більше. Він також не містить гілок.

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

& + | бітові оператори
nelsontruran

0

Я не знаю, але я використовую цей метод:

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

Надайте приклад використання нижче блоку коду, це допоможе ОП дізнатися, чи відповідає його цілі
Габріель Бальса Канту

0

Ось деякі методи розширення, які можуть допомогти

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

0

Якщо потрібно перевірити параметри методу, жодне з рішень не кидає ArgumentOutOfRangeException і дозволяє легко / правильну конфігурацію включно / ексклюзивних значень min / max.

Використовуйте так

public void Start(int pos)
{
    pos.CheckRange(nameof(pos), min: 0);

    if (pos.IsInRange(max: 100, maxInclusive: false))
    {
        // ...
    }
}

Я просто написав ці прекрасні функції. Також має перевагу відсутність розгалуження (єдиного, якщо) для дійсних значень. Найважче - створити відповідні повідомлення про виключення.

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

-2

Ви шукаєте in [1..100]? Це тільки Паскаль.


2
Неправда, це не тільки Паскаль. Багато сучасних мов мають такі функції. Наприклад, у Котліні його називають "узгодження зразків". Приклад when (number) { in 0..9 -> println("1 digit") in 10..99 -> println("2 digits") in 100..999 -> println("3 digits") }
this.myself
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.