ฉันจะหาฟังก์ชัน "แคลมป์" ใน. NET ได้ที่ไหน?


98

ฉันต้องการกำหนดค่าxเป็นช่วง[a, b]:

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

นี่เป็นเรื่องพื้นฐานทีเดียว แต่ผมไม่เห็นฟังก์ชั่น "ยึด" ในห้องสมุดชั้น - System.Mathอย่างน้อยไม่ได้อยู่ใน

(สำหรับผู้ที่ไม่รู้จะ "ยึด" ค่าคือเพื่อให้แน่ใจว่าอยู่ระหว่างค่าสูงสุดและค่าต่ำสุดบางค่าหากมากกว่าค่าสูงสุดค่าสูงสุดจะถูกแทนที่ด้วยค่าสูงสุดเป็นต้น)


2
@ Danvil: ไม่มี "C # Class Library" คุณหมายถึง ".NET Framework"
John Saunders

1
ยังไม่มีอะไรเป็น C # 7.1?
joce

1
@JohnSaunders ฉันไม่เชื่อว่านั่นเป็นความจริงอย่างเคร่งครัดstackoverflow.com/questions/807880/…
Adam Naylor

ถ้าฉันถามถึงวิธี "จำกัด " ค่าโปรแกรมเมอร์ที่พูดภาษาอังกฤษทุกคนในโลกจะรู้ทันทีว่าฉันหมายถึงอะไร ส่วนใหญ่โปรแกรมเมอร์ทุกคนจะรู้ดี หลังจากทำธุรกิจมา 30 ปีขึ้นไปฉันต้องหาคำตอบว่า "แคลมป์" หมายถึงอะไรในวันนี้ คล้ายกับ "การฉีดพึ่งพา" - "การกำหนดพารามิเตอร์" เป็นสิ่งที่ชัดเจนเช่นนี้ไม่มีใครเคยเขียนหนังสือเกี่ยวกับเรื่องนี้
Bob

@ บ็อบคำบางคำมีความหมายทางประวัติศาสตร์ แคลมป์เป็นหนึ่งในนั้น en.wikipedia.org/wiki/Clamping_(graphics)หรือkhronos.org/registry/OpenGL-Refpages/gl4/html/clamp.xhtmlหรือdocs.microsoft.com/en-us/windows/win32/direct3dhlsl/… "จำกัด "จะทำให้เข้าใจผิดโดยเฉพาะ" ขีด จำกัด "นั้นมีความหมายที่แตกต่างออกไปในทางคณิตศาสตร์อยู่แล้ว
kaalus

คำตอบ:


141

คุณสามารถเขียนวิธีการขยาย:

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

วิธีการขยายจะอยู่ในคลาสแบบคงที่เนื่องจากเป็นฟังก์ชันระดับต่ำจึงควรอยู่ในเนมสเปซหลักในโปรเจ็กต์ของคุณ จากนั้นคุณสามารถใช้วิธีการในไฟล์โค้ดใด ๆ ที่มีคำสั่งการใช้สำหรับเนมสเปซเช่น

using Core.ExtensionMethods

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

.NET Core 2.0

เริ่มต้นด้วย. NET Core 2.0 System.Mathตอนนี้มีClampวิธีที่สามารถใช้แทน:

using System;

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

1
ฉันจะวางสิ่งนี้ไว้ที่ไหนและเรียก CompareTo ช้ากว่าการเปรียบเทียบกับ <(สำหรับประเภทอินทิกรัล)
Danvil

1
ในคลาสแบบคงที่และในเฟรมเวิร์ก. NET (ไม่แน่ใจเกี่ยวกับโมโนคอมแพค ฯลฯ ) ทั่วไปควรได้รับการคอมไพล์ใหม่สำหรับประเภทและ CompareTo อินไลน์ดังนั้นจึงไม่มีการลงโทษด้านประสิทธิภาพ
Robert Fraser

