Doğru döngüler nasıl yazılır?


65

Döngü yazarken çoğu zaman genellikle yanlış sınır koşulları (örneğin: yanlış sonuç) yazıyorum veya döngü sonlandırmaları hakkındaki varsayımlarım yanlıştır (örneğin: sonsuz çalışan döngü). Her ne kadar bazı deneme yanılmalarından sonra varsayımlarımı doğrulamış olsam da, kafamdaki doğru hesaplama modelinin olmayışı yüzünden çok sinirli oldum.

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

Başlangıçta yaptığım hatalar:

  1. J> = 0 yerine j> 0 tuttum.
  2. Dizi [j + 1] = değer veya dizi [j] = değer olup olmadığı kafamı karıştırdı.

Bu tür hatalardan kaçınmak için araçlar / zihinsel modeller nelerdir?


6
Hangi şartlar altında j >= 0bunun bir hata olduğuna inanıyorsunuz ? İlk önce kontrol etmeden array[j]ve erişmeden olduğunuzda daha dikkatli olurdum . array[j + 1]array.length > (j + 1)
Ben Cottrell

5
@LightnessRacesinOrbit’in söylediğine benzer şekilde, muhtemelen daha önce çözülmüş olan problemleri çözüyorsunuz. Genel olarak, bir veri yapısı üzerinden yapmanız gereken herhangi bir döngüyü konuşursanız, bazı çekirdek modüllerde veya sınıflarda zaten vardır Array.prototype(JS örneğinde). Bu map, tüm dizilerde olduğu gibi çalıştığı için en son koşullarla karşılaşmanızı engeller . Hepsini bir araya getirmekten kaçınmak için yukarıdaki dilimi ve concat'ı kullanarak çözebilirsiniz: codepen.io/anon/pen/ZWovdg?editors=0012 Bir döngü yazmanın en doğru yolu hiç yazmamaktır .
Jed Schneider

13
Aslında, devam et ve çözülmüş problemleri çöz. Buna pratik denir. Sadece onları yayınlamakla uğraşma. Yani, çözümleri geliştirmek için bir yol bulamazsanız. Bununla birlikte, tekerleğin yeniden icat edilmesi tekerleğin daha fazlasını içerir. Tüm tekerlek kalite kontrol sistemi ve müşteri desteğini içerir. Yine de, özel jantlar güzel.
candied_orange 17:16

53
Korkarım burada yanlış yöne gidiyoruz. CodeYogi saçmalığını vermek, onun örneği iyi bilinen bir algoritmanın parçası olduğu için oldukça temelsizdir. Yeni bir şey icat ettiğini iddia etmedi. Bir döngü yazarken bazı yaygın sınır hatalarından nasıl kaçınılacağını soruyor. Kütüphaneler çok yol kat etti ama yine de halka yazmayı bilen insanlar için bir gelecek görüyorum.
candied_orange 17:16

5
Genel olarak, döngüler ve indekslerle uğraşırken, endekslerin elementler arasında işaret ettiğini ve kendinizi yarı açık aralıklarla tanıdığınızı öğrenmelisiniz (aslında, bunlar aynı kavramların iki yüzüdür). Bu gerçekleri elde ettiğinizde, ilmeklerin / indekslerin çoğu kafa çizilmemesi tamamen kaybolur.
Matteo Italia

Yanıtlar:


208

Ölçek

Hayır, cidden, test et.

20 yıldan fazla bir süredir kod yazıyorum ve hala ilk kez doğru bir döngü yazmak için kendime güvenmiyorum. Çalıştığından şüphelendiğimden önce çalıştığını kanıtlayan testler yazıp çalıştırıyorum. Her sınır koşulunun her bir tarafını test edin. Mesela rightIndex0'dan bir ne yapmalı? -1 nasıl?

Basit tutmak

Başkaları bir bakışta ne yaptığını göremiyorsa, çok zorlaştırıyorsunuzdur. Anlaşılması kolay bir şey yazabileceğiniz anlamına gelirse, performansı yok saymaktan çekinmeyin. Sadece gerçekten ihtiyaç duyduğunuz muhtemel olmayan olaylarda daha hızlı yapın. Ve o zaman bile, bir kez bile sizi yavaşlatan şeyin ne olduğunu tam olarak bildiğinizden eminsiniz. Gerçek bir Büyük O iyileştirmesi başarabilirseniz, bu aktivite anlamsız olmayabilir, ancak o zaman bile kodunuzu olabildiğince okunabilir hale getirin.

Teker teker kapalı

Parmaklarınızı sayma ve parmaklarınız arasındaki boşlukları sayma arasındaki farkı öğrenin. Bazen boşluklar aslında önemli olan şeydir. Parmaklarının seni rahatsız etmesine izin verme. Baş parmağınızın parmak olup olmadığını bilin. Pinky ve başparmak arasındaki boşluğun bir boşluk olarak sayılıp sayılmadığını bilin.

Yorumlar

Kodda kaybolmadan önce ingilizce ne demek istediğinizi söylemeye çalışın. Beklentilerinizi açıkça belirtin. Kodun nasıl çalıştığını açıklama. Ne yaptığını neden yaptığını açıkla. Uygulama detaylarını bunun dışında tutun. Yorumu değiştirmeye gerek kalmadan kodu yeniden düzenlemek mümkün olmalıdır.

En iyi yorum iyi bir isim.

Söylemeniz gereken her şeyi iyi bir adla söyleyebiliyorsanız, bir yorumla tekrar söylemeyin.

Soyutlamalar

Nesneler, işlevler, diziler ve değişkenler, yalnızca verilen adlar kadar iyi olan soyutlamalardır. İnsanların içlerine baktıklarında, buldukları şey karşısında şaşırmayacaklarını garanti eden isimler verin.

Kısa isimler

Kısa ömürlü şeyler için kısa isimler kullanın. igüzel bir dar döngüdeki bir indeks için küçük bir kapsam anlamına gelir. Eğer ibirbirleriyle karıştırılabilecek diğer fikir ve isimlerle aynı çizgide yayılmak için yeterince uzun yaşarsa, uzun ve güzel bir açıklayıcı isim iverme zamanı gelmiştir i.

Uzun isimler

Asla sadece çizgi uzunluğu dikkate alındığında bir ismi kısaltmayın. Kodunuzu düzenlemek için başka bir yol bulun.

Beyaz boşluk

Kusurlar okunamayan kodda gizlenmeyi sever. Diliniz girintileme stilinizi seçmenize izin veriyorsa, en azından tutarlı olun. Kodunuzu bir ses akışı gibi göstermeyin. Kod, oluşumunda ilerliyormuş gibi görünmelidir.

Döngü yapıları

Dilinizdeki döngü yapılarını öğrenin ve inceleyin. Bir hata ayıklayıcısını vurgulamak bir for(;;)döngü vurgulamak çok öğretici olabilir. Tüm formları öğren. while, do while, while(true), for each. Kurtulabileceğiniz en basitini kullanın. Pompayı doldurmaya bakın . Ne varsa breakve continueonlara sahipseniz yapın. Arasındaki farkı bilir c++ve ++c. Kapanması gereken her şeyi her zaman kapattığınız sürece erken dönmekten korkmayın. Sonunda, açtığınızda otomatik olarak kapanması için onu işaretleyen bir şeyi engeller veya tercihen: • İfade kullanma / Kaynakları ile Dene .

Döngü alternatifleri

Yapabiliyorsanız döngüde başka bir şey yapsın. Gözlerde daha kolay ve zaten hata ayıklandı. Bunlar çeşitli biçimlerde gelir: koleksiyonları veya izin akışları map(), reduce(), foreach(), ve diğer bu tür yöntemler bir lambda geçerlidir. Gibi özel işlevler için bak Arrays.fill(). Özyinelemeler de var ama sadece özel durumlarda işleri kolaylaştırmasını bekliyorlar. Genellikle alternatifin neye benzediğini görene kadar özyinelemeyi kullanmayın.

Oh ve test et.

Test et, test et, test et.

Testten bahsettim mi?

Bir şey daha vardı. Hatırlayamıyorum. T ile başladı ...


36
İyi cevap, ama belki de testten bahsetmelisin. Birim testinde sonsuz döngü ile nasıl baş edilir? Böyle bir döngü testleri kesmez mi ???
GameAlchemist

139
@GameAlchemist Bu pizza testi. Eğer kodum zaman içinde çalışmayı bırakmazsa, bir pizza yapmak beni zorluyor, bir şeylerin yanlış olduğundan şüphelenmeye başladım. Tabii Alan Turing'in durma problemi için bir tedavi yok, ama en azından anlaşmadan bir pizza alıyorum.
candied_orange 17:16

12
@CodeYogi - aslında, çok yaklaşabilir. Tek bir değerde çalışan bir testle başlayın. Kodu bir döngü olmadan uygulayın. Ardından iki değer üzerinde çalışan bir test yazın. Döngüyü uygulayın. Öyle çok böyle bir hata yaparsanız hemen her koşullar bir veya iki testin diğer başarısız olur, çünkü böyle yaparsanız döngü yanlış bir sınır koşulu alırsınız olası.
Jules

15
@CodeYogi TDD'ye verilen tüm kredileri hesaplayın ancak Test >> TDD. Bir değerin çıktısı test edilebiliyor, kodunuza ikinci bir dizi göz atmak test ediyor (bunu bir kod incelemesi olarak resmileştirebilirsiniz, ancak çoğu zaman 5 dakikalık bir konuşma için birini yakalarım). Bir test, başarısız olma niyetinizin bir ifadesini vermeniz için herhangi bir şanstır. Cehennem kodunu annenle bir fikir konuşarak test edebilirsin. Duştaki kiremitlere bakarken kodumda hatalar buldum. TDD, her dükkanda bulamadığınız etkili bir formalize disiplindir. Bir zamanlar insanların test etmediği hiçbir yere kodlamadım.
candied_orange 17:16

12
TDD'yi duymadan önce, yıllar ve yıllar boyunca kodlama ve test ediyordum. Şimdilik sadece pantolon giyerken kodlamada harcanan yıllar ile o yılların korelasyonunu fark ettim.
candied_orange

72

Programlama yaparken aşağıdakileri düşünmeniz yararlı olacaktır:

ve keşfedilmemiş bölgeleri araştırırken (örneğin, endekslerle hokkabazlık yapmak gibi) sadece bunları düşünmek değil, aslında iddialarla kodda açıkça ifade etmek çok, çok yararlı olabilir .

Orijinal kodunu alalım:

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

Ve elimizde ne olduğunu kontrol edin:

  • ön koşul: array[0..rightIndex]sıralanır
  • koşul sonrası: array[0..rightIndex+1]sıralanır
  • değişmez: 0 <= j <= rightIndexama biraz gereksiz görünüyor; veya yorumlarda belirtildiği gibi, bir "yuvarlak" ın sonunda for n in [j, rightIndex+1] => array[j] > value.
  • değişmez: bir "yuvarlak" sonunda array[0..rightIndex+1], sıralanır

Böylece önce bir dizi diliminde çalışan is_sortedbir minfonksiyonun yanı sıra bir fonksiyon yazabilir ve sonra şunu söyleyebilirsiniz:

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

Ayrıca döngü durumunuzun biraz karmaşık olduğu da bir gerçektir; şeyleri bölerek kendinize kolaylaştırmak isteyebilirsiniz:

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

Şimdi, döngü (basittir jgider rightIndexiçin 0).

Son olarak, şimdi bunun test edilmesi gerekiyor:

  • sınır koşullarını düşünün ( rightIndex == 0, rightIndex == array.size - 2)
  • valuedaha küçük array[0]veya daha büyük olduğunu düşününarray[rightIndex]
  • düşünmek valueeşit olma array[0], array[rightIndex]ya da bazı orta indeksi

Ayrıca, çıldırtmayı hafife almayın . Hataları yakalamak için iddialarınız var, bu nedenle rastgele bir dizi oluşturun ve yönteminizi kullanarak sıralayın. Bir iddia ortaya çıkarsa, bir hata buldunuz ve test takımınızı uzatabilirsiniz.


8
@CodeYogi: İle ... testler. Mesele şu ki, her şeyi iddialarda ifade etmek pratik olmayabilir : eğer iddia sadece kodu tekrarlarsa, yeni bir şey getirmez (tekrarlama kaliteye yardımcı olmaz). İşte bu yüzden, döngüde iddia etmedim ki, 0 <= j <= rightIndexya array[j] <= valueda sadece kodu tekrar edecekti. Öte yandan, is_sortedyeni bir garanti getiriyor, bu yüzden değerli. Daha sonra, testler bunun için var. insert([0, 1, 2], 2, 3)İşlevinize çağrı yaparsanız ve çıktı değilse [0, 1, 2, 3]bir hata bulmuşsunuz demektir.
Matthieu M.

3
@MatthieuM. Bir iddianın değerini iskonto etmeyin çünkü sadece kodu kopyalar. En önemlisi, kodu yeniden yazmaya karar verirseniz, bunlar çok değerli iddialar olabilir. Test, çoğaltıcı olma hakkına sahiptir. Aksine, iddiaların tek bir kod uygulamasına bağlanıp bağlanmadığını, herhangi bir yeniden yazmanın iddiaları geçersiz kılacağını düşünün. O zaman vaktini boşa harcıyorsun. Bu arada güzel cevap.
candied_orange 17:16

1
@CandiedOrange: Kodu kopyalayarak tam anlamıyla kastediyorum array[j+1] = array[j]; assert(array[j+1] == array[j]);. Bu durumda, değer çok düşük görünüyor (bir kopyala / yapıştır). Anlamını kopyalarsanız ancak başka bir şekilde ifade ederseniz, o zaman daha değerli hale gelir.
Matthieu M.

10
Hoare'in kuralları: 1969'dan bu yana doğru döngüler yazmaya yardımcı olmak. "Yine de, bu teknikler on yıldan uzun bir süredir bilinmesine rağmen, çoğu porgramcı onları hiç duymamış".
Joker_vD

1
@MatthieuM. Çok düşük bir değere sahip olduğuna katılıyorum. Ama bunun kopya / yapıştır olmasından kaynaklandığını sanmıyorum. Demek yeniden kırmak istedim, insert()böylece eski bir diziden yeni diziye kopyalayarak çalıştı. Bu diğerlerinden ayrılmadan yapılabilir assert. Ama bu sonuncusu değil. Sadece diğerlerinin ne kadar iyi asserttasarlandıklarını gösterir.
candied_orange

29

Birim testi / TDD kullanın

Dizilere gerçekten bir fordöngüden erişmeniz gerekiyorsa , birim sınama ve özellikle test odaklı geliştirme hatalarını önleyebilirsiniz .

Sıfırdan büyük değerleri ters sırayla alan bir yöntem uygulamanız gerektiğini düşünün. Hangi test vakalarını düşünebilirsiniz?

  1. Bir dizi sıfıra üstün bir değer içerir.

    Fiili: [5]. Beklenen: [5].

    Gereksinimleri karşılayan en basit uygulama, kaynak sırasını arayan kişiye geri döndürmekten ibarettir.

  2. Bir dizi, her ikisi de sıfıra üstün olan iki değer içerir.

    Fiili: [5, 7]. Beklenen: [7, 5].

    Şimdi sadece diziyi geri döndüremezsiniz, ancak geri çevirmelisiniz. Bir for (;;)döngü, başka bir dil yapısını veya kütüphane yöntemini kullanır mısınız ?

  3. Bir dizi, biri sıfır olmak üzere üç değer içerir.

    Fiili: [5, 0, 7]. Beklenen: [7, 5].

    Şimdi değerleri filtrelemek için kodu değiştirmelisiniz. Yine, bu bir ififade veya favori çerçeve yönteminize yapılan bir çağrı ile ifade edilebilir .

  4. Algoritmanıza bağlı olarak (bu, beyaz kutu testi olduğundan, uygulama önemlidir), özellikle boş sıralama [] → []durumunu ele almanız gerekebilir veya olmayabilir. Veya tüm değerlerin negatif [-4, 0, -5, 0] → []olduğu kenar durumunun doğru kullanıldığından veya hatta sınır negatif değerlerinin:: olduğundan emin olabilirsiniz [6, 4, -1] → [4, 6]; [-1, 6, 4] → [4, 6]. Bununla birlikte, çoğu durumda, yalnızca yukarıda açıklanan üç sınava sahip olacaksınız: herhangi bir ek sınama kodunuzu değiştirmenize neden olmaz ve bu nedenle ilgisiz olur.

