Tôi có thể tìm thấy chức năng "kẹp" trong .NET ở đâu?


95

Tôi muốn kẹp một giá trị xvào một phạm vi[a, b] :

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

Điều này khá cơ bản. Nhưng tôi không thấy một hàm "kẹp" trong thư viện lớp - ít nhất là không có trongSystem.Math .

(Đối với người không biết "kẹp" một giá trị là đảm bảo rằng nó nằm giữa một số giá trị lớn nhất và nhỏ nhất. Nếu nó lớn hơn giá trị tối đa, thì nó được thay thế bằng giá trị tối đa, v.v.)


2
@Danvil: Không có "Thư viện lớp C #". Ý bạn là "Khuôn khổ .NET".
John Saunders

1
Vẫn không có gì như C # 7.1?
joce

1
@JohnSaunders Tôi không tin điều đó hoàn toàn đúng với stackoverflow.com/questions/807880/…
Adam Naylor

Nếu tôi hỏi làm thế nào để "giới hạn" một giá trị thì mọi lập trình viên nói tiếng Anh trên thế giới sẽ biết ngay ý tôi. Rất có thể mọi lập trình viên sẽ biết. Sau hơn 30 năm kinh doanh, tôi phải đi tìm hiểu ngày hôm nay "kẹp" có nghĩa là gì. Tương tự như "dependency injection" - "tham số hóa" là một điều hiển nhiên mà chưa ai viết sách về nó.
Bob

@Bob Một số từ có ý nghĩa lịch sử, được xác định rõ. Kẹp là một trong số đó. en.wikipedia.org/wiki/Clamping_(graphics) hoặc khronos.org/registry/OpenGL-Refpages/gl4/html/clamp.xhtml hoặc docs.microsoft.com/en-us/windows/win32/direct3dhlsl/… "Giới hạn "sẽ gây hiểu lầm, đặc biệt là" giới hạn "đã có một nghĩa khác trong toán học.
kaalus

Câu trả lời:


137

Bạn có thể viết một phương thức mở rộng:

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

Các phương thức mở rộng đi trong các lớp tĩnh - vì đây là một hàm cấp thấp, nên có lẽ nó sẽ nằm trong một số không gian tên cốt lõi trong dự án của bạn. Sau đó, bạn có thể sử dụng phương thức này trong bất kỳ tệp mã nào có chứa chỉ thị using cho không gian tên, ví dụ:

using Core.ExtensionMethods

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

.NET Core 2.0

Bắt đầu với .NET Core 2.0 System.Mathbây giờ có một Clampphương pháp có thể được sử dụng thay thế:

using System;

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

1
Tôi sẽ đặt cái này ở đâu và gọi CompareTo chậm hơn so với so sánh với <(đối với các loại tích phân)?
Danvil

1
Trong một lớp tĩnh và trong khuôn khổ .NET (không chắc chắn về mono, compact, v.v.), chung nên được biên dịch lại cho kiểu và có nội tuyến CompareTo, do đó không bị phạt về hiệu suất.
Robert Fraser

1
@Frasier Trừ khi đây là mã cực kỳ nhạy cảm về hiệu suất, bạn sẽ không thể tạo ra bất kỳ mức tăng hiệu suất có ý nghĩa nào bằng cách làm như vậy. Có nó là chung chung có lẽ hữu ích hơn là tiết kiệm một vài micro giây.
MgSam

4
Điều tốt khi hạn chế với phiên bản chung của IComparablelà không có quyền anh xảy ra. Điều này phải chạy rất nhanh. Hãy nhớ rằng với doublefloat, CompareTophương thức tương ứng với một tổng số thứ tự NaNnhỏ hơn tất cả các giá trị khác, bao gồm cả NegativeInfinity. Vì vậy, nó không tương đương với <toán tử. Nếu bạn đã sử dụng <với kiểu dấu phẩy động, bạn cũng sẽ phải xem xét cách xử lý NaN. Điều này không liên quan đến các loại số khác.
Jeppe Stig Nielsen

1
Bạn sẽ cần phải xem xét cách điều trị NaNtrong cả hai trường hợp. Phiên bản có <>sẽ xuất ra NaNvà sử dụng NaNcho minhoặc maxsẽ làm một kẹp một mặt hiệu quả. Với CompareTonó sẽ luôn trở lại NaNnếu maxNaN.
Herman

30

Chỉ cần sử dụng Math.MinMath.Max:

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

Điều đó có nghĩa là int a0 = x > a ? x : a; return a0 < b ? a0 : b(mặc dù cho kết quả chính xác) không hoàn toàn lý tưởng.
Ông Smith,

12
và tại sao vậy?
d7samurai