1
@Frasier เว้นแต่ว่านี่เป็นรหัสที่มีความละเอียดอ่อนด้านประสิทธิภาพเป็นพิเศษคุณไม่น่าจะได้รับประสิทธิภาพที่มีความหมายจากการทำเช่นนั้น การมีแบบทั่วไปน่าจะมีประโยชน์มากกว่าการประหยัดเวลาเพียงไม่กี่ไมโครวินาที
MgSam

5
ข้อดีของการ จำกัดรุ่นทั่วไปIComparableคือไม่มีการชกมวยเกิดขึ้น สิ่งนี้ควรจะวิ่งเร็วมาก โปรดจำไว้ว่ามีdoubleและfloatที่CompareToสอดคล้องกับวิธีการในการสั่งซื้อทั้งหมดที่NaNมีค่าน้อยกว่าค่าอื่น ๆ NegativeInfinityทั้งหมดรวมทั้ง ดังนั้นจึงไม่เทียบเท่ากับตัว<ดำเนินการ หากคุณใช้<กับประเภทจุดลอยตัวคุณจะต้องพิจารณาวิธีการรักษาNaNด้วย สิ่งนี้ไม่เกี่ยวข้องกับตัวเลขประเภทอื่น ๆ
Jeppe Stig Nielsen

1
คุณจะต้องพิจารณาวิธีการรักษาNaNในทั้งสองกรณี เวอร์ชันที่มี<และ>จะส่งออกNaNและใช้NaNสำหรับminหรือmaxจะทำการยึดด้านเดียวอย่างมีประสิทธิภาพ ด้วยCompareToมันก็จะกลับมาNaNถ้ามีmax NaN
Herman

32

เพียงใช้Math.MinและMath.Max:

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

นั่นแปลว่าสิ่งint a0 = x > a ? x : a; return a0 < b ? a0 : bที่ (แม้ว่าจะให้ผลลัพธ์ที่ถูกต้อง) ไม่เหมาะอย่างแน่นอน
Mr. Smith

12
แล้วทำไมล่ะ?
d7samurai

4
@ d7samurai ถ้าเรารู้ว่า min <= max จะทำให้Math.Min(Math.Max(x, min), max)เกิดการเปรียบเทียบมากกว่าที่จำเป็นถ้า x <min
Jim Balter

@ JimBalter ในทางทฤษฎีนี่เป็นเรื่องจริง หากคุณดูวิธีการใช้งาน CompareTo () โดยทั่วไปคำตอบที่ยอมรับอาจใช้การเปรียบเทียบได้ไม่เกิน 6 รายการ ฉันไม่รู้ว่าถ้าคอมไพเลอร์ฉลาดพอและสอดแทรก CompareTo () และลบการเปรียบเทียบที่ไม่จำเป็นออกไป
quinmars

1
นี่เป็นสิ่งที่ดีสำหรับกรณีที่คุณต้องทำเพียงครั้งเดียวจากนั้นฟังก์ชั่นใหม่ทั้งหมดที่ให้ความรู้สึกเหมือนโอเวอร์คิล
feos

26

ลอง:

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

7
ฮึ วงเล็บซ้ำซ้อนน่าเกลียด! หากคุณกำลังจะเป็นอัจฉริยะที่ชั่วร้ายกับผู้ดำเนินการสองฝ่ายอย่างน้อยก็ควรทำอย่างถูกต้องและกำจัดสิ่งเหล่านั้นด้วย! 😂
XenoRo

9
@XenoRo วงเล็บ "ซ้ำซ้อน" เหล่านี้คือสิ่งที่ทำให้อ่านได้
ชัดเจนกว่า

2
@Cleaner - 1) หากคุณต้องการความสามารถในการอ่านจะหลีกเลี่ยงสอง ternaries และจะใช้บล็อก IF แทน 2) คุณไม่ได้รับเรื่องตลกใช่ไหม xD
XenoRo

13

