Açık değişmez ve post-koşullu kesme / dönüş ile foreach döngüsü


17

Bu en popüler yolu bir değer Bir dizide olup olmadığını kontrol etmeyi (bana öyle geliyor):

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

Ancak, yıllar önce, muhtemelen Wirth veya Dijkstra tarafından okuduğum bir kitapta, bu stilin daha iyi olduğu söylendi (içinde bir çıkış olan bir süre döngüsüyle karşılaştırıldığında):

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

Bu şekilde, ek çıkış koşulu, döngü değişmezinin açık bir parçası haline gelir, döngü içinde gizli koşullar olmaz ve çıkışlardan çıkar, her şey yapılandırılmış bir şekilde daha açık ve daha açıktır. Genel olarak bu ikinci kalıbı mümkün olduğunda tercih ettim ve for-loop'u yalnızca -'den yinelemek için akullandım b.

Yine de ilk versiyonun daha az net olduğunu söyleyemem. Belki de en azından yeni başlayanlar için anlaşılması daha açık ve kolaydır. Yani hala kendime hangisinin daha iyi olduğu sorusunu soruyorum ?

Belki birisi yöntemlerden biri lehine iyi bir gerekçe verebilir mi?

Güncelleme: Bu, çoklu işlev dönüş noktaları, lambdalar veya bir dizide kendiliğinden bir öğe bulma sorunu değildir. Tek bir eşitsizlikten daha karmaşık değişmezlerle döngüler nasıl yazılır.

Güncelleme: Tamam, cevap veren ve yorum yapan kişilerin anlamını görüyorum: Burada bir for döngüsünden çok daha net ve okunabilir olan foreach döngüsünü karıştırdım. Bunu yapmamalıydım. Ancak bu da ilginç bir soru, bu yüzden onu olduğu gibi bırakalım: foreach-loop ve içeride ekstra bir durum veya açık bir döngü değişmezi ve sonrası bir post-condition ile bir while döngüsü. Görünüşe göre koşulu olan bir foreach döngüsü ve bir çıkış / ara kazanıyor. Ben foreach döngü olmadan ek bir soru oluşturacak (bağlantılı bir liste için).


2
Burada belirtilen kod örnekleri birkaç farklı sorunu karıştırmaktadır. Erken ve çoklu geri dönüşler (benim için yöntemin büyüklüğüne (gösterilmiyor)), dizi aramasına (lambdas içeren bir tartışma için yalvarır), foreach ve doğrudan indeksleme ... Bu soru daha net ve daha kolay olurdu bir kerede bu sorunlardan sadece birine odaklanmışsa cevap verin.
Erik Eidt


1
Bunların örnekler olduğunu biliyorum, ancak tam olarak bu kullanım durumunu işlemek için API'leri olan diller var. Iecollection.contains(foo)
Berin Loritsch

2
Kitabı bulmak ve gerçekte ne söylediğini görmek için şimdi tekrar okumak isteyebilirsiniz.
Thorbjørn Ravn Andersen

1
"Daha iyi" oldukça öznel bir kelimedir. Bununla birlikte, bir bakışta ilk sürümün ne yaptığını söyleyebiliriz. İkinci versiyonun aynı şeyi yapması biraz dikkat gerektirir.
David Hammen

Yanıtlar:


19

Bunun gibi basit döngüler için standart ilk sözdizimi çok daha net. Bazı insanlar birden fazla dönüşü kafa karıştırıcı veya bir kod kokusu olarak düşünür, ancak bu küçük bir kod parçası için bunun gerçek bir sorun olduğuna inanmıyorum.