4
@ d7samurai Nếu chúng ta biết rằng min <= max, Math.Min(Math.Max(x, min), max)kết quả là một phép so sánh nhiều hơn mức cần thiết nếu x <min.
Jim Balter

@JimBalter, về lý thuyết thì điều này đúng. Nếu bạn nhìn vào cách CompareTo () thường được triển khai, câu trả lời được chấp nhận có thể thực hiện tối đa 6 phép so sánh. Mặc dù vậy, tôi không biết, nếu trình biên dịch đủ thông minh và nội tuyến CompareTo () và loại bỏ các so sánh thừa.
quinmars

1
Điều này tốt cho những trường hợp bạn chỉ cần làm điều đó một lần, sau đó toàn bộ chức năng mới cho cảm giác như quá mức cần thiết.
feos

26

Thử:

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

6
Ặc! Những dấu ngoặc đơn thừa xấu xí! Nếu bạn sẽ trở thành một thiên tài xấu xa với các toán tử kép ba ba, ít nhất hãy làm đúng cách và loại bỏ chúng! 😂
XenoRo

8
@XenoRo Những dấu ngoặc "thừa" đó là thứ khiến nó có thể đọc được.
Rõ ràng hơn

2
@Cleaner - 1) Nếu bạn muốn dễ đọc, thì các cụm từ kép sẽ được tránh và thay vào đó, các khối IF sẽ được sử dụng. 2) Bạn không hiểu trò đùa, phải không? xD
XenoRo

13

Không có một cái, nhưng không quá khó để làm một cái. Tôi tìm thấy một cái ở đây: kẹp

Nó là:

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

Và nó có thể được sử dụng như:

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

Giải pháp này tốt hơn giải pháp được chấp nhận. Không mơ hồ.
aggsol

6
@CodeClown Giải pháp này dẫn đến một sự so sánh không cần thiết khi giá trị> tối đa và thứ tự đối số đảo ngược mời (và hầu như đảm bảo) lỗi. Tôi không biết bạn nghĩ gì về sự mơ hồ mà tránh được.
Jim Balter

Để nhất quán với triển khai Math.Clamp kế thừa, khuyên bạn nên chuyển đổi thứ tự của các tham số tối thiểu / tối đa:Clamp(T value, T min, T max)
josh poley


4

Chỉ chia sẻ giải pháp của Lee với các vấn đề và mối quan tâm của bình luận được giải quyết, nếu có thể:

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

Sự khác biệt:

Hạn chế: Không có kẹp một bên. Nếu maxNaN, luôn luôn trả về NaN(Xem comment Herman của ).


Một hạn chế khác là nameofkhông hoạt động đối với C # 5 trở xuống.
RoLYroLLs

0

Sử dụng các câu trả lời trước, tôi cô đọng nó thành đoạn mã dưới đây cho nhu cầu của tôi. Điều này cũng sẽ cho phép bạn kẹp một số chỉ bằng giá trị nhỏ nhất hoặc tối đa của nó.

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

Tại sao không return value.ClampedMinimum(min).ClampedMaximum(max);?
Henrik

0

Đoạn mã dưới đây hỗ trợ chỉ định giới hạn theo bất kỳ thứ tự nào (tức là bound1 <= bound2hoặc bound2 <= bound1). Tôi thấy điều này hữu ích cho các giá trị kẹp được tính toán từ phương trình tuyến tính ( y=mx+b) trong đó độ dốc của đường có thể tăng hoặc giảm.

Tôi biết: Đoạn mã bao gồm năm toán tử biểu thức điều kiện siêu xấu . Vấn đề là, nó hoạt động và các thử nghiệm dưới đây đã chứng minh điều đó. Vui lòng thêm các dấu ngoặc đơn không cần thiết nếu bạn muốn.

Bạn có thể dễ dàng tạo quá tải khác cho các loại số khác và về cơ bản sao chép / dán các bài kiểm tra.

Cảnh báo: So sánh số dấu phẩy động không đơn giản. Mã này không thực hiện doubleso sánh một cách mạnh mẽ. Sử dụng thư viện so sánh dấu phẩy động để thay thế việc sử dụng các toán tử so sánh.

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

Kiểm tra xUnit / FluentAssertions:

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

Nếu tôi muốn xác thực phạm vi của một đối số trong [min, max], tôi sử dụng lớp tiện dụng sau:

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

Lớp hoạt động cho tất cả các đối tượng IComparable. Tôi tạo một phiên bản với một phạm vi nhất định:

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

Tôi xác thực một đối số

range.Validate(value);

hoặc kẹp đối số vào phạm vi:

var v = range.Validate(value);
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.