ไม่มีอย่างใดอย่างหนึ่ง แต่ก็ไม่ยากเกินไปที่จะสร้าง ฉันพบที่นี่: ที่หนีบ

มันคือ:

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

และสามารถใช้งานได้เช่น:

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

วิธีนี้ดีกว่าวิธีที่ยอมรับ ไม่มีความคลุมเครือ
aggsol

6
@CodeClown โซลูชันนี้ส่งผลให้เกิดการเปรียบเทียบที่ไม่จำเป็นเมื่อค่า> สูงสุดและคำสั่งอาร์กิวเมนต์กลับด้านจะเชิญ (และแทบจะรับประกันได้) จุดบกพร่อง ฉันไม่รู้ว่าความกำกวมที่คุณคิดว่าจะหลีกเลี่ยงได้
Jim Balter

เพื่อความสอดคล้องกับการใช้ Math.Clamp แบบเดิมแนะนำให้เปลี่ยนลำดับของพารามิเตอร์ min / max:Clamp(T value, T min, T max)
josh poley


4

เพียงแบ่งปันวิธีแก้ปัญหาของ Leeพร้อมกับปัญหาและข้อกังวลของความคิดเห็นหากเป็นไปได้:

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

ความแตกต่าง:

ข้อ จำกัด : ไม่มีที่หนีบด้านเดียว ถ้าmaxเป็นNaNจะส่งคืนเสมอNaN(ดูความคิดเห็นของ Herman )


ข้อ จำกัด อีกประการหนึ่งnameofไม่สามารถใช้ได้กับ C # 5 หรือต่ำกว่า
RoLYroLLs

0

ใช้คำตอบก่อนหน้านี้ฉันย่อให้สั้นลงเป็นรหัสด้านล่างตามความต้องการของฉัน วิธีนี้จะช่วยให้คุณสามารถจับตัวเลขได้ด้วยค่าต่ำสุดหรือสูงสุดเท่านั้น

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

ทำไมไม่return value.ClampedMinimum(min).ClampedMaximum(max);?
Henrik

0

รหัสด้านล่างนี้รองรับการระบุขอบเขตตามลำดับใด ๆ (เช่นbound1 <= bound2หรือbound2 <= bound1) ฉันพบว่าสิ่งนี้มีประโยชน์สำหรับการจับค่าที่คำนวณจากสมการเชิงเส้น ( y=mx+b) ซึ่งความชันของเส้นสามารถเพิ่มหรือลดได้

ฉันรู้: รหัสประกอบด้วยห้าซุปเปอร์น่าเกลียดผู้ประกอบการนิพจน์เงื่อนไข สิ่งนี้คือใช้งานได้และการทดสอบด้านล่างพิสูจน์ได้ อย่าลังเลที่จะเพิ่มวงเล็บที่ไม่จำเป็นอย่างเคร่งครัดหากคุณต้องการ

คุณสามารถสร้างโอเวอร์โหลดอื่น ๆ สำหรับตัวเลขประเภทอื่น ๆ ได้อย่างง่ายดายและคัดลอก / วางการทดสอบ

คำเตือน: การเปรียบเทียบตัวเลขทศนิยมไม่ใช่เรื่องง่าย รหัสนี้ไม่ได้ใช้การdoubleเปรียบเทียบอย่างมีประสิทธิภาพ ใช้ไลบรารีการเปรียบเทียบจุดลอยตัวเพื่อแทนที่การใช้ตัวดำเนินการเปรียบเทียบ

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:

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] ฉันใช้คลาสที่มีประโยชน์ต่อไปนี้:

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

IComparableชั้นงานสำหรับวัตถุทั้งหมดที่มี ฉันสร้างอินสแตนซ์ด้วยช่วงหนึ่ง:

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

ฉันตรวจสอบข้อโต้แย้ง

range.Validate(value);

หรือยึดอาร์กิวเมนต์ไว้ที่ช่วง:

var v = range.Validate(value);
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.