3

I have a python list:

x = [1,1,1]

I set y equal to that list and then change y, and x changes because I have two pointers to the same place in memory.

y = x
y[1] = 7
print x
[1, 7, 1]

That's all good. Is there anyway I can make a list of x+y so that when I change x, I also change y? Here's some code which doesn't work but maybe clarifies my goal:

q = x + y
print q
[1, 7, 1, 1, 7, 1]
q[0] = 2
print q
[2, 7, 1, 1, 7, 1]

but I'd LIKE q to instead become:

[2, 7, 1, 2, 7, 1]

I hope that's clear, and I hope even more that it's achievable!

EDIT: To respond to the inquiries as to why this would be useful, I intend to use it as a ring buffer. Say I want to take the contents at position p and move them to position p+1: Instead of:

if p+1 == len(q):
    q[0]= q[p]
else: 
    q[p+1] =q[p]

I could just do:

q[p+1] = q[p]
TPM
  • 834
  • 4
  • 9
  • 20
  • 4
    Highly unlikely. Not with standard lists anyway. You can make a class with a mimicking functionality... – Eugene Sh. Apr 05 '18 at 19:39
  • 4
    It might be constructive to describe the circumstances where this kind of behavior would be useful. Since we already know that your exact attempt doesn't work, knowing the end goal could give us an idea about which requirements could be negotiated (ex. does q have to be a list? Does it have to be a _flat_ list? do its elements need to be immutable? Does it have to be created by literally adding x and y using the + operator?) without compromising the desired behavior. – Kevin Apr 05 '18 at 19:44
  • 1
    If you did subclass `list`, you could make a custom `__setitem__` method to handle this – JacobIRR Apr 05 '18 at 19:45
  • 1
    Possible duplicate of [Python: create sublist without copying](https://stackoverflow.com/questions/28354960/python-create-sublist-without-copying) – Sphinx Apr 05 '18 at 19:49

4 Answers4

1

What you are asking for is not achievable without making a new object.

When you concatenate lists, you are not modifying the original lists. You are returning a completely new list that has no references attached to the original list.

You can somewhat implement that functionality by creating your own integer object. Currently x[0] and y[0] refer to the same place in memory. Since integers are immutable, adding x and y causes you to create new integers.

An example of the implementation I described above is here:

class myInt:
    def __init__(self, val):
        self.val = val
    def setval(self, new):
        self.val = new
    def __repr__(self):
        return str(self.val)
x = [myInt(0), myInt(1), myInt(2)]
y = x
z = x + y
print(z)
>>>[0, 1, 2, 0, 1, 2]
z[0].setval(10)
>>>[10, 1, 2, 10, 1, 2]
Primusa
  • 13,136
  • 3
  • 33
  • 53
  • Thank you. This has the correct functionality, but I ended up using the answer from Gunther Jena below. With his, I don't have to use val and setval, which makes it a little more ergonomic. – TPM Apr 06 '18 at 14:39
1

You could create some sort of custom int or list object to mimic this behavior, but the most simple solution may be to change your structure to be a list of lists.

x = [1, 7, 1]
q = [x, x] # [[1, 7, 1], [1, 7, 1]]

x[0] = 2 # [[2, 7, 1], [2, 7, 1]]
q[0][2] = 3 # [[2, 7, 3], [2, 7, 3]]

But I don't really see how either of these structures would be useful.

Jared Goguen
  • 8,772
  • 2
  • 18
  • 36
1

This is doing the trick, even for deleting and inserting elements:

from collections import MutableSequence
from itertools import chain, islice

class ChainedListProxy(MutableSequence):
  def __init__(self, *lists):
    self._lists=lists

  def _resolve_element(self, index):
    """ returning list and subindex in that list """
    for l in self._lists:
      if index>=len(l):
        index-=len(l)
      else:
        return l, index
    raise IndexError('index out of range')

  def __getitem__(self, index):
    l, i=self._resolve_element(index)
    return l[i]

  def __delitem__(self, index):
    l, i=self._resolve_element(index)
    del l[i]

  def __setitem__(self, index, value):
    if isinstance(index, slice):
      indicies=index.indices(len(self))
    l, i=self._resolve_element(index)
    l[i]=value

  def insert(self, index, value):
    l, i=self._resolve_element(index)
    l.insert(i, value)

  def __len__(self):
    return sum( (len(l) for l in self._lists) )

Usage:

>>> x=[1,2,3]
>>> q=ChainedListProxy(x,x)
>>> q[0]
1
>>> q[3]=5
>>> q[0]
5
>>> list(q)
[5, 2, 3, 5, 2, 3]
Günther Jena
  • 3,706
  • 3
  • 34
  • 49
  • This does what I wanted. Thank you. I actually ended up doing: chainedListProxy(x,x,x) and I use the middle third, which allows overflow wrap-arounds in both directions without concern for end conditions. I tried to tweak the code so that q[0] indexed the first value in the middle third, but couldn't quite sort it out. – TPM Apr 06 '18 at 14:33
  • if you use `q=ChainedListProxy(x,x,x)` indexing with `q[0]` it is indexing the first element of x in this case, which is also the middle third. Maybe it would be better to describe the problem you want to solve rather than what you want to implement (see [xy problem](https://mywiki.wooledge.org/XyProblem) ) – Günther Jena Apr 06 '18 at 15:32
  • I want to be able to index the x array with values outside of the array and have it magically wrap around. so q[-2] would be the same as q[1] or q[4] when I have a list of length 3. As it turns out, your code ALREADY does that, so I didn't have to muddle around after all. This is actually really useful for a lot of applications, so I'm surprised that there seems to be a "why would anyone want to do that" sentiment permeating . – TPM Apr 06 '18 at 17:00
1

If you're looking for a wrap-around access to lists you have some other possibilities.

Use modulo operator via x[index % len(x)]:

>>> x[0 % len(x)]
1
>>> x[1 % len(x)]
2
>>> x[2 % len(x)]
3
>>> x[3 % len(x)]
1
>>> x[3000 % len(x)]
1
>>> x[-1 % len(x)]
3
>>> x[-2 % len(x)]
2
>>> x[-3000 % len(x)]
1

Use the same pattern for setting and deleting items if needed.

You can encapsulate this in a class too:

from collections import UserList
class WrapAroundList(UserList):
  def __getitem__(self, index):
    return super().__getitem__(index % len(self))
  def __setitem__(self, index, value):
    super().__setitem__(index % len(self), value)

Using it like this:

>>> q=WrapAroundList([1,2,3])
>>> p=2
>>> q[p+1]=q[p]
>>> q
[3, 2, 3]
>>> q[-1]
3
>>> q[0]=5
>>> q[3]
5
>>> len(q)
3
Günther Jena
  • 3,706
  • 3
  • 34
  • 49