Daha yüksek soyutlama seviyesinde çalışın

Ancak, çoğu durumda, mevcut kitaplıkları / çerçeveleri kullanarak daha yüksek bir soyutlama düzeyinde çalışarak bu hataların çoğundan kaçınabilirsiniz. Bu kütüphaneler / çerçeveler dizilere geri dönme, sıralama, bölme ve birleştirme, dizilere veya çift bağlantılı listelerde vb. Değer ekleme veya kaldırma olanağı sağlar.

Genellikle, sınır koşullarını alakasız kontrol ederek yapmak foreachyerine kullanılabilir for: dil sizin için yapar. Python gibi bazı diller bile for (;;)yapıya sahip değil , yalnızca for ... in ....

C # 'da dizilerle çalışırken LINQ özellikle uygundur.

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

fordeğişkenine kıyasla çok daha okunabilir ve daha az hata eğilimlidir :

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

    yield return value;
}

3
Aslında, asıl sorunuza göre, seçimin bir yandan TDD kullanarak ve doğru çözümü alarak, diğer yandan test bölümünü atlayarak ve sınır koşullarını yanlış anladığıma dair bir izlenimim var.
Arseni Mourzenko

18
: Odadaki fili söylemeliyiz olduğunuz için teşekkür ederiz döngüler kullanmayan hiç . Neden insanlar hala 1985 gibi kodluyorlar (ve ben cömert oluyorum) benden öte. BOCTAOE.
Jared Smith,

4
@JaredSmith Bilgisayar gerçekten bir kez bu kodu çalıştırırsa, orada atlamak için bir talimat yoktur. LINQ kullanarak döngüden soyutlanıyorsunuz , ancak hala orada. Bunu , ressam Shlemiel'in zor işleri hakkında bir şeyler öğrenemeyen iş arkadaşlarına anlattım . Kodlarda soyutlanmış olsalar ve kod sonuç olarak daha okunaklı olsalar bile, döngülerin nerede gerçekleştiğini anlayamamak, neredeyse her zaman açıklanamayan bir kişinin açıklamak için kaybedeceği performans sorunlarına neden olur.
bir CVn

6
@ MichaelKjörling: LINQ kullandığınızda, döngü orada, ancak bir for(;;)yapı bu döngü için çok açıklayıcı olmaz . Önemli bir yönü, LINQ'nun (Python'daki ve diğer dillerdeki benzer unsurlardaki liste kavrayışlarının yanı sıra) en azından asıl soru kapsamında sınır koşullarını çoğunlukla alakasız hale getirmesidir. Ancak, özellikle tembel bir değerlendirme söz konusu olduğunda, LINQ kullanırken başlığın altında ne olduğunu anlama gereği konusunda daha fazla anlaşamam.
Arseni Mourzenko

4
@ MichaelKjörling LINQ hakkında mutlaka konuşmuyordum ve amacınızı anlayamıyordum. forEach, map, LazyIteratorVb dilin derleyici ya da çalışma ortamı sağladığı ve tartışmasız olan edilir az geri her tekrarında boya kovasına yürüme olması muhtemeldir. Bu, okunabilirlik ve tek tek hatalar, bu özelliklerin modern dillere eklenmesinin iki nedenidir.
Jared Smith,

15

Kodunuzu test eden diğer insanlarla aynı fikirdeyim. Ancak, ilk etapta doğru almak da güzel. Birçok durumda sınır koşullarını yanlış anlama eğilimindeyim, bu yüzden bu sorunları önlemek için zihinsel hileler geliştirdim.

0 indeksli bir diziyle normal şartlarınız şöyle olacak:

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

veya

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

Bu modeller ikinci nitelikte olmalı, onlar hakkında hiç düşünmek zorunda kalmamalısınız.

Fakat her şey bu kesin kalıbı izlemiyor. Yani doğru yazıp yazmadığınızdan emin değilseniz, işte bir sonraki adım:

Değerleri girin ve kodu kendi beyninizde değerlendirin. Düşünebildiğin kadar düşünmesini basitleştir. İlgili değerler 0 ise ne olur? 1s ise ne olur?

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

Örneğinizde [j] = değer mi yoksa [j + 1] = değer mi olduğundan emin değilsiniz. Manuel olarak değerlendirmeye başlama zamanı:

Dizi uzunluğu 0 olduğunda ne olur? Cevap açık hale gelir: rightIndex, (uzunluk - 1) == -1 olmalıdır, bu nedenle j -1 'de başlar, bu nedenle dizin 0'a eklemek için 1 eklemeniz gerekir.

Bu yüzden son koşulu doğru yaptık, ancak döngünün içini değil.

1 elementli, 10'lu bir diziniz varsa ve 5 eklemeye çalışırsak ne olur? Tek bir elemanla, rightIndex 0'dan başlamalıdır. Bu nedenle, ilk döngü, j = 0, yani "0> = 0 && 10> 5". 5'i 0 dizinine eklemek istediğimiz için, 10'un dizin 1'e taşınması gerekir, bu nedenle dizi [1] = dizi [0]. J 0 olduğunda bu gerçekleştiğinden, dizi [j + 1] = dizi [j + 0].

Büyük bir dizi ve bazı keyfi yerlere ne girdiğini hayal etmeye çalışırsanız, beyniniz muhtemelen boğulacak. Ancak basit 0/1/2 boyutlu örneklere sadık kalırsanız, hızlı bir zihinsel geçiş yapmak ve sınır koşullarınızın nerede bozulduğunu görmek kolay olmalı.

Daha önce çit direkleri problemini hiç duymadığınızı ve düz bir çizgide 100 çit direkleri olduğunu, aralarında kaç segment bulunduğunu hayal edin. Kafanızda 100 çit direği hayal etmeye çalışırsanız, sadece bunalmış olacaksınız. Peki geçerli bir çit yapmak için en az çit direkleri nedir? Bir çit yapmak için 2'ye ihtiyacınız var, 2 yazı hayal edin ve yazılar arasındaki tek bir bölümün zihinsel görüntüsü onu açıkça ortaya koyuyor. Orada oturup direkleri ve parçaları sayarak oturmak zorunda değilsiniz, çünkü problemi beyniniz için sezgisel olarak açık bir hale getirdiniz.

Doğru olduğunu düşündüğünüzde, bir teste tabi tutmanız ve bilgisayarın yapması gerekeni düşündüğünden emin olmasından emin olun, ancak bu noktada sadece bir formalite olmalıdır.


4
Gerçekten sevdiğim for (int i = 0; i < length; i++). Bu alışkanlığa girdiğimde, <=neredeyse bu kadar sık kullanmayı bıraktım ve döngüler daha kolaylaştı. Ancak for (int i = length - 1; i >= 0; i--)şunlara kıyasla aşırı karmaşık görünüyor: for (int i=length; i--; )(daha küçük bir kapsam / ömre sahip whileolmaya çalışmadığım sürece döngü olarak yazmak daha da mantıklı olacaktır i). Sonuç, i == uzunluk-1 (başlangıçta) ile i == 0 arasındaki döngüde devam eder, sadece işlevsel fark, while()sürümün döngüden sonra i == -1 ile bitmesidir (eğer çalışıyorsa), i = yerine = 0.
TOOGAM

2
@TOOGAM (int i = length; i--;) C / C ++ ile çalışır, çünkü 0 yanlış olarak değerlendirilir, ancak tüm diller bu denkliğe sahip değildir. Sanırım söyleyebilirim ki ben--> 0
Bryce Wagner

Doğal olarak, > 0istenen işlevselliği elde etmek için " " gerektiren bir dil kullanıyorsanız , o zaman bu dil için gerekli oldukları için bu karakterlerin kullanılması gerekir. Yine de, bu durumlarda bile, sadece " > 0" kullanmak , ilk çıkarmadan önce iki parçalı bir işlemi yapmaktan ve sonra da " >= 0" kullanmaktan daha basittir . Biraz tecrübeyle, >= 0döngü test koşullarımda eşit işareti (örn., " ") Çok daha az kullanmaya alışma alışkanlığım olduğunu öğrendim ve sonuçta ortaya çıkan kod genellikle o zamandan daha kolay geldi.
TOOGAM

