पूर्ववत करें / फिर से लागू करें


84

मुझे पूर्ववत / पुन: कार्यक्षमता को लागू करने के बारे में कुछ विचार दें - जैसे हमारे पास पाठ संपादकों में हैं। मुझे क्या एल्गोरिदम का उपयोग करना चाहिए और मैं क्या पढ़ सकता हूं। धन्यवाद।


3
हो सकता है कि आपके सॉफ़्टवेयर (टेक्स्ट प्रोसेसिंग; ग्राफिक्स? डेटाबेस?) और संभवतः प्लेटफ़ॉर्म / प्रोग्रामिंग भाषाओं में काम करने वाले क्षेत्र के बारे में कुछ और विवरण जोड़ें।
पीका

जवाबों:


94

मैं पूर्ववत के प्रकारों के दो प्रमुख प्रभागों के बारे में जानता हूं

  • सहेजें स्थिति: पूर्ववत की एक श्रेणी वह जगह है जहाँ आप वास्तव में इतिहास की स्थिति को बचाते हैं। इस स्थिति में ऐसा होता है कि हर बिंदु पर आप स्मृति के किसी स्थान पर स्थिति को सहेजते रहते हैं। जब आप एक पूर्ववत करना चाहते हैं, तो आप बस वर्तमान स्थिति को स्वैप करते हैं और उस राज्य में स्वैप करते हैं जो पहले से ही मेमोरी में था। उदाहरण के लिए, यह एडोब फोटोशॉप में इतिहास के साथ या Google क्रोम में बंद टैब को फिर से खोलने के साथ किया जाता है।

वैकल्पिक शब्द

  • सामान्य स्थिति: दूसरी श्रेणी वह है जहां राज्यों को खुद बनाए रखने के बजाय, आपको बस याद रखना चाहिए कि क्या कार्य थे। जब आपको पूर्ववत करने की आवश्यकता होती है, तो आपको उस विशेष कार्रवाई के एक तार्किक रिवर्स करने की आवश्यकता होती है। एक सरल उदाहरण के लिए, जब आप पूर्ववत का समर्थन करने वाले कुछ पाठ संपादक में एक Ctrl+ करते हैं B, तो इसे एक बोल्ड एक्शन के रूप में याद किया जाता है । अब प्रत्येक क्रिया के साथ उसके तार्किक प्रतिवर्तन का मानचित्रण होता है। इसलिए, जब आप एक Ctrl+ करते हैं Z, तो यह एक विपरीत क्रिया तालिका से दिखता है और पाता है कि पूर्ववत क्रिया फिर से Ctrl+ होती है B। यह प्रदर्शन किया जाता है और आपको अपना पिछला राज्य मिलता है। इसलिए, यहां आपकी पिछली स्थिति को मेमोरी में संग्रहीत नहीं किया गया था, लेकिन जब आपको इसकी आवश्यकता होती है तो उत्पन्न होती है।

टेक्स्ट एडिटर्स के लिए, इस तरह से स्टेट को जेनरेट करना बहुत कम् यूट्यूट इंटेंसिव नहीं है, लेकिन एडोब फोटोशॉप जैसे प्रोग्राम्स के लिए, यह बहुत कम्‍प्‍यूटरीली इंटेंसिव या सिर्फ सादा असंभव हो सकता है। उदाहरण के लिए - ब्लर एक्शन के लिए, आप डी-ब्लर एक्शन निर्दिष्ट करेंगे , लेकिन यह आपको मूल स्थिति में कभी नहीं पहुंचा सकता क्योंकि डेटा पहले ही खो चुका है। इसलिए, स्थिति के आधार पर - एक तार्किक उलट कार्रवाई की संभावना और इसकी व्यवहार्यता, आपको इन दो व्यापक श्रेणियों के बीच चयन करने की आवश्यकता है, और फिर उन्हें जिस तरह से आप चाहते हैं उन्हें लागू करें। बेशक, यह एक संकर रणनीति है जो आपके लिए काम करती है।

साथ ही, कभी-कभी जीमेल की तरह, एक समय सीमित पूर्ववत संभव है क्योंकि कार्रवाई (मेल भेजना) पहले कभी नहीं की जाती है। तो, आप वहां "पूर्ववत" नहीं कर रहे हैं, आप कार्रवाई केवल "नहीं" कर रहे हैं।


