จะตรวจสอบว่าตัวเลขอยู่ในช่วงได้อย่างงดงามได้อย่างไร?


157

ฉันจะทำสิ่งนี้อย่างสง่างามด้วย C # และ. NET 3.5 / 4 ได้อย่างไร

ตัวอย่างเช่นตัวเลขสามารถอยู่ระหว่าง 1 ถึง 100

ฉันรู้ว่าง่ายถ้าจะพอเพียง; แต่คำสำคัญสำหรับคำถามนี้คือความสง่างาม สำหรับโครงการของเล่นของฉันไม่ใช่เพื่อการผลิต

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


23
Re: "แก้ไข" ของคุณ - ที่เรียบง่ายเป็นสง่า ผมเองพบว่าถ้ามีคำสั่งสง่างามมากขึ้นกว่าวิธีที่ไม่ได้มาตรฐานใด ๆ ของการทำเครื่องหมายนี้ ...
Reed Copsey

4
"ทุกสิ่งควรทำอย่างง่ายที่สุด แต่ไม่ง่ายกว่า" - Albert Einstein
corsiKa

3
@Sergio: ฉันไม่รู้สึกว่าฉันเป็นคนอวดดี ฉันรู้สึกว่าผู้คนมักใช้วิธีการขยายและเครื่องมืออื่น ๆ ในภาษาเพื่อใช้แทนสิ่งที่เรียบง่ายอยู่แล้ว มีหลายร้อยวิธีในการเปรียบเทียบค่า int สองค่า แต่การใช้อะไรก็ได้ แต่ยิ่งชัดเจนว่าเป็นตัวเลือกที่แย่ IMO
Reed Copsey

3
@Sergio: ฉันเดาแล้วฉันไม่เห็นประเด็นของคำถาม;)
Reed Copsey

6
@Sergio: ถ้าifไม่ใช่ "บาร็อค" อย่าแก้ไข
StriplingWarrior

คำตอบ:


154

มีตัวเลือกมากมาย:

int x = 30;
if (Enumerable.Range(1,100).Contains(x))
    //true

if (x >= 1 && x <= 100)
    //true

นอกจากนี้ตรวจสอบโพสต์ SOนี้สำหรับตัวเลือก regex


334
Enumerable.Range มีการสร้างจำนวนเต็มก่อนแล้ววนซ้ำแต่ละรายการเพื่อค้นหา นั่นเป็นความคิดและประสิทธิภาพที่แย่มากเมื่อเทียบกับการตรวจสอบค่าที่แตกต่างกันอย่างมาก ฉันคิดว่าเราควรใช้ moto เพราะส่วนต่อขยายของ LINQ นั้นยอดเยี่ยมไม่ได้หมายความว่าควรใช้กับทุกสิ่ง
Matthew Abbott


15
ฉันเห็นด้วยว่านี่เป็นความคิดที่น่ากลัว แต่ประสิทธิภาพสูง แต่ OP ต้องการบางสิ่งที่แปลกใหม่กว่าifแถลงการณ์ สิ่งนี้ทำให้สำเร็จได้อย่างแน่นอน ... ;)
Tim Coker

10
ควรทราบว่าพารามิเตอร์ที่สองไม่ใช่ "หยุด" แต่ "นับ" ดังนั้นตัวอย่างเช่น Enumerable.Range (150, 300) .Contains (400) จะส่งกลับค่าจริง
Shathur

5
กรุณาอย่าใช้คำตอบนี้ มันจะมีประสิทธิภาพที่น่ากลัวหากช่วงของคุณค่อนข้างใหญ่ โปรดดูคำตอบโดย @ olivier-jacot-descombes
Aaron Hudon

95

คุณหมายถึง?

if(number >= 1 && number <= 100)

หรือ

bool TestRange (int numberToCheck, int bottom, int top)
{
  return (numberToCheck >= bottom && numberToCheck <= top);
}

1
คุณไม่ต้องการ "เป็น" ในนั้น ... มันจะไม่คอมไพล์ (มิฉะนั้นฉันเห็นด้วย 100%)
Reed Copsey