1
@BryceWagner yapmanız gerekiyorsa i-- > 0, neden klasik şakayı denemiyorsunuz i --> 0!
porglezomp

3
@porglezomp Ah, evet, gider operatörü . C, C ++, Java ve C # dahil olmak üzere çoğu C benzeri dilde buna sahiptir.
CVn

11

Kafamdaki doğru hesaplama modelinin olmayışı yüzünden çok sinirlenmiştim.

Bu soruya çok ilginç bir nokta var ve bu yorumu üretti: -

Tek bir yol var: sorununuzu daha iyi anlayın. Ama bu sorunuzun kadar genel. - Thomas Junk

... ve Thomas haklı. Bir işlev için net bir amacı olmayan bir kırmızı bayrak olmalıdır - derhal DURDURmanız, bir kalem ve kağıt almanız, IDE'den uzak durmanız ve sorunu doğru şekilde çözmeniz gerektiğinin açık bir göstergesidir; ya da en azından akıl sağlığını kontrol et. Ne yaptığını kontrol et.

Tam bir karmaşa haline gelen çok sayıda işlev ve sınıf gördüm çünkü yazarlar sorunu tam olarak tanımlamadan önce uygulamayı tanımlamaya çalıştı. Ve başa çıkmak çok kolay.

Sorunu tam olarak anlamadıysanız, aynı zamanda en uygun çözümü kodlama ihtimaliniz de yoktur (verimlilik veya netlik açısından) ya da TDD metodolojisinde gerçekten kullanışlı birim testleri oluşturamazsınız.

Kodunuzu buraya bir örnek olarak alın, örneğin henüz göz önünde bulundurmadığınız birçok olası kusur içeriyor: -

  • Ya rightIndex çok düşükse? (ipucu: o olacaktır veri kaybına neden)
  • rightIndex dizi sınırlarının dışındaysa ne olur? (bir istisna mı alacaksınız yoksa kendinize bir tampon taşması mı yarattınız?)

Kodun performansı ve tasarımı ile ilgili birkaç başka husus var ...

  • bu kodun ölçeklendirilmesi gerekecek mi? Diziyi en iyi seçenek sıralı tutmak mı yoksa diğer seçeneklere mi bakmalısınız (bağlantılı bir liste gibi)?
  • Varsayımlarınızdan emin olabilir misiniz? (dizinin sıralanacağını garanti edebilir misiniz, yoksa değilse?)
  • tekerleği yeniden icat ediyor musun? Sıralanmış diziler bilinen bir problemdir, mevcut çözümleri araştırdınız mı? Dilinizde zaten mevcut olan bir çözüm var mı ( SortedList<t>C # gibi)?
  • bir seferde bir dizi girişini manuel olarak kopyalamalısınız? Diliniz JScript'inki gibi ortak işlevler sunuyor Array.Insert(...)mu? bu kod daha açık olur mu?

Bu kodun geliştirilmesinin pek çok yolu vardır, ancak bu kodun ne yapması gerektiğini doğru bir şekilde tanımlayana kadar, kod geliştirmezsiniz, sadece işe yarayacaklarını umarak birlikte hackliyorsunuzdur. İçine zaman ayır ve hayatın kolaylaşacak.


2
Dizinlerinizi varolan bir işleve (Array.Copy gibi) geçirseniz bile, ilişkili koşulların doğru olması için hala düşünülmesi gerekebilir. 0 uzunluk ve 1 uzunluk ve 2 uzunluk durumlarında neler olduğunu hayal etmek, çok az veya çok fazla kopyalamadığınızdan emin olmanın en iyi yolu olabilir.
Bryce Wagner

@BryceWagner - Kesinlikle doğru, ama aslında hangi problemin ne olduğu konusunda net bir fikrim olmadan, OP’nin çok ötesinde olan bir 'vur ve umut' stratejisinde karanlıkta yaşayarak çok fazla zaman harcayacağınızı çözüyorsunuz. Bu noktada en büyük sorun.
James Snell

2
@Kodyoji - Diğerlerinin de belirttiği gibi, problemi zayıf bir şekilde alt problemlere böldünüz, ve bu yüzden bir dizi cevap problemi önleme yolu olarak çözme yaklaşımınızdan bahseder. Kişisel olarak almanız gereken bir şey değil, sadece bizde olanlardan deneyim edin.
James Snell

2
@CodeYogi, bu siteyi Stack Overflow ile karıştırdığınızı düşünüyorum. Bu site bir bilgisayar terminalinde değil bir beyaz tahtadaki soru-cevap oturumunun karşılığıdır . "Bana kodu göster", yanlış sitede bulunduğuna dair oldukça açık bir göstergedir.
Wildcard

2
@Wildcard +1: "Bana kodu göster", bana göre, bu cevabın neden doğru olduğunu ve belki de bunun sadece insani bir faktör / tasarım problemi olduğunu daha iyi göstermek için yollar üzerinde çalışmam gerektiğini gösteren mükemmel bir gösterge. insan sürecindeki değişikliklerle ele alınmalıdır - hiçbir kod kodu öğretemezdi.
James Snell

10

Bire bir hatalar , en yaygın programlama hatalarından biridir. Tecrübeli geliştiriciler bile bazen bunu yanlış anlıyor. Yüksek seviyeli diller, genellikle tamamen açık bir şekilde endekslenmeyi önleyen foreachveya buna benzer yineleme yapılarına mapsahiptir. Ancak bazen örneğinizde olduğu gibi açıkça endekslemeye ihtiyaç duyabilirsiniz.

Buradaki zorluk, dizi hücrelerinin aralıklarının nasıl düşünüleceğidir . Net bir zihinsel model olmadan, son noktaların ne zaman dahil edileceği veya çıkarılacağı kafa karıştırıcı hale gelir.

Dizi aralıklarını tanımlarken, kural, alt sınır eklemek, üst sınır hariç tutmaktır . Örneğin, 0..3 aralığı, hücreler 0,1,2'dir. Bu kongre, örneğin 0-endeksli dillere boyunca kullanılır slice(start, end)JavaScript'teki yöntem endeksi ile başlayan SubArray döndürür startkadar ancak dahil değil indeksi end.

Dizi dizinleri arasındaki dizi hücreleri arasındaki kenarları açıklayan düşündüğünüzde daha açıktır . Aşağıdaki çizim 9 uzunluğunda bir dizidir ve hücrelerin altındaki sayılar kenarlara hizalanır ve dizi bölümlerini tanımlamak için kullanılır. Örn: Şekil 2.5'den daha açık bir şekilde görülmektedir, bu hücreler 2,3,4'tür.

┌───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ 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

Bu model, dizi uzunluğunun bir dizinin üst sınırı olmasıyla tutarlıdır. 5 uzunluğundaki bir dizi, hücrelerin 0..5 değerine sahiptir, yani, beş hücre 0,1,2,3,4'tür. Bu aynı zamanda, bir segmentin uzunluğunun daha yüksek eksi olduğu, alt sınırın yani 2..5 segmentinin 5-2 = 3 hücreye sahip olduğu anlamına gelir.

Yukarı veya aşağı yinelemede bu modelin göz önünde bulundurulması, uç noktaların ne zaman dahil edileceği veya çıkarılacağı çok daha açık bir şekilde anlaşılır. Yukarı doğru yineleme yaparken başlangıç ​​noktasını eklemeniz, ancak bitiş noktasını hariç tutmanız gerekir. Aşağıya doğru tekrarlarken, başlangıç ​​noktasını (yüksek sınır) hariç tutmanız gerekir, ancak bitiş noktasını (alt sınır) dahil etmeniz gerekir.

Kodunuzda aşağı doğru yinelediğiniz için, düşük sınır olan 0'ı eklemeniz gerekir, bu nedenle tekrarlayın j >= 0.

Bu göz önüne alındığında, rightIndexargümanın alt dizideki son dizini temsil etmesi tercihiniz konvansiyonu bozar. Bu, yinelemeye her iki bitiş noktasını da (0 ve rightIndex) dahil etmeniz gerektiği anlamına gelir. Ayrıca, boş kesimi göstermeyi de zorlaştırır (sıralamaya başlarken ihtiyacınız olan). İlk değeri girerken -1 değerini rightIndex olarak kullanmanız gerekir. Bu oldukça doğal görünüyor. rightIndexSegmentten sonraki dizini belirtmek daha doğal görünüyor , bu nedenle 0 boş bölümü temsil ediyor.

Elbette, kodunuz kafa karıştırıcıdır çünkü sıralanan alt diziyi bir ile genişletir, ilk sıralanan alt diziden hemen sonra öğenin üzerine yazar. Yani j indeksinden okuyorsunuz ama değeri j + 1 olarak yazıyor. Burada, j'nin yerleştirmeden önce ilk alt dizideki konum olduğundan emin olmalısınız. Dizin işlemleri çok zorlaştığında, onu bir kılavuz kağıda çizmeme yardımcı oluyor.


4
@KodYogi: Bir kağıdın üzerine ızgara olarak küçük bir dizi çizerim ve sonra el ile bir yinelemenin içinden bir kalemle geçerim. Bu, gerçekte ne olduğunu açıklığa kavuşturmamda yardımcı olur, örneğin bir hücre dizisinin doğru kaydırıldığı ve yeni değerin yerleştirildiği yer.
JacquesB

3
"Bilgisayar biliminde iki zor şey var: önbellek geçersiz kılma, bir şeyleri isimlendirme ve tek tek hatalar."
Dijital Travma

1
@CodeYogi: Ne dediğimi göstermek için küçük bir şema eklendi.
JacquesB

1
Özel olarak son iki pars okumaya değer harika bir fikir, kafa karışıklığı da for döngüsünün doğası gereğidir, hatta doğru endeksi buldum, hatta döngü sonlandırmadan bir kez önce azalır ve bu nedenle beni bir adım geri alır.
CodeYogi

1
Çok. Mükemmel. Cevap. Ve bu kapsayıcı / ayrıcalıklı endeks konvansiyonunun aynı zamanda myArray.Lengthya da myList.Count- her zaman sıfır tabanlı "en sağdaki" endeksten daha fazlası olan değeriyle de motive edildiğini eklerdim. ... Açıklamanın uzunluğu, bu açık kodlama sezgilerinin pratik ve basit uygulamasına inanır. TL; DR kalabalığı eksik.
radarbob

5

Sorunuza giriş, kodlamayı doğru bir şekilde öğrenemediğinizi düşündürüyor. Zorunlu bir dilde birkaç haftadan daha uzun bir süredir programlama yapan herkes, ilk kez döngü sınırlarını ilk kez, vakaların% 90'ından fazlasında alıyor olmalıdır. Belki de problemi yeterince düşünmeden önce kodlamaya başlamak için acele ediyorsundur.

Döngü yazmayı (yeniden) öğrenerek bu eksikliği gidermenizi öneriyorum - ve bir kaç saat boyunca kağıt kalemle çalışmayı öneriyorum. Bunu yapmak için bir öğleden sonra alın. Ardından, konuyu üzerinde çalışana kadar gerçekten 45 dakika geçirin.

Her şey çok iyi bir sınama, ancak genel olarak döngü sınırlarınızı (ve kodunuzun geri kalanını) doğru bir şekilde almanız beklenmektedir.


4
OP'nin becerileri konusunda çok iddialı olmazdım. Sınır hataları yapmak, özellikle de işe alım röportajı gibi stresli bir bağlamda kolaydır. Deneyimli bir geliştirici bu hataları da yapabilir, ancak açıkça görülüyor ki, deneyimli bir geliştirici bu tür hataları ilk etapta test ederek önleyecektir.
Arseni Mourzenko

3
@MainMa - Mark daha duyarlı olabilirken, bence haklı olduğunu düşünüyorum - Mülakat stresi var ve sorunu tanımlamayı düşünmeden sadece birlikte hackleniyor. Sorunun ifade edilme şekli son derece güçlüdür ve bu, uzun vadede en iyi şekilde çözülebilecek bir şeydir, sağlam bir temele sahip olduğunuzdan emin olun, IDE'yi hacklemeden değil
James Snell

@JamesSnell Kendin hakkında kendine güvendiğini düşünüyorum. Kurallara bakın ve belgelendirildiğini düşündüğünüzü söyleyin bana? Açıkça görürseniz, sorunu çözemediğim hiçbir yer yok mu? Sadece aynı hatayı tekrar etmekten nasıl kaçınacağımı bilmek istedim. Sanırım tüm programlarınızı bir kerede düzeltiyorsunuz.
CodeYogi

4
@CodeYogi Eğer 'deneme yanılma' yapmak zorunda kalırsanız ve 'sinir bozuyorsunuz' ve kodlamada 'aynı hataları yapıyorsanız', o zaman bunlar yazmaya başlamadan önce probleminizi yeterince anlamadığınızın işaretleridir. . Kimse anlamadığınızı söylemiyor, ancak kodunuzun daha iyi düşünülmüş olabileceği ve almak ve onlardan öğrenmek ya da onlardan seçim yapmak istediğinizi belirttiğinin işareti.
James Snell,

2
@CodeYogi ... ve sorduğunuzdan beri, nadiren döngüsel ve dallanmam yanlış olur; çünkü kod yazmadan önce neye ihtiyacım olduğunu net bir şekilde anlamaya başladım, sıralı gibi basit bir şey yapmak zor değil. Dizi sınıfı Bir programcı olarak yapılması en zor şeylerden biri sorun olduğunuzu kabul etmek, ancak bunu yapana kadar gerçekten iyi kod yazmaya başlamayacaksınız.
James Snell

3

Belki de yorumuma biraz et koymalıyım:

Tek bir yol var: sorununuzu daha iyi anlayın. Ama bu sorunuzun kadar genel

Amacın

Her ne kadar bazı deneme yanılmalarından sonra varsayımlarımı doğrulamış olsam da, kafamdaki doğru hesaplama modelinin olmayışı yüzünden çok sinirli oldum.

Okuduğumda trial and erroralarm çanlarım çalmaya başladı. Elbette birçoğumuz zihin durumunu biliyor, ne zaman biri küçük bir sorunu çözmek istiyorsa ve başını başka şeylerin etrafına sardı ve bir ya da başka bir şekilde, kodunu yapmak için seemne yapılması gerekiyorsa onu yapmak için tahmin etmeye başladık . Bazı acayip çözümler bundan ortaya çıkıyor - bazıları saf deha ; ama dürüst olmak gerekirse: çoğu değil . Ben bu durumu bilerek dahil ettim.

Somut probleminizden bağımsız olarak, nasıl geliştirileceği hakkında sorular sordunuz:

1) Test

