Неуместные преобразования в списке Python?

#python #list

#python #Список

Вопрос:

Мне нужно выполнить множество преобразований (поменять элементы местами, удалить элементы) в списке и иметь возможность эффективно отменить эти изменения. В идеале я хотел бы выполнять операции, которые не работают в исходном списке (на месте), но возвращают представление в исходном списке вместе с преобразованием (и каким-то образом скрывают эту деталь реализации за интерфейсом списка). Поскольку списки большие, и я ожидаю только несколько преобразований, моя интуиция заключается в том, что неуместные преобразования ограничат объем копируемых данных.

Есть ли какой-либо встроенный тип Python для этого или у кого-нибудь есть представление о том, как это эффективно реализовать?

Обновить

Некоторый код для иллюстрации идеи:

 class Transformation(object):
    pass


class SwapTransformation(Transformation):
    def __init__(self, index1, index2):
        self.index1 = index1
        self.index2 = index2


class RemoveTransformation(Transformation):
    def __init__(self, index):
        self.index = index


class MyList(object): # or MyList(list)?
    def __init__(self, src, transformation=None):
        self._src = src
        self._transformation = transformation

    def swap(self, index1, index2):
        return MyList(self, SwapTransformation(index1, index2))

    # Need to overwrite all of these to honour the particular transformation.
    # All the below methods need to be applied to self._src
    #extend
    #append
    #remove
    #pop
    #__delitem__
    #__setitem__
    #__iadd__
    #__imul__
    #__setslice__
    #__delslice__
  


Использование

 l = [1, 2, 3, 4]
m1 = MyList(l)

print m1
[1, 2, 3, 4]

m2 = m1.remove(3)
print m2
[1, 2, 4]

print m1
[1, 2, 3, 4]

m3 = m2.swap(0, 1)
print m3
[2, 1, 4]
print m2
[1, 2, 4]

# Still possible to retract the changes by accessing `m1` or `m2`. 
# Original list is shared between them to save memory and costly 
# copying operations; none of these operations (swap/remove) 
# required a copy of `l`
  

Комментарии:

1. Я бы предложил определить объект, который хранит исходный список, список примененных преобразований (чтобы вы могли вернуться) и конечный результат…

2. @Don Я понимаю, что вы предлагаете сделать копию списка перед применением преобразования. Это то, чем я сейчас занимаюсь. Однако, поскольку список относительно большой, создание копии приводит к значительному снижению производительности (не говоря уже об объеме памяти, необходимом для создания точки сохранения перед каждым преобразованием). Я ищу немного более разумное решение.

3. Я бы вообще не делал копию списка, особенно если он большой. Вместо этого дайте вашим Transformation классам такие методы, как apply и undo ; затем вы можете сохранить эти преобразования в стеке и отменить их позже. Именно так это делают большинство редакторов.

4. Вы правы. Окончательный список результатов был создан только для того, чтобы избежать повторного применения преобразований, и вы можете пропустить его, как предлагает tobias_k. Но, если вы хотите сохранить ссылку на исходный список вместо копии, убедитесь, что исходный список не изменится (вы могли бы сохранить контрольную сумму для проверки целостности)

5. @Don: Исходный список не будет изменен, и все производные неизменяемы, поэтому изменение на месте не должно быть проблемой (это то же самое для numpy массивов и pandas фрейма данных). @tobias_k: Шаблон команды, который вы описываете, является решением, но это не позволило бы, скажем, m2 и m3 параллельно сравнивать их (например) или создавать несколько разных преобразований одного и того же исходного списка, что было бы неплохо иметь…

Ответ №1:

Один простой способ сделать это — иметь один единственный объект, который хранит список, а затем иметь несколько ссылающихся объектов. Ссылающиеся объекты будут сохранять преобразования и выполнять их на основном объекте, когда они необходимы. Это будет работать точно так же, как ваш пример, но никогда не имея более одной копии списка.

Чего вы достигнете (это самая захватывающая часть, поэтому я ставлю ее на первое место):

 m1 = MyListRef([1, 2, 3, 4])
m2 = m1.remove(2)
m3 = m2.swap(0, 2)
m4 = m2.swap(0, 1) #not a linear series of transformations

print(m1, m2, m3, m4, m1)
#[1, 2, 3, 4] [1, 2, 4] [4, 2, 1] [2, 1, 4] [1, 2, 3, 4]

