Lookahead updating a list of dictionaries

Viewed 102

I have a list of dictionaries that looks like this:

someList = [{'a':3}, {'a':4}, {'a':6}]

Assuming that the length of someList is >1000 , what is the most efficient and pythonic way to obtain the following list of dictionaries:

someList = [{'prev_a':0, 'a': 3 , 'next_a': 4}, {'prev_a':3, 'a':4 , 'next_a' : 6}, {'prev_a':4, 'a': 6 , 'next_a': 0}]

Where:

  • next_a has the value of a in the succeeding dictionary and

  • prev_a has the value of a in the preceding dictionary

  • first and last values for prev_a and next_a, respectively, can be 0

Update 1:

The number of variables, such as a can be more than one; the dictionary could look like this:

someList = [{'a':2,'b':4,'c':9}]
dict_key_values = ['a','c']

In this case the list I want to get should only feature the prev_ and next_ values of a and c

someList = [{'prev_a':4, 'a': 6 , 'next_a': 0, 'prev_c':4, 'c': 3 , 'next_c': 0, 'b':1}, ...]

(Without the use of Pandas)

Edit

My initial approach was to enumerate(someList), use someList[i-1]['a']and someList[i+1]['a'] to access the element before and after to get prev_a and next_a.

The problem with this approach is that when the number of variables such as a increase, the code becomes hard to manage

2 Answers

You can use itertools to get multiple iterators over the same list. Then it only has to take up memory for the current elements. You can use islice to skip one, and chain to prepend one. Then zip them together into triples to iterate over all three offsets at once.

If you like, you can use a comprehension to build the triples into dicts. (But depending on your use case, it might be more time-efficient to skip this step and use the triples directly.)

from itertools import chain, islice, zip_longest

def lookahead(someList, dict_key_values):
    filldict = dict.fromkeys(dict_key_values, 0)
    prefixes = ["prev_", "next_"]
    return [
        {prefix+k: v
         for prefix, d in zip(prefixes, [p, n])
         for k, v in d.items() if k in filldict}
        | c
        for p, c, n in
        zip_longest(
            chain([filldict], someList),
            someList,
            islice(someList, 1, None),
            fillvalue=filldict,
        )
    ]

The zip_longest will fill in extra values after the iterators are exhausted up till the longest one.

>>> someList = [{'a':2,'b':4,'c':9}]
>>> dict_key_values = ['a','c']
>>> lookahead(someList, dict_key_values)
[{'prev_a': 0, 'prev_c': 0, 'next_a': 0, 'next_c': 0, 'a': 2, 'b': 4, 'c': 9}, {'prev_a': 2, 'prev_c': 9, 'next_a': 0, 'next_c': 0, 'a': 0, 'c': 0}]

If you prefer to stop at the shortest one (the islice in this case), use the builtin zip instead (and omit the fillvalue).

Depending on how you are planning to access the content of the list, i would think an implementaiton of a doubly linked list would be a good option. With that, the content of each Node would be independent from the links between the nodes, enabling you to alter attributes of a Node without having to change the logic when creating the list itself.

an example here: https://realpython.com/linked-lists-python/#how-to-use-doubly-linked-lists

Related