Bu diğerleri tarafından söylendi ve eklemek için değerli hiçbir şey olmaz

2) Problem Analizi

Buna bir tavsiyede bulunmak zor. Size verebileceğim sadece iki ipucu var, bu da muhtemelen bu konudaki becerilerinizi geliştirmenize yardımcı olur:

  • bariz ve en önemsiz olanı uzun vadede en etkili olanıdır: birçok sorunu çöz. Pratik yaparken ve tekrarlarken, gelecekteki görevler için size yardımcı olan zihniyeti geliştirirsiniz. Programlama, sıkı çalışma pratiği ile geliştirilecek diğer aktiviteler gibidir.

Code Katas, biraz yardımcı olabilecek bir yol.

Harika bir müzisyen olmak için nasıl? Teoriyi bilmek ve enstrümanınızın mekaniğini anlamak için yardımcı olur. Yetenek sahibi olmak için yardımcı olur. Fakat nihayetinde, büyüklük pratik yapmaktan gelir; Teoriyi tekrar tekrar uygulamak, her zaman daha iyi olmak için geri bildirimleri kullanmak.

Kod Kata

Çok sevdiğim bir site: Kod Savaşları

Mücadeleyle ustalık kazanın Başkalarıyla gerçek kod mücadelelerinde eğitim alarak becerilerinizi geliştirin

Programlama becerilerinizi geliştirmenize yardımcı olan nispeten küçük problemlerdir. Ve ben en çok sevdiğim Kod Wars karşılaştırmak olabilir yani, senin birine çözümünü başkalarına .

Ya da belki topluluktan geri bildirim alabileceğiniz Exercism.io'ya bir göz atmalısınız .

  • Diğer tavsiyeler neredeyse önemsizdir: Problemleri çözmeyi öğrenin. Kendinizi eğitmek zorundasınız, problemleri gerçekten küçük problemlere bölmek zorundasınız. Eğer dersen sorunları var yazı döngüler Eğer bütün bir yapı olarak döngü görmek ve parçalar halinde Deconstruct olmadığını, sen hata yaparsanız,. Her şeyi adım adım ayırmayı öğrenirseniz, bu tür hatalardan kaçınmayı öğrenirsiniz.