4
@ Ben เพียงแค่รอจนกว่าฉันจะลองและสิทธิบัตรมันเกินไป :)
kemiller2002

ฉันคิดว่านี่เป็นทางออกที่มั่นคงที่สุด แต่ไม่ใช่ว่าผู้ถามหาอย่างสง่างามใช่ไหม?
Kevin Simple

สิ่งเดียวที่ฉันจะเปลี่ยนคือการเพิ่มคำหลักคงที่ในวิธีการ ;-)
Robert S.

ต้องการแฟล็กขอบเขตคือ InRange (ตัวเลข, ลดลง, LOWER_IS_INCLUSIVE, Upperbound, UPPER_IS_EXCLUSIVE) เพื่ออนุญาตให้ <vs <= ฉันเขียนสิ่งนี้ตั้งใจที่จะเป็นคนพูดปด
William T. Mallard

56

เพียงเพิ่มเสียงที่นี่คุณสามารถสร้างวิธีการขยาย:

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
@Ben: ฉันไปที่หัวข้อซึ่งพูดว่า "อยู่ในช่วง" (ซึ่งฉันไม่คิดว่าคลุมเครือในเรื่องนั้น) แต่คุณพูดถูกในคำถามที่ว่า "ระหว่าง 1 ถึง 100" (ซึ่งก็คือ แน่นอนไม่ชัดเจน)
Adam Robinson เมื่อ

48

อย่างที่คนอื่นพูดใช้ง่าย ๆ ถ้า

คุณควรคิดถึงการสั่งซื้อ

เช่น

1 <= x && x <= 100

อ่านง่ายกว่า

x >= 1 && x <= 100

19
"ง่ายขึ้น" อยู่ในสายตาของคนดู ฉันชอบที่จะมีตัวแปรที่เป็นปัญหาทางด้านซ้ายและค่าคงที่หรือตัวแปรที่ไม่เป็นปัญหาทางด้านขวา
อดัมโรบินสัน

15
ในPerl 61 <= x <= 100คุณจะเขียน
Jordão

2
การสั่งซื้อจำนวนมากนั้นชัดเจนที่สุดในตอนแรก - แต่คุณสามารถฝึกสายตา / ใจสำหรับคำสั่งซื้ออื่น ๆ โดยเฉพาะ - ฉันชอบกลอุบายของการวางค่าคงที่ทางซ้ายเสมอ ถ้าคุณว่าคอมไพเลอร์จะบอกคุณเมื่อคุณพิมพ์แทน= ==มันไม่ได้ช่วยอะไรกับผู้ประกอบการสัมพันธ์ที่ไม่เท่าเทียมกัน - แต่มันง่ายที่จะคุ้นเคยกับการใช้มันอย่างสม่ำเสมอ
davidbak

1
ฉันต้องการเพิ่มว่าโซลูชันนี้ไม่มีประโยชน์ในทุกกรณี พิจารณาxคือการเรียกใช้ฟังก์ชันที่ซับซ้อนหรือ Linq-expression ที่ใช้เวลานาน ในกรณีนี้คุณจะทำสองครั้งซึ่งไม่ใช่สิ่งที่ดี แน่ใจว่าคุณควรเก็บค่าไว้ในตัวแปรโลคอลชั่วคราว แต่มีบางกรณี (เช่นในคำสั่ง else-if-statement) ซึ่งคุณต้องการเรียกใช้ฟังก์ชันหลังจากที่อื่น ๆ ด้วยตัวแปรชั่วคราวคุณต้องเรียกพวกเขาก่อนหน้านี้ วิธีการขยาย (กล่าวถึงในคำตอบอื่น ๆ ) เป็นทางออกที่ดีที่สุดในกรณีเหล่านั้น
Robert S.

4
ฉันชอบลำดับบรรทัดจำนวนมากและสำหรับการทดสอบประกอบเช่น x <10 || 20 <x สำหรับฉันมันตะโกน "x อยู่นอกช่วง 10 - 20"
William T. Mallard

44

ในรหัสการผลิตฉันจะเขียน

1 <= x && x <= 100

ง่ายต่อการเข้าใจและอ่านง่าย


