简体   繁体   中英

How to implement an undo()-Method for a List-Class in Python

Im kinda new with python and I got a task to create a class "UndoList" (type list) with an undo()-method. This method should undo typical list-operations like, append, insert, remove...

>>> ul = UndoList([1,2,3])
>>> ul.append(4), print(ul), undo(ul), print(ul)
[1,2,3,4]
[1,2,3]
>>> ul.remove(3), print(ul), undo(ul), print(ul)
[1,2]
[1,2,3]
...

This undo()-method should only undo one operation (as u can see in the example). My teacher gave me the hint, to save the value of the list in the instance before every operation.

This is my class:

class UndoList(list):

   def __init__(self, lis):
       list.__init__(self, lis)
       self.lis = []

   def __append__(self, lis):
       list.__add__(self, lis)
       return lis

   def undo(self):
       return self

a1 = UndoList([1,2,3])
print(a1), a1.append(4), print(a1)   #[1,2,3] [1,2,3,4]
a1.undo(), print(a1)                 #[1,2,3,4]

So now my question: how can i create an instance in my class to save my actual list before I do any operation? And is it possible to just retrun this instance in my undo-method?

Thanks!

Here's some code that will get you started. Really though, it's best to avoid sub-classing Python's standard types because to do it properly you generally need to override every method, which can be rather tedious and error-prone.

Note that the append method is called append , not __append__ . :) And that the methods which mutate a list in-place return None , not the list.

from copy import deepcopy

class UndoList(list):
    def __init__(self, *args):
        super().__init__(*args)
        self.old = []

    def append(self, item):
        self.old = deepcopy(self[:])
        super().append(item)

    def extend(self, items):
        self.old = deepcopy(self[:])
        super().extend(items)

    def undo(self):
        temp = deepcopy(self[:])
        self[:] = self.old
        self.old = temp


a = UndoList([1, 2, 3])
print(a)

a.append(4)
print(a)
a.undo()
print(a)
a.undo()
print(a)

a.extend([5, 6])
print(a)
a.undo()
print(a)

output

[1, 2, 3]
[1, 2, 3, 4]
[1, 2, 3]
[1, 2, 3, 4]
[1, 2, 3, 4, 5, 6]
[1, 2, 3, 4]

We use def __init__(self, *args) so that we can call UndoList() with no args to get an empty UndoList.

As 9000 mentions in the comments, you probably don't need deepcopy here. It consumes extra RAM by recursively copying every list item (except for immutable items), and it's slow. Using deepcopy does make UndoList robust. OTOH, it also means that items restored from .old are copies of the original items, and in some cases that is undesirable - if other objects refer to those items then the back-up process breaks that connection.

If you want to experiment with this, simply change the code that backs up the list to

self.old = self[:]

and the undo method becomes

def undo(self):
    self[:], self.old = self.old, self[:]

The sane way to do this is to build a new class using Abstract Base Classes rather than sub-classing list .

It's simple. but tedious: you add a history attribute to the list object. This is a stack of previous variable states. Every change operation needs to push its current state onto the object's history before the change. The undo operation simply pops the most recent one.

You've already noted that you'll have to redefine all of the change operations (such as the __append__ in your class).

Let us understand it by examples: Given an empty array initially, we have to implement four functions.

We have queries of the following types:

  1. Add(value) : Add the value to the array.

  2. Remove(value) : Remove the value from the array.

  3. Undo: Reverses the last action performed on the array.

  4. Redo: Restores the most recent UNDO operation performed on the array.

Examples: Input : List=[] Output : add(1) print(List) List=[1]

          add(2)  print(List)  List=[1,2]
          add(3)  print(List) List=[1,2,3] 
          add(4)  print(List)  List=[1,2,3,4]
          undo()  print(List) List=[1,2,3]
          undo()  print(List)  List=[1,2]
          redo()   print(List)  List=[1,2,3]
          redo()  print(List) List=[1,2,3,4]
          remove(3) print(List)  List=[1,2,4]
          remove(1)  print(List) List=[2,4]
          remove(2)  print(List)  List=[4]
          undo() print(List)  List=[2,4]
          undo() print(List)  List=[1,2,4]
          undo() print(List)  List=[1,2,3,4]
          redo()  print(List)  List=[1,2,4]

