วิธีการเขียนลูปที่ถูกต้อง?


65

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

/**
 * Inserts the given value in proper position in the sorted subarray i.e. 
 * array[0...rightIndex] is the sorted subarray, on inserting a new value 
 * our new sorted subarray becomes array[0...rightIndex+1].
 * @param array The whole array whose initial elements [0...rightIndex] are 
 * sorted.
 * @param rightIndex The index till which sub array is sorted.
 * @param value The value to be inserted into sorted sub array.
 */
function insert(array, rightIndex, value) {
    for(var j = rightIndex; j >= 0 && array[j] > value; j--) {
        array[j + 1] = array[j];
    }   
    array[j + 1] = value; 
};

ข้อผิดพลาดที่ฉันทำตอนแรกคือ:

  1. แทนที่จะเป็น j> = 0 ฉันเก็บมันไว้เป็น j> 0
  2. สับสนว่าอาร์เรย์ [j + 1] = ค่าหรืออาร์เรย์ [j] = ค่า

เครื่องมือ / แบบจำลองทางจิตเพื่อหลีกเลี่ยงความผิดพลาดดังกล่าวคืออะไร?


6
ภายใต้สถานการณ์ใดที่คุณเชื่อว่าj >= 0เป็นความผิดพลาด? ฉันจะมีมากขึ้นระวังความจริงที่ว่าคุณกำลังเข้าถึงarray[j]และโดยไม่ต้องตรวจสอบแรกที่array[j + 1] array.length > (j + 1)
Ben Cottrell

5
คล้ายกับสิ่งที่ @LightnessRacesinOrbit กล่าวว่าคุณมีแนวโน้มที่จะแก้ปัญหาที่ได้รับการแก้ไขแล้ว โดยทั่วไปแล้วการวนลูปใด ๆ ที่คุณต้องทำในโครงสร้างข้อมูลมีอยู่แล้วในโมดูลหลักหรือคลาส (บนArray.prototypeในตัวอย่างของ JS) สิ่งนี้จะป้องกันคุณจากการเผชิญหน้ากับเงื่อนไขของขอบเนื่องจากบางอย่างเช่นการmapทำงานในอาร์เรย์ทั้งหมด คุณสามารถแก้ปัญหาข้างต้นโดยใช้ชิ้นและ concat เพื่อหลีกเลี่ยงการวนซ้ำกัน: codepen.io/anon/pen/ZWovdg?editors=0012วิธีที่ถูกต้องที่สุดในการเขียนลูปคือไม่ต้องเขียนมันเลย
Jed Schneider

13
ที่จริงแล้วไปข้างหน้าและแก้ปัญหาที่แก้ไขแล้ว มันเรียกว่าการปฏิบัติ ไม่ต้องกังวลกับการเผยแพร่ นั่นคือถ้าคุณไม่หาวิธีในการปรับปรุงการแก้ปัญหา ที่กล่าวว่าการปรับแต่งล้อเกี่ยวข้องกับมากกว่าล้อ มันเกี่ยวข้องกับระบบควบคุมคุณภาพล้อทั้งหมดและการสนับสนุนลูกค้า ถึงกระนั้นขอบแบบกำหนดเองก็ดี
candied_orange

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

5
โดยทั่วไปเมื่อต้องรับมือกับลูปและดัชนีคุณควรเรียนรู้ว่าดัชนีชี้ไปที่องค์ประกอบต่าง ๆและทำความคุ้นเคยกับช่วงเวลาครึ่งเปิด (จริง ๆ แล้วมันเป็นสองแนวคิดที่เหมือนกัน) เมื่อคุณได้รับข้อเท็จจริงเหล่านี้แล้วการวนหัว / การทำดัชนีส่วนใหญ่จะหายไปอย่างสมบูรณ์
Matteo Italia

คำตอบ:


208

ทดสอบ

ไม่ทดสอบอย่างจริงจัง

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

ง่าย ๆ เข้าไว้

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

ปิดทีละอัน

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

ความคิดเห็น

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

ความคิดเห็นที่ดีที่สุดคือชื่อที่ดี

หากคุณสามารถพูดทุกสิ่งที่คุณต้องการพูดด้วยชื่อที่ดีอย่าพูดอีกครั้งด้วยความคิดเห็น

นามธรรม

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

ชื่อสั้น ๆ

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

ชื่อยาว ๆ

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

ช่องว่าง

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

วนรอบโครงสร้าง

เรียนรู้และทบทวนโครงสร้างลูปในภาษาของคุณ การดูไฮไลต์ดีบั๊กเกอร์for(;;)วนซ้ำ สามารถให้คำแนะนำได้มาก เรียนรู้ทุกรูปแบบ while, do while, ,while(true) for eachใช้สิ่งที่ง่ายที่สุดที่คุณสามารถทำได้ เงยหน้าขึ้นมองpriming ปั๊ม เรียนรู้สิ่งที่breakและcontinueทำอย่างไรถ้าคุณมีพวกเขา ทราบความแตกต่างระหว่างและc++ ++cอย่ากลัวที่จะกลับมาเร็วเท่าที่คุณจะปิดทุกอย่างที่จำเป็นต้องปิด สุดท้ายบล็อกหรือบางสิ่งบางอย่างโดยเฉพาะอย่างยิ่งที่เครื่องหมายมันปิดอัตโนมัติเมื่อคุณเปิด: การใช้คำสั่ง / ลองกับทรัพยากร

ทางเลือกวนซ้ำ

ปล่อยให้อย่างอื่นทำลูปถ้าคุณทำได้ ง่ายต่อสายตาและดีบั๊กแล้ว เหล่านี้มาในหลายรูปแบบ: คอลเลกชันหรือลำธารที่อนุญาตmap(), reduce(), foreach()และวิธีการอื่น ๆ เช่นที่ใช้แลมบ์ดา Arrays.fill()มองหาฟังก์ชั่นพิเศษเช่น นอกจากนี้ยังมีการเรียกซ้ำ แต่คาดหวังว่าจะทำให้สิ่งต่าง ๆ เป็นเรื่องง่ายในกรณีพิเศษ โดยทั่วไปจะไม่ใช้การเรียกซ้ำจนกว่าคุณจะเห็นสิ่งที่เป็นทางเลือก

โอ้และทดสอบ

ทดสอบทดสอบทดสอบ

ฉันพูดถึงการทดสอบหรือไม่

มีอีกอย่างหนึ่งคือ จำไม่ได้ เริ่มต้นด้วย T ...


36
คำตอบที่ดี แต่บางทีคุณควรพูดถึงการทดสอบ หนึ่งจะจัดการกับวงวนไม่สิ้นสุดในการทดสอบหน่วยได้อย่างไร การวนซ้ำไม่ทำลายการทดสอบหรือไม่
GameAlchemist

139
@GameAlchemist นั่นคือการทดสอบพิซซ่า หากรหัสของฉันไม่หยุดทำงานในเวลาที่ฉันต้องทำพิซซ่าฉันเริ่มสงสัยว่ามีบางอย่างผิดปกติ แน่นอนว่ามันไม่ใช่วิธีแก้ปัญหาการหยุดชะงักของอลันทัวริง แต่อย่างน้อยฉันก็ได้รับพิซซ่าเป็นข้อตกลง
candied_orange

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

15
@CodeYogi เพื่อนที่ครบกำหนดเครดิตให้กับ TDD แต่การทดสอบ >> TDD การส่งออกค่าสามารถทดสอบได้รับตาที่สองในรหัสของคุณคือการทดสอบ (คุณสามารถทำเป็นระเบียบนี้เป็นการตรวจสอบรหัส แต่ฉันมักจะคว้าใครสักคนสำหรับการสนทนา 5 นาที) การทดสอบคือโอกาสใด ๆ ที่คุณแสดงเจตนาที่จะล้มเหลว นรกคุณสามารถทดสอบรหัสของคุณโดยการพูดคุยผ่านความคิดกับแม่ของคุณ ฉันพบข้อบกพร่องในรหัสของฉันเมื่อจ้องมองที่กระเบื้องในห้องอาบน้ำ TDD เป็นระเบียบวินัยที่มีประสิทธิภาพซึ่งคุณไม่พบในร้านค้าทุกแห่ง ฉันไม่เคยเขียนรหัสที่ไหนก็ตามที่ผู้คนไม่ได้ทดสอบ
candied_orange

12
ฉันได้รับการเข้ารหัสและทดสอบหลายปีและหลายปีก่อนที่ฉันจะเคยได้ยินเกี่ยวกับ TDD เป็นเพียงตอนนี้ที่ฉันตระหนักถึงความสัมพันธ์ของปีที่ผ่านมากับปีที่ใช้รหัสในขณะที่สวมใส่กางเกงไม่มี
candied_orange

