【问题标题】:How to implement an undo()-Method for a List-Class in Python如何在 Python 中为 List-Class 实现 undo()-Method
【发布时间】:2017-11-01 16:22:02
【问题描述】:

我对 python 有点陌生,我有一个任务是使用 undo() 方法创建一个类“UndoList”(类型列表)。此方法应该撤消典型的列表操作,例如追加、插入、删除...

>>> 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]
...

这个 undo() 方法应该只撤消一个操作(如您在示例中所见)。我的老师给了我提示,在每次操作之前将列表的值保存在实例中。

这是我的课:

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]

所以现在我的问题是:如何在我的类中创建一个实例来保存我的实际列表,然后再进行任何操作?是否可以在我的撤消方法中重新运行此实例?

谢谢!

【问题讨论】:

  • 那么,你想让我们为你解决你的课堂练习吗?
  • 你应该实现某种history 属性。
  • 你的意思是只需要支持单级undo?
  • @PM2Ring 没错。我只需要撤消一个操作

标签: python list python-3.x class undo


【解决方案1】:

这很简单。但很乏味:您将history 属性添加到列表对象。这是一堆以前的变量状态。每个更改操作都需要在更改之前将其当前状态推送到对象的historyundo 操作只是弹出最近的一个。

您已经注意到,您必须重新定义所有更改操作(例如您班级中的__append__)。

【讨论】:

    【解决方案2】:

    这里有一些代码可以帮助您入门。但实际上,最好避免对 Python 的标准类型进行子类化,因为要正确执行此操作,您通常需要重写 every 方法,这可能相当乏味且容易出错。

    请注意,append 方法称为append,而不是__append__。 :) 就地改变列表的方法返回None,而不是列表。

    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)
    

    输出

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

    我们使用def __init__(self, *args),这样我们就可以在没有参数的情况下调用UndoList() 来获得一个空的UndoList。

    正如 cmets 中提到的 9000 次一样,您可能在这里不需要deepcopy。它通过递归复制每个列表项(不可变项除外)消耗额外的 RAM,而且速度很慢。使用deepcopy 确实使UndoList 健壮。 OTOH,这也意味着从.old 恢复的项目是原始项目的副本,在某些情况下这是不可取的 - 如果其他对象引用这些项目,则备份过程会中断该连接。

    如果您想对此进行试验,只需将备份列表的代码更改为

    self.old = self[:]
    

    undo 方法变为

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

    明智的做法是使用Abstract Base Classes 构建一个新类,而不是使用list 的子类。

    【讨论】:

    • 这无疑是一个可行的解决方案,但是一个非常内存密集型的解决方案。 OP 可能没有要求保护列表中的可变数据免受更改。
    • 这很好用!非常感谢,伙计。我现在将尝试使用抽象基类来构建它。非常感谢!
    • @9000 或许deepcopy 是矫枉过正,我同意appendextend 不是真正必要的,但remove 是必要的,因为在undo 调用之前,删除的项目可能会被其他代码改变,然后undo 不会将列表恢复到之前的状态。 OTOH,在使用列表时总是有可能让其他代码改变列表项......
    • @9000 我已根据您的评论更新了我的答案。
    【解决方案3】:

    让我们通过例子来理解它: 最初给定一个空数组,我们必须实现四个函数。

    我们有以下类型的查询:

    1. Add(value) : 将值添加到数组中。

    2. Remove(value) : 从数组中删除值。

    3. 撤消:撤消对数组执行的最后一个操作。

    4. 重做:恢复最近对阵列执行的 UNDO 操作。

    示例: 输入:列表=[] 输出: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]
    

    方法:这个问题可以通过使用 2 个列表来解决,首先,我们将创建一个 undo 列表来跟踪最后执行的操作,它将是一个元组列表,格式为(操作, index, value) 这里如果 operation='a' 意味着执行的最后一个动作是将元素添加到列表中,如果 operation='r' 意味着执行的最后一个动作是从列表中删除元素列表。

    第二个列表将是重做列表,它将跟踪执行的撤消操作,以便恢复最近的撤消操作。

    # 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()

    【讨论】:

    • 请不要随意使用 -- 代替换行符,因为这会将所有内容变成标题,从而变得不可读。我已经在这里为你解决了这个问题。
    • 感谢您的指正。这是我在这个平台上的第一篇文章,但我会从现在开始处理它。花时间帮助我对你来说是一件非常好的事情。非常感谢。
    猜你喜欢
    • 2020-12-31
    • 2021-09-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多