Home > Mobile >  Convert flattened list to list of dictionaries
Convert flattened list to list of dictionaries

Time:10-29

I have the list containing strings and numbers. I am trying to convert into dictionary based on the type if it is string otherwise it would become a key.

Input:

lst = ['A', 1, 3, 4, 'B', 5, 'C', 2, 'D', 4]

Output:

[{'A': [1, 3, 4]}, {'B': [5]}, {'C': [2]}, {'D': 4}]

This is my working code so far, I it is definitely not as optimized as it could be:

main_array = []
small_array = []
se = {}
key = None
for i in range(len(lst)-1):
    print(i)
    if i == len(lst)-2:
        if type(lst[i]) == str and type(lst[i 1]) == str:
            main_array.append(lst[i])
            main_array.append(lst[i 1])
        elif type(lst[i]) == str and type(lst[i 1]) != str:
            main_array.append({lst[i]: lst[i 1]})
        elif type(lst[i]) != str and type(lst[i 1]) == str:
            small_array.append(lst[i])
            se.update({key: small_array})
            main_array.append(se)
            se = {}
            small_array = []
            main_array.append(lst[i 1])
        elif lst[i] != type(str) and lst[i   1] != type(str):
            small_array.append(lst[i])
            small_array.append(lst[i 1])
            se.update({key: small_array})
            main_array.append(se)
            se = {}
            small_array = []
    else:
        if type(lst[i]) == str and i != len(lst)-1:
            if type(lst[i 1]) == str:
                main_array.append(lst[i])
            elif type(lst[i 1]) != str:
                key = lst[i]
        elif type(lst[i]) != str and i != len(lst)-1:
            if type(lst[i 1]) == str:
                small_array.append(lst[i])
                se.update({key: small_array})
                main_array.append(se)
                se = {}
                small_array = []
            elif type(lst[i 1]) != str:
                small_array.append(lst[i])
print(main_array)

Is there any way to optimize this code as I am intending to avoid nested loops?

CodePudding user response:

It is much better to create an intermediate dictionary, then convert that dictionary to your desired result. Like this:

import collections

lst = ['A', 1, 3, 4, 'B', 5, 'C', 2, 'D', 4]

dct = collections.defaultdict(lambda: [])
key = None
for el in lst:
    if type(el) == str:
        key = el
        continue
    dct[key].append(el)

result = [{key: dct[key]} for key in dct]
print(result)

CodePudding user response:

Try using a temporary list variable to build up the list until you encounter a str value in the list:

L = ['A', 1, 3, 4, 'B', 5, 'C', 2, 'D', 4]

res = {}
curr: list

for e in L:
    if type(e) == str:
        res[e] = curr = []
    else:
        curr.append(e)

print(res)

Print:

{'A': [1, 3, 4], 'B': [5], 'C': [2], 'D': [4]}

In case you really want a list of single-value dict elements:

L = ['A', 1, 3, 4, 'B', 5, 'C', 2, 'D', 4]

res = []

add = list.append
curr: list

for e in L:
    if type(e) == str:
        curr = []
        add(res, {e: curr})
    else:
        add(curr, e)

print(res)

Output:

[{'A': [1, 3, 4]}, {'B': [5]}, {'C': [2]}, {'D': [4]}]

I was actually curious, so I timed it. It seems close to 40% faster than using an approach with defaultdict.

from timeit import timeit
from collections import defaultdict
from itertools import groupby

L = ['A', 1, 3, 4, 'B', 5, 'C', 2, 'D', 4]

# 1.385
print('dict:          ', timeit("""
res = []

add = list.append
curr: list

for e in L:
    if type(e) == str:
        curr = []
        add(res, {e: curr})
    else:
        add(curr, e)
""", globals=globals()))

# 1.619
print('no temp list:  ', timeit("""
res = []
key = None
add = list.append

for el in L:
    if type(el) == str:
        key = el
        add(res, {el: []})
        continue
    add(res[-1][key], el)
""", globals=globals()))

# 2.150
print('defaultdict:   ', timeit("""
dct = defaultdict(list)
key = None

for el in L:
    if type(el) == str:
        key = el
    else:
        dct[key].append(el)

result = [{key: dct[key]} for key in dct]
""", globals=globals()))

# 2.578
print('groupby:       ', timeit("""
groups = groupby(L, type)
result = []
try:
    while groups:
        result.append({ list(next(groups)[1])[0] : list(next(groups)[1]) })
except StopIteration:
    pass
""", globals=globals()))

CodePudding user response:

Just for fun, you could use itertools.groupby to group your list by the type of value, then iterate the result to get the key and value pairs for each dict to be pushed to the result list:

import itertools

lst = ['A', 1, 3, 4, 'B', 5, 'C', 2, 'D', 4]
groups = itertools.groupby(lst, type)
result = []
try:
    while groups:
        result.append({ list(next(groups)[1])[0] : list(next(groups)[1]) })
except StopIteration:
    pass

Output:

[
 {'A': [1, 3, 4]},
 {'B': [5]},
 {'C': [2]},
 {'D': [4]}
]

CodePudding user response:

One way to do this is to simply add a new dictionary every time a string comes up, then append to that list until the next string. Like this:

lst = ['A', 1, 3, 4, 'B', 5, 'C', 2, 'D', 4]

res = []
key = None
for el in lst:
    if type(el) == str:
        key = el
        res.append({el: []})
        continue
    res[-1][key].append(el)

print(res)

I believe that this would be much faster than my other answer. All you need is one result array and one variable to keep track of the key.


When I tested it, this method is slightly slower than rv.kvetch's answer, however there is no need to store a temporary list. This method is cleaner (no need to add the last list) and uses less memory in theory.

  • Related