นี่คือวิธีการที่ชาญฉลาดที่ลดจำนวนการเปรียบเทียบจากสองเป็นหนึ่งโดยใช้คณิตศาสตร์บางอย่าง แนวคิดคือปัจจัยหนึ่งในสองข้อนี้กลายเป็นลบหากจำนวนนั้นอยู่นอกช่วงและศูนย์หากจำนวนนั้นเท่ากับหนึ่งในขอบเขต:

หากรวมอยู่ในขอบเขต:

(x - 1) * (100 - x) >= 0

หรือ

(x - min) * (max - x) >= 0

หากขอบเขตเป็นเอกสิทธิ์:

(x - 1) * (100 - x) > 0

หรือ

(x - min) * (max - x) > 0

3
ตามมาตรฐานของฉันโดยวิธีการแก้ปัญหาที่งดงามที่สุดที่น่าสนใจคือสำหรับฉันมันก็ดูเหมือนว่าจะทำงานได้เร็วกว่าการตรวจสอบทั้งสองของนิพจน์ที่บอกว่ามันดูเหมือนไม่สอดคล้องกันมากขึ้น ถ้ามีงานวิจัยใดที่ทำเร็วกว่ากัน
Thomas Lindvall

3
ทดสอบโซลูชันของคุณบน javascript และแม่นยำด้วยตัวเลขทศนิยมถึง 14 ทศนิยม มันเป็นข้อมูลโค้ดที่ดีมาก มันจะเพิ่มคุณอีกสามเท่าถ้าทำได้
rubbyrubber

4
แม้ว่าจะมีปัญหาเล็กน้อยหากมีการบวกจำนวนมากเข้าด้วยกันก็สามารถล้นได้! XD คุณอาจต้องจำไว้เมื่อเขียนรหัสของคุณ
BrainStorm.exe

2
คำถามนี้ถามถึงความสง่างามและมีความเป็นวิชาการมากกว่าคุณค่าเชิงปฏิบัติ ส่วนตัวฉันจะใช้1 < x && x < 100รหัสง่าย ๆในการผลิต ง่ายต่อการเข้าใจ
Olivier Jacot-Descombes

1
สำหรับผู้ที่กังวลเกี่ยวกับประสิทธิภาพ1 < x & x < 100(ไม่มี && ลัดวงจร) สั่งคอมไพเลอร์ว่าสามารถประเมินได้เสมอx < 100ไม่ว่าผลลัพธ์จะเป็น1 < xเช่นไร น่าแปลก (เนื่องจากการคาดคะเนสาขา) มันเร็วกว่าที่จะทำสิ่งนี้ง่ายกว่าการข้ามบางครั้ง
Tom Leys

23

ฉันเสนอสิ่งนี้:

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)

ง่ายต่อการอ่าน (ใกล้เคียงกับภาษามนุษย์) และใช้งานได้กับประเภทที่เปรียบเทียบกันได้ (จำนวนเต็มสองเท่าประเภทที่กำหนดเอง ... )

การมีโค้ดที่อ่านง่ายเป็นสิ่งสำคัญเพราะผู้พัฒนาจะไม่เสีย "วงจรสมอง" เพื่อทำความเข้าใจ ในช่วงการเข้ารหัสที่ยาวนานทำให้สมองรอบทำให้ผู้พัฒนาเหนื่อยล้าเร็วขึ้นและมีแนวโน้มที่จะเกิดข้อผิดพลาด


3
ฉันจะลดความซับซ้อนมากยิ่งขึ้นโดยใช้คำว่าระหว่างและมีธงบูลีนเพื่อตรวจสอบว่ารวมหรือไม่
Ben

ดี. ง่ายต่อการเข้าใจ ฉันเปลี่ยนชื่อIsInRange. I'm not that keen on Ben's inclusive boolean as that requires a few more brain cycles. It has the advantage that it can be used in any class that that implements IComparer. This is in my Extensions now along with LiesWithin / LiesInside. Just can't decide which. NotOutside จะทำงานได้ แต่ฉันไม่ชอบเงื่อนไขเชิงลบ
Paulustrious

21

