Foreach-loop พร้อม break / return vs. while-loop ที่มี invariant และ post-condition อย่างชัดเจน


17

นี่เป็นวิธีที่นิยมมากที่สุด (ดูเหมือนฉัน) ในการตรวจสอบว่าค่าอยู่ในอาร์เรย์หรือไม่:

for (int x : array)
{
    if (x == value)
        return true;
}
return false;        

อย่างไรก็ตามในหนังสือที่ฉันอ่านเมื่อหลายปีก่อนโดยอาจ Wirth หรือ Dijkstra มันก็บอกว่าสไตล์นี้ดีกว่า (เมื่อเทียบกับ while-loop ที่มีทางออกด้านใน):

int i = 0;
while (i < array.length && array[i] != value)
    i++;
return i < array.length;

วิธีนี้เงื่อนไขการออกเพิ่มเติมกลายเป็นส่วนหนึ่งที่ชัดเจนของลูปค่าคงที่ไม่มีเงื่อนไขที่ซ่อนอยู่และออกจากภายในลูปทุกอย่างชัดเจนมากขึ้นและมากขึ้นด้วยวิธีการเขียนโปรแกรมแบบมีโครงสร้าง ฉันมักจะแนะนำนี้รูปแบบหลังเมื่อใดก็ตามที่เป็นไปได้และใช้for-loop เพียงสำทับจากไปab

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

บางทีใครบางคนสามารถให้เหตุผลที่ดีแก่คนใดคนหนึ่งในวิธีการ?

อัปเดต: นี่ไม่ใช่คำถามของจุดคืนค่าฟังก์ชัน, lambdas หรือการค้นหาองค์ประกอบในอาร์เรย์ต่อคำถาม มันเกี่ยวกับวิธีการเขียนลูปที่มีค่าคงที่ที่ซับซ้อนกว่าความไม่เท่าเทียมเพียงครั้งเดียว

อัปเดต: โอเคฉันเห็นจุดของคนที่ตอบและแสดงความคิดเห็น: ฉันผสมวง foreach ที่นี่ซึ่งตัวมันเองนั้นชัดเจนและอ่านได้มากกว่าในขณะที่วนซ้ำ ฉันไม่ควรทำอย่างนั้น แต่นี่ก็เป็นคำถามที่น่าสนใจดังนั้นเราควรปล่อยให้มันเป็นแบบนี้: foreach-loop และเงื่อนไขพิเศษภายในหรือ while-loop ที่มีค่าคงที่ loop ที่ชัดเจนและ post-condition หลังจากนั้น ดูเหมือนว่า foreach-loop ที่มีเงื่อนไขและ exit / break กำลังชนะ ฉันจะสร้างคำถามเพิ่มเติมโดยไม่มี foreach-loop (สำหรับรายการที่ลิงก์)


2
ตัวอย่างโค้ดที่อ้างถึงที่นี่ผสมปัญหาที่แตกต่างกันหลายประการ ผลตอบแทนต้น & หลาย ๆ อัน (ซึ่งสำหรับฉันไปที่ขนาดของวิธีการ (ไม่แสดง)), การค้นหาอาร์เรย์ (ซึ่งขอให้มีการอภิปรายที่เกี่ยวข้องกับ lambdas), foreach เทียบกับการจัดทำดัชนีโดยตรง ... คำถามนี้จะชัดเจนและง่ายขึ้น ตอบถ้ามันมุ่งเน้นเพียงหนึ่งในปัญหาเหล่านี้ในเวลา
Erik Eidt


1
ฉันรู้ว่าสิ่งเหล่านี้เป็นตัวอย่าง แต่มีภาษาที่มี API เพื่อจัดการกรณีการใช้งานนั้น Iecollection.contains(foo)
Berin Loritsch

2
คุณอาจต้องการค้นหาหนังสือและอ่านซ้ำตอนนี้เพื่อดูสิ่งที่หนังสือพูดจริง ๆ
Thorbjørn Ravn Andersen

1
"ดีกว่า" เป็นคำอัตนัย ที่กล่าวว่าหนึ่งสามารถบอกได้อย่างรวดเร็วว่ารุ่นแรกทำอะไร ว่ารุ่นที่สองทำสิ่งเดียวกันแน่นอนต้องใช้การตรวจสอบข้อเท็จจริง
David Hammen

