Di mana saya dapat menemukan fungsi "penjepit" di .NET?


98

Saya ingin menjepit nilai xke kisaran [a, b]:

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

Ini cukup mendasar. Tapi saya tidak melihat fungsi "penjepit" di perpustakaan kelas - setidaknya tidak di System.Math.

(Untuk orang yang tidak sadar untuk "menjepit" nilai adalah memastikan bahwa nilainya berada di antara beberapa nilai maksimum dan minimum. Jika lebih besar dari nilai maks, maka diganti dengan nilai maks, dll.)


2
@Danvil: Tidak ada "C # Class Library". Maksud Anda "The .NET Framework".
John Saunders

1
Masih belum ada pada C # 7.1?
joce

1
@JohnSaunders Saya tidak percaya itu benar stackoverflow.com/questions/807880/…
Adam Naylor

Jika saya bertanya bagaimana "membatasi" nilai, setiap programmer berbahasa Inggris di dunia akan langsung tahu apa yang saya maksud. Kemungkinan besar setiap programmer pasti tahu. Setelah 30+ tahun dalam bisnis ini, saya harus mencari tahu apa arti "penjepit" hari ini. Mirip dengan "injeksi ketergantungan" - "parameterisasi" adalah hal yang sangat jelas tidak ada yang pernah menulis buku tentangnya.
Bob

@Bob Beberapa kata memiliki makna historis dan terdefinisi dengan baik. Clamp adalah salah satunya. en.wikipedia.org/wiki/Clamping_(graphics) atau khronos.org/registry/OpenGL-Refpages/gl4/html/clamp.xhtml atau docs.microsoft.com/en-us/windows/win32/direct3dhlsl/… "Batas "akan menyesatkan, apalagi" batas "sudah memiliki arti yang berbeda dalam matematika.
kaalus

Jawaban:


141

Anda bisa menulis metode ekstensi:

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

Metode ekstensi masuk dalam kelas statis - karena ini adalah fungsi tingkat rendah, mungkin harus masuk ke beberapa ruang nama inti dalam proyek Anda. Anda kemudian dapat menggunakan metode dalam file kode apa pun yang berisi direktif penggunaan untuk namespace misalnya

using Core.ExtensionMethods

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

.NET Core 2.0

Dimulai dengan .NET Core 2.0 System.Mathsekarang memiliki Clampmetode yang dapat digunakan sebagai gantinya:

using System;

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

1
Di mana saya akan meletakkan ini dan memanggil CompareTo lebih lambat daripada membandingkan dengan <(untuk tipe integral)?
Danvil

1
Dalam kelas statis, dan dalam kerangka .NET (tidak yakin tentang mono, kompak, dll.), Generik harus dikompilasi ulang untuk jenisnya, dan CompareTo sebaris, jadi tidak ada penalti kinerja.
Robert Fraser

1
@ Frasier Kecuali jika ini adalah kode yang sangat sensitif terhadap kinerja, Anda kemungkinan besar tidak akan memperoleh peningkatan kinerja yang berarti dengan melakukannya. Membuatnya menjadi generik mungkin lebih berguna daripada menghemat beberapa mikrodetik.
MgSam

5
Hal yang baik tentang membatasi ke versi generikIComparable adalah tidak ada tinju yang terjadi. Ini seharusnya berjalan sangat cepat. Ingatlah bahwa dengan doubledan float, CompareTometode tersebut sesuai dengan urutan total NaNyang kurang dari semua nilai lainnya, termasuk NegativeInfinity. Jadi tidak setara dengan <operator. Jika Anda menggunakan <dengan tipe floating-point, Anda harus mempertimbangkan bagaimana memperlakukan NaNjuga. Ini tidak relevan untuk tipe numerik lainnya.
Jeppe Stig Nielsen

1
Anda perlu mempertimbangkan bagaimana merawat NaNdalam kedua kasus tersebut. Versi dengan <dan >akan mengeluarkan NaNdan menggunakan NaNuntuk minatau maxakan secara efektif membuat penjepit satu sisi. Dengan CompareToitu akan selalu kembali NaNjika maxada NaN.
Herman

32

Cukup gunakan Math.Mindan Math.Max:

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

Itu diterjemahkan ke int a0 = x > a ? x : a; return a0 < b ? a0 : bmana (meskipun memberikan hasil yang benar) tidak terlalu ideal.
Tn. Smith