9
कभी-कभी यह बचाने वाले राज्यों और "आगे" कार्यों का मिश्रण रखने के लिए सहायक हो सकता है। एक सरल दृष्टिकोण के रूप में, यदि हर 5 कार्यों में से एक ने "प्रमुख सेव स्टेट" रखा, और अंतिम "प्रमुख सेव स्टेट" के बाद भी सेव स्टेट्स को रखा, तो कोई पहले स्टेट्स को बचाने के लिए पहले कुछ पूर्ववत संचालन कर सकता था, और कोई कस्टम कर सकता था पिछले प्रमुख बचाओ से 4 क्रियाओं को दोहराकर अगले पूर्ववत करें। कुछ हद तक सामान्य दृष्टिकोण यह होगा कि राज्य को बचाने के विभिन्न स्तरों के लिए पावर-ऑफ-टू प्रगति का उपयोग किया जाए, इस प्रकार ओ (1) फॉरवर्ड पुनरावृत्तियों का उपयोग करते हुए एन-लेवल पूर्ववत के लिए राज्यों को बचाने की आवश्यकता है।
2

जवाब में इस संकर दृष्टिकोण को भी जोड़ना चाहिए। यह बहुत ही संभव है जहां पिछले राज्य को उत्पन्न करके पूर्ववत करना बहुत जटिल है और संपादित किया जा रहा डेटा बहुत बड़ा है। दोनों के बीच एक संतुलन। एक निश्चित 4-लंबाई या पावर-ऑफ -2 लंबाई प्रगति के बजाय कई रणनीतियों को अपना सकता है। राज्य को बचाने की तरह जब भी पिछले राज्य का निर्माण बहुत जटिल होता है।
ज़ूकास्टोस

20

मैंने दो पाठ संपादकों को खरोंच से लिखा है, और वे दोनों पूर्ववत / पुन: कार्यक्षमता के एक बहुत ही आदिम रूप को रोजगार देते हैं। "आदिम" से मेरा मतलब है कि कार्यक्षमता को लागू करना बहुत आसान था, लेकिन यह बहुत बड़ी फ़ाइलों में अनौपचारिक है (जैसे >> 10 एमबी)। हालाँकि, सिस्टम बहुत लचीला है; उदाहरण के लिए, यह पूर्ववत के असीमित स्तरों का समर्थन करता है।

असल में, मैं एक संरचना की तरह परिभाषित करता हूं

type
  TUndoDataItem = record
    text: /array of/ string;
    selBegin: integer;
    selEnd: integer;
    scrollPos: TPoint;
  end;

और फिर एक सरणी परिभाषित करें

var
  UndoData: array of TUndoDataItem;

फिर इस सरणी का प्रत्येक सदस्य पाठ की सहेजी स्थिति को निर्दिष्ट करता है। अब, पाठ के प्रत्येक संपादन पर (वर्ण कुंजी नीचे, बैकस्पेस डाउन, की-डाउन, कट / पेस्ट, माउस द्वारा स्थानांतरित किया गया चयन), मैं (पुनः) एक सेकंड का (शुरू) बोलता हूं। जब ट्रिगर किया जाता है, टाइमर UndoDataसरणी के नए सदस्य के रूप में वर्तमान स्थिति को बचाता है ।

पूर्ववत करें (Ctrl + Z), मैं संपादक को राज्य में पुनर्स्थापित करता हूं UndoData[UndoLevel - 1]और UndoLevelएक-एक करके घटाता हूं । डिफ़ॉल्ट रूप से, सरणी UndoLevelके अंतिम सदस्य के सूचकांक के बराबर है UndoData। Redo (Ctrl + Y या Shift + Ctrl + Z) पर, मैं संपादक को राज्य में पुनर्स्थापित करता हूं UndoData[UndoLevel + 1]और UndoLevelएक-एक करके बढ़ाता हूं । बेशक, यदि संपादन टाइमर को सरणी UndoLevelकी लंबाई (शून्य से एक) के बराबर नहीं होने पर ट्रिगर किया जाता है UndoData, तो मैं इस सरणी के सभी आइटम को बाद में साफ कर देता हूं UndoLevel, जैसा कि Microsoft विंडोज प्लेटफॉर्म पर आम है (लेकिन Emacs बेहतर है, अगर मुझे याद है सही ढंग से - Microsoft विंडोज दृष्टिकोण का नुकसान यह है कि, यदि आप बहुत सारे बदलावों को पूर्ववत करते हैं और फिर गलती से बफर को संपादित करते हैं, तो पिछली सामग्री (जो कि अनिच्छुक थी) स्थायी रूप से खो जाती है)। आप सरणी की इस कमी को छोड़ना चाह सकते हैं।