คำตอบ:


19

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

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

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


56

มันง่ายมาก

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

เวอร์ชัน 'ปรับปรุง' ที่สองฉันต้องอ่านหลาย ๆ ครั้งและตรวจสอบให้แน่ใจว่าเงื่อนไขขอบทั้งหมดถูกต้อง

ZERO DOUBT มีรูปแบบการเข้ารหัสที่ดีกว่า (อันแรกดีกว่ามาก)

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

อย่างไรก็ตามในกรณีพิเศษนี้ฉันสามารถบอกคุณได้ว่าทำไมอัลกอริทึมแรกนั้นชัดเจนมากขึ้น: ฉันรู้ว่า C ++ ทำซ้ำในรูปแบบคอนเทนเนอร์ของคอนเทนเนอร์ดูเหมือนและทำอะไร ฉันทำให้เป็นเรื่องภายใน บางคน UNFAMILIAR (ไวยากรณ์ใหม่) ที่มีไวยากรณ์นั้นอาจต้องการรูปแบบที่สอง

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


4
ใหม่มีความเกี่ยวข้องเหมือนเดิมในมาตรฐานปี 2554 นอกจากนี้การสาธิตครั้งที่สองไม่ใช่ C ++
Deduplicator

วิธีการแก้ปัญหาอื่นถ้าคุณต้องการที่จะใช้เป็นจุดทางออกเดียวที่จะตั้งธงแล้วlongerLength = true return longerLength
Cullub

@Dupuplicator ทำไมการสาธิต C ++ ครั้งที่สองไม่ใช่? ฉันไม่เห็นสาเหตุหรือไม่ฉันขาดสิ่งที่ชัดเจน
Rakete1111

2
@ อาร์เรย์ Rakete1111 ดิบไม่ได้มีคุณสมบัติใด ๆ lengthเช่น ถ้ามันถูกประกาศเป็นอาร์เรย์จริงและไม่ใช่ตัวชี้พวกเขาสามารถใช้sizeofหรือถ้ามันเป็นstd::arrayฟังก์ชั่นสมาชิกที่ถูกต้องsize()คือไม่มีlengthคุณสมบัติ
IllusiveBrian

@IllusiveBrian: sizeofจะอยู่ในไบต์ ... ทั่วไปมากที่สุดนับตั้งแต่ C ++ std::size()17
Deduplicator

9
int i = 0;
while (i < array.length && array[i] != value)
    i++;
return i < array.length;

[…] ทุกอย่างชัดเจนขึ้นและมากขึ้นด้วยวิธีการเขียนโปรแกรมที่มีโครงสร้าง

ไม่มาก ตัวแปรiมีอยู่นอกขณะที่ลูปที่นี่และจึงเป็นส่วนหนึ่งของขอบเขตด้านนอกในขณะที่ (ปุนตั้งใจ) xของfor-loop มีอยู่ภายในขอบเขตของลูปเท่านั้น ขอบเขตเป็นวิธีสำคัญอย่างหนึ่งในการแนะนำโครงสร้างการเขียนโปรแกรม



1
@ruakh ฉันไม่แน่ใจว่าจะเอาอะไรไปจากความคิดเห็นของคุณ มันเจอกับสิ่งที่ค่อนข้างก้าวร้าวราวกับว่าคำตอบของฉันตรงข้ามกับสิ่งที่เขียนในหน้าวิกิ กรุณาอธิบายอย่างละเอียด
null

"การเขียนโปรแกรมแบบโครงสร้าง" เป็นคำศัพท์ทางศิลปะที่มีความหมายเฉพาะและ OP นั้นถูกต้องตามความเป็นจริงว่ารุ่น # 2 เป็นไปตามกฎของการเขียนโปรแกรมแบบมีโครงสร้างขณะที่รุ่นที่ 1 ไม่มี จากคำตอบของคุณดูเหมือนว่าคุณไม่คุ้นเคยกับคำศัพท์ทางศิลปะและตีความคำศัพท์อย่างแท้จริง ฉันไม่แน่ใจว่าทำไมความคิดเห็นของฉันถึงรุนแรงเกินไป ฉันหมายถึงมันเป็นเพียงข้อมูล
ruakh