72

เมื่อการเขียนโปรแกรมมันมีประโยชน์ที่จะคิดว่า:

และเมื่อสำรวจอาณาเขตจดที่แผนที่ (เช่นการเล่นกลกับดัชนี) มันอาจจะมากมากที่มีประโยชน์ไม่เพียงแค่คิดเกี่ยวกับบรรดา แต่จริง ๆ แล้วทำให้พวกเขาอย่างชัดเจนในรหัสที่มีการยืนยัน

ลองใช้รหัสเดิมของคุณ:

/**
 * Inserts the given value in proper position in the sorted subarray i.e. 
 * array[0...rightIndex] is the sorted subarray, on inserting a new value 
 * our new sorted subarray becomes array[0...rightIndex+1].
 * @param array The whole array whose initial elements [0...rightIndex] are 
 * sorted.
 * @param rightIndex The index till which sub array is sorted.
 * @param value The value to be inserted into sorted sub array.
 */
function insert(array, rightIndex, value) {
    for(var j = rightIndex; j >= 0 && array[j] > value; j--) {
        array[j + 1] = array[j];
    }   
    array[j + 1] = value; 
};

และตรวจสอบสิ่งที่เรามี:

  • เงื่อนไขล่วงหน้า: array[0..rightIndex]ถูกจัดเรียง
  • โพสต์เงื่อนไข: array[0..rightIndex+1]เรียง
  • ไม่แปรเปลี่ยน: 0 <= j <= rightIndexแต่ดูเหมือนซ้ำซ้อนเล็กน้อย หรือเป็น @Jules ที่ระบุไว้ในการแสดงความคิดเห็นในตอนท้ายของ "รอบ" for n in [j, rightIndex+1] => array[j] > valueที่
  • ค่าคงที่: ในตอนท้ายของ "รอบ" array[0..rightIndex+1]จะถูกจัดเรียง

ดังนั้นก่อนอื่นคุณสามารถเขียนis_sortedฟังก์ชั่นได้เช่นเดียวกับminฟังก์ชั่นที่ทำงานบนชิ้นส่วนของอาร์เรย์แล้วยืนยัน:

function insert(array, rightIndex, value) {
    assert(is_sorted(array[0..rightIndex]));

    for(var j = rightIndex; j >= 0 && array[j] > value; j--) {
        array[j + 1] = array[j];

        assert(min(array[j..rightIndex+1]) > value);
        assert(is_sorted(array[0..rightIndex+1]));
    }   
    array[j + 1] = value; 

    assert(is_sorted(array[0..rightIndex+1]));
};

นอกจากนี้ยังมีความจริงที่ว่าสภาพลูปของคุณค่อนข้างซับซ้อน คุณอาจต้องการทำให้ตัวเองง่ายขึ้นโดยแยกสิ่งต่าง ๆ ออกมา:

function insert(array, rightIndex, value) {
    assert(is_sorted(array[0..rightIndex]));

    for (var j = rightIndex; j >= 0; j--) {
        if (array[j] <= value) { break; }

        array[j + 1] = array[j];

        assert(min(array[j..rightIndex+1]) > value);
        assert(is_sorted(array[0..rightIndex+1]));
    }   
    array[j + 1] = value; 

    assert(is_sorted(array[0..rightIndex+1]));
};

ตอนนี้ห่วงตรงไปตรงมา ( jไปจากrightIndexไป0)

ในที่สุดตอนนี้จะต้องมีการทดสอบ:

  • คิดถึงเงื่อนไขขอบเขต ( rightIndex == 0, rightIndex == array.size - 2)
  • คิดว่าvalueเล็กกว่าarray[0]หรือใหญ่กว่านั้นarray[rightIndex]
  • คิดvalueเป็นเท่ากับarray[0], array[rightIndex]หรือดัชนีกลางบางส่วน

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


8
@CodeYogi: ด้วย ... การทดสอบ สิ่งนี้คือมันอาจเป็นไปไม่ได้ที่จะแสดงทุกอย่างในการยืนยัน: หากการยืนยันเพียงทำซ้ำรหัสแล้วมันจะไม่นำสิ่งใหม่มา (การทำซ้ำไม่ได้ช่วยในเรื่องคุณภาพ) นี่คือเหตุผลที่นี่ฉันไม่ได้ยืนยันในวงที่0 <= j <= rightIndexหรือarray[j] <= valueมันก็จะทำซ้ำรหัส ในทางกลับกันis_sortedการรับประกันแบบใหม่จึงมีค่า หลังจากนั้นนั่นคือสิ่งที่ใช้ทดสอบ หากคุณโทรinsert([0, 1, 2], 2, 3)ไปที่ฟังก์ชั่นของคุณและเอาต์พุตไม่[0, 1, 2, 3]ได้แสดงว่าคุณพบข้อบกพร่อง
Matthieu M.

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

1
@CandiedOrange: array[j+1] = array[j]; assert(array[j+1] == array[j]);โดยการทำซ้ำรหัสผมหมายถึงตัวอักษร ในกรณีนี้ค่าดูเหมือนต่ำมาก (เป็นสำเนา / วาง) หากคุณทำซ้ำความหมาย แต่แสดงออกในแบบอื่นมันจะมีค่ามากกว่า
Matthieu M.

10
กฎของ Hoare: ช่วยในการเขียนลูปที่ถูกต้องตั้งแต่ปี 1969 "ถึงแม้เทคนิคเหล่านี้จะเป็นที่รู้จักกันมานานกว่าทศวรรษ แต่ porgrammers ส่วนใหญ่ไม่เคยได้ยินมาก่อน"
Joker_vD

1
@MatthieuM ฉันยอมรับว่ามันมีค่าต่ำมาก แต่ฉันไม่คิดว่ามันเกิดจากมันเป็นสำเนา / วาง ว่าฉันต้องการ refactor insert()เพื่อให้มันทำงานโดยการคัดลอกจากอาร์เรย์เก่าไปยังอาร์เรย์ใหม่ ที่สามารถทำได้โดยไม่ต้องทำลายอื่น ๆ ของคุณassert's แต่ไม่ใช่อันสุดท้าย เพียงแสดงให้เห็นว่าคนอื่นassertได้รับการออกแบบอย่างดีเพียงใด
candied_orange

29

ใช้การทดสอบหน่วย / TDD

ถ้าคุณต้องการจริงๆในการเข้าถึงลำดับผ่านforห่วงคุณสามารถหลีกเลี่ยงความผิดพลาดที่ผ่านการทดสอบหน่วยและโดยเฉพาะอย่างยิ่งการทดสอบขับเคลื่อนการพัฒนา

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

  1. ลำดับประกอบด้วยหนึ่งค่าที่เหนือกว่าศูนย์

    [5]จริง: [5]ที่คาดว่าจะ

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

  2. ลำดับประกอบด้วยค่าสองค่าทั้งสองมีค่ามากกว่าศูนย์

    [5, 7]จริง: [7, 5]ที่คาดว่าจะ

    ตอนนี้คุณไม่สามารถส่งคืนลำดับ แต่คุณควรย้อนกลับ คุณจะใช้การfor (;;)วนซ้ำการสร้างภาษาอื่นหรือวิธีการไลบรารีไม่สำคัญ

  3. ลำดับมีสามค่าหนึ่งเป็นศูนย์

    [5, 0, 7]จริง: [7, 5]ที่คาดว่าจะ

    ตอนนี้คุณควรเปลี่ยนรหัสเพื่อกรองค่า อีกครั้งนี้สามารถแสดงผ่านifคำสั่งหรือการเรียกวิธีการกรอบที่คุณชื่นชอบ

  4. ทั้งนี้ขึ้นอยู่กับอัลกอริทึมของคุณ (เนื่องจากนี่คือการทดสอบกล่องสีขาวเรื่องการใช้งาน) คุณอาจจำเป็นต้องจัดการกับ[] → []กรณีลำดับว่างเปล่าโดยเฉพาะหรืออาจจะไม่ หรือคุณอาจจะมั่นใจว่ากรณีขอบที่ค่าทั้งหมดเป็นลบจะถูกจัดการอย่างถูกต้องหรือแม้กระทั่งการที่ค่าลบเขตแดนคือ:[-4, 0, -5, 0] → [] [6, 4, -1] → [4, 6]; [-1, 6, 4] → [4, 6]อย่างไรก็ตามในหลายกรณีคุณจะมีเพียงการทดสอบสามแบบที่ได้อธิบายไว้ข้างต้น: การทดสอบเพิ่มเติมใด ๆ จะไม่ทำให้คุณเปลี่ยนรหัสของคุณและจะไม่เกี่ยวข้อง