Yukarıda da belirttiğim gibi bazen böyle bir durumdasınız - "basit" şeyleri "ölü basit" görevlere bölmenin zor olduğunu; ama çok yardımcı olur.

Profesyonel olarak programlama ilk olarak öğrendiğimde , kodumun hatalarını ayıklama konusunda büyük problemlerim olduğunu hatırlıyorum . Problem neydi? Hybris - Hata kodun böyle ve böyle bir bölgesinde olamaz , çünkü olamayacağını biliyorum . Ve sonuç olarak? Öğrenmek zorunda kaldım , analiz etmek yerine kodun üzerinden dolaştım - talimat için kodumu kırmak için sıkıcı olsa bile .

3) Bir Alet Kemeri Geliştirin

Dilinizi ve araçlarınızı bilmenin yanı sıra - bunların geliştiricilerin önce düşündükleri parlak şeyler olduğunu biliyorum - Algoritmaları (aka okuma).

İşte başlamak için iki kitap:

Bu, yemek pişirmeye başlamak için bazı tarifler öğrenmek gibidir. İlk başta ne yapacağınızı bilemezsiniz, bu yüzden bakmak zorundasınız, önceki şefler sizin için ne pişirdi. Aynısı algoritmalar için de geçerlidir. Algoritmalar, sıradan yemekler için yemek tarifleri gibidir (veri yapıları, sıralama, karma vb.) Eğer onları kalpten tanıyorsanız (en azından denemek için), iyi bir başlangıç ​​noktanız vardır.

3a) Programlama yapılarını bilir

Bu nokta bir türevdir - demek ki. Dilinizi bilin - ve daha iyisi: Dilinizde hangi yapıların mümkün olduğunu bilin .

Kötü veya verimsiz kod için ortak bir nokta bazen programcının farklı döngü türleri ( for-, while-ve do-loops) arasındaki farkı bilmediğidir. Bir şekilde hepsi birbirinin yerine kullanılabilir; ancak bazı durumlarda başka bir döngü yapısı seçerek daha şık bir kod elde edilebilir.

Ve Duff’un cihazı var ...

Not:

Aksi halde, yorumunuz Donal Trump’tan iyi değildir.

Evet, tekrar kodlamayı iyi yapmalıyız!

Stackoverflow için yeni bir slogan.


Ah, size çok ciddi bir şey söylememe izin verin. Bahsettiğin her şeyi yaptım ve sana bu sitelerdeki linkimi bile verebilirim. Ama benim için sinir bozucu olan şey, sorumun cevabını almak yerine programlama konusunda mümkün olan her tavsiyeyi alıyorum. Sadece bir adam şu ana pre-postkadar şartlardan bahsetti ve bunu takdir ediyorum.
CodeYogi

Söylediklerine göre, sorunun nerede olduğunu hayal etmek zor. Belki bir metafor yardımcı olur: benim için "nasıl görebilirim" demeye benziyor - benim için bariz cevap "gözlerini kullanmak" çünkü görmek benim için çok doğal, hayal edemiyorum, nasıl göremiyorum. Aynısı sorunuz için de geçerli.
Thomas Junk,

"Deneme ve yanılma" konusundaki alarm zilleriyle tamamen katılıyorum. Sorun çözme zihniyetini tam olarak öğrenmenin en iyi yolunun algoritmaları çalıştırmak ve kağıt ve kalemle kodlamak olduğunu düşünüyorum.
Wildcard

Um ... neden programlama konusundaki cevabınızın ortasında bağlamsız alıntı yapılan siyasi bir adayda dilbilgisi bakımından zayıf bir peki yok mu?
Wildcard

2

Sorunu doğru anladıysam, sorunuz ilk aşamadan itibaren nasıl doğru döngüler almayı düşüneceğinizi, döngünüzün doğru olduğundan nasıl emin olamayacağımı (cevabın diğer cevaplarda açıklandığı şekilde test edeceği).

İyi bir yaklaşım olarak düşündüğüm ilk yinelemeyi herhangi bir döngü olmadan yazmak. Bunu yaptıktan sonra, yinelemeler arasında nelerin değişmesi gerektiğini fark etmelisiniz.

0 ya da 1 gibi bir sayı mı? O zaman büyük olasılıkla bir for için ihtiyacınız var ve tombala, ayrıca başlangıç ​​i. O zaman, aynı şeyi kaç kez çalıştırmak istediğinizi düşünün ve son şartınız da olacaktır.

Tam olarak kaç kez çalışacağını bilmiyorsanız, bunun için bir zamana ihtiyacınız olmaz, ancak bir süre veya bir süre için.

Teknik olarak, herhangi bir döngü başka bir döngüye çevrilebilir, ancak doğru döngüyü kullanırsanız kodun okunması kolaydır, bu yüzden bazı ipuçları:

  1. Kendinizi bir for içinde if () {...; break;} yazarken bulursanız, biraz zamana ihtiyacınız var ve zaten şartınız var.

  2. "Her ne kadar" belki de herhangi bir dilde en çok kullanılan döngüdür, fakat imo olmamalıdır. Kendinizi yazma bulursanız bool ok = True; while (check) {bir şeyler yapın ve umarım bir noktada tamam'ı değiştirin}; o zaman bir zamana ihtiyaç duymazsınız, ama zaman, çünkü ilk tekrarlamayı yapmak için ihtiyacınız olan her şeye sahip olursunuz.

Şimdi biraz bağlamda ... Programlanmaya ilk başladığımda (Pascal) İngilizce konuşamadım. Benim için, "için" ve "süre" çok anlamlı değildi, ama "tekrar" (C'deyken) anahtar kelimesi anadilimde hemen hemen aynı, bu yüzden her şey için kullanırdım. Benim düşünceme göre, tekrarlamak (yapmak) en doğal döngüdür, çünkü neredeyse her zaman bir şeyin yapılmasını istersiniz ve daha sonra bir hedefe ulaşılana kadar tekrar ve tekrar yapılmasını istersiniz. "Çünkü", size bir yineleyici sağlayan bir kısayoldur ve koşulu kodun başlangıcına garip bir şekilde yerleştirir, neredeyse her zaman, bir şeyler olana kadar bir şeyler yapılmasını istediğiniz halde. Ayrıca, while sadece if () için bir kestir. Kısayollar sonra için güzel


2

Doğru bir döngü geliştirmek için ön / post koşulları ve değişmezleri nasıl kullanacağına dair daha ayrıntılı bir örnek vereceğim. Birlikte bu tür iddialara şartname ya da sözleşme denir.

Bunu her döngü için yapmaya çalışmanı önermiyorum. Ancak umarım ilgili düşünce sürecini görmeyi yararlı bulursunuz.

Bunu yapmak için, yönteminizi bu tür özelliklerin doğruluğunu kanıtlamak için tasarlanmış Microsoft Dafny adlı bir araca çevireceğim . Ayrıca her döngünün sonlandırılmasını da kontrol eder. Lütfen Dafny'nin bir fordöngü olmadığını ve whilebunun yerine bir döngü kullanmam gerektiğini unutmayın.

Sonunda, bu spesifikasyonları, döngünüzün biraz daha basit bir versiyonunu tasarlamak için nasıl kullanabileceğinizi göstereceğim. Bu basit döngü sürümü , sizin ilk sezginiz gibi, döngü koşuluna j > 0ve atamaya neden olur array[j] = value.

Dafny, bu döngülerin her ikisinin de doğru olduğunu ve aynı şeyi yaptığını ispatlayacaktır .

Daha sonra tecrübelerime dayanarak, nasıl doğru geri döngü yazacağımı, gelecekte bu durumla karşı karşıya kalmanız durumunda size yardımcı olacak genel bir iddiada bulunacağım.

Birinci Bölüm - Metodun şartname yazımı

Karşılaştığımız ilk zorluk, yöntemin gerçekte ne yapması gerektiğini belirlemek. Bu amaçla, yöntemin davranışını belirten öncesi ve sonrası koşulları tasarladım. Belirtimi daha kesin yapmak için value, eklendiği dizini döndürmesini sağlayacak yöntemi geliştirdim .

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..])

