.NET'te "kelepçe" işlevini nerede bulabilirim?


98

xBir aralığa bir değer sabitlemek istiyorum [a, b]:

x = (x < a) ? a : ((x > b) ? b : x);

Bu oldukça basit. Ama sınıf kitaplığında bir "kelepçe" işlevi görmüyorum - en azından içinde değil System.Math.

(Bir değeri "kenetlemeyi" bilmeyenler için, bazı maksimum ve minimum değerler arasında kaldığından emin olmaktır. Maksimum değerden büyükse, maksimum ile değiştirilir, vb.)


2
@Danvil: "C # Sınıf Kitaplığı" yok. ".NET Framework" demek istiyorsun.
John Saunders

1
Hala C # 7.1'den itibaren bir şey yok mu?
Joce

1
@JohnSaunders Bunun kesinlikle doğru olduğuna inanmıyorum stackoverflow.com/questions/807880/…
Adam Naylor

Bir değeri nasıl "sınırlayacağımı" sorsam, dünyadaki her bir İngilizce konuşan programcı ne demek istediğimi hemen anlar. Büyük ihtimalle her programcı bilirdi. Sektörde geçen 30 yılı aşkın bir sürenin ardından, bugün "kıskaç" ın ne anlama geldiğini öğrenmek zorunda kaldım. "Bağımlılık enjeksiyonu" na benzer - "parametreleştirme", hiç kimsenin üzerine kitap yazmadığı açık bir şeydir.
Bob

@Bob Bazı kelimelerin tarihsel, iyi tanımlanmış anlamları vardır. Kelepçe bunlardan biridir. en.wikipedia.org/wiki/Clamping_(graphics) veya khronos.org/registry/OpenGL-Refpages/gl4/html/clamp.xhtml veya docs.microsoft.com/en-us/windows/win32/direct3dhlsl/… "Sınır "yanıltıcı olur, özellikle" limit "zaten matematikte farklı bir anlama sahiptir.
kaalus

Yanıtlar:


143

Bir uzatma yöntemi yazabilirsiniz:

public static T Clamp<T>(this T val, T min, T max) where T : IComparable<T>
{
    if (val.CompareTo(min) < 0) return min;
    else if(val.CompareTo(max) > 0) return max;
    else return val;
}

Uzantı yöntemleri statik sınıflara girer - bu oldukça düşük seviyeli bir işlev olduğundan, muhtemelen projenizdeki bazı çekirdek ad alanlarına girmelidir. Daha sonra yöntemi ad alanı için bir using yönergesi içeren herhangi bir kod dosyasında kullanabilirsiniz.

using Core.ExtensionMethods

int i = 4.Clamp(1, 3);

.NET Core 2.0

NET Core 2.0'dan başlayarak System.Mathartık Clampbunun yerine kullanılabilecek bir yöntem var:

using System;

int i = Math.Clamp(4, 1, 3);

1
Bunu nereye koyarım ve CompareTo 'yu <ile karşılaştırmaktan daha yavaş (integral türleri için) çağırıyor mu?
Danvil

1
Statik bir sınıfta ve .NET çerçevesinde (mono, compact, vb. Hakkında emin değiller), jenerik, tür için yeniden derlenmeli ve CompareTo satır içi olarak yazılmalıdır, bu nedenle performans kaybı olmaz.
Robert Fraser

1
@Frasier Bu ultra performansa duyarlı kod olmadığı sürece, bunu yaparak anlamlı bir performans kazanımı elde etme olasılığınız düşüktür. Genel olması, birkaç mikrosaniyeden tasarruf etmekten muhtemelen daha kullanışlıdır.
MgSam

5
Genel sürümüyle sınırlandırmanın iyi yanı IComparable, kutunun gerçekleşmemesidir. Bu çok hızlı çalışmalı. doubleVe ile float, CompareToyöntemin NaN, dahil tüm diğer değerlerden daha az olduğu bir toplam sıraya karşılık geldiğini unutmayın NegativeInfinity. Yani <operatöre eşdeğer değildir . Eğer kullandıysanız <bir kayan noktalı tipi ile, sen nasıl davranacağını düşünmek gerekir NaNayrıca. Bu, diğer sayısal türler için geçerli değildir.
Jeppe Stig Nielsen