उदाहरण के लिए, एक अलग प्रकार के कार्यक्रम में, एक छवि संपादक, एक ही तकनीक को लागू किया जा सकता है, लेकिन निश्चित रूप से, एक पूरी तरह से अलग UndoDataItemसंरचना के साथ। एक अधिक उन्नत दृष्टिकोण, जिसे उतनी मेमोरी की आवश्यकता नहीं है, केवल पूर्ववत स्तरों के बीच के परिवर्तनों को सहेजना है (अर्थात, "अल्फ़ा \ nbeta \ Gamma" और "अल्फ़ा \ nbeta \ ngamma \ ndelta" को बचाने के बजाय, आप कर सकते हैं) "अल्फा \ nbeta \ ngamma" और "ADD \ ndelta" सहेजें, यदि आप देखते हैं कि मेरा क्या मतलब है)। बहुत बड़ी फ़ाइलों में, जहां फ़ाइल आकार की तुलना में प्रत्येक परिवर्तन छोटा होता है, इससे पूर्ववत डेटा की मेमोरी उपयोग में बहुत कमी आएगी, लेकिन इसे लागू करने के लिए मुश्किल है, और संभवतः अधिक त्रुटि-प्रवण है।


@AlexanderSuraphel: मुझे लगता है कि वे "अधिक उन्नत" दृष्टिकोण का उपयोग करेंगे।
एंड्रियास रिब्रांडैंड

14

ऐसा करने के कई तरीके हैं, लेकिन आप कमांड पैटर्न को देखना शुरू कर सकते हैं । अपने कार्यों के माध्यम से वापस (पूर्ववत करें) या फ़ॉरवर्ड (रीडू) करने के लिए आदेशों की एक सूची का उपयोग करें। C # में एक उदाहरण यहां पाया जा सकता है


8

थोड़ा देर से, लेकिन यहाँ जाता है: आप विशेष रूप से पाठ संपादकों का उल्लेख करते हैं, जो अनुसरण करता है एक एल्गोरिथ्म का वर्णन करता है जिसे आप जो भी संपादित कर रहे हैं उसे अनुकूलित किया जा सकता है। इसमें शामिल सिद्धांत उन कार्यों / निर्देशों की एक सूची रखना है जिन्हें आपके द्वारा किए गए प्रत्येक परिवर्तन को फिर से बनाने के लिए स्वचालित किया जा सकता है। मूल फ़ाइल में परिवर्तन न करें (यदि खाली नहीं है), तो इसे बैक-अप के रूप में रखें।

आपके द्वारा मूल फ़ाइल में किए गए परिवर्तनों की एक अग्रेषित-पिछड़ी हुई-लिंक रखें। इस सूची को अस्थायी रूप से एक अस्थायी फ़ाइल में सहेजा जाता है, जब तक कि उपयोगकर्ता वास्तव में परिवर्तनों को सहेजता है: जब ऐसा होता है तो आप नई फ़ाइल में परिवर्तन लागू करते हैं, पुराने को कॉपी करते हैं और साथ ही परिवर्तनों को लागू करते हैं; फिर मूल फ़ाइल को बैकअप में बदलें, और नई फ़ाइल का नाम सही नाम में बदलें। (आप सहेजे गए परिवर्तन-सूची को रख सकते हैं, या इसे हटा सकते हैं और बाद की परिवर्तनों की सूची से बदल सकते हैं।)

लिंक्ड-लिस्ट में प्रत्येक नोड में निम्न जानकारी होती है:।

  • परिवर्तन का प्रकार: आप या तो डेटा सम्मिलित करते हैं, या आप डेटा हटाते हैं: डेटा को "बदलने" का अर्थ है एक के deleteबाद एकinsert
  • फ़ाइल में स्थिति: एक ऑफसेट या लाइन / स्तंभ-जोड़ी हो सकती है
  • डेटा बफर: यह कार्रवाई के साथ शामिल डेटा है; यदि insertयह वह डेटा है जिसे डाला गया था; यदि delete, वह डेटा जो हटा दिया गया था।

लागू करने के लिए Undo, आप 'करंट-नोड' पॉइंटर या इंडेक्स का उपयोग करके लिंक्ड-लिस्ट की पूंछ से पीछे की ओर काम करते हैं: जहां परिवर्तन था insert, आप एक डिलीट तो करते हैं लेकिन लिंक्ड-लिस्ट को अपडेट किए बिना; और जहां यह था deleteआप लिंक्ड-सूची बफर में डेटा से डेटा डालें। उपयोगकर्ता से प्रत्येक 'पूर्ववत करें' आदेश के लिए ऐसा करें। Redo'करंट-नोड' पॉइंटर को आगे बढ़ाता है और नोड के अनुसार बदलाव को अंजाम देता है। क्या उपयोगकर्ता को पूर्ववत करने के बाद कोड में बदलाव करना चाहिए, पूंछ के लिए makecurrent-node 'संकेतक के बाद सभी नोड्स को हटा दें, और पूंछ को' वर्तमान-नोड 'संकेतक के बराबर सेट करें। उपयोगकर्ता के नए परिवर्तन पूंछ के बाद डाले जाते हैं। और इसके बारे में है।