ทำงานในระดับที่สูงขึ้น

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

โดยปกติforeachสามารถใช้แทนforทำให้เงื่อนไขขอบเขตการตรวจสอบที่ไม่เกี่ยวข้อง: ภาษาทำเพื่อคุณ บางภาษาเช่นงูหลามไม่ได้มีfor (;;)การสร้าง for ... in ...แต่เพียง

ใน C # นั้น LINQ สะดวกมากเมื่อทำงานกับลำดับ

var result = source.Skip(5).TakeWhile(c => c > 0);

สามารถอ่านได้มากขึ้นและมีข้อผิดพลาดน้อยกว่าเมื่อเปรียบเทียบกับforตัวแปร:

for (int i = 5; i < source.Length; i++)
{
    var value = source[i];
    if (value <= 0)
    {
        break;
    }

    yield return value;
}

3
จากคำถามเดิมของคุณฉันรู้สึกว่าตัวเลือกอยู่ในมือเดียวโดยใช้ TDD และได้รับวิธีการแก้ไขที่ถูกต้องและในทางกลับกันข้ามส่วนการทดสอบและรับเงื่อนไขขอบเขตผิด
Arseni Mourzenko

18
ขอบคุณสำหรับการเป็นหนึ่งที่จะกล่าวถึงช้างในห้องพัก: ไม่ได้ใช้ลูปที่ทุกคน ทำไมคนยังคงรหัสเช่น 1985 (และฉันใจกว้าง) อยู่นอกเหนือฉัน BOCTAOE
Jared Smith

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

6
@ MichaelKjörling: เมื่อคุณใช้ LINQ ที่วงจะมี แต่สร้างจะไม่พรรณนามากของวงนี้ สิ่งสำคัญคือ LINQ (เช่นเดียวกับรายการความเข้าใจใน Python และองค์ประกอบที่คล้ายกันในภาษาอื่น ๆ ) ทำให้เงื่อนไขขอบเขตส่วนใหญ่ไม่เกี่ยวข้องอย่างน้อยที่สุดภายในขอบเขตของคำถามต้นฉบับ อย่างไรก็ตามฉันไม่สามารถตกลงเพิ่มเติมเกี่ยวกับความต้องการที่จะเข้าใจว่าเกิดอะไรขึ้นภายใต้ประทุนเมื่อใช้ LINQ โดยเฉพาะเมื่อพูดถึงการประเมินที่ขี้เกียจ for(;;)
Arseni Mourzenko

4
@ MichaelKjörlingฉันไม่จำเป็นต้องพูดถึง LINQ และฉันก็ไม่เห็นจุดของคุณ forEach, map, LazyIteratorและอื่น ๆ ที่ให้บริการโดยคอมไพเลอร์หรือ Runtime Environment ภาษาและมีเนื้อหาน้อยมีแนวโน้มที่จะเดินกลับไปที่ถังสีในแต่ละซ้ำ ข้อผิดพลาดในการอ่านและผิดพลาดคือเหตุผลสองประการที่เพิ่มคุณสมบัติเหล่านี้ลงในภาษาสมัยใหม่
Jared Smith

15

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

ด้วยอาเรย์ 0 ดัชนีเงื่อนไขปกติของคุณจะเป็น:

for (int i = 0; i < length; i++)

หรือ

for (int i = length - 1; i >= 0; i--)

รูปแบบเหล่านั้นควรกลายเป็นธรรมชาติที่สองคุณไม่ควรคิดถึงสิ่งเหล่านั้นเลย

แต่ไม่ใช่ทุกอย่างตามรูปแบบที่แน่นอน ดังนั้นหากคุณไม่แน่ใจว่าคุณเขียนถูกต้องหรือไม่นี่คือขั้นตอนต่อไปของคุณ:

เสียบค่าและประเมินโค้ดในสมองของคุณเอง ทำให้เป็นเรื่องง่ายที่จะคิดเท่าที่จะทำได้ จะเกิดอะไรขึ้นถ้าค่าที่เกี่ยวข้องเป็น 0 จะเกิดอะไรขึ้นถ้าพวกเขาอายุ 1 ปี?

for(var j = rightIndex; j >= 0 && array[j] > value; j--) {
    array[j + 1] = array[j];
}   
array[j + 1] = value;

ในตัวอย่างของคุณคุณไม่แน่ใจว่าควรจะเป็น [j] = value หรือ [j + 1] = value เวลาที่จะเริ่มประเมินด้วยตนเอง:

จะเกิดอะไรขึ้นถ้าคุณมีความยาวของอาร์เรย์ 0 คำตอบจะชัดเจน: rightIndex จะต้อง (length - 1) == -1 ดังนั้น j เริ่มต้นที่ -1 ดังนั้นเมื่อต้องการแทรกที่ดัชนี 0 คุณต้องเพิ่ม 1

ดังนั้นเราจึงพิสูจน์ได้ว่าเงื่อนไขสุดท้ายนั้นถูกต้อง แต่ไม่ใช่ภายในวง

จะเกิดอะไรขึ้นถ้าคุณมีอาร์เรย์ที่มี 1 องค์ประกอบ 10 และเราพยายามแทรก 5 ด้วยองค์ประกอบเดียว rightIndex ควรเริ่มต้นที่ 0 ดังนั้นครั้งแรกที่ผ่านลูป j = 0 ดังนั้น "0> = 0 && 10> 5" เนื่องจากเราต้องการแทรก 5 ที่ดัชนี 0 ดังนั้น 10 ควรย้ายไปยังดัชนี 1 ดังนั้นอาร์เรย์ [1] = อาร์เรย์ [0] เนื่องจากสิ่งนี้เกิดขึ้นเมื่อ j เป็น 0 ดังนั้นอาร์เรย์ [j + 1] = array [j + 0]

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

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

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


4
for (int i = 0; i < length; i++)ผมชอบ เมื่อฉันเข้าสู่นิสัยนี้ฉันหยุดใช้<=บ่อยและรู้สึกว่าลูปง่ายขึ้น แต่for (int i = length - 1; i >= 0; i--)ดูเหมือนจะซับซ้อนเกินไปเมื่อเทียบกับ: for (int i=length; i--; )(ซึ่งอาจจะเหมาะสมกว่าที่จะเขียนเป็นwhileวนซ้ำเว้นแต่ว่าฉันพยายามทำให้iขอบเขต / ชีวิตมีขนาดเล็กลง) ผลลัพธ์ยังคงวิ่งผ่านลูปด้วย i == length-1 (เริ่มแรก) ถึง i == 0 โดยมีความแตกต่างในการทำงานเพียงอย่างเดียวว่าwhile()เวอร์ชั่นลงท้ายด้วย i == -1 หลังจากลูป (ถ้ามันยังมีชีวิตอยู่) แทน i = = 0
TOOGAM

2
@TOOGAM (int i = length; i--;) ทำงานใน C / C ++ เนื่องจาก 0 ประเมินว่าเป็นเท็จ แต่ไม่ใช่ทุกภาษาที่มีความเท่าเทียมกัน ฉันเดาว่าคุณอาจพูดว่า i -> 0.
Bryce Wagner

โดยปกติหากใช้ภาษาที่ต้องใช้ " > 0" เพื่อให้ได้ฟังก์ชั่นที่ต้องการควรใช้อักขระดังกล่าวเพราะภาษานั้นต้องการ แต่ถึงกระนั้นแม้ในกรณีดังกล่าวโดยใช้เพียง " > 0" จะง่ายกว่าการทำกระบวนการสองส่วนของครั้งแรกลบหนึ่งแล้วยังใช้ " >= 0" เมื่อฉันเรียนรู้ว่าจากประสบการณ์เพียงเล็กน้อยฉันก็มีนิสัยต้องการใช้เครื่องหมายที่เท่าเทียมกัน (เช่น " >= 0") ในเงื่อนไขการทดสอบลูปของฉันน้อยลงบ่อยครั้งและโค้ดผลลัพธ์มักจะรู้สึกง่ายขึ้นตั้งแต่นั้นมา
TOOGAM

1
@BryceWagner ถ้าคุณต้องทำi-- > 0ทำไมไม่ลองเรื่องตลกแบบคลาสสิกi --> 0!
porglezomp

3
@porglezomp อาใช่ไปที่ผู้ประกอบการ ภาษา C ส่วนใหญ่รวมถึง C, C ++, Java และ C # มีภาษานั้น
CVn

11

ฉันหงุดหงิดเกินไปเพราะขาดรูปแบบการคำนวณที่ถูกต้องในหัวของฉัน

เป็นจุดที่น่าสนใจมากสำหรับคำถามนี้และมันสร้างความคิดเห็นนี้: -