Approach: The problem can be solved by using 2 lists, firstly, we will make an undo list which will keep track of the last operation performed, it will be a list of tuples and it will be of the format (operation, index, value) here if the operation='a' means that the last action that was performed was of adding the element to the list and if the operation='r' means that last action that was performed was of removing the element from the list.

The second list will be the redo list which will keep track of the undo operations performed so that it can restore the most recent undo operation.

 # Python Program to implement the above approach. # this is the main list which will be printed after doing any of the above operations main_list = [] # this is the list for tracking the operations being performed undo_list = [] # this is the redo list which will keep track of the undo operations done. redo_list = [] \\ def add(value): """ this is the function to add the value to the list """ # index at will we will add the value idx=len(main_list) # value will be added to the main_list main_list.append(value) # we will update undo_list, by appending the operation as 'r', as we are adding value to the list, so its undo operation will do the reverse of it, so we will append operation as 'r'. undo_list.append(('r',idx,value)) print(main_list) def remove(value): """ this is the function to remove the value from the list """ # length of the main_list length=len(main_list) # if the length of the main_list is 0 if(length==0): return # if the value is not present in the main_list if value not in main_list: return # index of the value that we have to remove idx = main_list.index(value) # removing value from the main_list main_list.remove(value) # we will update undo_list, by appending the operation as 'a', as we are removing value from the list , so its undo operation will do the reverse of it , so we will append operation as 'a'. undo_list.append(('a', idx, value)) print(main_list) def undo(): """ this is the function to undo the value """ #length of the undo_list length = len(undo_list) # if the length of the undo_list is 0 ,means there is nothing to do undo operation if(length==0): return # selecting the latest undo operation that we have to perform cur_tuple=undo_list.pop(); # selecting the type of the operation that we have to perform cur_tuple_operation=cur_tuple[0] # selecting the index at which we will perform latest undo operation. cur_tuple_index=cur_tuple[1] # selecting the value on which we will perform the latest undo operation cur_tuple_value=cur_tuple[2] # if the operation we have to do undo is 'a' if(cur_tuple_operation=='a'): # adding value to main_list main_list.insert(cur_tuple_index,cur_tuple_value) # also we will update redo_list by appending the operaion as 'r' as the undo current operation is 'a' , so redo operation will restore the most recent undo operation beging performed. redo_list.append(('r',cur_tuple_index,cur_tuple_value)) # if the operation we have to do undo is 'r' elif(cur_tuple_operation=='r') : # removing the value from the main_list main_list.pop(cur_tuple_index) # also we will update redo_list,by appending the operation as 'a', as the undo current operation is 'r' , so redo operation will restore the most recent undo operation beging performed. redo_list.append(('a',cur_tuple_index,cur_tuple_value)) print(main_list) def redo(): """ this is the function to redo the value """ #length of the redo_list length=len(redo_list) # if the length of the redo list is 0 if(length==0): return # selecting the latest redo operation that we have to perform. cur_tuple=redo_list.pop(); # selecting the type of the operation that we have to perform cur_tuple_operation=cur_tuple[0] # selecting the index at which we will perform latest redo operation. cur_tuple_index=cur_tuple[1] # selecting the value on which we will perform the latest redo operation. cur_tuple_value=cur_tuple[2] # if the operation we have to do redo is 'a' if(cur_tuple_operation=='a'): # adding value to main_list main_list.insert(cur_tuple_index,cur_tuple_value) # if the operation we have to do redo is 'r' elif(cur_tuple_operation=='r'): # removing the value from the main_list main_list.pop(cur_tuple_index) print(main_list) add(1) add(2) add(3) remove(2) add(4) add(5) undo() undo() undo() undo() undo() undo() redo() redo() redo() redo() redo() redo()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM