ฉันจะหลีกเลี่ยงผลตอบแทนที่ไร้ประโยชน์ในวิธี Java ได้อย่างไร


115

ฉันมีสถานการณ์ที่returnคำสั่งที่ซ้อนอยู่ในสองforลูปจะมาถึงเสมอในทางทฤษฎี

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

Attached เป็นวิธีการจากการกำหนดที่สร้างจำนวนเต็มแบบสุ่มและส่งกลับการวนซ้ำที่วนซ้ำจนกว่าจะพบจำนวนเต็มสุ่มที่สองซึ่งสร้างขึ้นภายในช่วงที่ส่งผ่านไปยังเมธอดเป็นพารามิเตอร์ int

private static int oneRun(int range) {
    int[] rInt = new int[range+1]; // Stores the past sequence of ints.
    rInt[0] = generator.nextInt(range); // Inital random number.

    for (int count = 1; count <= range; count++) { // Run until return.
        rInt[count] = generator.nextInt(range); // Add randint to current iteration.
        for (int i = 0; i < count; i++) { // Check for past occurence and return if found.
            if (rInt[i] == rInt[count]) {
                return count;
            }
        }
    }
    return 0; // Never reached
}

9
หากไม่ถึงรายการสุดท้ายคุณสามารถใช้while(true)แทนการวนซ้ำที่จัดทำดัชนีได้ สิ่งนี้บอกคอมไพเลอร์ว่าลูปจะไม่กลับมา
Boris the Spider

101
เรียกฟังก์ชันที่มีช่วงเป็น 0 (หรือหมายเลขอื่น ๆ ที่น้อยกว่า 1) ( oneRun(0)) และคุณจะเห็นว่าคุณไม่สามารถเข้าถึงได้อย่างรวดเร็วreturn
mcfedr

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

4
@ หวังว่าจะเป็นประโยชน์นั่นคือคำตอบที่แท้จริงแน่นอน ไม่ใช่ผลตอบแทนที่ไร้ประโยชน์ แต่อย่างใด!
Mr Lister

4
@ หวังว่าไม่เป็นประโยชน์เพราะการnextIntโยนข้อยกเว้นสำหรับrange < 0กรณีเดียวที่ผลตอบแทนมาถึงคือเมื่อrange == 0
njzk2

คำตอบ:


343

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

private static int oneRun(int range) {
    int[] rInt = new int[range+1]; // Stores the past sequence of ints.
    rInt[0] = generator.nextInt(range); // Inital random number.

    for (int count = 1; count <= range; count++) {
        ...
    }

    throw new AssertionError("unreachable code reached");
}

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

6
หากคุณมั่นใจอย่างแน่นอนว่าโค้ดของคุณไม่สามารถเข้าถึง "โค้ดที่ไม่สามารถเข้าถึงได้" นั้นได้จากนั้นให้สร้างการทดสอบหน่วยสำหรับกรณีขอบทั้งหมดที่อาจเกิดขึ้น (ช่วงคือ 0, ช่วงคือ -1, ช่วงคือต่ำสุด / สูงสุด int) ตอนนี้อาจเป็นวิธีส่วนตัว แต่นักพัฒนาซอฟต์แวร์รายต่อไปอาจไม่ใช้วิธีนี้ วิธีจัดการกับค่าที่ไม่คาดคิด (ทิ้งข้อยกเว้นคืนค่าผิดพลาดโดยไม่ทำอะไรเลย) ขึ้นอยู่กับว่าคุณจะใช้วิธีนี้อย่างไร จากประสบการณ์ของฉันคุณเพียงแค่ต้องการบันทึกข้อผิดพลาดและส่งคืน
Rick Ryker

22
บางทีนั่นควรจะเป็นthrow new AssertionError("\"unreachable\" code reached");
โบฮีเมียน

8
ฉันจะเขียนสิ่งที่ฉันใส่ไว้ในคำตอบในโปรแกรมการผลิต
John Kugelman

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

36

ดังที่@BoristheSpider ชี้ให้เห็นว่าคุณสามารถตรวจสอบให้แน่ใจว่าreturnคำสั่งที่สองไม่สามารถเข้าถึงความหมายได้:

private static int oneRun(int range) {
    int[] rInt = new int[range+1]; // Stores the past sequence of ints.
    int count = 0;

    while (true) {
        rInt[count] = generator.nextInt(range); // Add randint to current iteration.
        for (int i = 0; i < count; i++) { // Check for past occurence and return if found.
            if (rInt[i] == rInt[count]) {
                return count;
            }
        }
        count++;
    }
}

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


1
ตรง: เงื่อนไขนี้ไม่ได้ใช้เพื่อควบคุมลูปจริงๆดังนั้นจึงไม่ควรอยู่ที่นั่น อย่างไรก็ตามฉันจะเขียนสิ่งนี้for(int count = 0; true; count++)แทน
cmaster - คืนสถานะ monica

3
@cmaster: คุณสามารถละเว้นtrue:for(int count = 0; ; count++) …
Holger

@ โฮลเจอร์อา. ฉันไม่แน่ใจในเรื่องนี้เพราะเป็นเรื่องเกี่ยวกับ java และฉันไม่ได้สัมผัสภาษานั้นมาหลายปีแล้วเนื่องจากฉันใช้ภาษา C / C ++ มากขึ้น ดังนั้นเมื่อฉันเห็นการใช้while(true)ฉันคิดว่า java อาจจะเข้มงวดกว่านี้เล็กน้อย ยินดีที่ได้รู้ว่าไม่จำเป็นต้องกังวล ... จริงๆแล้วฉันชอบtrueเวอร์ชั่นที่ละไว้มากกว่านี้มาก: มันทำให้เห็นภาพชัดเจนว่าไม่มีเงื่อนไขให้ดู :-)
cmaster - คืนสถานะโมนิกา

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

1
@ l0b0 คำถามทั้งหมดน่าจะดีกว่าสำหรับการตรวจสอบโค้ดเนื่องจากคำเตือนนั้นเกี่ยวข้องกับคุณภาพของโค้ด
Sulthan

18

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

private static int oneRun(int range) {
    int returnValue=-1;

    int[] rInt = new int[range+1]; // Stores the past sequence of ints.
    rInt[0] = generator.nextInt(range); // Inital random number.

    OUTER: for (int count = 1; count <= range; count++) { // Run until return.
        rInt[count] = generator.nextInt(range); // Add randint to current iteration.   
        for (int i = 0; i < count; i++) { // Check for past occurence and return if found.
            if (rInt[i] == rInt[count]) {
                returnValue = count;
                break OUTER;
            }
        }
    }
    return returnValue;
}

สิ่งนี้จะไม่ล้มเหลวในการรวบรวมเพราะreturnValueสามารถใช้แบบไม่กำหนดค่าเริ่มต้นได้หรือไม่?
pts

1
เป็นไปได้มากที่สุด วัตถุประสงค์ของโพสต์คือการแสดงวิธีการแยกออกจากลูปทั้งสองตามที่ผู้โพสต์ต้นฉบับถามเกี่ยวกับเรื่องนี้ OP มั่นใจว่าการดำเนินการจะไม่สิ้นสุดของวิธีการดังนั้นสิ่งที่คุณนำมาสามารถแก้ไขได้โดยการเริ่มต้น returnValue เป็นค่าใด ๆ เมื่อมีการประกาศ
David Choweller

4
ไม่ใช่ป้ายกำกับที่คุณควรหลีกเลี่ยงด้วยวิธีใด ๆ
Serverfrog

2
ฉันเชื่อว่าคุณกำลังคิดถึง gotos
David Choweller

4
ป้ายกำกับในภาษาโปรแกรมระดับสูง (-ish) ป่าเถื่อนสุด ๆ ...
xDaizu

13

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

Set<Integer> previous = new HashSet<Integer>();

int randomInt = generator.nextInt(range);
previous.add(randomInt);

for (int count = 1; count <= range; count++) {
    randomInt = generator.nextInt(range);
    if (previous.contains(randomInt)) {
       break;
    }

    previous.add(randomInt);
}

return previous.size();

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

ตอนนี้เรารู้แล้วว่าเราไม่ต้องการcountดัชนีนั้นด้วยซ้ำ:

Set<Integer> previous = new HashSet<Integer>();

int randomInt = generator.nextInt(range);

while (!previous.contains(randomInt)) {          
    previous.add(randomInt);      
    randomInt = generator.nextInt(range);
}

return previous.size();

8

