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 for
döngü olmadığını ve while
bunun 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 > 0
ve 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+1
yerine 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 j
yerine daha sık kullanılabiliyor j+1
.
J 'de başlat rightIndex+1
Döngü koşulunu değiştirin j > 0 && arr[j-1] > value
Atama olarak değiştir arr[j] := value
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 for
birç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, for
yapı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+1
iç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;
}
j >= 0
bunun bir hata olduğuna inanıyorsunuz ? İlk önce kontrol etmedenarray[j]
ve erişmeden olduğunuzda daha dikkatli olurdum .array[j + 1]
array.length > (j + 1)