มีทางเดียวเท่านั้น: เข้าใจปัญหาของคุณดีขึ้น แต่นั่นเป็นเรื่องทั่วไปตามคำถามของคุณ - ขยะโทมัส

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

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

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

ยกตัวอย่างโค้ดของคุณที่นี่มีข้อบกพร่องมากมายที่คุณยังไม่ได้พิจารณา: -

  • เกิดอะไรขึ้นถ้า rightIndex ต่ำเกินไป (เบาะแส: มันจะเกี่ยวข้องกับการสูญเสียข้อมูล)
  • เกิดอะไรขึ้นถ้า rightIndex อยู่นอกขอบเขตอาร์เรย์? (คุณจะได้รับข้อยกเว้นหรือคุณเพิ่งสร้างบัฟเฟอร์โอเวอร์โฟลเอง)

มีปัญหาอื่นอีกเล็กน้อยที่เกี่ยวข้องกับประสิทธิภาพและการออกแบบของรหัส ...

  • รหัสนี้จะต้องขยายขนาดหรือไม่ การจัดเรียงอาร์เรย์เป็นตัวเลือกที่ดีที่สุดหรือคุณควรพิจารณาตัวเลือกอื่น ๆ (เช่นรายการที่ลิงก์หรือไม่)
  • คุณมั่นใจในสมมติฐานของคุณหรือไม่? (คุณสามารถรับประกันเรียงแถวและถ้ามันไม่?)
  • คุณสร้างล้อใหม่หรือไม่ อาร์เรย์ที่เรียงลำดับเป็นปัญหาที่รู้จักกันดีคุณเคยศึกษาวิธีแก้ปัญหาที่มีอยู่หรือไม่ มีวิธีแก้ไขปัญหาในภาษาของคุณ (เช่นSortedList<t>ใน C #) หรือไม่?
  • คุณควรจะคัดลอกรายการอาร์เรย์ทีละรายการด้วยตนเอง? หรือภาษาของคุณมีฟังก์ชั่นทั่วไปเช่น JScript Array.Insert(...)หรือไม่? รหัสนี้จะชัดเจนขึ้นหรือไม่

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


2
แม้ว่าคุณจะส่งดัชนีของคุณไปยังฟังก์ชั่นที่มีอยู่ (เช่น Array.Copy) แต่ก็อาจต้องใช้ความคิดที่จะได้รับเงื่อนไขที่ถูกต้อง การจินตนาการว่าเกิดอะไรขึ้นในสถานการณ์ความยาว 0 และ 1 และ 2 ความยาวอาจเป็นวิธีที่ดีที่สุดเพื่อให้แน่ใจว่าคุณไม่ได้คัดลอกน้อยเกินไปหรือมากเกินไป
ไบรซ์วากเนอร์

@BryceWagner - จริง แต่ไม่มีความคิดที่ชัดเจนว่าปัญหาใดที่คุณกำลังแก้อยู่คุณจะต้องใช้เวลามากมายกับความตื่นเต้นในความมืดในกลยุทธ์ 'hit and hope' ซึ่งอยู่ไกลจาก OP ปัญหาที่ใหญ่ที่สุด ณ จุดนี้
James Snell

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

2
@CodeYogi ฉันคิดว่าคุณอาจสับสนกับเว็บไซต์นี้ด้วย Stack Overflow ไซต์นี้เทียบเท่าเซสชันถาม - ตอบที่ไวท์บอร์ดไม่ใช่ที่คอมพิวเตอร์ปลายทาง "แสดงรหัสให้ฉัน" เป็นข้อบ่งชี้ที่ชัดเจนว่าคุณอยู่ในเว็บไซต์ที่ไม่ถูกต้อง
Wildcard

2
@ Wildcard +1: "แสดงรหัสให้ฉัน" คือตัวบ่งชี้ที่ยอดเยี่ยมว่าทำไมคำตอบนี้ถูกต้องและฉันอาจต้องพยายามหาวิธีที่ดีกว่าเพื่อแสดงให้เห็นว่ามันเป็นปัญหาของมนุษย์ / ปัจจัยการออกแบบที่สามารถทำได้เท่านั้น ได้รับการแก้ไขโดยการเปลี่ยนแปลงในกระบวนการของมนุษย์ - ไม่มีรหัสจำนวนใดสามารถสอนได้
James Snell

10

ข้อผิดพลาด Off-by-one เป็นหนึ่งในข้อผิดพลาดทั่วไปของการเขียนโปรแกรม แม้แต่นักพัฒนาที่มีประสบการณ์ก็ยังทำผิดในบางครั้ง ภาษาระดับสูงมักจะมีโครงสร้างการวนซ้ำเช่นforeachหรือmapหลีกเลี่ยงการทำดัชนีอย่างชัดเจนโดยสิ้นเชิง แต่บางครั้งคุณต้องมีการทำดัชนีอย่างชัดเจนเช่นในตัวอย่างของคุณ

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

เมื่ออธิบายช่วงอาร์เรย์การประชุมคือการรวมถึงขอบเขตล่างแยกบนปก ตัวอย่างเช่นช่วง 0..3 คือเซลล์ 0,1,2 การประชุมนี้จะใช้ภาษาตลอด 0 การจัดทำดัชนีสำหรับตัวอย่างslice(start, end)วิธีการใน JavaScript ผลตอบแทน subarray ที่เริ่มต้นด้วยดัชนีstartขึ้นไปแต่ไม่รวมถึงendดัชนี

จะชัดเจนขึ้นเมื่อคุณคิดถึงดัชนีช่วงที่อธิบายถึงขอบระหว่างเซลล์อาร์เรย์ ภาพประกอบด้านล่างนี้คืออาร์เรย์ที่มีความยาว 9 และหมายเลขด้านล่างของเซลล์นั้นจัดชิดกับขอบและเป็นสิ่งที่ใช้เพื่ออธิบายเซ็กเมนต์อาร์เรย์ เช่นชัดเจนจากภาพประกอบมากกว่าช่วงที่ 2..5 คือเซลล์ 2,3,4

┌───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │   -- cell indexes, e.g array[3]
└───┴───┴───┴───┴───┴───┴───┴───┴───┘
0   1   2   3   4   5   6   7   8   9   -- segment bounds, e.g. slice(2,5) 
        └───────────┘ 
          range 2..5

โมเดลนี้สอดคล้องกับการมีความยาวของอาเรย์เป็นขอบเขตบนของอาเรย์ อาร์เรย์ที่มีความยาว 5 มีเซลล์ 0..5 ซึ่งหมายความว่ามีห้าเซลล์ 0,1,2,3,4 นี่ก็หมายความว่าความยาวของเซกเมนต์มีค่าสูงกว่าลบด้วยขอบล่างเช่นเซกเมนต์ 2..5 มี 5-2 = 3 เซลล์

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

เมื่อคุณทำซ้ำลงในรหัสของคุณคุณต้องรวมต่ำผูกพัน, 0, j >= 0เพื่อให้คุณย้ำในขณะที่

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

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


4
@CodeYogi: ฉันจะวาดอาร์เรย์ขนาดเล็กเป็นตารางบนกระดาษแล้วก้าวผ่านการวนซ้ำของวงด้วยตนเองด้วยดินสอ สิ่งนี้ช่วยให้ฉันอธิบายสิ่งที่เกิดขึ้นจริงเช่นช่วงของเซลล์ถูกเลื่อนไปทางขวาและตำแหน่งที่แทรกค่าใหม่
JacquesB

3
"มีสองสิ่งที่ยากในวิทยาการคอมพิวเตอร์: การทำให้แคชใช้ไม่ได้การตั้งชื่อสิ่งของและข้อผิดพลาดแบบแยกจากกัน"
Digital Trauma

1
@CodeYogi: เพิ่มแผนภาพขนาดเล็กเพื่อแสดงสิ่งที่ฉันกำลังพูดถึง
JacquesB

1
ความเข้าใจที่ยอดเยี่ยมโดยเฉพาะอย่างยิ่งคุณสอง pars ที่มีมูลค่าการอ่านความสับสนก็เป็นเพราะลักษณะของการวนรอบแม้ฉันจะหาดัชนีที่ถูกต้องการวนซ้ำลดลง j หนึ่งครั้งก่อนการสิ้นสุดและทำให้ฉันถอยหลังหนึ่งก้าว
CodeYogi

1
มาก. ยอดเยี่ยม ตอบ. และฉันขอเพิ่มเติมว่าอนุสัญญาดัชนีแบบรวม / เอกสิทธิ์นี้ยังได้รับแรงบันดาลใจจากค่าของmyArray.LengthหรือmyList.Count- ซึ่งมักจะมากกว่าดัชนี "rightmost" ที่เป็นศูนย์เสมอ ... ความยาวของคำอธิบายปฏิเสธการใช้งานจริงและเรียบง่ายของฮิวริสติกการเข้ารหัสที่ชัดเจนเหล่านี้ ฝูงชน TL; DR หายไป
Radarbob

5

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

ฉันขอแนะนำให้คุณแก้ไขข้อบกพร่องนี้โดย (เรียนรู้เพิ่มเติม) การเรียนรู้วิธีการเขียนลูป - และฉันขอแนะนำให้คุณใช้เวลาสองสามชั่วโมงในการเขียนลูปด้วยกระดาษและดินสอ ใช้เวลาช่วงบ่ายออกไปทำสิ่งนี้ จากนั้นใช้เวลา 45 นาทีต่อวันในการทำงานในหัวข้อจนกว่าคุณจะได้รับมันจริงๆ

เป็นการทดสอบที่ดีมาก แต่คุณควรทำการทดสอบโดยคาดหวังว่าคุณจะได้รับขอบเขตการวนซ้ำ (และส่วนที่เหลือของรหัสของคุณ) ได้อย่างถูกต้อง


4
ฉันไม่กล้าแสดงออกถึงทักษะของ OP การทำผิดขอบเขตเป็นเรื่องง่ายโดยเฉพาะอย่างยิ่งในบริบทที่เครียดเช่นการสัมภาษณ์งาน นักพัฒนาที่มีประสบการณ์สามารถทำผิดพลาดได้เช่นกัน แต่เห็นได้ชัดว่านักพัฒนาที่มีประสบการณ์จะหลีกเลี่ยงข้อผิดพลาดดังกล่าวตั้งแต่แรกผ่านการทดสอบ
Arseni Mourzenko

3
@MainMa - ฉันคิดว่าในขณะที่ Mark อาจมีความอ่อนไหวมากขึ้นฉันคิดว่าเขาถูกต้อง - มีความเครียดในการสัมภาษณ์และมีเพียงการแฮ็กรหัสเข้าด้วยกันโดยไม่คำนึงถึงการกำหนดปัญหา วิธีที่คำถามนั้นได้รับการพูดออกมานั้นมีความสำคัญอย่างยิ่งต่อสิ่งหลังนี้และเป็นสิ่งที่สามารถแก้ไขได้ในระยะยาวโดยการทำให้แน่ใจว่าคุณมีรากฐานที่มั่นคงไม่ใช่การแฮ็คที่ IDE
James Snell

@ JamesSnell ฉันคิดว่าคุณมีความมั่นใจในตัวเองมากขึ้น ดูรหัสและบอกสิ่งที่ทำให้คุณคิดว่ามันอยู่ภายใต้เอกสาร? หากคุณเห็นชัดเจนไม่มีที่ไหนบอกว่าฉันไม่สามารถแก้ปัญหาได้? ฉันแค่อยากจะรู้วิธีหลีกเลี่ยงการทำผิดพลาดซ้ำ ๆ ฉันคิดว่าคุณทำให้โปรแกรมทั้งหมดของคุณถูกต้องในครั้งเดียว
CodeYogi

4
@CodeYogi หากคุณต้องทำ 'ทดลองใช้และข้อผิดพลาด' และคุณ 'ได้รับความผิดหวัง' และ 'ทำผิดพลาดเหมือนกัน' ด้วยการเข้ารหัสของคุณจากนั้นเป็นสัญญาณว่าคุณไม่เข้าใจปัญหาของคุณดีพอก่อนที่จะเริ่มเขียน . ไม่มีใครพูดว่าคุณไม่เข้าใจ แต่รหัสของคุณน่าจะดีกว่าและพวกเขาเป็นสัญญาณว่าคุณกำลังดิ้นรนซึ่งคุณมีทางเลือกที่จะรับและเรียนรู้จากหรือไม่
James Snell

2
@CodeYogi ... และเมื่อคุณถามฉันไม่ค่อยได้วนและแยกผิดเพราะฉันมีจุดที่มีความเข้าใจที่ชัดเจนของสิ่งที่ฉันต้องบรรลุก่อนที่ฉันจะเขียนรหัสมันไม่ยากที่จะทำในสิ่งที่ง่ายเหมือนเรียง คลาสอาเรย์ ในฐานะโปรแกรมเมอร์หนึ่งในสิ่งที่ยากที่สุดที่ต้องทำคือยอมรับว่าคุณมีปัญหา แต่จนกระทั่งคุณทำอย่างนั้นคุณจะไม่เริ่มเขียนโค้ดที่ดีจริงๆ
James Snell

3

บางทีฉันควรใส่เนื้อในความคิดเห็นของฉัน:

มีทางเดียวเท่านั้น: เข้าใจปัญหาของคุณดีขึ้น แต่นั่นเป็นเรื่องทั่วไปตามคำถามของคุณ

จุดของคุณคือ

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

เมื่อฉันอ่านtrial and errorเสียงปลุกของฉันจะเริ่มดังขึ้น แน่นอนว่าพวกเราหลายคนรู้ถึงสภาพจิตใจเมื่อเราต้องการแก้ไขปัญหาเล็ก ๆ น้อย ๆ และทำให้เขาคิดมากกับสิ่งอื่นและเริ่มคาดเดาด้วยวิธีใดวิธีหนึ่งเพื่อให้ได้รหัสseemที่ควรทำ โซลูชัน hackish ออกมาในครั้งนี้ - และบางส่วนของพวกเขาบริสุทธิ์อัจฉริยะ ; แต่จะซื่อสัตย์: ที่สุดของพวกเขาไม่ได้ ฉันรวมอยู่ด้วยซึ่งรู้สถานะนี้

เป็นอิสระจากปัญหาที่เป็นรูปธรรมของคุณคุณถามคำถามเกี่ยวกับวิธีการปรับปรุง:

1) การทดสอบ