@ruakh ฉันไม่เห็นด้วยกับรุ่นที่ 2 ว่าสอดคล้องกับกฎมากขึ้นในทุกด้านและอธิบายว่าในคำตอบของฉัน
null

คุณพูดว่า "ฉันไม่เห็นด้วย" ราวกับว่ามันเป็นเรื่องส่วนตัว แต่ก็ไม่ใช่ การย้อนกลับจากภายในลูปเป็นการละเมิดกฎของการเขียนโปรแกรมแบบโครงสร้าง ฉันแน่ใจว่าผู้ที่ชื่นชอบการเขียนโปรแกรมเชิงโครงสร้างจำนวนมากเป็นแฟนตัวยงของตัวแปรที่มีการกำหนดขอบเขตน้อยที่สุด แต่ถ้าคุณลดขอบเขตของตัวแปรโดยออกจากการเขียนโปรแกรมเชิงโครงสร้างแล้วคุณได้ออกจากการเขียนโปรแกรมเชิงโครงสร้าง ที่.
ruakh

2

ลูปทั้งสองมีความหมายต่างกัน:

  • การวนรอบแรกเพียงตอบคำถามใช่ / ไม่ใช่อย่างง่าย: "อาร์เรย์มีวัตถุที่ฉันกำลังค้นหาหรือไม่" มันทำในลักษณะที่สั้นที่สุด

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

เนื่องจากคำตอบของคำถามที่สองนั้นให้ข้อมูลมากกว่าคำตอบของคำถามแรกอย่างเคร่งครัดคุณสามารถเลือกที่จะตอบคำถามที่สองแล้วรับคำตอบของคำถามแรก นั่นคือสิ่งที่สายreturn i < array.length;ทำอยู่แล้ว

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

  • การใช้ตัวแปรตัวแรกของลูปนั้นใช้ได้
  • การเปลี่ยนตัวแปรตัวแรกให้ตั้งค่าboolตัวแปรและตัวแบ่งก็ปรับได้เช่นกัน (หลีกเลี่ยงreturnข้อความที่สองคำตอบมีให้ในตัวแปรแทนที่จะส่งคืนฟังก์ชัน)
  • ใช้งานstd::findได้ดี (ใช้รหัสซ้ำ!)
  • อย่างไรก็ตามการเขียนรหัสการค้นหาอย่างชัดเจนแล้วลดคำตอบให้กับ a boolไม่ใช่

จะดีถ้า downvoters จะแสดงความคิดเห็น ...
cmaster - reinstate monica

2

ฉันจะแนะนำตัวเลือกที่สามโดยสิ้นเชิง:

return array.find(value);

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

เปรียบเทียบการแปลงอาร์เรย์หนึ่งให้เป็นอีกอาร์เรย์หนึ่งด้วย for for loop:

int[] doubledArray = new int[array.length];
for (int i = 0; i < array.length; i++) {
  doubledArray[i] = array[i] * 2;
}

และการใช้mapฟังก์ชันสไตล์ JavaScript :

array.map((value) => value * 2);

หรือรวมอาเรย์:

int sum = 0;
for (int i = 0; i < array.length; i++) {
  sum += array[i];
}

เมื่อเทียบกับ:

array.reduce(
  (sum, nextValue) => sum + nextValue,
  0
);

ใช้เวลานานแค่ไหนที่คุณจะเข้าใจสิ่งนี้

int[] newArray = new int[array.length];
int numValuesAdded = 0;

for (int i = 0; i < array.length; i++) {
  if (array[i] >= 0) {
    newArray[numValuesAdded] = array[i];
    numValuesAdded++;
  }
}

กับ

array.filter((value) => (value >= 0));

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

ภาษาสมัยใหม่ส่วนใหญ่รวมถึงJavaScript , Ruby , C #และJavaใช้รูปแบบของการโต้ตอบการทำงานกับอาร์เรย์และคอลเลกชันที่คล้ายกันนี้

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