ด้วยวิธีการขยายที่ไม่เหมาะสมเราสามารถรับโซลูชัน "หรูหรา" ต่อไปนี้:

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!");
        }
    }
}

ชอบวิธีแก้ปัญหา! Btw เพื่อสนับสนุนการรวมสร้างด้วยค่า:enum Inclusive , , และผ่านสำหรับพารามิเตอร์ของฟังก์ชันอีกหนึ่งชนิดที่มีค่าเริ่มต้นให้ปรับปรุงการทำงานของร่างกายในการจับ, , ค่า :)LowerUpperAllInenum InclusiveInclusive.AllToAllLowerUpper
Nikita

7

หากนี่เป็นเรื่องง่ายifคุณก็แค่ต้องการ หากสิ่งนี้เกิดขึ้นในหลาย ๆ ที่คุณอาจต้องการพิจารณาสองสิ่งนี้:

  • PostSharp ตกแต่งเมธอดด้วยแอ็ตทริบิวต์ที่ 'โค้ด' ลงในเมธอดหลังการคอมไพล์ ฉันไม่รู้แน่ชัด แต่ฉันสามารถจินตนาการได้ว่าสามารถใช้กับสิ่งนี้ได้

สิ่งที่ต้องการ:

[Between("parameter", 0, 100)]
public void Foo(int parameter)
{
}
  • สัญญารหัส มีข้อได้เปรียบที่สามารถตรวจสอบข้อ จำกัด ณ เวลารวบรวมโดยการตรวจสอบโค้ดของคุณและสถานที่ที่ใช้รหัสของคุณ

+1 สำหรับสัญญารหัส มันเป็นเรื่องเฉพาะสำหรับการตรวจสอบความถูกต้องของพารามิเตอร์ แต่เป็นกรณีการใช้งานบ่อยและการตรวจสอบแบบสแตติกมีศักยภาพที่จะเป็นประโยชน์อย่างยิ่ง
Dan Bryant


5

การใช้&&นิพจน์เพื่อเข้าร่วมการเปรียบเทียบสองรายการเป็นวิธีที่ยอดเยี่ยมที่สุดในการทำเช่นนี้ หากคุณลองใช้วิธีการขยายแบบแฟนซีและเช่นนั้นคุณจะพบกับคำถามว่าจะรวมขอบด้านบนขอบล่างหรือทั้งสองอย่าง เมื่อคุณเริ่มเพิ่มตัวแปรเพิ่มเติมหรือเปลี่ยนชื่อส่วนขยายเพื่อระบุว่ามีอะไรรวมอยู่รหัสของคุณจะยาวขึ้นและอ่านยากขึ้น (สำหรับโปรแกรมเมอร์ส่วนใหญ่) นอกจากนี้เครื่องมือเช่น Resharper จะเตือนคุณหากการเปรียบเทียบของคุณไม่สมเหตุสมผล ( number > 100 && number < 1) ซึ่งจะไม่ทำถ้าคุณใช้วิธีการ ('i.IsBetween (100, 1)')

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

Contract.Requires(number > 1 && number < 100)

นี่เป็นวิธีที่ฉลาดกว่าif(...) throw new Exception(...)และคุณยังสามารถรับคำเตือนเวลาคอมไพล์ได้หากมีคนพยายามโทรหาวิธีการของคุณโดยไม่ทำให้หมายเลขนั้นอยู่ในขอบเขตก่อน


2
FYI ตัววิเคราะห์แบบคงที่ของสัญญามีความสุขมากขึ้นเมื่อข้อ จำกัด ขอบเขตล่างและขอบเขตบนถูกแบ่งออกเป็นคำสั่งต้องแยกต่างหาก
Dan Bryant

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

2