8

मेरा केवल दो सेंट है कि आप संचालन का ट्रैक रखने के लिए दो स्टैक का उपयोग करना चाहेंगे। हर बार जब उपयोगकर्ता कुछ ऑपरेशन करता है, तो आपके प्रोग्राम को उन कार्यों को "निष्पादित" स्टैक पर रखना चाहिए। जब उपयोगकर्ता उन ऑपरेशनों को पूर्ववत करना चाहता है, तो बस "प्रदर्शन" स्टैक से पॉप ऑपरेशनों को "रिकॉल" स्टैक पर रखें। जब उपयोगकर्ता उन कार्यों को फिर से करना चाहता है, तो "रिकॉल" स्टैक से पॉप आइटम और स्टैक "प्रदर्शन" करने के लिए उन्हें वापस धकेल दें।

आशा है कि इससे सहायता मिलेगी।


3

यदि क्रियाएँ प्रतिवर्ती हैं। उदाहरण के लिए 1 जोड़ना, एक खिलाड़ी चाल आदि देखें कि पूर्ववत / फिर से लागू करने के लिए कमांड पैटर्न का उपयोग कैसे करें । लिंक का पालन करें आप कैसे करना है पर एक विस्तृत उदाहरण मिलेगा।

यदि नहीं, तो @Lazer द्वारा बताए अनुसार सहेजे गए राज्य का उपयोग करें ।


2

आप किसी मौजूदा पूर्ववत / फिर से तैयार किए गए ढांचे के उदाहरण का अध्ययन कर सकते हैं, पहला Google हिट कोडप्लेक्स (.NET के लिए) पर है । मुझे नहीं पता कि यह किसी भी अन्य ढांचे की तुलना में बेहतर या बदतर है, उनमें से बहुत सारे हैं।

यदि आपका लक्ष्य आपके आवेदन में पूर्ववत / पुनः कार्यक्षमता है, तो आप एक मौजूदा ढांचा चुन सकते हैं, जो आपके आवेदन के लिए उपयुक्त हो।
यदि आप सीखना चाहते हैं कि कैसे अपने पूर्ववत करें / फिर से बनाना है तो आप स्रोत कोड डाउनलोड कर सकते हैं और दोनों पैटर्न पर नज़र डाल सकते हैं और विवरण को कैसे तार कर सकते हैं।


2

इसके लिए मेमेंटो पैटर्न बनाया गया था।

इसे स्वयं लागू करने से पहले, ध्यान दें कि यह काफी सामान्य है, और कोड पहले से मौजूद है - उदाहरण के लिए, यदि आप .Net में कोडिंग कर रहे हैं, तो आप IEditableObject का उपयोग कर सकते हैं ।


1

चर्चा में जोड़ते हुए, मैंने इस बारे में सोचने के आधार पर UNDO और REDO को लागू करने के बारे में एक ब्लॉग पोस्ट लिखा कि क्या सहज है: http://adamkulidjian.com/undo-and-redo.html


1
ऊपर दिए गए ब्लॉग पोस्ट में "इंजेक्शन इन द मिडिल" की आपकी चर्चा की मैंने सराहना की। मेरा दिमाग सभी गाँठों में बँधा हुआ था जो यह पता लगाने की कोशिश कर रहा था कि अगर कोई एक या एक से अधिक कार्यों से वंचित है, तो उसने कुछ नया किया, फिर वापस पूर्ववत् और / या फिर से करना शुरू कर दिया। आपके चित्रण ने मुझे यह समझने में मदद की कि क्यों उपयोगकर्ता के हिस्से पर एक नई कार्रवाई के बाद कतार के "पुनर्वसन" हिस्से को साफ करना संन्यास बनाए रखने का एक समझदार तरीका है। धन्यवाद!
डेन रॉबिन्सन

@DanRobinson आपकी प्रतिक्रिया के लिए बहुत बहुत धन्यवाद। मैंने चित्रों के साथ ब्लॉगपोस्ट को समझने के लिए एक साफ और आसान लिखने में बहुत समय लगाया, इसलिए मुझे खुशी है कि मुझे फर्क पड़ा। :)
एडम

