patternpythonMinor
Rotating list in python
Viewed 0 times
listrotatingpython
Problem
This is a list class which allows users to "turn" the list while keeping track of the current position.
For efficiency, the list is not actually rotated on each rotation, but instead an offset keeps track of where the "0" in the list is. However, this makes addition and multiplication less efficient as these operations require to bring the list "in order" first.
Another attempt has been made in the comments at this link upon which this class is loosely based.
```
class Ring(UserList.UserList):
def __init__(self, initlist=None, offset=0):
self.offset=offset
self.data = []
if initlist is not None:
# XXX should this accept an arbitrary sequence?
if type(initlist) == type(self.data):
self.data[:] = initlist
elif isinstance(initlist, UserList):
self.data[:] = initlist.data[:]
else:
self.data = list(initlist)
def __repr__(self): return repr(self.data[len(self.data)-self.offset:len(self.data)]+self.data[0:len(self.data)-self.offset])
def __lt__(self, other): return self.data[len(self.data)-self.offset:len(self.data)]+self.data[0:len(self.data)-self.offset] self.__cast(other)
def __ge__(self, other): return self.data[len(self.data)-self.offset:len(self.data)]+self.data[0:len(self.data)-self.offset] >= self.__cast(other)
def __cast(self, other):
if isinstance(other, Ring):
return other.data[len(other.data)-other.offset:len(other.data)]+other.data[0:len(other.data)-other.offset]
elif isinstance(other, type(self.data)):
return other
else:
return list(other)
def __cmp__(self, other):
return cmp(self.data[len(self.data)-self.offset:len(self.data)]+self.data[0:len(self.data)-self.offset], self.__cast(other))
def __getitem__(self, i):
if abs(i)>=len(self.data):
return self.data[i] # raise IndexError
elif (i-self.offset)=len(self.data):
For efficiency, the list is not actually rotated on each rotation, but instead an offset keeps track of where the "0" in the list is. However, this makes addition and multiplication less efficient as these operations require to bring the list "in order" first.
Another attempt has been made in the comments at this link upon which this class is loosely based.
```
class Ring(UserList.UserList):
def __init__(self, initlist=None, offset=0):
self.offset=offset
self.data = []
if initlist is not None:
# XXX should this accept an arbitrary sequence?
if type(initlist) == type(self.data):
self.data[:] = initlist
elif isinstance(initlist, UserList):
self.data[:] = initlist.data[:]
else:
self.data = list(initlist)
def __repr__(self): return repr(self.data[len(self.data)-self.offset:len(self.data)]+self.data[0:len(self.data)-self.offset])
def __lt__(self, other): return self.data[len(self.data)-self.offset:len(self.data)]+self.data[0:len(self.data)-self.offset] self.__cast(other)
def __ge__(self, other): return self.data[len(self.data)-self.offset:len(self.data)]+self.data[0:len(self.data)-self.offset] >= self.__cast(other)
def __cast(self, other):
if isinstance(other, Ring):
return other.data[len(other.data)-other.offset:len(other.data)]+other.data[0:len(other.data)-other.offset]
elif isinstance(other, type(self.data)):
return other
else:
return list(other)
def __cmp__(self, other):
return cmp(self.data[len(self.data)-self.offset:len(self.data)]+self.data[0:len(self.data)-self.offset], self.__cast(other))
def __getitem__(self, i):
if abs(i)>=len(self.data):
return self.data[i] # raise IndexError
elif (i-self.offset)=len(self.data):
Solution
Your code is long, very long. Instead of looking for premature optimizations, I'd ask you if you really can't do this with just the built-in
If you subtype list, then you can get all the methods and so allows us to do something as simple as:
And yes this does work as you want:
Lets assume that you do need the performance, so we'll be going from your code. Take the following example:
Not using \$O\$, what is the problem?
It's changing the list twice. Instead I'd set the list to the updated list and check if the offset is not 0. Then either way check if they are the same.
You can make a small function that does this:
I'd say the biggest problem with this, is the unneeded
After this you can re-write your special method:
But that's a lot of boiler-plate for each of the special methods.
And so I'd change
It needs to take the function, and the data. We know that the first argument will be
This allows you to make most of the special methods with ease:
Now we have all the not-so-easy to optimize functions made.
But this still leaves us with a lot of functions to optimize.
To optimize these, I'd make a new function that transforms the input index to the correct shifted index.
Something simple like:
Which allows us to do (you always have to use tuple unpacking
This allows us to re-write
Which is really nice compared to what it was before.
And so you should be able to change the rest of your functions using the above, to much smaller functions.
list?If you subtype list, then you can get all the methods and so allows us to do something as simple as:
class Ring(list):
def turn(self, amount):
if amount != 0:
super(Ring, self).__init__(self[amount:] + self[:amount])
def __repr__(self):
# To show the object is not a list.
return 'Ring' + super(Ring, self).__repr__()And yes this does work as you want:
# input
r = Ring([1, 2, 3])
print(r)
r.turn(2)
print(r)
r.turn(-1)
print(r)
r += [1]
print(r)
#output
Ring[1, 2, 3]
Ring[3, 1, 2]
Ring[2, 3, 1]
Ring[2, 3, 1, 1]Lets assume that you do need the performance, so we'll be going from your code. Take the following example:
r = Ring([1, 2, 3, 4, 5])
r.turn(3)
print r == [1, 2, 3] or r == [4, 5]Not using \$O\$, what is the problem?
It's changing the list twice. Instead I'd set the list to the updated list and check if the offset is not 0. Then either way check if they are the same.
You can make a small function that does this:
def update_once(self):
if self.offset:
self.data = self.data[len(self.data)-self.offset:len(self.data)]+self.data[0:len(self.data)-self.offset]
self.offset = 0I'd say the biggest problem with this, is the unneeded
len(self.data) and 0. Instead remove them.After this you can re-write your special method:
def __eq__(self, other):
self.update_once()
return list.__eq__(self.data, other)But that's a lot of boiler-plate for each of the special methods.
And so I'd change
update_once to be a wrapper.It needs to take the function, and the data. We know that the first argument will be
self and we'll write it with that in mind.def update_once(fn):
def call(self, other):
if self.offset:
self.data = self.data[-self.offset:] + self.data[:-self.offset]
self.offset = 0
return fn(self.data, other)
return callThis allows you to make most of the special methods with ease:
def update_once(fn):
def call(self, *args):
if self.offset:
self.data = self.data[-self.offset:] + self.data[:-self.offset]
self.offset = 0
return fn(self.data, *args)
return call
class Ring(UserList.UserList):
def __init__(self, initlist=None, offset=0):
self.offset = offset
self.data = []
if initlist is not None:
# XXX should this accept an arbitrary sequence?
if type(initlist) == type(self.data):
self.data[:] = initlist
elif isinstance(initlist, UserList):
self.data[:] = initlist.data[:]
else:
self.data = list(initlist)
@update_once
def __repr__(self):
return 'Ring' + repr(self)
__lt__ = update_once(list.__lt__)
__le__ = update_once(list.__le__)
__eq__ = update_once(list.__eq__)
__ne__ = update_once(list.__ne__)
__gt__ = update_once(list.__gt__)
__ge__ = update_once(list.__ge__)
__add__ = update_once(list.__add__)
__mul__ = update_once(list.__mul__)
__rmul__ = update_once(list.__rmul__)Now we have all the not-so-easy to optimize functions made.
But this still leaves us with a lot of functions to optimize.
To optimize these, I'd make a new function that transforms the input index to the correct shifted index.
Something simple like:
def _shift(self, *indexes):
size = len(self.data)
offset = self.offset
for index in indexes:
yield (index + offset) % sizeWhich allows us to do (you always have to use tuple unpacking
a, = not a =):a, b = self._shift(1, -1)
a, = self._shift(1)This allows us to re-write
pop:def pop(self, i=-1):
i, = self._shift(i)
return self.data.pop(i)Which is really nice compared to what it was before.
And so you should be able to change the rest of your functions using the above, to much smaller functions.
Code Snippets
class Ring(list):
def turn(self, amount):
if amount != 0:
super(Ring, self).__init__(self[amount:] + self[:amount])
def __repr__(self):
# To show the object is not a list.
return 'Ring' + super(Ring, self).__repr__()# input
r = Ring([1, 2, 3])
print(r)
r.turn(2)
print(r)
r.turn(-1)
print(r)
r += [1]
print(r)
#output
Ring[1, 2, 3]
Ring[3, 1, 2]
Ring[2, 3, 1]
Ring[2, 3, 1, 1]r = Ring([1, 2, 3, 4, 5])
r.turn(3)
print r == [1, 2, 3] or r == [4, 5]def update_once(self):
if self.offset:
self.data = self.data[len(self.data)-self.offset:len(self.data)]+self.data[0:len(self.data)-self.offset]
self.offset = 0def __eq__(self, other):
self.update_once()
return list.__eq__(self.data, other)Context
StackExchange Code Review Q#140117, answer score: 5
Revisions (0)
No revisions yet.