ถ้าคุณต้องการที่จะเขียนรหัสมากกว่าง่าย ๆ บางทีคุณสามารถ: สร้างวิธีการขยายที่เรียกว่า IsB Between

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.Approicient) ซึ่งสร้างขึ้นในสภาพแวดล้อมนั้น ในทางปฏิบัติโดยทั่วไปคุณจะต้องพัฒนาแนวคิดของคุณเองอย่างรอบคอบว่าการเปรียบเทียบนั้นหมายถึงคอมพิวเตอร์ที่ใช้ตัวเลขจริงหรือไม่สำหรับประเภทของสถานการณ์ที่คุณเป็นวิศวกรรม (อย่าพูดถึงว่าถ้าคุณทำอะไรเช่นบางทีผู้ควบคุมผู้ควบคุม PID หรือคนอื่น ๆ ปัญหาทั้งหมดกลายเป็นเรื่องสำคัญและยากมากมันจะกลายเป็นธรรมชาติของโครงการ

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

1
แทนที่ if and else ด้วยreturn (value >= Min && value <= Max);
AeroX

วิธีที่ยอดเยี่ยมในการเขียนการเปรียบเทียบคือ "ตามลำดับตรรกะ ... " ถ้า (Min <= value && value <= Max) นั่นสวยกว่ามาก
Fattie

2
เพิ่มเติมเกี่ยวกับคำถามนี้มันจึงน่าแปลกใจที่ไม่มีใครได้กล่าวถึงปัญหาสำคัญในโครงการโลกแห่งความจริง (โดยเฉพาะอย่างยิ่งถ้าคุณเป็นวิศวกรเกม) คือการที่คุณต้องจัดการกับปัญหาประมาณ ในซอฟต์แวร์โลกแห่งความจริงคุณไม่เคย "แค่ทำการเปรียบเทียบ" (ไม่ว่าจะเป็นความเสมอภาคหรือ <,>) คุณต้องพิจารณาและจัดการกับปัญหาข้อผิดพลาดขึ้นอยู่กับสถานการณ์ในมือ ฉันแก้ไขในภาคผนวกของคำตอบนี้ (คำตอบเดียวที่ถูกต้องที่นี่!) เนื่องจากไม่อนุญาตให้ตอบเพิ่มเติม
Fattie

ขอบคุณสำหรับการสังเกตและภาคผนวก
Tony

2

สาเหตุที่ฉันไม่ได้คิดค้นคำตอบอื่น ๆ ทั้งหมดนี่เป็นเพียงการดำเนินการของฉัน:

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

2

แก้ไข: คำตอบใหม่ให้ ฉันเพิ่งเริ่มใช้ C # เมื่อฉันเขียนคำตอบแรกสำหรับคำถามนี้และในอดีตเมื่อตอนนี้ฉันรู้ว่า "ทางออก" ของฉันคือ / ไร้เดียงสาและไม่มีประสิทธิภาพ

คำตอบเดิมของฉัน: ฉันจะไปกับรุ่นที่ง่ายขึ้น:

if(Enumerable.Range(1,100).Contains(intInQuestion)) { ...DoStuff; }

วิธีที่ดีกว่า

เมื่อฉันไม่ได้เห็นวิธีแก้ปัญหาอื่นใดที่มีประสิทธิภาพมากกว่า (จากการทดสอบของฉันอย่างน้อย) ฉันจะให้มันอีก

วิธีใหม่และดีกว่าที่ใช้กับช่วงลบ :

// Returns true if x is in range [min..max], else false 
bool inRange(int x, int min=1, int max=100) => ((x - max)*(x - min) <= 0);

สามารถใช้กับทั้งช่วงบวกและลบและค่าเริ่มต้นเป็นช่วง

1..100 (รวม) และใช้xเป็นหมายเลขเพื่อตรวจสอบตามด้วยช่วงตัวเลือกที่กำหนดโดยminและmaxและ

การเพิ่มตัวอย่างสำหรับการวัดที่ดี

ตัวอย่างที่ 1:

// Returns true if x is in range [min..max], else false 
bool inRange(int x, int min=1, int max=100) => ((x - max)*(x - min) <= 0);

Console.WriteLine(inRange(25));
Console.WriteLine(inRange(1));
Console.WriteLine(inRange(100));
Console.WriteLine(inRange(25, 30, 150));
Console.WriteLine(inRange(-25, -50, 0));

ผลตอบแทน:

True
True
True
False
True

ตัวอย่างที่ 2: การใช้รายการของ ints แบบสุ่มระหว่าง 1 ถึง 150

// Returns true if x is in range [min..max], else false 
bool inRange(int x, int min=1, int max=100) => ((x - max)*(x - min) <= 0);

// Generate 100000 ints between 1 and 150
var intsToCheck = new List<int>();
var randGen = new Random();
for(int i = 0; i < 100000; ++i){
    intsToCheck.Add(randGen.Next(150) + 1);
}

var counter = 0;
foreach(int n in intsToCheck) {
    if(inRange(n)) ++counter;
}

Console.WriteLine("{0} ints found in range 1..100", counter);

ผลตอบแทน:

66660 ints found in range 1..100

เวลาดำเนินการ: 0.016 วินาที


ใช่ฉันกำลังแสดงความคิดเห็นในความคิดเห็นต่อคำตอบของฉันจาก 2013 :) @RyanTheLeach: คำตอบของฉันสำหรับคำถามนี้แตกต่างจากคำตอบ "ยอมรับ" ในขณะนี้อย่างไร? ฉันรู้ว่ามันไม่ได้เป็นเส้นทางลัดที่มีประสิทธิภาพที่สุด แต่ "แย่มาก"? การจัดสรรและวนซ้ำ 100 ints นั้นแย่แค่ไหน? ในปี 1950 อาจไม่เป็นที่ยอมรับของสังคม แต่ ...
cseder

