C#と.NET 3.5/4でこれをエレガントに行うにはどうすればよいですか?
たとえば、1〜100の数値を指定できます。
単純な場合で十分だとわかっています。しかし、この質問のキーワードは優雅です。これは私のおもちゃプロジェクト用であり、生産用ではありません。
この質問はスピードについてではなく、コードの美しさについてでした。効率などについて話すのはやめてください。合唱団に説教していることを覚えておいてください。
多くのオプションがあります:
int x = 30;
if (Enumerable.Range(1,100).Contains(x))
//true
if (x >= 1 && x <= 100)
//true
また、正規表現オプションについては、この SO post を確認してください。
という意味ですか?
if(number >= 1 && number <= 100)
または
bool TestRange (int numberToCheck, int bottom, int top)
{
return (numberToCheck >= bottom && numberToCheck <= top);
}
ここにノイズを追加するだけで、拡張メソッドを作成できます。
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行しかない場合に実行する愚かなことのように思えます。
他の人が言ったように、単純なifを使用します。
順序について考える必要があります。
例えば
1 <= x && x <= 100
より読みやすい
x >= 1 && x <= 100
数学を使用して、比較の数を2つから1つに減らすことができます。考え方は、数値が範囲外にある場合は2つの要因のいずれかが負になり、数値が範囲の1つに等しい場合はゼロになるということです。
境界が含まれる場合:
(x - 1) * (100 - x) >= 0
または
(x - min) * (max - x) >= 0
境界が排他的である場合:
(x - 1) * (100 - x) > 0
または
(x - min) * (max - x) > 0
ただし、実動コードでは、単に1 < x && x < 100
と書くだけで理解しやすくなります。
私はこれを提案します:
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)
読みやすく(人間の言語に近い)、同等のタイプ(整数、ダブル、カスタムタイプなど)で動作します。
開発者はそれを理解するために「脳のサイクル」を無駄にしないため、コードを読みやすくすることが重要です。長いコーディングセッションでは、無駄な脳のサイクルにより、開発者は疲れやすくなり、バグが発生しやすくなります。
拡張メソッドを少し悪用すると、次の「エレガントな」ソリューションが得られます。
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!");
}
}
}
&&
式を使用して2つの比較を結合することは、これを行う最も簡単な方法です。派手な拡張メソッドなどを使用しようとすると、上限、下限、またはその両方を含めるかどうかの問題が発生します。追加の変数を追加したり、含まれる内容を示すために拡張名を変更したりすると、コードが長くなり、読みにくくなります(大多数のプログラマーにとって)。さらに、Resharperなどのツールは、比較が意味をなさない場合(number > 100 && number < 1
)に警告を表示しますが、メソッド( 'i.IsBetween(100、1)')を使用する場合は警告を表示しません。
私が行う唯一のコメントは、例外をスローする意図で入力をチェックしている場合、コードコントラクトの使用を検討する必要があるということです。
Contract.Requires(number > 1 && number < 100)
これはif(...) throw new Exception(...)
よりもエレガントであり、番号が最初に境界内にあることを確認せずに誰かがメソッドを呼び出そうとすると、コンパイル時の警告が表示されることさえあります。
if (value > 1 && value < 100)
{
// do work
}
else
{
// handle outside of range logic
}
他のすべての答えは私が発明したのではなく、ここでは私の実装です:
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);
単純な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.Approximately)を使用している(可能性が高い)ことに注意してください。実際には、通常、エンジニアリングしている状況のタイプに応じて、実数のコンピューター表現に対する比較の意味に関する独自の概念を慎重に開発する必要があります。 (おそらく、コントローラー、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;
}
このようなものはどうですか?
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;
}
}
次のような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);
そうすれば、別のタイプに再利用できます。
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);
}
}
使用法
double numberToBeChecked = 7;
var result = numberToBeChecked.IsBetween(100,122);
var result = 5.IsBetween(100,120);
var result = 8.0.IsBetween(1.2,9.6);
Cでは、時間効率が重要であり、整数オーバーフローがラップする場合、if ((unsigned)(value-min) <= (max-min)) ...
を実行できます。 「max」と「min」が独立変数の場合、(max-min)の余分な減算は時間を浪費しますが、その式がコンパイル時に事前計算できる場合、または実行時に一度計算して多数をテストできる場合同じ範囲に対する数値、値が範囲内にある場合でも上記の式は効率的に計算される可能性があります(値の大部分が有効範囲を下回る場合、if ((value >= min) && (value <= max)) ...
を使用する方が高速になる場合があります早期終了値が最小より小さい場合)。
ただし、そのような実装を使用する前に、ターゲットマシンのベンチマークを行います。一部のプロセッサでは、2つの比較が独立して行われる場合があるため、2部式はすべての場合で高速になりますが、減算と比較の方法では、比較を実行する前に減算を完了する必要があります。
古いお気に入りの新しい工夫:
public bool IsWithinRange(int number, int topOfRange, int bottomOfRange, bool includeBoundaries) {
if (includeBoundaries)
return number <= topOfRange && number >= bottomOfRange;
return number < topOfRange && number > bottomOfRange;
}
私はよりシンプルなバージョンで行きます:
if(Enumerable.Range(1,100).Contains(intInQuestion)) { ...DoStuff; }
わかりませんが、私はこの方法を使用します:
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);
}
これらは役立ついくつかの拡張メソッドです
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;
}
「数値」が範囲内にあるかどうかを確認するときは、意味を明確にする必要があり、2つの数値が等しいとはどういう意味ですか?一般に、すべての浮動小数点数を「イプシロンボール」と呼ばれるものでラップする必要があります。これは、小さな値を選択し、2つの値が近い場合は同じものであると言います。
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);
これらの2つのヘルパーを配置し、必要な精度なしで任意の数値をdoubleとしてキャストできると想定しています。必要なのは列挙型と別のメソッドだけです
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");
}
}
今ではこれはあなたが望んでいたものよりもはるかに多いかもしれませんが、常に丸めを処理し、値が丸められたかどうか、どこに丸められたかを覚えようとすることを妨げます。必要に応じて、これを任意のイプシロンで動作するように拡張し、イプシロンを変更できるようにすることができます。
私は、境界が切り替わる(つまり、値の順序がわからない)場合にそれを行うエレガントな方法を探していました。
これは、?:が存在する新しいバージョンのC#でのみ機能します。
bool ValueWithinBounds(float val, float bounds1, float bounds2)
{
return bounds1 >= bounds2 ?
val <= bounds1 && val >= bounds2 :
val <= bounds2 && val >= bounds1;
}
もちろん、目的に合わせて=記号を変更できます。型キャストにも興味があります。範囲内のフロートリターンが必要でした(または等しい)
2つの境界値のどちらが大きいかを最初に判断する必要がないため、エレガントです。また、ブランチも含まれていません。
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 );
}