ที่ถูกกล่าวโดยผู้อื่นและฉันจะไม่มีอะไรมีค่าเพิ่ม

2) การวิเคราะห์ปัญหา

มันยากที่จะให้คำแนะนำ มีคำแนะนำเพียงสองข้อที่ฉันสามารถให้คุณได้ซึ่งอาจช่วยให้คุณพัฒนาทักษะของคุณในหัวข้อนั้น:

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

Code Katas เป็นวิธีที่อาจช่วยได้บ้าง

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

รหัสกะตะ

เว็บไซต์เดียวที่ฉันชอบมาก: Code Wars

บรรลุความเชี่ยวชาญผ่านการท้าทายพัฒนาทักษะของคุณโดยการฝึกอบรมกับผู้อื่นเกี่ยวกับการท้าทายรหัสจริง

ปัญหาเหล่านี้ค่อนข้างเล็กซึ่งช่วยให้คุณพัฒนาทักษะการเขียนโปรแกรมได้ และสิ่งที่ผมชอบมากที่สุดในรหัส Warsคือว่าคุณสามารถเปรียบเทียบของคุณวิธีการแก้ปัญหาให้เป็นหนึ่งในคนอื่น ๆ

หรือบางทีคุณควรดูExercism.ioซึ่งคุณจะได้รับคำติชมจากชุมชน

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

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

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

3) พัฒนา Toolbelt

นอกเหนือจากการรู้ภาษาและเครื่องมือของคุณ - ฉันรู้ว่าสิ่งเหล่านี้เป็นสิ่งที่นักพัฒนาคิดว่าเป็นอันดับแรก - เรียนรู้อัลกอริทึม (การอ่านอาคา)

นี่คือหนังสือสองเล่มที่เริ่มต้นด้วย:

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

3a) รู้โครงสร้างการเขียนโปรแกรม

จุดนี้เป็นอนุพันธ์ - ดังนั้นต้องบอกว่า รู้ภาษาของคุณ - และดีกว่า: รู้ว่าสิ่งใดเป็นไปได้ในภาษาของคุณ

จุดที่พบบ่อยสำหรับรหัสที่ไม่ดีหรือ inefficent เป็นบางครั้งที่โปรแกรมเมอร์ไม่ทราบความแตกต่างระหว่างความแตกต่างของลูป ( for-, while-และdo-loops) พวกมันสามารถใช้แทนกันได้ทั้งหมด แต่ในบางสถานการณ์การเลือกโครงสร้างลูปอื่นจะนำไปสู่โค้ดที่หรูหรา

และมีอุปกรณ์ของ Duff ...

PS:

มิฉะนั้นความคิดเห็นของคุณไม่ดีไปกว่า Donal Trump

ใช่เราควรทำให้การเข้ารหัสดีขึ้นอีกครั้ง!

คำขวัญใหม่สำหรับ Stackoverflow


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

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

