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

टेक्स्ट एडिटर्स के लिए, इस तरह से स्टेट को जेनरेट करना बहुत कम् यूट्यूट इंटेंसिव नहीं है, लेकिन एडोब फोटोशॉप जैसे प्रोग्राम्स के लिए, यह बहुत कम्प्यूटरीली इंटेंसिव या सिर्फ सादा असंभव हो सकता है। उदाहरण के लिए - ब्लर एक्शन के लिए, आप डी-ब्लर एक्शन निर्दिष्ट करेंगे , लेकिन यह आपको मूल स्थिति में कभी नहीं पहुंचा सकता क्योंकि डेटा पहले ही खो चुका है। इसलिए, स्थिति के आधार पर - एक तार्किक उलट कार्रवाई की संभावना और इसकी व्यवहार्यता, आपको इन दो व्यापक श्रेणियों के बीच चयन करने की आवश्यकता है, और फिर उन्हें जिस तरह से आप चाहते हैं उन्हें लागू करें। बेशक, यह एक संकर रणनीति है जो आपके लिए काम करती है।
साथ ही, कभी-कभी जीमेल की तरह, एक समय सीमित पूर्ववत संभव है क्योंकि कार्रवाई (मेल भेजना) पहले कभी नहीं की जाती है। तो, आप वहां "पूर्ववत" नहीं कर रहे हैं, आप कार्रवाई केवल "नहीं" कर रहे हैं।
मैंने दो पाठ संपादकों को खरोंच से लिखा है, और वे दोनों पूर्ववत / पुन: कार्यक्षमता के एक बहुत ही आदिम रूप को रोजगार देते हैं। "आदिम" से मेरा मतलब है कि कार्यक्षमता को लागू करना बहुत आसान था, लेकिन यह बहुत बड़ी फ़ाइलों में अनौपचारिक है (जैसे >> 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" सहेजें, यदि आप देखते हैं कि मेरा क्या मतलब है)। बहुत बड़ी फ़ाइलों में, जहां फ़ाइल आकार की तुलना में प्रत्येक परिवर्तन छोटा होता है, इससे पूर्ववत डेटा की मेमोरी उपयोग में बहुत कमी आएगी, लेकिन इसे लागू करने के लिए मुश्किल है, और संभवतः अधिक त्रुटि-प्रवण है।
ऐसा करने के कई तरीके हैं, लेकिन आप कमांड पैटर्न को देखना शुरू कर सकते हैं । अपने कार्यों के माध्यम से वापस (पूर्ववत करें) या फ़ॉरवर्ड (रीडू) करने के लिए आदेशों की एक सूची का उपयोग करें। C # में एक उदाहरण यहां पाया जा सकता है ।
थोड़ा देर से, लेकिन यहाँ जाता है: आप विशेष रूप से पाठ संपादकों का उल्लेख करते हैं, जो अनुसरण करता है एक एल्गोरिथ्म का वर्णन करता है जिसे आप जो भी संपादित कर रहे हैं उसे अनुकूलित किया जा सकता है। इसमें शामिल सिद्धांत उन कार्यों / निर्देशों की एक सूची रखना है जिन्हें आपके द्वारा किए गए प्रत्येक परिवर्तन को फिर से बनाने के लिए स्वचालित किया जा सकता है। मूल फ़ाइल में परिवर्तन न करें (यदि खाली नहीं है), तो इसे बैक-अप के रूप में रखें।
आपके द्वारा मूल फ़ाइल में किए गए परिवर्तनों की एक अग्रेषित-पिछड़ी हुई-लिंक रखें। इस सूची को अस्थायी रूप से एक अस्थायी फ़ाइल में सहेजा जाता है, जब तक कि उपयोगकर्ता वास्तव में परिवर्तनों को सहेजता है: जब ऐसा होता है तो आप नई फ़ाइल में परिवर्तन लागू करते हैं, पुराने को कॉपी करते हैं और साथ ही परिवर्तनों को लागू करते हैं; फिर मूल फ़ाइल को बैकअप में बदलें, और नई फ़ाइल का नाम सही नाम में बदलें। (आप सहेजे गए परिवर्तन-सूची को रख सकते हैं, या इसे हटा सकते हैं और बाद की परिवर्तनों की सूची से बदल सकते हैं।)
लिंक्ड-लिस्ट में प्रत्येक नोड में निम्न जानकारी होती है:।
deleteबाद एकinsertinsertयह वह डेटा है जिसे डाला गया था; यदि delete, वह डेटा जो हटा दिया गया था।लागू करने के लिए Undo, आप 'करंट-नोड' पॉइंटर या इंडेक्स का उपयोग करके लिंक्ड-लिस्ट की पूंछ से पीछे की ओर काम करते हैं: जहां परिवर्तन था insert, आप एक डिलीट तो करते हैं लेकिन लिंक्ड-लिस्ट को अपडेट किए बिना; और जहां यह था deleteआप लिंक्ड-सूची बफर में डेटा से डेटा डालें। उपयोगकर्ता से प्रत्येक 'पूर्ववत करें' आदेश के लिए ऐसा करें। Redo'करंट-नोड' पॉइंटर को आगे बढ़ाता है और नोड के अनुसार बदलाव को अंजाम देता है। क्या उपयोगकर्ता को पूर्ववत करने के बाद कोड में बदलाव करना चाहिए, पूंछ के लिए makecurrent-node 'संकेतक के बाद सभी नोड्स को हटा दें, और पूंछ को' वर्तमान-नोड 'संकेतक के बराबर सेट करें। उपयोगकर्ता के नए परिवर्तन पूंछ के बाद डाले जाते हैं। और इसके बारे में है।
मेरा केवल दो सेंट है कि आप संचालन का ट्रैक रखने के लिए दो स्टैक का उपयोग करना चाहेंगे। हर बार जब उपयोगकर्ता कुछ ऑपरेशन करता है, तो आपके प्रोग्राम को उन कार्यों को "निष्पादित" स्टैक पर रखना चाहिए। जब उपयोगकर्ता उन ऑपरेशनों को पूर्ववत करना चाहता है, तो बस "प्रदर्शन" स्टैक से पॉप ऑपरेशनों को "रिकॉल" स्टैक पर रखें। जब उपयोगकर्ता उन कार्यों को फिर से करना चाहता है, तो "रिकॉल" स्टैक से पॉप आइटम और स्टैक "प्रदर्शन" करने के लिए उन्हें वापस धकेल दें।
आशा है कि इससे सहायता मिलेगी।
यदि क्रियाएँ प्रतिवर्ती हैं। उदाहरण के लिए 1 जोड़ना, एक खिलाड़ी चाल आदि देखें कि पूर्ववत / फिर से लागू करने के लिए कमांड पैटर्न का उपयोग कैसे करें । लिंक का पालन करें आप कैसे करना है पर एक विस्तृत उदाहरण मिलेगा।
यदि नहीं, तो @Lazer द्वारा बताए अनुसार सहेजे गए राज्य का उपयोग करें ।
आप किसी मौजूदा पूर्ववत / फिर से तैयार किए गए ढांचे के उदाहरण का अध्ययन कर सकते हैं, पहला Google हिट कोडप्लेक्स (.NET के लिए) पर है । मुझे नहीं पता कि यह किसी भी अन्य ढांचे की तुलना में बेहतर या बदतर है, उनमें से बहुत सारे हैं।
यदि आपका लक्ष्य आपके आवेदन में पूर्ववत / पुनः कार्यक्षमता है, तो आप एक मौजूदा ढांचा चुन सकते हैं, जो आपके आवेदन के लिए उपयुक्त हो।
यदि आप सीखना चाहते हैं कि कैसे अपने पूर्ववत करें / फिर से बनाना है तो आप स्रोत कोड डाउनलोड कर सकते हैं और दोनों पैटर्न पर नज़र डाल सकते हैं और विवरण को कैसे तार कर सकते हैं।
इसके लिए मेमेंटो पैटर्न बनाया गया था।
इसे स्वयं लागू करने से पहले, ध्यान दें कि यह काफी सामान्य है, और कोड पहले से मौजूद है - उदाहरण के लिए, यदि आप .Net में कोडिंग कर रहे हैं, तो आप IEditableObject का उपयोग कर सकते हैं ।
चर्चा में जोड़ते हुए, मैंने इस बारे में सोचने के आधार पर UNDO और REDO को लागू करने के बारे में एक ब्लॉग पोस्ट लिखा कि क्या सहज है: http://adamkulidjian.com/undo-and-redo.html
एक मूल पूर्ववत / फिर से लागू करने की सुविधा का एक तरीका स्मृति चिह्न और कमांड डिज़ाइन पैटर्न दोनों का उपयोग करना है।
मेमेंटो का उद्देश्य किसी वस्तु की स्थिति को बाद में बहाल करना है। एक अनुकूलन उद्देश्य में यह स्मृति चिन्ह जितना संभव हो उतना छोटा होना चाहिए।
आदेश जरूरत पड़ने पर एक वस्तु (एक आदेश) में पैटर्न समाहित कुछ निर्देशों पर अमल करने के लिए।
इन दो अवधारणाओं के आधार पर आप एक बुनियादी पूर्ववत / फिर से इतिहास लिख सकते हैं, जैसे टाइपस्क्रिप्ट में निम्नलिखित एक कोडित ( सामने के पुस्तकालय इंटरेक्टो से निकाला और अनुकूलित )।
ऐसा इतिहास दो धारों पर निर्भर करता है:
एल्गोरिथ्म के भीतर टिप्पणियाँ प्रदान की जाती हैं। बस इस बात पर ध्यान दें कि किसी पूर्ववत ऑपरेशन पर, रेडो स्टैक को साफ़ करना होगा! इसका कारण आवेदन को एक स्थिर स्थिति में आने देना है: यदि आप अपने द्वारा किए गए कुछ कार्यों को फिर से करने के लिए अतीत में जाते हैं, तो आपके पूर्व के कार्यों का कोई अस्तित्व नहीं होगा क्योंकि आप भविष्य बदलते हैं।
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 के लिए एक ही बात)।
इस तरह के उदाहरण इंटरैक्टो प्रलेखन पृष्ठ पर विस्तृत हैं ।