Make a list out of two (or more) lists which refers to the original lists when appending new element

I would like to create a list of objects which stands for the whole category of objects with the same attribute (let's say the objects that are visible). But once I append the object to one of the subgroups it doesn't update the list of all visible objects (visible_objects). This is known Python behaviour to me but I would like to know what is the best practice to implement this so it appends the element also to the combined list (visible_objects) in a clean and seamless way. (i.e. everytime I append new element to one of the subgroups)

class A:
    def __init__(self,i):
        self.i=i
           
images=[A(1),A(2),A(3)]
images2=[A(4),A(5)]

visible_objects=images+images2

images.append(A(6))

print([x.i for x in images])
print([x.i for x in images2])
print([x.i for x in visible_objects])

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

I want the last list also contain 6 without reassigning visible_objects after every append. Thank you very much.

2 answers

  • answered 2021-02-23 17:13 DovaX

    Ok, so I think I kind of solved it but I'm not sure it is the best approach.

    class A:
        def __init__(self,i):
            self.i=i
               
    class MagicList:
        def __init__(self,elements=[],referenced_lists=None):
            self.elements=elements
            self.referenced_lists=referenced_lists
            self.embedded_in_lists=[]
            if self.referenced_lists is not None:
                self.elements=[]
                for i,magic_list in enumerate(self.referenced_lists):
                    for item in magic_list.elements:
                        self.elements.append(item)
                    self.referenced_lists[i].embedded_in_lists.append(self)
                                    
        def append(self,obj):
            self.elements.append(obj)
            for i,list1 in enumerate(self.embedded_in_lists):
               
                self.embedded_in_lists[i].elements=[]
                for j in range(len(list1.referenced_lists)):
                    self.embedded_in_lists[i].elements+=list1.referenced_lists[j].elements
            
        def pop(self,index):
            self.elements.pop(index)
            for i,item in enumerate(self.embedded_in_lists):
                item.elements.pop(index)
                  
        def __str__(self):
            return(str(self.elements))
       
            
    m=MagicList([1,3,4])
    m2=MagicList([2,5,8]) 
    m3=MagicList(referenced_lists=[m,m2])
    
    print(m)
    print(m2)
    print(m3) 
        
    images=MagicList([A(1),A(2),A(3)])
    images2=MagicList([A(4),A(5)])
    
    visible_objects=MagicList(referenced_lists=[images,images2])
    
    print([x.i for x in images.elements])
    print([x.i for x in visible_objects.elements])
    
    images.append(A(6))
    
    print([x.i for x in images.elements])
    print([x.i for x in visible_objects.elements])
    
    images.pop(3)
    
    print([x.i for x in images.elements])
    print([x.i for x in visible_objects.elements])
    

    Result:

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

  • answered 2021-02-23 17:32 Pranav Hosangadi

    Your answer is a good approach, but you could also do this by defining a __getitem__() in MagicList that returns the correct element. This way, you don't need to spend time cloning or copying elements from each list to a big "super-list", and modifications to the "sub-lists" are seamlessly reflected when you try to get an index of the MagicList object.

    class MagicList():
        def __init__(self, *referenced_lists):
            self.lists = referenced_lists
        
        def __getitem__(self, index):
            cum_len = 0
            for sublist in self.lists:
                sublist_start_index = cum_len
                sublist_end_index = cum_len + len(sublist)
                if sublist_end_index > index:
                    return sublist[index - sublist_start_index]
                cum_len = sublist_end_index
            raise IndexError("list index out of range")
    
        def __str__(self):
            return str([x for x in self])
    
        def add_list(self, new_list):
            if isinstance(new_list, list): # If new_list is a list, append it to self.lists
                self.lists.append(new_list)
            else: # Try to convert new_list to a list and append that
                try:
                    self.lists.append(list(new_list))
                except TypeError: # new_list is not an iterable, so list(new_list) throws a TypeError
                    self.lists.append([new_list])
    

    Then, you can do:

    l1  = [1, 2, 3, 4, 5]
    l2 = [100, 200, 300, 400, 500]
    
    m = MagicList(l1, l2) # You can give this any number of lists
    
    print("Original list: ")
    print(m)
    
    l1.append(-1)
    print("Appending to l1: ")
    print(m)
    
    l2.append(1000)
    print("Appending to l2: ")
    print(m)
    
    l1[0] = 'abc'
    print("Modifying l1: ")
    print(m)
    
    l2[-2] = 'def'
    print("Modifying l2: ")
    print(m)
    
    del l1[0]
    print("Removing from l1: ")
    print(m)
    
    del l2[-1]
    print("Removing from l2: ")
    print(m)
    

    This gives the output:

    Original list: 
    [1, 2, 3, 4, 5, 100, 200, 300, 400, 500]
    Appending to l1: 
    [1, 2, 3, 4, 5, -1, 100, 200, 300, 400, 500]
    Appending to l2: 
    [1, 2, 3, 4, 5, -1, 100, 200, 300, 400, 500, 1000]
    Modifying l1: 
    ['abc', 2, 3, 4, 5, -1, 100, 200, 300, 400, 500, 1000]
    Modifying l2: 
    ['abc', 2, 3, 4, 5, -1, 100, 200, 300, 400, 'def', 1000]
    Removing from l1: 
    [2, 3, 4, 5, -1, 100, 200, 300, 400, 'def', 1000]
    Removing from l2: 
    [2, 3, 4, 5, -1, 100, 200, 300, 400, 'def']
    

    Something to remember though: if you want to append / insert anything to MagicList that isn't in the referenced lists, you need to wrap that in a list.