@ RyanTheLeach ฉันไม่โทษคุณ ... ฉันได้อัปเดตคำตอบของฉันดังนั้นถ้าคุณรู้เกี่ยวกับวิธีการแก้ปัญหาที่มีประสิทธิภาพมากขึ้นโปรดอธิบายอย่างละเอียด!
cseder

1
ฉันได้ลบความคิดเห็นของฉันเนื่องจากพวกเขาไม่ได้ยืนอยู่แล้ว ขอบคุณสำหรับการแก้ไขดูเหมือนว่าโอเค
Ryan The Leach

1

บิดใหม่ในรายการโปรดเก่า:

public bool IsWithinRange(int number, int topOfRange, int bottomOfRange, bool includeBoundaries) {
    if (includeBoundaries)
        return number <= topOfRange && number >= bottomOfRange;
    return number < topOfRange && number > bottomOfRange;
}

3
จริงๆแล้วมีสี่กรณีรวม / รวมรวม / พิเศษพิเศษ / รวมและพิเศษ / พิเศษ
William T. Mallard

1

ใน C if ((unsigned)(value-min) <= (max-min)) ...ถ้าประสิทธิภาพเวลาเป็นสิ่งสำคัญและล้นจำนวนเต็มจะตัดใครจะทำ หาก 'max' และ 'min' เป็นตัวแปรอิสระการลบพิเศษสำหรับ (max-min) จะเสียเวลา แต่ถ้านิพจน์นั้นสามารถคำนวณล่วงหน้าได้ในเวลาคอมไพล์หรือถ้าสามารถคำนวณได้ครั้งเดียวในการทดสอบหลายครั้ง ตัวเลขกับช่วงเดียวกันนิพจน์ข้างต้นอาจคำนวณได้อย่างมีประสิทธิภาพแม้ในกรณีที่ค่าอยู่ในช่วง (ถ้าเศษของจำนวนมากจะต่ำกว่าช่วงที่ถูกต้องมันอาจจะเร็วกว่าที่จะใช้if ((value >= min) && (value <= max)) ...เพราะมันจะออกก่อนถ้าค่า น้อยกว่านาที)

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


1

แล้วเรื่องแบบนี้ล่ะ?

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

1

ฉันจะทำวัตถุช่วงบางสิ่งเช่นนี้:

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

ด้วยวิธีนี้คุณสามารถนำมาใช้ซ้ำสำหรับประเภทอื่นได้


Rangeวัตถุของคุณจำเป็นต้องใช้CompareToวิธีการเปรียบเทียบรายการไม่ใช่ตัว<ดำเนินการ
Servy

คุณพูดถูกแม้ว่าการใช้ IComparable คุณควรแทนที่โอเปอเรเตอร์ (อย่างน้อยนั่นคือสิ่งที่การวิเคราะห์รหัส VS ของฉันกำลังพูด) หมายความว่า <ใช้งานได้ แม้ว่าฉันอาจจะผิด แต่ฉันไม่มีประสบการณ์มากนักและนี่เป็นคำตอบแรกของฉันเกี่ยวกับ SO
IEatBagels