0

एक मूल पूर्ववत / फिर से लागू करने की सुविधा का एक तरीका स्मृति चिह्न और कमांड डिज़ाइन पैटर्न दोनों का उपयोग करना है।

मेमेंटो का उद्देश्य किसी वस्तु की स्थिति को बाद में बहाल करना है। एक अनुकूलन उद्देश्य में यह स्मृति चिन्ह जितना संभव हो उतना छोटा होना चाहिए।

आदेश जरूरत पड़ने पर एक वस्तु (एक आदेश) में पैटर्न समाहित कुछ निर्देशों पर अमल करने के लिए।

इन दो अवधारणाओं के आधार पर आप एक बुनियादी पूर्ववत / फिर से इतिहास लिख सकते हैं, जैसे टाइपस्क्रिप्ट में निम्नलिखित एक कोडित ( सामने के पुस्तकालय इंटरेक्टो से निकाला और अनुकूलित )।

ऐसा इतिहास दो धारों पर निर्भर करता है:

  • उन वस्तुओं के लिए स्टैक जो पूर्ववत हो सकते हैं
  • उन वस्तुओं के लिए स्टैक जिन्हें फिर से बनाया जा सकता है

एल्गोरिथ्म के भीतर टिप्पणियाँ प्रदान की जाती हैं। बस इस बात पर ध्यान दें कि किसी पूर्ववत ऑपरेशन पर, रेडो ​​स्टैक को साफ़ करना होगा! इसका कारण आवेदन को एक स्थिर स्थिति में आने देना है: यदि आप अपने द्वारा किए गए कुछ कार्यों को फिर से करने के लिए अतीत में जाते हैं, तो आपके पूर्व के कार्यों का कोई अस्तित्व नहीं होगा क्योंकि आप भविष्य बदलते हैं।

export class UndoHistory {
    /** The undoable objects. */
    private readonly undos: Array<Undoable>;

    /** The redoable objects. */
    private readonly redos: Array<Undoable>;

    /** The maximal number of undo. */
    private sizeMax: number;

    public constructor() {
        this.sizeMax = 0;
        this.undos = [];
        this.redos = [];
        this.sizeMax = 30;
    }

    /** Adds an undoable object to the collector. */
    public add(undoable: Undoable): void {
        if (this.sizeMax > 0) {
            // Cleaning the oldest undoable object
            if (this.undos.length === this.sizeMax) {
                this.undos.pop();
            }

            this.undos.push(undoable);
            // You must clear the redo stack!
            this.clearRedo();
        }
    }

    private clearRedo(): void {
        if (this.redos.length > 0) {
            this.redos.length = 0;
        }
    }

    /** Undoes the last undoable object. */
    public undo(): void {
        const undoable = this.undos.pop();
        if (undoable !== undefined) {
            undoable.undo();
            this.redos.push(undoable);
        }
    }

    /** Redoes the last undoable object. */
    public redo(): void {
        const undoable = this.redos.pop();
        if (undoable !== undefined) {
            undoable.redo();
            this.undos.push(undoable);
        }
    }
}

Undoableइंटरफेस काफी सरल है:

export interface Undoable {
    /** Undoes the command */
    undo(): void;
    /** Redoes the undone command */
    redo(): void;
}

अब आप अपने एप्लिकेशन पर काम करने वाले पूर्ववत आदेश लिख सकते हैं।

उदाहरण के लिए (अभी भी इंटरको के उदाहरणों पर आधारित), आप इस तरह से एक कमांड लिख सकते हैं:

export class ClearTextCmd implements Undoable {
   // The memento that saves the previous state of the text data
   private memento: string;

   public constructor(private text: TextData) {}
   
   // Executes the command
   public execute() void {
     // Creating the memento
     this.memento = this.text.text;
     // Applying the changes (in many 
     // cases do and redo are similar, but the memento creation)
     redo();
   }

   public undo(): void {
     this.text.text = this.memento;
   }

   public redo(): void {
     this.text.text = '';
   }
}

अब आप निष्पादित और UndoHistory उदाहरण के लिए कमांड जोड़ सकते हैं:

const cmd = new ClearTextCmd(...);
//...
undoHistory.add(cmd);

अंत में, आप इस इतिहास के लिए एक पूर्ववत बटन (या शॉर्टकट) बांध सकते हैं (Redo के लिए एक ही बात)।

इस तरह के उदाहरण इंटरैक्टो प्रलेखन पृष्ठ पर विस्तृत हैं ।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.