Search code examples
pythonlistdictionaryrecursionrecursive-datastructures

Recursive function to convert flat list of dicts to nested list of dicts of comments and replies


I'm implementing an API for threaded comments like reddit! but I have a problem that I don't know how to solve, I will give you just the necessary context:

I have a list of dicts, where each dict represent the information of a comment extracted from the dabatabase, it looks like this:

comments:List[Dict] = [
    {
        'body': 'holaaaaaaaaaaaaaaaaaaa',
        'id': UUID('b1865484-fdef-11eb-90ee-0242ac150003'),
        'level': 0,
        'parent_id': None,
        'reference_id': UUID('e480ae80-89e2-4d38-ba92-570e863fec87')
    },
    {
        'body': 'holaaaaaaaaaaaaaaaaaaa',
        'id': UUID('b76db7ac-fdef-11eb-90ee-0242ac150003'),
        'level': 1,
        'parent_id': UUID('b1865484-fdef-11eb-90ee-0242ac150003'),
        'reference_id': UUID('b621b10b-47c8-4492-881d-577e5a7a98af')
    },
    {
        'body': 'holaaaaaaaaaaaaaaaaaaa',
        'id': UUID('9a3cdb12-fe09-11eb-a65d-0242ac150003'),
        'level': 1,
        'parent_id': UUID('b1865484-fdef-11eb-90ee-0242ac150003'),
        'reference_id': UUID('09280706-4ab6-459a-86cc-dc953479c356')
    },
    {
        'body': 'holaaaaaaaaaaaaaaaaaaa',
        'id': UUID('a53c7522-fe09-11eb-a65d-0242ac150003'),
        'level': 2,
        'parent_id': UUID('9a3cdb12-fe09-11eb-a65d-0242ac150003'),
        'reference_id': UUID('124ece3a-a8ff-415b-8e98-66852d4d7e16')
    },
    {
        'body': 'holaaaaaaaaaaaaaaaaaaa',
        'id': UUID('a349cf9a-fe12-11eb-8cc0-0242ac150003'),
        'level': 3,
        'parent_id': UUID('a53c7522-fe09-11eb-a65d-0242ac150003'),
        'reference_id': UUID('e6dbbf9c-ff0b-4337-a9c5-aa9eedfebc07')
    }
]

and my objective is to convert that into a list of nested dicts, inserting the replies/sub-comments in a replies key inside each dict, I want it to look like this:

comments:List[Dict] = [
    {
        'body': 'holaaaaaaaaaaaaaaaaaaa',
        'id': UUID('b1865484-fdef-11eb-90ee-0242ac150003'),
        'level': 0,
        'parent_id': None,
        'reference_id': UUID('e480ae80-89e2-4d38-ba92-570e863fec87'),
        'replies': [
            {
                'body': 'holaaaaaaaaaaaaaaaaaaa',
                'id': UUID('b76db7ac-fdef-11eb-90ee-0242ac150003'),
                'level': 1,
                'parent_id': UUID('b1865484-fdef-11eb-90ee-0242ac150003'),
                'reference_id': UUID('b621b10b-47c8-4492-881d-577e5a7a98af')
            },
            {
                'body': 'holaaaaaaaaaaaaaaaaaaa',
                'id': UUID('9a3cdb12-fe09-11eb-a65d-0242ac150003'),
                'level': 1,
                'parent_id': UUID('b1865484-fdef-11eb-90ee-0242ac150003'),
                'reference_id': UUID('09280706-4ab6-459a-86cc-dc953479c356'),
                'replies': [
                    {
                        'body': 'holaaaaaaaaaaaaaaaaaaa',
                        'id': UUID('a53c7522-fe09-11eb-a65d-0242ac150003'),
                        'level': 2,
                        'parent_id': UUID('9a3cdb12-fe09-11eb-a65d-0242ac150003'),
                        'reference_id': UUID('124ece3a-a8ff-415b-8e98-66852d4d7e16'),
                        'replies': [
                            {
                                'body': 'holaaaaaaaaaaaaaaaaaaa',
                                'id': UUID('a349cf9a-fe12-11eb-8cc0-0242ac150003'),
                                'level': 3,
                                'parent_id': UUID('a53c7522-fe09-11eb-a65d-0242ac150003'),
                                'reference_id': UUID('e6dbbf9c-ff0b-4337-a9c5-aa9eedfebc07')
                            }
                        ]
                    }
                ]
            }
        ]
    }
]

Where the children are nested inside their parents. I tried a recursive function:

def parse(comments:List[Dict]) -> List[Dict]:
    output_nested = []
    while len(comments) > 0:
        comment = comments.pop(0)

        replies = [reply for reply in comments if reply['parent_id'] == comment['id']]
        replies = parse(replies)

        comment['replies'] = replies
        comments = [c for c in comments if c['parent_id'] != comment['id']]

        output_nested.append(comment)

    return output_nested

but it is not working as expected, and actually I run out of ideas on how to do this.


Solution

  • You can use recursion:

    #an object to make your basic input compatible with the function below
    class UUID:
       def __init__(self, _id):
          self.id = _id
       def __eq__(self, uuid):
          return self.id == getattr(uuid, 'id', None)
       def __repr__(self):
          return f'{self.__class__.__name__}("{self.id}")'
       
    comments = [{'body': 'holaaaaaaaaaaaaaaaaaaa', 'id': UUID("b1865484-fdef-11eb-90ee-0242ac150003"), 'level': 0, 'parent_id': None, 'reference_id': UUID("e480ae80-89e2-4d38-ba92-570e863fec87")}, {'body': 'holaaaaaaaaaaaaaaaaaaa', 'id': UUID("b76db7ac-fdef-11eb-90ee-0242ac150003"), 'level': 1, 'parent_id': UUID("b1865484-fdef-11eb-90ee-0242ac150003"), 'reference_id': UUID("b621b10b-47c8-4492-881d-577e5a7a98af")}, {'body': 'holaaaaaaaaaaaaaaaaaaa', 'id': UUID("9a3cdb12-fe09-11eb-a65d-0242ac150003"), 'level': 1, 'parent_id': UUID("b1865484-fdef-11eb-90ee-0242ac150003"), 'reference_id': UUID("09280706-4ab6-459a-86cc-dc953479c356")}, {'body': 'holaaaaaaaaaaaaaaaaaaa', 'id': UUID("a53c7522-fe09-11eb-a65d-0242ac150003"), 'level': 2, 'parent_id': UUID("9a3cdb12-fe09-11eb-a65d-0242ac150003"), 'reference_id': UUID("124ece3a-a8ff-415b-8e98-66852d4d7e16")}, {'body': 'holaaaaaaaaaaaaaaaaaaa', 'id': UUID("a349cf9a-fe12-11eb-8cc0-0242ac150003"), 'level': 3, 'parent_id': UUID("a53c7522-fe09-11eb-a65d-0242ac150003"), 'reference_id': UUID("e6dbbf9c-ff0b-4337-a9c5-aa9eedfebc07")}]
    def to_tree(p = None):
       return [{**i, **({'replies':k} if (k:=to_tree(i['id'])) else {})} 
                for i in comments if i['parent_id'] == p]
    
    full_comments = to_tree()
    

    Output:

    [{'body': 'holaaaaaaaaaaaaaaaaaaa', 'id': UUID("b1865484-fdef-11eb-90ee-0242ac150003"), 'level': 0, 'parent_id': None, 'reference_id': UUID("e480ae80-89e2-4d38-ba92-570e863fec87"), 'replies': [{'body': 'holaaaaaaaaaaaaaaaaaaa', 'id': UUID("b76db7ac-fdef-11eb-90ee-0242ac150003"), 'level': 1, 'parent_id': UUID("b1865484-fdef-11eb-90ee-0242ac150003"), 'reference_id': UUID("b621b10b-47c8-4492-881d-577e5a7a98af")}, {'body': 'holaaaaaaaaaaaaaaaaaaa', 'id': UUID("9a3cdb12-fe09-11eb-a65d-0242ac150003"), 'level': 1, 'parent_id': UUID("b1865484-fdef-11eb-90ee-0242ac150003"), 'reference_id': UUID("09280706-4ab6-459a-86cc-dc953479c356"), 'replies': [{'body': 'holaaaaaaaaaaaaaaaaaaa', 'id': UUID("a53c7522-fe09-11eb-a65d-0242ac150003"), 'level': 2, 'parent_id': UUID("9a3cdb12-fe09-11eb-a65d-0242ac150003"), 'reference_id': UUID("124ece3a-a8ff-415b-8e98-66852d4d7e16"), 'replies': [{'body': 'holaaaaaaaaaaaaaaaaaaa', 'id': UUID("a349cf9a-fe12-11eb-8cc0-0242ac150003"), 'level': 3, 'parent_id': UUID("a53c7522-fe09-11eb-a65d-0242ac150003"), 'reference_id': UUID("e6dbbf9c-ff0b-4337-a9c5-aa9eedfebc07")}]}]}]}]