Daha karmaşık döngüler için biraz daha tartışmalı hale gelir. Döngünün içeriği ekranınıza sığmıyorsa ve döngüde birkaç döndürme varsa, çoklu çıkış noktalarının kodun korunmasını zorlaştırabileceği konusunda bir argüman vardır. Örneğin, işlevden çıkmadan önce bazı durum bakım yöntemlerinin çalıştığından emin olmanız gerekiyorsa, döndürme ifadelerinden birine eklemeyi kaçırmak kolay olurdu ve bir hataya neden olursunuz. Tüm bitiş koşulları bir while döngüsünde kontrol edilebilirse, yalnızca bir çıkış noktanız vardır ve bu kodu bundan sonra ekleyebilirsiniz.

Bununla birlikte, döngülerle özellikle mümkün olduğunca çok mantığı ayrı yöntemlere koymaya çalışmak iyi olur. Bu, ikinci yöntemin avantajlara sahip olacağı birçok durumu önler. Açıkça ayrılmış mantığa sahip yalın döngüler, kullandığınız bu stillerin hangisinden daha önemli olacaktır. Ayrıca, uygulamanızın kod tabanının çoğu tek bir stil kullanıyorsa, bu stile sadık kalmalısınız.


56

Bu kolay.

Neredeyse hiçbir şey okuyucu için netlikten daha önemli değildir. İlk varyantı inanılmaz derecede basit ve net buldum.

İkinci 'geliştirilmiş' versiyon, birkaç kez okumak ve tüm kenar koşullarının doğru olduğundan emin olmak zorundaydım.

Daha iyi kodlama stili olan sıfır sıfır (ilk çok daha iyi).

Şimdi - insanlar için TEMİZ olan şey kişiden kişiye değişebilir. Bunun için herhangi bir objektif standart olduğundan emin değilim (bunun gibi bir foruma mesaj göndermek ve çeşitli insanların girişleri almak yardımcı olabilir).

Ancak bu özel durumda, ilk algoritmanın neden daha açık olduğunu söyleyebilirim: Bir kap sözdizimi üzerinde C ++ yinelemesinin neye benzediğini ve ne yaptığını biliyorum. İçselleştirdim. Bu sözdizimiyle UNFAMILIAR (yeni sözdizimi) biri ikinci varyasyonu tercih edebilir.

Ancak yeni sözdizimini öğrendikten ve anladıktan sonra, kullanabileceğiniz temel bir kavramdır. Döngü yineleme (ikinci) yaklaşımı ile, kullanıcının tüm dizi üzerinde döngü için tüm kenar koşullarını doğru şekilde kontrol ettiğini (örneğin, test için kullanılan daha az veya eşit, aynı dizin yerine daha az) doğru bir şekilde kontrol etmeniz gerekir. vb. için).


4
Yeni, 2011 standardında olduğu gibi göreceli. Ayrıca, ikinci demo elbette C ++ değildir.
Tekilleştirici

Alternatif bir çözüm Bir bayrak ayarlamak olacaktır tek bir çıkış noktası kullanmak istiyorsanız longerLength = trueo zaman return longerLength.
Cullub

@Deduplicator Neden ikinci demo C ++ değil? Neden belli bir şeyi kaçırmıyorum ya da özlüyorum?
Rakete1111

2
@ Rakete1111 Ham dizilerin hiçbir özelliği yoktur length. Eğer gerçekten bir işaretçi olarak değil bir dizi olarak bildirildiyse, kullanabilirler sizeofya std::arrayda doğru üye işlevi ise size(), bir lengthözellik yoktur.
IllusiveBrian

@IllusiveBrian: sizeofbayt cinsinden olurdu ... C ++ 17'den bu yana en genel olanı std::size().
Tekilleştirici

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

[…] Her şey yapılandırılmış bir şekilde daha açık ve daha açıktır .

Pek değil. Değişken iise burada döngü dışında bulunan ve (kelime oyunu amaçlı) ise, bu şekilde, dış kapsamına girmektedir xve for-loop sadece döngü kapsamında bulunmaktadır. Kapsam, programlamayı yapıya tanıtmanın çok önemli bir yoludur.



1
@ruakh Yorumunuzdan ne alacağınızdan emin değilim. Sanki cevabım wiki sayfasında yazılanlara karşı çıkıyormuş gibi biraz pasif-agresif görünüyor. Lütfen detaylandırın.
null