เนื่องจากค่าส่งคืนของคุณขึ้นอยู่กับตัวแปรของวงนอกคุณสามารถเปลี่ยนเงื่อนไขของวงนอกเป็นcount < rangeแล้วส่งคืนค่าสุดท้ายนี้ (ซึ่งคุณเพิ่งข้ามไป) ในตอนท้ายของฟังก์ชัน:

private static int oneRun(int range) {
    ...

    for (int count = 1; count < range; count++) {
        ...
    }
    return range;
}

ด้วยวิธีนี้คุณไม่จำเป็นต้องแนะนำรหัสที่ไม่มีวันเข้าถึง


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

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

5

ใช้ตัวแปร temp ตัวอย่างเช่น "ผลลัพธ์" และลบผลตอบแทนภายใน เปลี่ยนลูป for a while loop ด้วยเงื่อนไขที่เหมาะสม สำหรับฉันแล้วมันสวยกว่าเสมอที่จะมีผลตอบแทนเพียงครั้งเดียวเป็นคำสั่งสุดท้ายของฟังก์ชัน


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

@ Solomonoff'sSecret คำพูดของคุณไร้สาระ หมายความว่าเรา "โดยทั่วไป" ควรตั้งเป้าหมายสำหรับข้อความแสดงผลตอบแทนสองรายการขึ้นไป น่าสนใจ
เดวิด

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

@David ให้ฉันเขียนใหม่: ไม่มีเหตุผลทั่วไปที่จะมีการส่งคืนเพียงครั้งเดียว โดยเฉพาะอย่างยิ่งอาจมีสาเหตุ แต่โดยทั่วไปแล้วลัทธิขนส่งสินค้านั้นแย่ที่สุด
คืนสถานะ Monica

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

3

บางทีนี่อาจเป็นข้อบ่งชี้ว่าคุณควรเขียนโค้ดของคุณใหม่ ตัวอย่างเช่น:

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

3

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

เนื่องจากคุณต้องการให้เมธอดของคุณส่งคืนผลลัพธ์แรกเมื่อ rInt [i] เท่ากับ rInt [count] การใช้ตัวแปรที่กล่าวถึงข้างต้นเท่านั้นจึงไม่เพียงพอเนื่องจากเมธอดจะส่งคืนผลลัพธ์สุดท้ายเมื่อ rInt [i] เท่ากับ rInt [count] ทางเลือกหนึ่งคือการใช้ "break statement" สองรายการที่เรียกเมื่อเราได้ผลลัพธ์ที่ต้องการ ดังนั้นวิธีการจะมีลักษณะดังนี้:

private static int oneRun(int range) {

        int finalResult = 0; // the above-mentioned variable
        int[] rInt = new int[range + 1];
        rInt[0] = generator.nextInt(range);

        for (int count = 1; count <= range; count++) {
            rInt[count] = generator.nextInt(range);
            for (int i = 0; i < count; i++) {
                if (rInt[i] == rInt[count]) {
                    finalResult = count;
                    break; // this breaks the inside loop
                }
            }
            if (finalResult == count) {
                break; // this breaks the outside loop
            }
        }
        return finalResult;
    }

2

ฉันยอมรับว่าเราควรโยนข้อยกเว้นเมื่อมีข้อความที่ไม่สามารถเข้าถึงได้เกิดขึ้น แค่อยากจะแสดงให้เห็นว่าวิธีการเดียวกันนี้สามารถอ่านได้อย่างไร (ต้องใช้ java 8 stream)

private static int oneRun(int range) {
    int[] rInt = new int[range + 1];
    return IntStream
        .rangeClosed(0, range)
        .peek(i -> rInt[i] = generator.nextInt(range))
        .filter(i -> IntStream.range(0, i).anyMatch(j -> rInt[i] == rInt[j]))
        .findFirst()
        .orElseThrow(() -> new RuntimeException("Shouldn't be reached!"));
}

-1
private static int oneRun(int range) {
    int result = -1; // use this to store your result
    int[] rInt = new int[range+1]; // Stores the past sequence of ints.
    rInt[0] = generator.nextInt(range); // Inital random number.

    for (int count = 1; count <= range && result == -1; count++) { // Run until result found.
        rInt[count] = generator.nextInt(range); // Add randint to current iteration.   
        for (int i = 0; i < count && result == -1; i++) { // Check for past occurence and leave after result found.
            if (rInt[i] == rInt[count]) {
                result = count;
            }
        }
    }
    return result; // return your result
}

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