1
Her NaNiki durumda da nasıl tedavi edileceğini düşünmeniz gerekir . Versiyon <ve >çıkış olur NaNkullanılarak NaNiçin minya da maxetkili bir şekilde tek taraflı bir kelepçe olur. İle CompareTokendisine hep döneceğini NaNeğer maxDİR NaN.
Herman

33

Sadece Math.Minve Math.Max:

x = Math.Min(Math.Max(x, a), b);

Bu, int a0 = x > a ? x : a; return a0 < b ? a0 : bhangisinin (doğru sonuçlar vermesine rağmen) tam olarak ideal olmadığı anlamına gelir.
Bay Smith

12
ve neden böyle?
d7samurai

4
@ d7samurai Min <= max olduğunu biliyorsak, Math.Min(Math.Max(x, min), max)x <min ise gerekenden bir tane daha karşılaştırma ile sonuçlanır.
Jim Balter

@JimBalter, teoride bu doğrudur. CompareTo () 'nun tipik olarak nasıl uygulandığına bakarsanız, kabul edilen yanıt 6'ya kadar karşılaştırma alabilir. Yine de, derleyicinin yeterince akıllı olup olmadığını ve CompareTo () 'yu satır içine alıp gereksiz karşılaştırmaları kaldırıp kaldırmadığını bilmiyorum.
quinmars

1
Bu, yalnızca bir kez yapmanız gereken durumlar için iyidir, o zaman bunun için yepyeni bir işlev aşırı bir şeymiş gibi gelir.
feos

26

Deneyin:

public static int Clamp(int value, int min, int max)  
{  
    return (value < min) ? min : (value > max) ? max : value;  
}

7
Ugh! O çirkin gereksiz parantezler! Çift üçlü operatörlerle kötü bir dahi olacaksanız, en azından bunu doğru bir şekilde yapın ve onlardan da kurtulun! 😂
XenoRo

9
@XenoRo Bu "gereksiz" parantezler onu okunabilir kılan şeydir.
Daha açık

2
@Cleaner - 1) Okunabilirlik için gidiyorsanız, çift üçlülerden kaçınılır ve bunun yerine IF blokları kullanılır. 2) Şakayı anlamıyorsun, değil mi? xD
XenoRo

13

Bir tane yok ama yapmak o kadar da zor değil. Burada bir tane buldum: kelepçe

Bu:

public static T Clamp<T>(T value, T max, T min)
    where T : System.IComparable<T> {
        T result = value;
        if (value.CompareTo(max) > 0)
            result = max;
        if (value.CompareTo(min) < 0)
            result = min;
        return result;
    }

Ve şu şekilde kullanılabilir:

int i = Clamp(12, 10, 0); -> i == 10
double d = Clamp(4.5, 10.0, 0.0); -> d == 4.5

Bu çözüm, kabul edilenden daha iyidir. Belirsizlik yok.
aggsol

6
@CodeClown Bu çözüm, değer> max olduğunda gereksiz bir karşılaştırmaya neden olur ve tersine çevrilmiş bağımsız değişken sıralaması hataları davet eder (ve sanal olarak garanti eder). Hangi belirsizlikten kaçınıldığını düşündüğünüzü bilmiyorum.
Jim Balter

Eski Math.Clamp uygulamasıyla tutarlılık için min / maks parametrelerinin sırasını değiştirmenizi öneririz:Clamp(T value, T min, T max)
josh poley


4

Mümkünse, Lee'nin çözümünü yorumların ele alınan sorunları ve endişeleriyle paylaşmanız yeterli :

public static T Clamped<T>(this T value, T min, T max) where T : IComparable<T> {
    if (value == null) throw new ArgumentNullException(nameof(value), "is null.");
    if (min == null) throw new ArgumentNullException(nameof(min), "is null.");
    if (max == null) throw new ArgumentNullException(nameof(max), "is null.");
    //If min <= max, clamp
    if (min.CompareTo(max) <= 0) return value.CompareTo(min) < 0 ? min : value.CompareTo(max) > 0 ? max : value;
    //If min > max, clamp on swapped min and max
    return value.CompareTo(max) < 0 ? max : value.CompareTo(min) > 0 ? min : value;
}

Farklılıklar:

Sınırlamalar: Tek taraflı kelepçe yok. Eğer maxbir NaN, her zaman döndürür NaN(Bkz Herman'ın comment ).


Diğer bir sınırlama nameofC # 5 veya altı için çalışmaz.
RoLYroLLs

0

Önceki cevapları kullanarak, ihtiyaçlarım için aşağıdaki koda yoğunlaştırdım. Bu aynı zamanda bir sayıyı yalnızca min veya maks.

public static class IComparableExtensions
{
    public static T Clamped<T>(this T value, T min, T max) 
        where T : IComparable<T>
    {
        return value.CompareTo(min) < 0 ? min : value.ClampedMaximum(max);
    }

    public static T ClampedMinimum<T>(this T value, T min)
        where T : IComparable<T>
    {
        return value.CompareTo(min) < 0 ? min : value;
    }

    public static T ClampedMaximum<T>(this T value, T max)
        where T : IComparable<T>
    {
        return value.CompareTo(max) > 0 ? max : value;
    }
}

Neden olmasın return value.ClampedMinimum(min).ClampedMaximum(max);?
Henrik

0

Aşağıdaki kod, sınırların herhangi bir sırada (yani bound1 <= bound2, veya bound2 <= bound1) belirlenmesini destekler . Bunu y=mx+b, çizginin eğiminin arttığı veya azaldığı doğrusal denklemlerden ( ) hesaplanan kenetleme değerleri için yararlı buldum .

Biliyorum: Kod, beş süper çirkin koşullu ifade operatöründen oluşur . Mesele şu ki, işe yarıyor ve aşağıdaki testler bunu kanıtlıyor. İsterseniz, kesinlikle gereksiz parantezler eklemekten çekinmeyin.

Diğer sayısal türler için başka aşırı yüklemeleri kolayca oluşturabilir ve temel olarak testleri kopyalayabilir / yapıştırabilirsiniz.

Uyarı: Kayan noktalı sayıları karşılaştırmak kolay değildir. Bu kod, doublekarşılaştırmaları sağlam bir şekilde gerçekleştirmez. Karşılaştırma operatörlerinin kullanımlarını değiştirmek için bir kayan nokta karşılaştırma kitaplığı kullanın.

public static class MathExtensions
{
    public static double Clamp(this double value, double bound1, double bound2)
    {
        return bound1 <= bound2 ? value <= bound1 ? bound1 : value >= bound2 ? bound2 : value : value <= bound2 ? bound2 : value >= bound1 ? bound1 : value;
    }
}

xUnit / FluentAssertions testleri:

public class MathExtensionsTests
{
    [Theory]
    [InlineData(0, 0, 0, 0)]
    [InlineData(0, 0, 2, 0)]
    [InlineData(-1, 0, 2, 0)]
    [InlineData(1, 0, 2, 1)]
    [InlineData(2, 0, 2, 2)]
    [InlineData(3, 0, 2, 2)]
    [InlineData(0, 2, 0, 0)]
    [InlineData(-1, 2, 0, 0)]
    [InlineData(1, 2, 0, 1)]
    [InlineData(2, 2, 0, 2)]
    [InlineData(3, 2, 0, 2)]
    public void MustClamp(double value, double bound1, double bound2, double expectedValue)
    {
        value.Clamp(bound1, bound2).Should().Be(expectedValue);
    }
}

0

[Min, max] cinsinden bir argümanın aralığını doğrulamak istersem, aşağıdaki kullanışlı sınıfı kullanırım:

public class RangeLimit<T> where T : IComparable<T>
{
    public T Min { get; }
    public T Max { get; }
    public RangeLimit(T min, T max)
    {
        if (min.CompareTo(max) > 0)
            throw new InvalidOperationException("invalid range");
        Min = min;
        Max = max;
    }

    public void Validate(T param)
    {
        if (param.CompareTo(Min) < 0 || param.CompareTo(Max) > 0)
            throw new InvalidOperationException("invalid argument");
    }

    public T Clamp(T param) => param.CompareTo(Min) < 0 ? Min : param.CompareTo(Max) > 0 ? Max : param;
}

Sınıf, olan tüm nesneler için çalışır IComparable. Belirli bir aralığa sahip bir örnek oluşturuyorum:

RangeLimit<int> range = new RangeLimit<int>(0, 100);

Ya bir argümanı doğrularım

range.Validate(value);

veya argümanı aralığa sabitleyin:

var v = range.Validate(value);
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.