เห็นด้วยอย่างสมบูรณ์กับระฆังปลุกที่ "การทดลองและข้อผิดพลาด" ฉันคิดว่าวิธีที่ดีที่สุดในการเรียนรู้อย่างเต็มที่ในการแก้ปัญหาความคิดคือการใช้อัลกอริทึมและรหัสด้วยกระดาษและดินสอ
Wildcard

อืม ... ทำไมคุณถึงมีงานกระทุ้งที่ไม่ได้เกิดจากไวยากรณ์ที่ผู้สมัครทางการเมืองอ้างโดยไม่มีบริบทอยู่ตรงกลางคำตอบของคุณเกี่ยวกับการเขียนโปรแกรม
Wildcard

2

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

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

มันเป็นตัวเลขเช่น 0 หรือ 1 หรือไม่ ถ้าอย่างนั้นคุณก็ต้อง a และ bingo, คุณมี i เริ่มด้วย จากนั้นคิดว่ากี่ครั้งที่คุณต้องการเรียกใช้สิ่งเดียวกันและคุณจะมีเงื่อนไขสิ้นสุดของคุณ

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

ในทางเทคนิคแล้วลูปใด ๆ สามารถแปลเป็นลูปอื่น ๆ ได้ แต่โค้ดนั้นอ่านง่ายกว่าถ้าคุณใช้ลูปที่ถูกต้องดังนั้นนี่คือเคล็ดลับ:

  1. หากคุณพบว่าตัวเองกำลังเขียน if () {... ; break;} อยู่ภายใน a for คุณต้องใช้เวลาสักครู่และคุณมีเงื่อนไขอยู่แล้ว

  2. "ในขณะที่" อาจเป็นวงที่ใช้มากที่สุดในภาษาใด ๆ แต่ก็ไม่ควรเลียนแบบ หากคุณพบว่าตัวเองเขียนบูล ok = จริง ในขณะที่ (ทำเครื่องหมาย) {ทำบางสิ่งบางอย่างและหวังว่าจะเปลี่ยนตกลงในบางจุด}; จากนั้นคุณไม่จำเป็นต้องใช้เวลาสักครู่ แต่ทำในขณะที่เพราะมันหมายความว่าคุณมีทุกสิ่งที่คุณจำเป็นต้องทำซ้ำครั้งแรก

บริบทนิดหน่อย ... ตอนที่ฉันเรียนโปรแกรม (ปาสกาล) ครั้งแรกฉันไม่ได้พูดภาษาอังกฤษ สำหรับฉัน "สำหรับ" และ "ในขณะที่" ไม่สมเหตุสมผล แต่คำหลัก "ทำซ้ำ" (ทำขณะที่อยู่ใน C) เกือบจะเหมือนกันในภาษาแม่ของฉันดังนั้นฉันจะใช้มันสำหรับทุกสิ่ง ในความคิดของฉันทำซ้ำ (ทำในขณะที่) เป็นวงที่เป็นธรรมชาติที่สุดเพราะเกือบทุกครั้งที่คุณต้องการให้มีบางสิ่งที่จะทำแล้วคุณต้องการให้มันทำอีกครั้งและอีกครั้งจนกว่าจะถึงเป้าหมาย "สำหรับ" เป็นเพียงทางลัดที่ให้ตัววนซ้ำและวางเงื่อนไขไว้ที่จุดเริ่มต้นของรหัสแปลก ๆ แม้ว่าเกือบทุกครั้งที่คุณต้องการให้ทำอะไรจนกว่าจะมีอะไรเกิดขึ้น นอกจากนี้ในขณะที่เป็นเพียงทางลัดสำหรับ if () {do while ()} ทางลัดเป็นสิ่งที่ดีสำหรับภายหลัง


2

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

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

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

ในที่สุดฉันจะแสดงวิธีที่คุณสามารถใช้ข้อมูลจำเพาะดังกล่าวเพื่อออกแบบลูปเวอร์ชันของคุณที่ง่ายขึ้นเล็กน้อย เวอร์ชั่นลูปที่ง่ายกว่านี้คือ infact มีเงื่อนไขของลูปj > 0และการกำหนดarray[j] = value- ตามสัญชาตญาณเริ่มต้นของคุณ

Dafny จะพิสูจน์ให้เราเห็นว่าลูปทั้งสองนี้ถูกต้องและทำสิ่งเดียวกัน

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

ส่วนที่หนึ่ง - การเขียนข้อกำหนดสำหรับวิธีการ

ความท้าทายแรกที่เราเผชิญคือการกำหนดว่าควรทำอย่างไร ด้วยเหตุนี้ฉันจึงออกแบบเงื่อนไขก่อนและหลังซึ่งระบุพฤติกรรมของวิธีการ เพื่อให้ข้อมูลจำเพาะที่แน่นอนยิ่งขึ้นฉันได้ปรับปรุงวิธีการที่จะทำให้มันกลับดัชนีที่valueถูกแทรก

method insert(arr:array<int>, rightIndex:int, value:int) returns (index:int)
  // the method will modify the array
  modifies arr
  // the array will not be null
  requires arr != null
  // the right index is within the bounds of the array
  // but not the last item
  requires 0 <= rightIndex < arr.Length - 1
  // value will be inserted into the array at index
  ensures arr[index] == value 
  // index is within the bounds of the array
  ensures 0 <= index <= rightIndex + 1
  // the array to the left of index is not modified
  ensures arr[..index] == old(arr[..index])
  // the array to the right of index, up to right index is
  // shifted to the right by one place
  ensures arr[index+1..rightIndex+2] == old(arr[index..rightIndex+1])
  // the array to the right of rightIndex+1 is not modified
  ensures arr[rightIndex+2..] == old(arr[rightIndex+2..])

ข้อกำหนดนี้จับพฤติกรรมของวิธีการได้อย่างสมบูรณ์ สังเกตหลักของฉันเกี่ยวกับข้อกำหนดนี้ก็คือว่ามันจะง่ายถ้าขั้นตอนที่ถูกส่งผ่านค่ามากกว่าrightIndex+1 rightIndexแต่เนื่องจากฉันไม่สามารถดูได้ว่าวิธีการนี้ถูกเรียกจากที่ใดฉันไม่รู้ว่าการเปลี่ยนแปลงจะมีผลอย่างไรต่อโปรแกรมที่เหลือ

ส่วนที่สอง - การกำหนดค่าคงที่แบบวนซ้ำ

ตอนนี้เรามีข้อกำหนดสำหรับพฤติกรรมของวิธีการที่เรามีการเพิ่มสเปคของพฤติกรรมของวงที่จะโน้มน้าวให้ Dafny arrayที่ดำเนินห่วงจะยุติและจะส่งผลให้รัฐสุดท้ายที่ต้องการของ

ต่อไปนี้คือลูปดั้งเดิมของคุณซึ่งแปลเป็นไวยากรณ์ Dafny พร้อมการเพิ่มค่าคงที่ของลูป ฉันได้เปลี่ยนมันเพื่อกลับดัชนีที่แทรกค่า

{
    // take a copy of the initial array, so we can refer to it later
    // ghost variables do not affect program execution, they are just
    // for specification
    ghost var initialArr := arr[..];


    var j := rightIndex;
    while(j >= 0 && arr[j] > value)
       // the loop always decreases j, so it will terminate
       decreases j
       // j remains within the loop index off-by-one
       invariant -1 <= j < arr.Length
       // the right side of the array is not modified
       invariant arr[rightIndex+2..] == initialArr[rightIndex+2..]
       // the part of the array looked at by the loop so far is
       // shifted by one place to the right
       invariant arr[j+2..rightIndex+2] == initialArr[j+1..rightIndex+1]
       // the part of the array not looked at yet is not modified
       invariant arr[..j+1] == initialArr[..j+1] 
    {
        arr[j + 1] := arr[j];
        j := j-1;
    }   
    arr[j + 1] := value;
    return j+1; // return the position of the insert
}

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

โปรดทราบว่า Dafny กำลังสร้างข้อพิสูจน์ความถูกต้องที่นี่ นี่เป็นการรับประกันความถูกต้องที่แข็งแกร่งเกินกว่าที่จะได้รับจากการทดสอบ

ส่วนที่สาม - การวนรอบที่ง่ายกว่า

ตอนนี้เรามีข้อมูลจำเพาะของวิธีการที่จับพฤติกรรมของลูป เราสามารถปรับเปลี่ยนการใช้งานของลูปได้อย่างปลอดภัยในขณะที่ยังคงไว้ซึ่งความมั่นใจว่าเราไม่ได้เปลี่ยนพฤติกรรมของลูป

