मैं एक सही लूप विकसित करने के लिए पूर्व / पोस्ट की स्थिति और आक्रमणकारियों का उपयोग करने का एक और विस्तृत उदाहरण देने जा रहा हूं। एक साथ इस तरह के दावे को एक विनिर्देश या अनुबंध कहा जाता है।
मैं सुझाव नहीं दे रहा हूं कि आप प्रत्येक लूप के लिए ऐसा करने का प्रयास करें। लेकिन मुझे उम्मीद है कि इसमें शामिल विचार प्रक्रिया को देखने के लिए आपको उपयोगी मिलेगा।
ऐसा करने के लिए, मैं आपकी विधि का अनुवाद Microsoft Dafny नामक टूल में करूँगा , जो इस तरह के विनिर्देशों की शुद्धता को साबित करने के लिए बनाया गया है। यह प्रत्येक लूप की समाप्ति की भी जांच करता है। कृपया ध्यान दें कि डैफनी में for
लूप नहीं होता है इसलिए मुझे while
इसके बजाय लूप का उपयोग करना पड़ा है ।
अंत में मैं दिखाऊंगा कि आप अपने लूप के एक, निश्चित रूप से, थोड़ा सरल संस्करण डिजाइन करने के लिए इस तरह के विनिर्देशों का उपयोग कैसे कर सकते हैं। इस सरल लूप संस्करण में लूप की स्थिति j > 0
और असाइनमेंट है array[j] = value
- जैसा कि आपका प्रारंभिक अंतर्ज्ञान था।
डैफनी हमारे लिए साबित करेगी कि ये दोनों लूप सही हैं और एक ही काम करते हैं।
मैं फिर एक सामान्य दावा करूंगा, अपने अनुभव के आधार पर, कैसे सही बैकवर्ड लूप लिखने के बारे में, जो भविष्य में इस स्थिति का सामना करने पर शायद आपकी मदद करेगा।
भाग एक - विधि के लिए एक विनिर्देश लिखना
पहली चुनौती जिसका हम सामना कर रहे हैं वह यह निर्धारित करना है कि वास्तव में विधि क्या करने वाली है। इसके लिए मैंने पूर्व और बाद की स्थितियों को डिज़ाइन किया है जो विधि के व्यवहार को निर्दिष्ट करते हैं। विनिर्देश को और अधिक सटीक बनाने के लिए मैंने इसे इंडेक्स को वापस लाने के लिए विधि को बढ़ाया है जहां value
इसे डाला गया था।
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..])
यह विनिर्देश पूरी तरह से विधि के व्यवहार को पकड़ लेता है। इस विनिर्देश के बारे में मेरा मुख्य अवलोकन यह है कि यदि प्रक्रिया को rightIndex+1
इसके बजाय मान से पारित किया गया तो इसे सरल बनाया जाएगा rightIndex
। लेकिन जब से मैं यह नहीं देख पा रहा हूं कि इस पद्धति को कहा जाता है, मुझे नहीं पता कि बाकी कार्यक्रम पर उस परिवर्तन का क्या प्रभाव पड़ेगा।
भाग दो - एक लूप आक्रमणकारी का निर्धारण करना
अब हमारे पास विधि के व्यवहार के लिए एक विनिर्देश है, हमें लूप व्यवहार का एक विनिर्देश जोड़ना होगा जो डैफनी को आश्वस्त करेगा कि लूप को निष्पादित करना समाप्त हो जाएगा और वांछित अंतिम स्थिति में परिणाम होगा array
।
निम्नलिखित आपका मूल लूप है, जिसे लूप इनवेरिएंट के साथ डैफी सिंटैक्स में अनुवाद किया गया है। मैंने इसे इंडेक्स को वापस करने के लिए भी बदल दिया है जहां मूल्य डाला गया था।
{
// 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
}
यह डैफी में पुष्टि करता है। आप इस लिंक को फॉलो करके खुद देख सकते हैं । इसलिए आपका लूप उस विधि विनिर्देश को सही ढंग से लागू करता है जो मैंने भाग एक में लिखा था। आपको यह तय करने की आवश्यकता होगी कि क्या यह विधि विनिर्देश वास्तव में वह व्यवहार है जो आप चाहते थे।
ध्यान दें कि डैफनी यहाँ शुद्धता का प्रमाण प्रस्तुत कर रही है। यह शुद्धता की अधिक मजबूत गारंटी है जो संभवतः परीक्षण द्वारा प्राप्त की जा सकती है।
भाग तीन - एक सरल लूप
अब जब हमारे पास एक विधि विनिर्देश है जो लूप के व्यवहार को कैप्चर करता है। हम लूप के कार्यान्वयन को सुरक्षित रूप से संशोधित कर सकते हैं जबकि अभी भी विश्वास बनाए रखते हैं कि हमने लूप व्यवहार को नहीं बदला है।
मैंने लूप को संशोधित किया है ताकि यह लूप की स्थिति और अंतिम मूल्य के बारे में आपके मूल अंतर्ज्ञान से मेल खाता हो j
। मैं तर्क दूंगा कि यह लूप आपके प्रश्न में वर्णित लूप की तुलना में सरल है। यह j
बजाय उपयोग करने में अधिक बार सक्षम है j+1
।
J पर शुरू करो rightIndex+1
लूप कंडीशन को में बदलें j > 0 && arr[j-1] > value
को असाइनमेंट बदलें arr[j] := value
भीख के बजाय लूप के अंत में लूप काउंटर को घटाएं
यहाँ कोड है। ध्यान दें कि लूप के अपरिवर्तकों को भी अब लिखना आसान है:
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;
}
भाग चार - पिछड़े लूपिंग के बारे में सलाह
कुछ वर्षों में कई लूप लिखे जाने और सही साबित होने के बाद, मुझे पीछे की तरफ लूप करने के बारे में सामान्य सलाह मिली है।
यदि पिछड़ेपन की समाप्ति के बजाय लूप की शुरुआत में प्रदर्शन किया जाता है, तो पिछड़े (घटते) लूप के बारे में सोचना और लिखना लगभग हमेशा आसान होता है।
दुर्भाग्य से for
कई भाषाओं में लूप का निर्माण इसे कठिन बना देता है।
मुझे संदेह है (लेकिन यह साबित नहीं कर सकता) कि यह जटिलता क्या आपके अंतर्ज्ञान में अंतर का कारण है कि लूप क्या होना चाहिए और वास्तव में क्या होना चाहिए। आप आगे (वेतन वृद्धि) छोरों के बारे में सोचने के लिए उपयोग किए जाते हैं। जब आप एक पिछड़े (डीक्रिमिंग) लूप लिखना चाहते हैं तो आप उस ऑर्डर को उलटने की कोशिश करके लूप बनाने की कोशिश करते हैं जो आगे (इंक्रीमेंट) लूप में चीजें होती हैं। लेकिन जिस तरह से for
निर्माण कार्य के कारण आपको असाइनमेंट और लूप वैरिएबल अपडेट के क्रम को उलटने के लिए उपेक्षित किया जाता है - जो एक पिछड़े और आगे लूप के बीच संचालन के क्रम के सही उलट के लिए आवश्यक है।
पांच भाग - बोनस
पूर्णता के लिए, यदि आप rightIndex+1
विधि के बजाय पास करते हैं, तो यहां आपको प्राप्त कोड है rightIndex
। यह परिवर्तन उन सभी +2
ऑफसेट को समाप्त कर देता है जो अन्यथा लूप की शुद्धता के बारे में सोचने के लिए आवश्यक हैं।
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
गलती हुई है? मैं इस तथ्य से अधिक सावधान रहूंगा कि आप पहुंच रहे हैंarray[j]
औरarray[j + 1]
पहले जांच के बिनाarray.length > (j + 1)
।