Bu şartname, yöntemin davranışını tamamen ele geçirir. Bu şartname ile ilgili temel gözlemim, prosedürün değer rightIndex+1yerine geçirilmesinin basitleştirileceğidir rightIndex. Ancak bu yöntemin nereden çağrıldığını göremediğim için, değişimin programın geri kalanında ne gibi bir etkisi olacağını bilmiyorum.

İkinci Bölüm - değişmeyen döngü belirleme

Şimdi metodun davranışı için bir şartnameye sahibiz, Dafny'yi döngü çalıştırmanın sona ereceği ve istenen son durumla sonuçlanacağı konusunda ikna edecek olan döngü davranışının bir şartnamesini eklemek zorundayız array.

Orijinal döngü, aşağıda döngü değişmezleri eklenmiş Dafny sözdizimine çevrilir Ayrıca, değerin eklendiği dizini döndürmek için de değiştirdim.

{
    // 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
}

Bu Dafny'de doğrular. Bu linki takip ederek kendiniz görebilirsiniz . Böylece, döngünüz birinci bölümde yazdığım yöntem özelliklerini doğru şekilde uygular. Bu yöntem belirtiminin gerçekten istediğiniz davranış olup olmadığına karar vermeniz gerekir.

Dafny'nin burada bir doğruluk kanıtı ürettiğine dikkat edin. Bu, testle elde edilebilecek olandan çok daha güçlü bir doğruluk garantisidir.

Bölüm Üç - basit bir döngü

Şimdi döngünün davranışını yakalayan bir yöntem belirtimimiz var. Döngü davranışını değiştirmemiş olduğumuza dair güvenliğimizi korurken, döngünün uygulanmasını güvenle değiştirebiliriz.

Döngüyü, döngü koşulu ve son değeri hakkındaki orijinal sezgilerinizle eşleşecek şekilde değiştirdim j. Bu döngünün, sorunuzda tanımladığınız döngünden daha basit olduğunu iddia ediyorum. Kullanmak jyerine daha sık kullanılabiliyor j+1.

  1. J 'de başlat rightIndex+1

  2. Döngü koşulunu değiştirin j > 0 && arr[j-1] > value

  3. Atama olarak değiştir arr[j] := value

  4. Döngünün sonundaki döngü sayacını başlangıçtan ziyade azaltın

İşte kod. Döngü değişmezlerinin şimdi yazmak için biraz daha kolay olduğuna dikkat edin:

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

Dördüncü Bölüm - geri döngü hakkında tavsiyeler

Birkaç yıl boyunca birçok döngüyü doğru yazdıktan ve onayladıktan sonra, geriye doğru geri dönüş hakkında aşağıdaki genel tavsiyelere sahibim.

Düşüş sondan ziyade döngünün başlangıcında gerçekleştirilirse, geriye doğru (azalan) bir döngü düşünmek ve yazmak neredeyse her zaman kolaydır.

Ne yazık ki forbirçok dilde döngü yapısı bunu zorlaştırmaktadır.

Bu karmaşıklığın, döngünün ne olması gerektiği ve gerçekte ne olması gerektiği hakkındaki sezgilerinizdeki farka neden olan şey olduğundan şüpheleniyorum (ancak kanıtlayamıyorum). İleri (artan) döngüler hakkında düşünmeye alışkınsınız. Geriye doğru (azalan) bir döngü yazmak istediğinizde, ileriye dönük (artan) bir döngüde meydana gelen sırayı tersine çevirmeye çalışarak döngü oluşturmaya çalışırsınız. Ancak, foryapının çalışma şeklinden ötürü atama ve döngü değişkeni güncellemesinin sırasını tersine çevirmeyi ihmal ettiniz - ki bu geri ve ileri döngü arasındaki işlem sırasının gerçek bir tersine çevrilmesi için gerekli.

Beşinci Bölüm - bonus

Sadece bütünlük rightIndex+1için, yöntem yerine geçerseniz aldığınız kod rightIndex. Bu değişiklik +2, aksi takdirde döngünün doğruluğu hakkında düşünmek için gereken tüm ofsetleri ortadan kaldırır .

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
Eğer downvote eğer gerçekten bir yorum seviniriz
flamingpenguin

2

Bu hakkı kolayca ve akıcı bir şekilde elde etmek bir deneyim meselesidir. Dil doğrudan bunu ifade ya da işleyebilir basit yerleşik şey daha karmaşık bir durum kullandığınız izin vermez rağmen, ne düşünüyorsun "bir kez Revere'nin sırayla her bir öğeyi ziyaret" ve benzeri daha yüksek bir düzeydir daha deneyimli kodlayıcı bunu anında doğru ayrıntılara çevirir, çünkü bunu birçok kez yaptı.

Yazdığınız şey tipik çünkü O zaman bile, daha karmaşık durumlarda, yanlış almak kolaydır değil konserve ortak şey. Daha modern diller ve kütüphaneler ile, yok bunun için bir konserve yapı veya arama olmadığından kolay bir şey yazma. C ++ 'da modern mantra "kod yazmak yerine algoritmalar kullan" dır.

Dolayısıyla, doğru olduğundan emin olmanın yolu, özellikle bu tür bir şey için, sınır koşullarına bakmaktır . İşlerin değiştiği kenarlardaki birkaç durum için kafanızdaki kodu izleyin. Eğer index == array-max, ne olur? Ne hakkında max-1? Eğer kod yanlış bir dönüş yaparsa bu sınırlardan birinde olacaktır. Bazı döngüler, birinci veya son elemanın yanı sıra sınırları oluşturan doğru yapıdaki döngüsel yapı için endişelenmeye ihtiyaç duyar; Örneğin, başvurursanız a[I]ve minimum değer a[I-1]ne zaman olur I?

Ayrıca, (doğru) yineleme sayısının aşırı olduğu durumlara bakın: sınırlar buluşuyorsa ve 0 yineleme yapacaksanız, bu sadece özel bir durum olmadan mı işe yarar? Ve en düşük sınır aynı zamanda en yüksek sınır olan sadece 1 yinelemeye ne dersiniz?

Kenar boşluklarını (her kenarın her iki tarafı) incelemek, döngü yazarken yapmanız gerekenler ve kod incelemelerinde yapmanız gerekenlerdir.


1

Zaten bolca bahsettiğim konulardan uzak durmaya çalışacağım.

Bu tür hatalardan kaçınmak için araçlar / zihinsel modeller nelerdir?

Araçlar

Benim için, daha iyi forve whiledöngüler yazmak için en büyük araç, herhangi bir döngü forveya hiç whileyazmamaktır.

Çoğu modern dil, bu problemi bir şekilde veya başka şekilde hedeflemeye çalışır. Örneğin, Java, Iteratorkullanımı biraz zor olan, en başından beri, daha sonraki sürümlerde bunları daha kolay kullanmak için kısa yol sözdizimi getirmiştir. C #, onları da vardır vb.

Benim şu anda tercih dil, Yakut, fonksiyonel yaklaşımla (üstlenmiştir .each, .mapvs.) tam önünde. Bu çok güçlü. Üzerinde çalıştığım bazı Ruby kod tabanında hızlıca bir sayım yaptım: yaklaşık 10.000 satırda sıfır forve yaklaşık 5 var while.

Yeni bir dil seçmek zorunda kalsaydım, bunun gibi fonksiyonel / veri tabanlı döngüler aranıyorsa öncelik listesinde çok yüksek olurdu.

Zihinsel modeller

whileSadece bir adım yukarıda alabileceğiniz en soyut soyutlamanın bu olduğunu unutmayın goto. Kanımca, forüç halkanın da sıkıca birbirine yapışmasından dolayı bence daha iyi değil, daha da kötüleşiyor.

Öyleyse, forkullanıldığı bir çevrede kalıyorsam , 3 parçanın da basit ve daima aynı olduğundan emin olacağım. Bu yazacağım anlamına gelir

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

Fakat hiçbir şey çok daha karmaşık değil. Belki, belki bazen bir geri sayım olur, ama önlemek için elimden geleni yaparım.

Kullanıyorsam while, döngü koşulunu içeren kıvrılmış iç yüzdeliklerden uzak dururum. İçindeki test while(...)mümkün olduğunca basit olacak ve elimden geldiğince kaçınacağım break. Ayrıca döngü kısa olacaktır (kod satırlarını sayarak) ve daha büyük miktarlarda kod faktörü çıkarılacaktır.