ไม่คอมไพเลอร์ของคุณจะไม่บอกว่าใช้งานได้ สิ่งนี้จะไม่รวบรวม มันสมเหตุสมผลอย่างยิ่งสำหรับวัตถุที่จะใช้งานIComparableและไม่เกินตัว<ดำเนินการ
Servy

1

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

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

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

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

ตอนนี้อาจไกลเกินกว่าที่คุณต้องการ แต่มันทำให้คุณไม่สามารถปัดเศษได้ตลอดเวลาและพยายามจดจำว่าค่านั้นถูกปัดเศษและตำแหน่งใด หากคุณต้องการคุณสามารถขยายสิ่งนี้เพื่อทำงานกับ epsilon ใด ๆ และอนุญาตให้ epsilon ของคุณเปลี่ยนได้


1
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.IsB Between (100,122);

var result = 5.IsB Between (100,120);

var result = 8.0.IsB ระหว่าง (1.2,9.6);


1

หากคุณกังวลเกี่ยวกับความคิดเห็นโดย @Daap กับคำตอบที่ยอมรับและสามารถส่งผ่านค่าได้เพียงครั้งเดียวคุณสามารถลองหนึ่งในวิธีต่อไปนี้

bool TestRangeDistance (int numberToCheck, int bottom, int distance)
{
  return (numberToCheck >= bottom && numberToCheck <= bottom+distance);
}

//var t = TestRangeDistance(10, somelist.Count()-5, 10);

หรือ

bool TestRangeMargin (int numberToCheck, int target, int margin)
{
  return (numberToCheck >= target-margin && numberToCheck <= target+margin);
}

//var t = TestRangeMargin(10, somelist.Count(), 5);

1

เกี่ยวกับความสง่างามสิ่งที่ใกล้เคียงที่สุดกับสัญกรณ์ทางคณิตศาสตร์ ( a <= x <= b ) ช่วยเพิ่มความสามารถในการอ่าน:

public static bool IsBetween(this int value, int min, int max)
{
    return min <= value && value <= max;
}

สำหรับภาพประกอบเพิ่มเติม:

public static bool IsOutside(this int value, int min, int max)
{
    return value < min || max < value;
}

0

ฉันกำลังมองหาวิธีที่สง่างามที่จะทำในสิ่งที่ขอบเขตอาจจะเปลี่ยน (เช่น. ไม่แน่ใจว่าลำดับที่อยู่ในค่า)

สิ่งนี้จะทำงานกับ C # รุ่นที่ใหม่กว่าโดยที่มี:?

bool ValueWithinBounds(float val, float bounds1, float bounds2)
{
    return bounds1 >= bounds2 ?
      val <= bounds1 && val >= bounds2 : 
      val <= bounds2 && val >= bounds1;
}

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


0

สง่างามเพราะไม่ต้องการให้คุณพิจารณาว่าค่าขอบเขตสองค่าใดที่มากกว่าค่าแรก นอกจากนี้ยังไม่มีสาขา

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

& + | เป็นตัวดำเนินการระดับบิต
nelsontruran

0

ฉันไม่รู้ แต่ฉันใช้วิธีนี้:

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

โปรดแสดงตัวอย่างการใช้งานด้านล่างบล็อครหัสซึ่งจะช่วยให้ OP ทราบว่ามันเหมาะกับจุดประสงค์ของเขาหรือไม่
Gabriel Balsa Cantú

0

นี่เป็นวิธีการขยายที่สามารถช่วยได้

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

0

ถ้าเป็นการตรวจสอบความถูกต้องของพารามิเตอร์วิธีการแก้ปัญหาจะไม่มีวิธีใดที่ทำให้อาร์กิวเมนต์ ArgumentOutOfRangeException ง่ายขึ้นและอนุญาตการกำหนดค่าที่ง่าย / เหมาะสมของค่ารวม / เอกสิทธิ์ขั้นต่ำ / พิเศษ / สูงสุด