jฉันได้แก้ไขวงเพื่อให้ตรงกับสัญชาติญาณเดิมของคุณเกี่ยวกับสภาพห่วงและความคุ้มค่าสุดท้ายของ ฉันจะยืนยันว่าวงนี้ง่ายกว่าวงที่คุณอธิบายไว้ในคำถามของคุณ มันมักจะเป็นมากขึ้นสามารถที่จะใช้มากกว่าjj+1

  1. เริ่ม j ที่ rightIndex+1

  2. เปลี่ยนเงื่อนไขการวนซ้ำเป็น j > 0 && arr[j-1] > value

  3. เปลี่ยนการมอบหมายเป็น arr[j] := value

  4. ลดตัวนับลูปที่ส่วนท้ายของลูปแทนการเริ่มต้น

นี่คือรหัส โปรดทราบว่าค่าคงที่แบบวนซ้ำนั้นค่อนข้างง่ายต่อการเขียนในขณะนี้:

method insert2(arr:array<int>, rightIndex:int, value:int) returns (index:int)
  modifies arr
  requires arr != null
  requires 0 <= rightIndex < arr.Length - 1
  ensures 0 <= index <= rightIndex + 1
  ensures arr[..index] == old(arr[..index])
  ensures arr[index] == value 
  ensures arr[index+1..rightIndex+2] == old(arr[index..rightIndex+1])
  ensures arr[rightIndex+2..] == old(arr[rightIndex+2..])
{
    ghost var initialArr := arr[..];
    var j := rightIndex+1;
    while(j > 0 && arr[j-1] > value)
       decreases j
       invariant 0 <= j <= arr.Length
       invariant arr[rightIndex+2..] == initialArr[rightIndex+2..]
       invariant arr[j+1..rightIndex+2] == initialArr[j..rightIndex+1]
       invariant arr[..j] == initialArr[..j] 
    {
        j := j-1;
        arr[j + 1] := arr[j];
    }   
    arr[j] := value;
    return j;
}

ส่วนที่สี่ - คำแนะนำเกี่ยวกับการวนลูปย้อนกลับ

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

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

น่าเสียดายที่การforวนซ้ำสร้างในหลายภาษาทำให้ยาก

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

ส่วนที่ห้า - โบนัส

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

method insert3(arr:array<int>, rightIndex:int, value:int) returns (index:int)
  modifies arr
  requires arr != null
  requires 1 <= rightIndex < arr.Length 
  ensures 0 <= index <= rightIndex
  ensures arr[..index] == old(arr[..index])
  ensures arr[index] == value 
  ensures arr[index+1..rightIndex+1] == old(arr[index..rightIndex])
  ensures arr[rightIndex+1..] == old(arr[rightIndex+1..])
{
    ghost var initialArr := arr[..];
    var j := rightIndex;
    while(j > 0 && arr[j-1] > value)
       decreases j
       invariant 0 <= j <= arr.Length
       invariant arr[rightIndex+1..] == initialArr[rightIndex+1..]
       invariant arr[j+1..rightIndex+1] == initialArr[j..rightIndex]
       invariant arr[..j] == initialArr[..j] 
    {
        j := j-1;
        arr[j + 1] := arr[j];
    }   
    arr[j] := value;
    return j;
}

2
จะขอบคุณความคิดเห็นจริง ๆ ถ้าคุณ downvote
flamingpenguin

2

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

ถึงอย่างนั้นในกรณีที่ซับซ้อนมากขึ้นมันก็ง่ายที่จะผิดเพราะสิ่งที่คุณเขียนมักจะไม่ใช่สิ่งที่พบเห็นได้ทั่วไป ด้วยภาษาและห้องสมุดที่ทันสมัยมากขึ้นคุณจะไม่เขียนเรื่องง่าย ๆ เพราะมีโครงสร้างหรือการบรรจุกระป๋อง ใน C ++ มนต์ที่ทันสมัยคือ "ใช้อัลกอริทึมมากกว่าการเขียนรหัส"

ดังนั้นวิธีที่จะทำให้แน่ใจว่ามันเหมาะสมสำหรับชนิดของสิ่งโดยเฉพาะอย่างยิ่งนี้คือการมองไปที่เงื่อนไขขอบเขต ติดตามรหัสในหัวของคุณสำหรับกรณีเล็ก ๆ น้อย ๆ ที่ขอบของสิ่งที่เปลี่ยนแปลง ถ้าindex == array-maxเกิดอะไรขึ้น เกี่ยวกับmax-1อะไร หากรหัสผิดพลาดก็จะเป็นหนึ่งในขอบเขตเหล่านี้ บางลูปจำเป็นต้องกังวลเกี่ยวกับองค์ประกอบแรกหรือสุดท้ายเช่นเดียวกับโครงสร้างวนลูปรับขอบเขตขวา; เช่นถ้าคุณอ้างถึงa[I]และa[I-1]จะเกิดอะไรขึ้นเมื่อIมูลค่าขั้นต่ำคือเท่าใด

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

การตรวจสอบเคสขอบ (ทั้งสองด้านของแต่ละขอบ) คือสิ่งที่คุณควรทำเมื่อเขียนลูปและสิ่งที่คุณควรทำในการตรวจสอบโค้ด


1

ฉันจะพยายามหลีกเลี่ยงหัวข้อที่กล่าวถึงมากมายแล้ว

เครื่องมือ / แบบจำลองทางจิตเพื่อหลีกเลี่ยงความผิดพลาดดังกล่าวคืออะไร?

เครื่องมือ

สำหรับฉันเครื่องมือที่ใหญ่ที่สุดในการเขียนดีกว่าforและwhileลูปไม่ได้เขียนอะไรforหรือwhileลูปเลย

ภาษาสมัยใหม่ส่วนใหญ่พยายามตั้งเป้าหมายปัญหานี้ในบางรูปแบบหรืออื่น ๆ ตัวอย่างเช่น Java ในขณะที่มีIteratorสิทธิ์ตั้งแต่เริ่มต้นซึ่งเคยเป็น clunky ที่จะใช้แนะนำไวยากรณ์ทางลัดที่จะใช้พวกเขาได้ง่ายขึ้นในการเปิดตัว latler C # มีพวกเขาเช่นกันเป็นต้น

ภาษาที่ชื่นชอบในขณะที่ฉันกำลังทับทิมได้ดำเนินการในแนวทางการทำงาน ( .each, .mapฯลฯ ) เต็มรูปแบบด้านหน้า นี้เป็นอย่างมากที่มีประสิทธิภาพ ผมก็ไม่ได้นับอย่างรวดเร็วในบางทับทิมรหัสฐานผมทำงานเกี่ยวกับ: ประมาณ 10.000 บรรทัดของรหัสที่มีศูนย์forและประมาณ while5

หากฉันถูกบังคับให้เลือกภาษาใหม่มองหาลูปที่ทำงาน / ข้อมูลตามแบบนั้นจะสูงมากในรายการลำดับความสำคัญ

โมเดลจิต

โปรดทราบว่าwhileนี่เป็นขั้นต่ำสุดที่คุณจะได้รับเพียงขั้นตอนgotoเดียว ในความคิดของฉันforทำให้แย่ยิ่งกว่าแทนที่จะดีกว่าเพราะมันสับทั้งสามส่วนของวงให้แน่นเข้าด้วยกัน

ดังนั้นถ้าฉันอยู่ในสภาพแวดล้อมที่forมีการใช้งานแล้วฉันจะทำให้แน่ใจว่าทั้ง 3 ส่วนนั้นเรียบง่ายและเหมือนกันเสมอ หมายความว่าฉันจะเขียน