Özellikle eğer gerçek durumun karmaşık olduğu durumlarda, tespit etmesi çok kolay olan ve koşulu whileifadenin içine koyamayan bir "koşul değişkeni" kullanacağım :

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

(Ya da öyle bir şey, tabii ki doğru ölçülerde.)

Bu size "bu değişken 0 ile 10 arasında monotonla çalışıyor" veya "bu döngü yanlış / doğru olana kadar çalışır" olan çok kolay bir zihinsel model sunar. Beyinlerin çoğu bu soyutlama seviyesini iyi idare edebiliyor gibi görünüyor.

Umarım yardımcı olur.


1

Özellikle ters döngüler, mantıklı olmak zor olabilir, çünkü programlama dillerimizin çoğu, hem genel for döngüsü sözdiziminde hem de sıfır tabanlı yarı açık aralıkların kullanımıyla ileri yinelemeye önyargılıdır. Bu seçimlerin yapılacağı dillerin yanlış olduğunu söylemiyorum; Sadece bu seçimlerin ters döngüler hakkında düşünmeyi zorlaştırdığını söylüyorum.

Genel olarak, for-loop'un bir süre döngüsünün etrafında oluşturulmuş sözdizimsel bir şeker olduğunu unutmayın:

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

eşittir:

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

(init basamağında bildirilen değişkenleri döngüde yerel olarak tutmak için ek bir kapsam katmanıyla olabilir).

Bu, birçok ilmek türü için iyidir, ancak geriye doğru yürürken adımın son gelmesi gariptir. Geriye doğru çalışırken, döngü indeksini istediğimden sonraki değerle başlatmanın ve stepkısmı, döngünün tepesine taşımayı daha kolay buluyorum, bunun gibi:

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
}

veya, for döngüsü olarak:

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

Bu sinir bozucu görünebilir, çünkü adım ifadesi başlıktan ziyade vücuttadır. Bu, döngüsel sözdizimi için içsel önyargının talihsiz bir yan etkisidir. Bu nedenle, bazıları sizin yerine bunu yaptığınızı iddia edecektir:

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

Ancak, dizin değişkeniniz işaretsiz bir tür ise bu bir felakettir (C veya C ++ 'da olabileceği gibi).

Bunu akılda tutarak, ekleme işlevinizi yazalım.

  1. Geriye doğru çalışacağımızdan, döngü dizininin "geçerli" dizi yuvasından sonraki giriş olmasına izin vereceğiz. Yarı açık aralıklar çoğu programlama dilindeki aralıkları temsil etmenin doğal yoludur ve bize başvurmadan boş bir diziyi temsil etmenin bir yolunu sağladığı için, son elemanın indeksinden ziyade tamsayı boyutunu alacak şekilde tasarlardım. -1 gibi sihirli bir değere.

    function insert(array, size, value) {
      var j = size;
    
  2. Yeni değer önceki öğeden daha küçük olsa da , kaymaya devam edin. Orada Tabii ki, eğer önceki eleman sadece kontrol edilebilir olduğunu biz ilk biz çok başında olmadığın kontrol etmek zorunda önceki eleman,:

      while (j != 0 && value < array[j - 1]) {
        --j;  // now j become current
        array[j + 1] = array[j];
      }
    
  3. Bu j, yeni değeri istediğimiz yere bırakır .

      array[j] = value; 
    };
    

Jon Bentley tarafından Programlama İncileri , bu tür problemler için zihinsel modellerinizi oluşturmanıza yardımcı olabilecek ekleme sıralama (ve diğer algoritmalar) hakkında çok net bir açıklama sunar.


0

Bir fordöngünün gerçekte ne yaptığı ve nasıl çalıştığının mekaniği hakkında kafanız mı karıştı ?

for(initialization; condition; increment*)
{
    body
}
  1. İlk olarak, başlatma gerçekleştirilir
  2. Ardından durum kontrol edilir
  3. Durum doğruysa, vücut bir kez çalıştırılır. # 6 değilse
  4. Artış kodu yürütülür
  5. Goto # 2
  6. Döngünün sonu

Bu kodu farklı bir yapı kullanarak yeniden yazmanız yararlı olabilir. Burada bir while döngüsü kullanılarak aynı:

initialization
while(condition)
{
    body
    increment
}

İşte başka öneriler:

  • Bir foreach döngüsü gibi başka bir dil yapısı kullanabilir misiniz? Bu durum sizin için durum ve artış adımını önemser.
  • Harita veya Filtre işlevi kullanabilir misiniz? Bazı diller, sizin için bir koleksiyon içinde dahili olarak dolaşan bu adlara sahip fonksiyonlara sahiptir. Siz sadece koleksiyonu ve vücudu tedarik ediyorsunuz.
  • Kendinizi fordöngülerle tanıştırmak için daha fazla zaman harcamalısınız . Onları her zaman kullanırsın. Nasıl çalıştığını görmek için bir hata ayıklayıcısında bir for döngüsü atlamak öneririm.

* "Artırma" terimini kullanırken, sadece vücuttan sonra ve durum kontrolünden önce gelen bir kod olduğunu unutmayın. Bir düşüş ya da hiçbir şey olabilir.


1
neden aşağı oy?
user2023861

0

Ek içgörü girişimi

İçin önemsiz olmayan algoritmalar döngüler ile, aşağıdaki yöntemi deneyebilirsiniz:

  1. Sorunu simüle etmek için 4 konumlu sabit bir dizi oluşturun ve içine bazı değerler koyun ;
  2. İçin algoritma yazın verilen sorunu çözmek , herhangi bir döngü olmadan ve sabit kodlanmış indexings ile ;
  3. Bundan sonra, kodunuzdaki sabit kodlanmış dizinleri bir değişkenle değiştirin iveya jbu değişkenleri gerektiği gibi artırın / azaltın (ancak yine de herhangi bir döngü olmadan);
  4. Kodunuzu yeniden yazın ve önceki ve sonraki koşulları yerine getirerek tekrarlayan blokları bir döngü içine koyun ;
  5. [ isteğe bağlı ] döngünüzü istediğiniz formda olacak şekilde yeniden yazın (süre / süre / yapılacaktır);
  6. En önemlisi algoritmanızı doğru yazmaktır; Ondan sonra, gerektiğinde kodunuzu / döngülerinizi yeniden yönlendirir ve optimize edersiniz (ancak bu, kodu bir okuyucu için artık önemsiz hale getirebilir)

Senin sorunun

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

Döngünün gövdesini el ile birden çok kez yazın

4 pozisyonlu sabit bir dizi kullanalım ve algoritmayı döngüler olmadan manuel olarak yazmaya çalışalım:

           //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

Sabit kodlanmış değerleri kaldırarak yeniden yazma

//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

Bir döngüye çevir

İle 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 / loop'u istediğiniz şekilde optimize edin:

İle 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

ile 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: kod, girişin geçerli olduğunu ve bu dizinin tekrarları içermediğini varsayar;


-1

Bu yüzden mümkün olduğunda daha soyut işlemlerin lehine ham endekslerde faaliyet gösteren döngüler yazmaktan kaçınmalıydım.

Bu durumda şöyle bir şey yapardım (sözde kodda):

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

-3

Örneğinizde, döngünün gövdesi oldukça açık. Ve bazı unsurların en sonda değiştirilmesi gerektiği oldukça açık. Böylece kodu yazarsınız, ancak döngünün başlangıcı, bitiş koşulu ve son ödev olmadan.

Daha sonra koddan uzaklaşın ve gerçekleştirilmesi gereken ilk hareketin hangisi olduğunu bulun. İlk hareketin doğru olması için döngünün başlangıcını değiştirirsiniz. Yine koddan uzaklaşın ve gerçekleştirilmesi gereken son hareketin hangisi olduğunu bulun. Son koşulu doğru olacak şekilde bitiş koşulunu değiştirirsiniz. Ve son olarak, kodunuzdan uzaklaşın ve son ödevin ne olması gerektiğine karar verin ve kodu buna göre düzeltin.


1
Lütfen bir örnek verebilir misiniz?
CodeYogi

OP'nin bir örneği vardı.
gnasher729

2
Ne demek istiyorsun? Ben OP'im.
CodeYogi
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.