12
dan mengapa demikian?
d7samurai

4
@ d7samurai Jika kita tahu bahwa min <= max, Math.Min(Math.Max(x, min), max)menghasilkan satu perbandingan lebih dari yang diperlukan jika x <min.
Jim Balter

@JimBalter, secara teori ini benar. Jika Anda melihat bagaimana CompareTo () biasanya diterapkan, jawaban yang diterima dapat membutuhkan hingga 6 perbandingan. Saya tidak tahu, apakah kompilernya cukup pintar dan menyebariskan CompareTo () dan menghapus perbandingan yang berlebihan.
quinmars

1
Ini bagus untuk kasus ketika Anda hanya perlu melakukannya sekali, maka fungsi baru untuk itu terasa seperti berlebihan.
feos

26

Mencoba:

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

7
Ugh! Tanda kurung berlebihan yang jelek itu! Jika Anda akan menjadi seorang jenius yang jahat dengan operator terner ganda, setidaknya lakukan dengan benar dan singkirkan itu juga! 😂
XenoRo

9
@XenoRo Tanda kurung "redundan" itulah yang membuatnya dapat dibaca.
jelas

2
@ Cleaner - 1) Jika Anda menginginkan keterbacaan, terner ganda akan dihindari dan blok IF akan digunakan sebagai gantinya. 2) Anda tidak mengerti leluconnya, bukan? xD
XenoRo

13

Tidak ada, tapi tidak terlalu sulit untuk membuatnya. Saya menemukan satu di sini: penjepit

Ini:

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

Dan itu bisa digunakan seperti:

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

Solusi ini lebih baik daripada yang diterima. Tidak ada ambiguitas.
aggsol

6
@CodeClown Solusi ini menghasilkan perbandingan yang tidak perlu ketika value> max, dan urutan argumen terbalik mengundang (dan secara virtual menjamin) bug. Saya tidak tahu ambiguitas apa yang menurut Anda dihindari.
Jim Balter

Untuk konsistensi dengan implementasi Math.Clamp yang lama, rekomendasikan untuk mengganti urutan parameter min / max:Clamp(T value, T min, T max)
josh poley


4

Hanya membagikan solusi Lee dengan masalah dan kekhawatiran komentar yang ditangani, jika memungkinkan:

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

Perbedaan:

Keterbatasan: Tidak ada klem satu sisi. Jika maxada NaN, selalu kembali NaN(Lihat komentar Herman ).


Batasan lain adalah nameoftidak bekerja untuk C # 5 atau lebih rendah.
RoLYroLLs

0

Menggunakan jawaban sebelumnya, saya memadatkannya ke kode di bawah ini untuk kebutuhan saya. Ini juga akan memungkinkan Anda untuk menjepit angka hanya dengan min atau 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;
    }
}

Mengapa tidak return value.ClampedMinimum(min).ClampedMaximum(max);?
Henrik

0

Kode di bawah ini mendukung penentuan batas dalam urutan apapun (yaitu bound1 <= bound2, atau bound2 <= bound1). Saya telah menemukan ini berguna untuk nilai penjepitan yang dihitung dari persamaan linier ( y=mx+b) di mana kemiringan garis dapat meningkat atau menurun.

Saya tahu: Kode ini terdiri dari lima operator ekspresi bersyarat yang sangat jelek . Masalahnya, itu berhasil , dan tes di bawah ini membuktikannya. Jangan ragu untuk menambahkan tanda kurung yang tidak perlu jika Anda menginginkannya.

Anda dapat dengan mudah membuat kelebihan lain untuk tipe numerik lain dan pada dasarnya menyalin / menempel tes.

Peringatan: Membandingkan bilangan floating point tidaklah mudah. Kode ini tidak menerapkan doubleperbandingan dengan baik. Gunakan pustaka perbandingan titik mengambang untuk menggantikan penggunaan operator perbandingan.

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 tes:

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

Jika saya ingin memvalidasi rentang argumen dalam [min, max], saya menggunakan kelas praktis berikut:

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

Kelas bekerja untuk semua objek yang IComparable. Saya membuat instance dengan rentang tertentu:

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

Saya juga memvalidasi argumen

range.Validate(value);

atau jepit argumen ke kisaran:

var v = range.Validate(value);
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.