"Yapısal programlama" belirli bir anlama sahip bir sanat terimidir ve OP # 2 sürümü, # 1 sürümü yapmazken yapılandırılmış programlama kurallarına uygundur. Cevabınızdan, sanat terimine aşina olmadığınız ve terimi tam anlamıyla yorumladığınız anlaşılıyor. Yorumumun neden pasif-agresif olduğu konusunda emin değilim; Ben sadece bilgilendirici demek istedim.
ruakh

@ ruakh # 2 versiyonunun her açıdan kurallara daha uygun olduğunu kabul etmiyorum ve cevabımda bunu açıkladım.
null

"Sanmıyorum" dersiniz, sanki öznel bir şeymiş gibi, ama değil. Döngünün içinden dönmek, yapılandırılmış programlama kurallarının kategorik bir ihlalidir. Pek çok yapılandırılmış programlama meraklısının minimal kapsamda değişen değişkenlerin hayranları olduğuna eminim, ancak yapılandırılmış programlamadan uzaklaşarak bir değişkenin kapsamını azaltırsanız, yapılandırılmış programlama, dönemden ayrılırsınız ve değişkenin kapsamını azaltmak geri alınmaz söyledi.
ruakh

2

İki döngünün farklı semantiği vardır:

  • İlk döngü basit bir evet / hayır sorusunu cevaplar: "Dizi aradığım nesneyi içeriyor mu?" Bunu mümkün olan en kısa şekilde yapar.

  • İkinci döngü şu soruyu yanıtlar: "Dizi aradığım nesneyi içeriyorsa, ilk eşleşmenin dizini nedir?" Yine, bunu mümkün olan en kısa şekilde yapar.

İkinci sorunun cevabı, birincinin cevabından kesinlikle daha fazla bilgi sağladığından, ikinci soruyu cevaplamayı ve ardından ilk sorunun cevabını türetmeyi seçebilirsiniz. return i < array.length;Zaten çizginin yaptığı da budur .

Zaten mevcut, daha esnek bir aracı tekrar kullanamazsanız, sadece amaca uygun olan aracı kullanmanın en iyisi olduğuna inanıyorum . yani:

  • Döngünün ilk varyantını kullanmak iyidir.
  • İlk varyantı sadece bir booldeğişken ve mola ayarlamak için değiştirmek de iyidir. (İkinci returnifadeden kaçınır , yanıt bir işlev dönüşü yerine bir değişkende kullanılabilir.)
  • Kullanımı std::findiyidir (kodun yeniden kullanımı!).
  • Ancak, açıkça bir bulguyu kodlamak ve sonra a'ya cevabı azaltmak booldeğildir.

Eğer downvoters yorum bırakacak olsaydı iyi olurdu ...
cmaster - reinstate monica

2

Tamamen üçüncü bir seçenek önereceğim:

return array.find(value);

Bir dizi üzerinde yinelemenin birçok farklı nedeni vardır: Belirli bir değerin var olup olmadığını kontrol edin, diziyi başka bir diziye dönüştürün, toplam değeri hesaplayın, bazı değerleri diziden filtreleyin ... Döngü için düz kullanırsanız, net değil bir bakışta özellikle for döngüsünün nasıl kullanıldığı. Bununla birlikte, çoğu modern dil, dizi veri yapılarında bu farklı amaçları çok açık hale getiren zengin API'lere sahiptir.

Bir diziyi for döngüsüyle diğerine dönüştürmeyi karşılaştırın:

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

ve JavaScript stili bir mapişlev kullanarak :

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

Veya bir dizi toplamak:

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

e karşı:

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

Bunun ne yaptığını anlamanız ne kadar sürer?

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

e karşı

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