ใช้แบบนี้

public void Start(int pos)
{
    pos.CheckRange(nameof(pos), min: 0);

    if (pos.IsInRange(max: 100, maxInclusive: false))
    {
        // ...
    }
}

ฉันเพิ่งเขียนฟังก์ชั่นที่สวยงามเหล่านี้ นอกจากนี้ยังมีข้อดีของการไม่มีการแตกแขนง (ถ้ามี) สำหรับค่าที่ถูกต้อง ส่วนที่ยากที่สุดคือการสร้างข้อความข้อยกเว้นที่เหมาะสม

/// <summary>
/// Returns whether specified value is in valid range.
/// </summary>
/// <typeparam name="T">The type of data to validate.</typeparam>
/// <param name="value">The value to validate.</param>
/// <param name="min">The minimum valid value.</param>
/// <param name="minInclusive">Whether the minimum value is valid.</param>
/// <param name="max">The maximum valid value.</param>
/// <param name="maxInclusive">Whether the maximum value is valid.</param>
/// <returns>Whether the value is within range.</returns>
public static bool IsInRange<T>(this T value, T? min = null, bool minInclusive = true, T? max = null, bool maxInclusive = true)
    where T : struct, IComparable<T>
{
    var minValid = min == null || (minInclusive && value.CompareTo(min.Value) >= 0) || (!minInclusive && value.CompareTo(min.Value) > 0);
    var maxValid = max == null || (maxInclusive && value.CompareTo(max.Value) <= 0) || (!maxInclusive && value.CompareTo(max.Value) < 0);
    return minValid && maxValid;
}

/// <summary>
/// Validates whether specified value is in valid range, and throws an exception if out of range.
/// </summary>
/// <typeparam name="T">The type of data to validate.</typeparam>
/// <param name="value">The value to validate.</param>
/// <param name="name">The name of the parameter.</param>
/// <param name="min">The minimum valid value.</param>
/// <param name="minInclusive">Whether the minimum value is valid.</param>
/// <param name="max">The maximum valid value.</param>
/// <param name="maxInclusive">Whether the maximum value is valid.</param>
/// <returns>The value if valid.</returns>
public static T CheckRange<T>(this T value, string name, T? min = null, bool minInclusive = true, T? max = null, bool maxInclusive = true)
where T : struct, IComparable<T>
{
    if (!value.IsInRange(min, minInclusive, max, maxInclusive))
    {
        if (min.HasValue && minInclusive && max.HasValue && maxInclusive)
        {
            var message = "{0} must be between {1} and {2}.";
            throw new ArgumentOutOfRangeException(name, value, message.FormatInvariant(name, min, max));
        }
        else
        {
            var messageMin = min.HasValue ? GetOpText(true, minInclusive).FormatInvariant(min) : null;
            var messageMax = max.HasValue ? GetOpText(false, maxInclusive).FormatInvariant(max) : null;
            var message = (messageMin != null && messageMax != null) ?
                "{0} must be {1} and {2}." :
                "{0} must be {1}.";
            throw new ArgumentOutOfRangeException(name, value, message.FormatInvariant(name, messageMin ?? messageMax, messageMax));
        }
    }
    return value;
}

private static string GetOpText(bool greaterThan, bool inclusive)
{
    return (greaterThan && inclusive) ? "greater than or equal to {0}" :
        greaterThan ? "greater than {0}" :
        inclusive ? "less than or equal to {0}" :
        "less than {0}";
}

public static string FormatInvariant(this string format, params object?[] args) => string.Format(CultureInfo.InvariantCulture, format, args);

-2

คุณกำลังมองหาin [1..100]? นั่นเป็นปาสกาลเท่านั้น


2
ไม่จริงมันไม่เพียง แต่ Pascal ภาษาสมัยใหม่หลายแห่งมีคุณสมบัติเช่นนี้ ใน Kotlin ตัวอย่างเช่นเรียกว่า "การจับคู่รูปแบบ" ตัวอย่าง when (number) { in 0..9 -> println("1 digit") in 10..99 -> println("2 digits") in 100..999 -> println("3 digits") }
this.myself
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.