limit = ...;
for (idx = 0; idx < limit; idx++) { 

แต่ไม่มีอะไรซับซ้อนมากขึ้น บางทีบางทีฉันอาจจะมีการนับถอยหลังบางครั้ง แต่ฉันจะพยายามทำให้ดีที่สุดเพื่อหลีกเลี่ยง

หากใช้whileฉันยังคงชัดเจนว่า shenannigans ภายในที่ซับซ้อนเกี่ยวข้องกับสภาพลูป การทดสอบภายในwhile(...)จะง่ายที่สุดและฉันจะหลีกเลี่ยงbreakให้ดีที่สุดเท่าที่จะทำได้ นอกจากนี้การวนซ้ำจะสั้น (การนับจำนวนบรรทัดของโค้ด) และจะมีการแยกเอาโค้ดที่มีขนาดใหญ่กว่าออก

โดยเฉพาะอย่างยิ่งถ้าสภาพที่เกิดขึ้นจริงในขณะนั้นซับซ้อนฉันจะใช้ "เงื่อนไขตัวแปร" ซึ่งเป็นเรื่องง่ายมากที่จะมองเห็นและไม่วางเงื่อนไขลงในwhileคำสั่งตัวเอง:

repeat = true;
while (repeat) {
   repeat = false; 
   ...
   if (complex stuff...) {
      repeat = true;
      ... other complex stuff ...
   }
}

(หรืออะไรทำนองนั้นในระดับที่ถูกต้องแน่นอน)

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

หวังว่าจะช่วย


1

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

โดยทั่วไปจำไว้ว่า for-loop นั้นเป็นเพียงน้ำตาลซินแทคติคที่สร้างขึ้นรอบ ๆ วง while:

// pseudo-code!
for (init; cond; step) { body; }

เทียบเท่ากับ:

// pseudo-code!
init;
while (cond) {
  body;
  step;
}

(อาจเป็นเลเยอร์พิเศษของขอบเขตเพื่อให้ตัวแปรประกาศในขั้นตอน init ภายในกับลูป)

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

auto i = v.size();  // init
while (i > 0) {  // simpler condition because i is one after
    --i;  // step before the body
    body;  // in body, i means what you'd expect
}

หรือเป็นห่วงสำหรับ:

for (i = v.size(); i > 0; ) {
    --i;  // step
    body;
}

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

for (i = v.size() - 1; i >= 0; --i) {
    body;
}

แต่นั่นเป็นหายนะหากตัวแปรดัชนีของคุณเป็นประเภทที่ไม่ได้ลงชื่อ (อาจเป็นในภาษา C หรือ C ++)

ด้วยสิ่งนี้ในใจลองเขียนฟังก์ชั่นการแทรกของคุณ

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

    function insert(array, size, value) {
      var j = size;
    
  2. ในขณะที่ค่าใหม่นั้นมีขนาดเล็กกว่าองค์ประกอบก่อนหน้า แน่นอนองค์ประกอบที่ก่อนหน้านี้สามารถตรวจสอบได้เฉพาะในกรณีที่มีเป็นองค์ประกอบที่ก่อนหน้านี้ดังนั้นเราจึงต้องตรวจสอบว่าเราไม่ได้อยู่ที่จุดเริ่มต้นมาก:

      while (j != 0 && value < array[j - 1]) {
        --j;  // now j become current
        array[j + 1] = array[j];
      }
    
  3. สิ่งนี้เหลือjที่เราต้องการคุณค่าใหม่

      array[j] = value; 
    };
    

การเขียนโปรแกรม Pearlsโดย Jon Bentley ให้คำอธิบายที่ชัดเจนมากเกี่ยวกับการเรียงลำดับการแทรก (และอัลกอริธึมอื่น ๆ ) ซึ่งสามารถช่วยสร้างแบบจำลองทางจิตของคุณสำหรับปัญหาประเภทนี้


0

คุณเพียงแค่สับสนเกี่ยวกับการforวนรอบที่แท้จริงและกลไกของการทำงานอย่างไร

for(initialization; condition; increment*)
{
    body
}
  1. ก่อนการเริ่มต้นจะดำเนินการ
  2. จากนั้นตรวจสอบสภาพ
  3. หากเงื่อนไขเป็นจริงร่างกายจะทำงานหนึ่งครั้ง ถ้าไม่ใช่ goto # 6
  4. รหัสเพิ่มถูกดำเนินการ
  5. ไปที่ # 2
  6. ปลายวง

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

initialization
while(condition)
{
    body
    increment
}

นี่คือคำแนะนำอื่น ๆ :

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

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


1
ทำไม downvote
user2023861

0

พยายามทำความเข้าใจเพิ่มเติม

สำหรับอัลกอริทึมที่ไม่สำคัญกับลูปคุณสามารถลองใช้วิธีการต่อไปนี้:

  1. สร้างอาร์เรย์คงที่ที่มี 4 ตำแหน่งและใส่ค่าบางอย่างเพื่อจำลองปัญหา
  2. เขียนอัลกอริทึมของคุณเพื่อแก้ปัญหาที่กำหนด , โดยไม่ต้องห่วงใด ๆและมี indexings ยากรหัส ;
  3. หลังจากนั้นให้แทนที่การทำดัชนีแบบ hard-codedในรหัสของคุณด้วยตัวแปรบางตัวiหรือj, และการเพิ่ม / ลดตัวแปรเหล่านี้ตามความจำเป็น (แต่ยังไม่มีลูปใด ๆ );
  4. เขียนโค้ดของคุณใหม่และวางบล็อคที่ซ้ำ ๆ กันไว้ในวงวนเพื่อเติมเต็มเงื่อนไขก่อนและหลังการโพสต์
  5. [ ไม่บังคับ ] เขียนลูปของคุณอีกครั้งเพื่อให้อยู่ในรูปแบบที่คุณต้องการ (สำหรับ / ในขณะที่ / ทำในขณะที่);
  6. สิ่งสำคัญที่สุดคือการเขียนอัลกอริทึมของคุณอย่างถูกต้อง หลังจากนั้นคุณปรับโครงสร้างและยกเลิกการเลือกรหัส / ลูปของคุณหากจำเป็น (แต่อาจทำให้โค้ดไม่น่ารำคาญสำหรับผู้อ่านอีกต่อไป)

ปัญหาของคุณ

//TODO: Insert the given value in proper position in the sorted subarray
function insert(array, rightIndex, value) { ... };

เขียนเนื้อความของลูปด้วยตนเองหลายครั้ง

ลองใช้อาเรย์คงที่กับ 4 ตำแหน่งแล้วลองเขียนอัลกอริทึมด้วยตนเองโดยไม่ต้องวนซ้ำ:

           //0 1 2 3
var array = [2,5,9,1]; //array sorted from index 0 to 2
var leftIndex = 0;
var rightIndex = 2;
var value = array[3]; //placing the last value within the array in the proper position

//starting here as 2 == rightIndex

if (array[2] > value) {
    array[3] = array[2];
} else {
    array[3] = value;
    return; //found proper position, no need to proceed;
}

if (array[1] > value) {
    array[2] = array[1];
} else {
    array[2] = value;
    return; //found proper position, no need to proceed;
}

if (array[0] > value) {
    array[1] = array[0];
} else {
    array[1] = value;
    return; //found proper position, no need to proceed;
}

array[0] = value; //achieved beginning of the array

//stopping here because there 0 == leftIndex

เขียนซ้ำลบค่าฮาร์ดโค้ด

//consider going from 2 to 0, going from "rightIndex" to "leftIndex"

var i = rightIndex //starting here as 2 == rightIndex

if (array[i] > value) {
    array[i+1] = array[i];
} else {
    array[i+1] = value;
    return; //found proper position, no need to proceed;
}

i--;
if (i < leftIndex) {
    array[i+1] = value; //achieved beginning of the array
    return;
}

if (array[i] > value) {
    array[i+1] = array[i];
} else {
    array[i+1] = value;
    return; //found proper position, no need to proceed;
}

i--;
if (i < leftIndex) {
    array[i+1] = value; //achieved beginning of the array
    return;
}

if (array[i] > value) {
    array[i+1] = array[i];
} else {
    array[i+1] = value;
    return; //found proper position, no need to proceed;
}

i--;
if (i < leftIndex) {
    array[i+1] = value; //achieved beginning of the array
    return;
}

//stopping here because there 0 == leftIndex

แปลเป็นวง

ด้วยwhile:

var i = rightIndex; //starting in rightIndex

while (true) {
    if (array[i] > value) { //refactor: this can go in the while clause
        array[i+1] = array[i];
    } else {
        array[i+1] = value;
        break; //found proper position, no need to proceed;
    }

    i--;
    if (i < leftIndex) { //refactor: this can go (inverted) in the while clause
        array[i+1] = value; //achieved beginning of the array
        break;
    }
}

Refactor / rewrite / optimise loop ตามที่คุณต้องการ:

ด้วยwhile:

var i = rightIndex; //starting in rightIndex

while ((array[i] > value) && (i >= leftIndex)) {
    array[i+1] = array[i];
    i--;
}

array[i+1] = value; //found proper position, or achieved beginning of the array

ด้วยfor:

for (var i = rightIndex; (array[i] > value) && (i >= leftIndex); i--) {
    array[i+1] = array[i];
}

array[i+1] = value; //found proper position, or achieved beginning of the array

PS:รหัสสมมติว่าอินพุตถูกต้องและอาร์เรย์นั้นไม่มีการทำซ้ำ


-1

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

ในกรณีนี้ฉันจะทำสิ่งนี้ (ในรหัสหลอก):

array = array[:(rightIndex - 1)] + value + array[rightIndex:]

-3

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

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


1
คุณช่วยยกตัวอย่างได้ไหม
CodeYogi

OP มีตัวอย่าง
gnasher729

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