Her üç durumda da, for döngüsü kesinlikle okunabilir olsa da, for döngüsünün nasıl kullanıldığını anlamak ve tüm sayaçların ve çıkış koşullarının doğru olup olmadığını kontrol etmek için birkaç dakika harcamanız gerekir. Modern lambda tarzı işlevler, döngülerin amaçlarını son derece açık hale getirir ve çağrılan API işlevlerinin doğru bir şekilde uygulandığından emin olabilirsiniz.

JavaScript , Ruby , C # ve Java da dahil olmak üzere çoğu modern dil, diziler ve benzer koleksiyonlarla bu işlevsel etkileşim stilini kullanır.

Genel olarak, döngüler için kullanmanın mutlaka yanlış olduğunu düşünmüyorum ve bu kişisel bir zevk meselesi olsa da, kendimi dizilerle bu tarz bir çalışma biçimini kullanmayı çok iyi buluyorum. Bunun nedeni, her döngünün ne yaptığını belirlemede artan netliktir. Diliniz standart kütüphanelerinde benzer özelliklere veya araçlara sahipse, bu stili de benimsemeyi öneririm!


2
Tavsiye array.findetmek, daha sonra uygulamayı en iyi şekilde tartışmamız gerektiğinden soruyu akla getirir array.find. Yerleşik bir findişlemle donanım kullanmadığınız sürece , buraya bir döngü yazmamız gerekir.
Barmar

2
@Barmar katılmıyorum. Cevabımda da belirttiğim gibi, yoğun olarak kullanılan birçok dil findstandart kütüphanelerinde olduğu gibi işlevler sağlıyor . Kuşkusuz, bu kütüphaneler finddöngüler için kullanarak ve akrabalarını uygular , ancak iyi bir işlev bunu yapar: teknik ayrıntıları işlevin tüketicisinden uzaklaştırır ve programcının bu ayrıntılar hakkında düşünmesine gerek kalmaz. Bu nedenle find, bir for döngüsü ile uygulanmasına rağmen , yine de kodu daha okunabilir hale getirmeye yardımcı olur ve standart kitaplıkta sık sık kullanıldığından, kullanımı anlamlı bir ek yük veya risk eklemez.
Kevin

4
Ancak bir yazılım mühendisi bu kütüphaneleri uygulamak zorundadır. Aynı yazılım mühendisliği ilkeleri, uygulama programcıları olarak kütüphane yazarları için geçerli değil mi? Soru, genel olarak döngüler yazmakla ilgilidir, belirli bir dilde bir dizi öğesi aramanın en iyi yolu değildir
Barmar

4
Başka bir deyişle, bir dizi elemanını aramak, farklı döngü tekniklerini göstermek için kullandığı basit bir örnektir.
Barmar

-2

Her şey tam olarak 'daha iyi' ile kastedilen şeye bağlıdır. Pratik programcılar için, genellikle verimli anlamına gelir - yani bu durumda, doğrudan döngüden çıkmak bir ekstra karşılaştırmayı önler ve bir Boolean sabitinin döndürülmesi iki kez karşılaştırmayı önler; bu döngüleri kurtarır. Dijkstra, doğrulanması daha kolay kod yapmakla daha ilgilidir . [Avrupa'daki CS eğitiminin, ekonomik güçlerin kodlama uygulamasına hâkim olduğu ABD'deki CS eğitiminden çok daha 'ciddiye doğru olduğunu' kanıtladığı anlaşılıyor]


3
PMar, performans açısından her iki döngü de hemen hemen eşdeğerdir - her ikisinin de iki karşılaştırması vardır.
Danila Piatov

1
Biri gerçekten performansla ilgilenseydi, daha hızlı bir algoritma kullanırdı. diziyi sıralayıp ikili bir arama yapın veya Hashtable kullanın.
user949300

Danila - bunun arkasında hangi veri yapısının olduğunu bilmiyorsunuz. Bir yineleyici her zaman hızlıdır. Endeksli erişim doğrusal bir zaman olabilir ve uzunluk bile mevcut değildir.
gnasher729
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.