Java yönteminde gereksiz bir dönüşü nasıl önleyebilirim?


115

returnİki fordöngü içinde iç içe geçmiş ifadeye teorik olarak her zaman ulaşılacağı bir durumum var .

Derleyici buna katılmaz ve döngünün returndışında bir ifade gerektirir for. Şu anki anlayışımın ötesinde olan bu yöntemi optimize etmenin zarif bir yolunu bilmek istiyorum ve kırma girişimlerimden hiçbiri işe yaramıyor gibi görünüyor.

Ekte, rastgele tamsayılar üreten ve bir int parametresi olarak yönteme geçirilen bir aralık içinde oluşturulan ikinci bir rastgele tamsayı bulunana kadar tekrarlanan yinelemeleri döndüren bir atamadan bir yöntem eklenmiştir.

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
Son öğeye asla ulaşılmazsa, while(true)dizinlenmiş döngü yerine bir döngü kullanabilirsiniz. Bu, derleyiciye döngünün asla geri dönmeyeceğini söyler.
Örümcek Boris

101
işlevi 0 (veya 1'den küçük herhangi bir sayı) ( oneRun(0)) olarak return
çağırın

55
Sağlanan aralık negatif olduğunda geri dönüşe ulaşılır. Ayrıca giriş aralığı için 0 doğrulamanız var, bu nedenle şu anda onu başka bir şekilde yakalayamıyorsunuz.
HopefullyHelpful

4
@HopefullyHelpful Tabii ki gerçek cevap bu. Hiç de gereksiz bir dönüş değil!
Mr Lister

4
@HopefullyHelpful hayır, çünkü için nextIntbir istisna atıyor range < 0Geri dönüşün ulaşıldığı tek durumrange == 0
njzk2

Yanıtlar:


343

Derleyicinin buluşsal yöntemi sonuncuyu atlamanıza asla izin vermez return. Asla ulaşılmayacağından eminseniz throw, durumu netleştirmek için onu a ile değiştirirdim .

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
Sadece okunabilirlik değil, aynı zamanda kodunuzda bir sorun olup olmadığını öğrenmenizi sağlar. Jeneratörde bir hata olabilir.
JollyJoker

6
KESİNLİKLE kodunuzun bu "erişilemez koda" asla ulaşamayacağından eminseniz, ortaya çıkabilecek tüm uç durumlar için bazı birim testleri oluşturun (aralık 0, aralık -1, aralık min / maks int). Şu anda özel bir yöntem olabilir, ancak bir sonraki geliştirici bu şekilde tutmayabilir. Beklenmeyen değerleri nasıl işleyeceğiniz (bir istisna atma, bir hata değeri döndürme, hiçbir şey yapmama) yöntemi nasıl kullanacağınıza bağlıdır. Tecrübelerime göre, genellikle sadece bir hatayı kaydetmek ve geri dönmek istersiniz.
Rick Ryker

22
Belki bu olmalıthrow new AssertionError("\"unreachable\" code reached");
Bohemian

8
Bir üretim programında cevabıma tam olarak ne koyduğumu yazardım.
John Kugelman

1
Verilen örnek için başa çıkmanın throw, asla ulaşılamayacak bir eklemekten daha iyi bir yolu var . Çoğu zaman insanlar, altta yatan sorun hakkında çok fazla düşünmeden "hepsini yönetecek bir çözümü" çabucak uygulamak isterler. Ancak programlama, kodlamadan çok daha fazlasıdır. Özellikle algoritmalar söz konusu olduğunda. Verilen problemi (dikkatlice) incelerseniz, dış fordöngünün son yinelemesini çarpanlara ayırabileceğinizi ve dolayısıyla "yararsız" dönüşü yararlı biriyle değiştirebileceğinizi fark edeceksiniz ( bu yanıta bakın ).
a_guest

36

@BoristheSpider'ın belirttiği gibi , ikinci returnifadenin anlamsal olarak erişilemez olduğundan emin olabilirsiniz :

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

Derler ve iyi çalışır. Ve eğer bir ArrayIndexOutOfBoundsExceptionşey alırsanız, açıkça herhangi bir şey atmak zorunda kalmadan uygulamanın anlamsal olarak yanlış olduğunu anlarsınız.


1
Kesinlikle: Koşul aslında döngüyü kontrol etmek için kullanılmadığından orada olmamalıdır. Ancak bunu for(int count = 0; true; count++)onun yerine yazardım.
cmaster - reinstate monica

3
@cmaster: şunları atlayabilirsiniz true:for(int count = 0; ; count++) …
Holger

@Holger Ah. Bundan emin değildim çünkü bu java ile ilgili ve C / C ++ ile daha fazla ilgilendiğim için yıllardır bu dile dokunmadım. Yani kullanımını görünce while(true)belki java'nın bu konuda biraz daha katı olduğunu düşündüm. Endişelenmenize gerek olmadığını bilmek güzel ... Aslında, ihmal edilen truesürümü çok daha iyi seviyorum :
Bakılması

3
"For" olarak değiştirmemeyi tercih ederim. Bir "while-true", bloğun, içindeki bir şey onu kırmadığı sürece koşulsuz döngü yapmasının beklendiği açık bir bayraktır. Atlanmış bir koşulu olan "for", bu kadar net bir şekilde iletişim kurmaz; daha kolay gözden kaçabilir.
Corrodias

1
@ l0b0 Uyarının kod kalitesiyle ilgili olduğu düşünüldüğünde, tüm soru kod incelemesi için muhtemelen daha iyi olacaktır.
Sulthan

18

İki fordöngüden çıkmayı sorduğunuz için, bunu yapmak için bir etiket kullanabilirsiniz (aşağıdaki örneğe bakın):

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

Bu returnValue, başlatılmamış olarak kullanılabileceği için derlenemez mi?
pts

1
Büyük ihtimalle. Gönderinin amacı, orijinal posterin bunu sorduğu gibi, her iki döngüden nasıl çıkılacağını göstermekti. OP, yürütmenin asla yöntemin sonuna gelmeyeceğinden emindi, bu nedenle ortaya çıkardığınız şey, beyan edildiğinde herhangi bir değere returnValue başlatarak düzeltilebilir.
David Choweller

4
Etiketler, herhangi bir şekilde kaçınmanızın daha iyi olacağı şeyler değil mi?
Serverfrog

2
Gotos'u düşündüğüne inanıyorum.
David Choweller

4
Yüksek (-ish) seviyeli bir programlama dilinde etiketler. Absolutely
barbaric

13

İddia etmek iyi bir hızlı çözüm olsa da. Genel olarak bu tür sorunlar, kodunuzun çok karmaşık olduğu anlamına gelir. Kodunuza baktığımda, bir dizinin önceki sayıları tutmasını gerçekten istemediğiniz açık. İstersiniz 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();

Şimdi, döndürdüğümüz şeyin aslında setin boyutu olduğuna dikkat edin. Kod karmaşıklığı ikinci dereceden doğrusala düşmüştür ve hemen daha okunabilir hale gelmiştir.

Artık bu countdizine ihtiyacımız olmadığını fark edebiliriz :

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

Dönüş değeriniz dış döngünün değişkenine dayandığından, dış döngünün koşulunu değiştirebilir count < rangeve ardından bu son değeri (az önce atlamış olduğunuz) işlevin sonunda döndürebilirsiniz:

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

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

Bu şekilde asla ulaşılamayacak bir kod girmenize gerek kalmaz.


Ancak bu, eşleşme bulunduğunda iki döngü katmanından kopmayı gerektirmez mi? İfade için birine indirgemenin bir yolunu görmüyorum, yeni bir rastgele sayının üretilmesi ve bir başkası oluşturulmadan öncekilerle karşılaştırılması gerekiyor.
Oliver Benning

Hâlâ iç içe geçmiş iki döngü ile kaldınız (ikinciyi atladım ...) ancak dış döngü son yinelemesiyle azaltıldı. Bu son yinelemeye karşılık gelen durum, en son dönüş ifadesi tarafından ayrı ayrı ele alınır. Bu, örneğiniz için zarif bir çözüm olsa da, bu yaklaşımın okunmasının daha zor hale geldiği senaryolar vardır; örneğin dönüş değeri iç döngüye bağlıysa, sondaki tek dönüş ifadesi yerine, ek bir döngü (dış döngünün son yinelemesinin iç döngüsünü temsil eden) kalır.
a_guest

5

Geçici değişken kullanın, örneğin "sonuç" ve iç dönüşü kaldırın. Bir while döngüsü için for döngüsünü uygun koşulla değiştirin. Bana göre, işlevin son ifadesi olarak yalnızca bir dönüşün olması her zaman daha zariftir.


Eh, çok iç gibi görünüyor eğer dış while koşulu olabilir. Her zaman için bir süre olduğunu unutmayın (ve daha zarif, çünkü planlamadığınız için her zaman tüm yinelemeleri gözden geçirin). Döngüler için iç içe geçmiş bu ikisi kesinlikle basitleştirilebilir. Tüm bu kod "çok karmaşık" kokuyor.
David

@ Solomonoff'sSecret İfadeniz absürt. "Genel olarak" iki veya daha fazla dönüş ifadesini hedeflememiz gerektiği anlamına gelir. İlginç.
David

@ Solomonoff'sSecret Bu, tercih edilen programlama stilleri meselesidir ve geçen hafta topal olan bu hafta neyin havalı olduğuna bakılmaksızın, yalnızca bir çıkış noktasına sahip olmanın kesin avantajları vardır ve yöntemin sonunda (sadece bir döngü düşünün. birçoğu return resultetrafına serpildi ve sonra resultonu iade etmeden önce bulunanları bir kez daha kontrol etmek istediğini düşünüyor ve bu sadece bir örnek). Eksileri de olabilir, ancak "Genel olarak, yalnızca bir dönüşün olması için iyi bir neden yoktur" gibi geniş bir ifade su tutmaz.
SantiBailors

@David Yeniden ifade edeyim: Tek bir dönüşün olması için genel bir neden yok. Belirli durumlarda nedenler olabilir, ancak tipik olarak en kötü haliyle kargo kültüdür.
Monica'yı

1
Bu, kodu daha okunaklı kılan şeydir. Kafanızda "eğer buysa bulduğumuzu geri verin; yoksa devam edin; bir şey bulamazsak bu diğer değeri geri verin" dersiniz. Daha sonra kodunuzun iki dönüş değeri olmalıdır. Her zaman bir sonraki geliştirici için onu hemen anlaşılır kılan şeyleri kodlayın. Bizim gözümüze iki gibi görünse de derleyicinin bir Java yönteminden yalnızca bir dönüş noktası vardır.
Rick Ryker

3

Belki de bu, kodunuzu yeniden yazmanız gerektiğinin bir göstergesidir. Örneğin:

  1. 0 .. aralık-1 tamsayılarından oluşan bir dizi oluşturun. Tüm değerleri 0 olarak ayarlayın.
  2. Bir döngü gerçekleştirin. Döngüde rastgele bir sayı oluşturun. Değerin 1 olup olmadığını görmek için listenize, o dizine bakın, eğer öyleyse, döngüden çıkın. Aksi takdirde, bu dizindeki değeri 1 olarak ayarlayın
  3. Listedeki 1'leri sayın ve bu değeri döndürün.

3

Return ifadesine sahip olan ve içinde döngü / döngü bulunan yöntemler her zaman döngü (ler) dışında bir dönüş ifadesi gerektirir. Döngünün dışındaki bu ifadeye asla ulaşılmasa bile. Bu gibi durumlarda, gereksiz dönüş ifadelerinden kaçınmak için, yöntemin başlangıcında, yani ilgili döngü (ler) in öncesinde ve dışında ilgili türde bir değişken, sizin durumunuzda bir tamsayı tanımlayabilirsiniz. Döngü içinde istenen sonuca ulaşıldığında, ilgili değeri bu önceden tanımlanmış değişkene atayabilir ve onu döngünün dışındaki return ifadesi için kullanabilirsiniz.

Yönteminizin rInt [i] eşittir rInt [sayım] olduğunda ilk sonucu döndürmesini istediğiniz için, yalnızca yukarıda belirtilen değişkeni uygulamak yeterli değildir çünkü yöntem rInt [i], rInt [sayım] 'a eşit olduğunda son sonucu döndürecektir. Seçeneklerden biri, istenen sonuca sahip olduğumuzda çağrılan iki "break ifadesi" uygulamaktır. Dolayısıyla yöntem şuna benzer:

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

Ulaşılamaz ifadenin ortaya çıktığı yerde bir istisna atılması gerektiğine katılıyorum. Sadece aynı yöntemin bunu daha okunaklı bir şekilde nasıl yapabileceğini göstermek istedim (java 8 akışı gereklidir).

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
}

Döngünün içindeyken göz ardı edilebilecek çok sayıda result == -1kontrol yapılacağından kod da verimsizdir return...
Willem Van Onsem
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.