2
แนะนำarray.findbegs array.findคำถามที่เราแล้วมีการหารือเกี่ยวกับวิธีที่ดีที่สุดในการดำเนินการ นอกจากว่าคุณกำลังใช้ฮาร์ดแวร์ที่มีการfindทำงานในตัวเราต้องเขียนลูปที่นั่น
Barmar

2
@Barmar ฉันไม่เห็นด้วย ตามที่ฉันระบุไว้ในคำตอบของฉันภาษาที่ใช้งานหนักจำนวนมากมีฟังก์ชั่นเช่นfindในไลบรารีมาตรฐานของพวกเขา ไม่ต้องสงสัยเลยว่าห้องสมุดเหล่านี้มีการใช้งานfindและเครือข่ายที่ใช้สำหรับลูป แต่นั่นเป็นสิ่งที่ฟังก์ชั่นที่ดีทำ: มันสรุปรายละเอียดทางเทคนิคออกไปจากผู้บริโภคของฟังก์ชั่นทำให้โปรแกรมเมอร์นั้นไม่จำเป็นต้องคิดถึง ดังนั้นแม้ว่าfindจะมีการนำมาใช้กับลูป for มันก็ยังช่วยให้โค้ดอ่านได้ง่ายขึ้นและเนื่องจากเป็นบ่อยในไลบรารี่มาตรฐานการใช้มันจึงไม่มีค่าใช้จ่ายหรือความเสี่ยงที่มีความหมาย
เควิน

4
แต่วิศวกรซอฟต์แวร์จะต้องนำไลบรารี่เหล่านี้ไปใช้ หลักการทางวิศวกรรมซอฟต์แวร์เดียวกันไม่ได้นำไปใช้กับผู้เขียนห้องสมุดในฐานะโปรแกรมเมอร์แอปพลิเคชันหรือไม่ คำถามเกี่ยวกับการเขียนลูปโดยทั่วไปไม่ใช่วิธีที่ดีที่สุดในการค้นหาองค์ประกอบอาเรย์ในภาษาใดภาษาหนึ่ง
Barmar

4
เพื่อให้เป็นอีกวิธีหนึ่งการค้นหาองค์ประกอบอาร์เรย์เป็นเพียงตัวอย่างง่ายๆที่เขาใช้ในการสาธิตเทคนิคการวนซ้ำที่แตกต่างกัน
Barmar

-2

ทุกอย่างเดือดลงไปอย่างแม่นยำสิ่งที่มีความหมายโดย 'ดีกว่า' สำหรับโปรแกรมเมอร์ที่ใช้งานจริงมันหมายถึงมีประสิทธิภาพ - เช่นในกรณีนี้การออกโดยตรงจากลูปจะหลีกเลี่ยงการเปรียบเทียบพิเศษหนึ่งครั้งและการส่งคืนบูลีนคงที่จะหลีกเลี่ยงการเปรียบเทียบซ้ำ นี้ช่วยประหยัดรอบ Dijkstra เป็นกังวลมากขึ้นกับการทำรหัสที่เป็นเรื่องง่ายที่จะพิสูจน์ความถูกต้อง [ดูเหมือนว่าสำหรับฉันแล้วการศึกษา CS ในยุโรปนั้นใช้ 'การพิสูจน์ความถูกต้องของรหัส' อย่างจริงจังมากกว่าการศึกษา CS ในสหรัฐอเมริกาซึ่งกองกำลังทางเศรษฐกิจมีแนวโน้มที่จะควบคุมการเขียนโค้ด]


3
PMar ประสิทธิภาพทั้งสองลูปนั้นเทียบเท่ากันมากทีเดียว - ทั้งคู่มีการเปรียบเทียบสองแบบ
Danila Piatov

1
ถ้าใครสนใจเรื่องประสิทธิภาพจริงๆจะใช้อัลกอริทึมที่เร็วกว่า เช่นเรียงลำดับอาร์เรย์และทำการค้นหาแบบไบนารีหรือใช้ Hashtable
949300

Danila - คุณไม่รู้ว่าโครงสร้างข้อมูลอยู่เบื้องหลังอะไร ตัววนซ้ำเร็วเสมอ การเข้าถึงดัชนีสามารถเป็นเวลาเชิงเส้นและความยาวไม่จำเป็นต้องมีอยู่
gnasher729
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.