print (m1.list is m2.list is m3.list is m4.list)
# True
  

Код для достижения этого:

Во-первых, вам нужен объект для хранения единственной копии списка. Кроме того, он хранит текущую историю выполненных преобразований, чтобы он мог «сбросить» список в исходное состояние, выполнив обратные преобразования:

 class MyList(object):
    def __init__(self, src):
        self.src = src
        self.history = []

    def doTransformations(self, transformations):
        self._resetTransformations()
        self.history = transformations[:]
        for transformation in transformations:
            self.src = transformation.transform(self.src)

    def _resetTransformations(self):
        self.history.reverse()
        for transformation in self.history:
            self.src = transformation.reversetransform(self.src)
  

Во-вторых, вам нужен объект, с которым вы будете взаимодействовать, который будет запрашивать преобразования MyList . Он также хранит список преобразований, выполненных для обеспечения возможности нелинейных преобразований (как в приведенном выше примере):

 class MyListRef(object):
    ''' OBS: Call 'self.list.doTransformations(self.transformations)' in every
             builtin function you want to use, such as __str__(). '''
    def __init__(self, src, transformations=[]):
        self.list = MyList(src) if type(src) == list else src
        self.transformations = transformations
    def __str__(self):
        self.list.doTransformations(self.transformations)
        return str(self.list.src)

    def _doTransformation(self, transformation):
        transformations = self.transformations   [transformation]
        self.list.doTransformations(transformations)
        return MyListRef(self.list, transformations)

    def remove(self, idx):
        return self._doTransformation(RemoveTransformation(idx))
    def swap(self, idx1, idx2):
        return self._doTransformation(SwapTransformation(idx1, idx2))
  

И не забывать, наконец, о самих объектах преобразования. Убедитесь, что вы меняете только списки, а не копируете их. Кроме того, всем им нужен reversetransform() метод, который противоположен самому преобразованию. Это вызывается, когда MyList сбрасывает свои преобразования:

 class Transformation(object):
    pass

class SwapTransformation(Transformation):
    def __init__(self, idx1, idx2):
        self.idx1 = idx1
        self.idx2 = idx2

    def transform(self, mylist):
        tmp1 = mylist[self.idx1]
        mylist[self.idx1] = mylist[self.idx2]
        mylist[self.idx2] = tmp1
        return mylist

    def reversetransform(self, mylist):
        return self.transform(mylist)

class RemoveTransformation(Transformation):
    def __init__(self, idx):
        self.idx = idx

    def transform(self, mylist):
        self.removed = mylist.pop(self.idx)
        return mylist

    def reversetransform(self, mylist):
        mylist.insert(self.idx, self.removed)
        return mylist
  

Этого достаточно, чтобы приведенный выше пример работал так, как вы хотите.

Комментарии:

1. Интересный подход, но все же не то, что я имел в виду. Исходный список видоизменяется, и цепочка преобразований применяется каждый раз, когда вы обращаетесь к списку. Я хотел бы иметь что-то, что ведет себя как «представление» в исходном списке (например, пропуск элемента, когда преобразование удаления было применено во время итерации). Прошу прощения, если мой вопрос недостаточно описал это…

2. @orange Подход, который вы описываете, будет ограничен доступом только к списку путем итерации по нему. Как только вы захотите получить доступ к отдельным элементам в преобразованном списке или сравнить два списка (без итерации), вам придется либо сделать копию, либо фактически изменить ее.

3. Хм. Я не уверен, что это так. Вы можете выполнить перегрузку __getitem__ так же, как вы это делаете __iter__ , и имитировать желаемое поведение преобразованного списка (т. Е. в случае проверки подкачки, если запрашиваются замененные индексы, и поменять их местами, если требуется).

4. @orange Верно, это может сработать. Однако я не верю, что это было бы более простым или быстрым решением, чем это. Вы можете попробовать. Или добавьте большую награду, чтобы кто-то другой захотел. 😉 Однако я все еще не уверен, почему вам не нравится изменение исходного списка. Результат был бы почти таким же.

5. Я попробую, как только у меня будет немного больше времени, и опубликую здесь. Исходный список используется в другом месте и является частью более крупного объекта, внутренний список которого не следует изменять (в противном случае другие объекты также должны быть изменены или объект поврежден). Тем не менее, я ставлю вам 1 за хорошее решение (при других обстоятельствах я был бы рад принять его).