4

I want to convert this list:

[1,2,3,4,5]

Into this dict:

{ 1 :
  { 2 :
    { 3 :
      { 4 : 5 }}}}

This doesn't sound too complicated but I'm stumped when it's time to assign a value to a key deeper than the surface. I have a recursive function for finding how deep my dictionary goes but I don't know how to tell my algorithm to "add the new key here".

mkrieger1
  • 19,194
  • 5
  • 54
  • 65
MrPoulet
  • 91
  • 1
  • 1
  • 6

4 Answers4

6

You are looking for a recursive function that builds a dictionary with the first list element as a key and the transformed rest of the list as the value:

l = [1, 2, 3, 4, 5]

def l2d(l):  
    if len(l) < 2: # Not good
        raise Exception("The list is too short")
    if len(l) == 2: # Base case
        return {l[0]: l[1]}
    # Recursive case
    return {l[0]: l2d(l[1:])}

l2d(l)
# {1: {2: {3: {4: 5}}}}

Another interesting approach is to use functools.reduce:

from functools import reduce
reduce(lambda tail,head: {head: tail}, reversed(l))
# {1: {2: {3: {4: 5}}}}

It progressively applies a dictionary construction function to the first element of the list and the rest of it. The list is reversed first, so the construction naturally starts at the end. If the list is too short, the function returns its first element, which may or may not be desirable.

The "reduce" solution is MUCH FASTER, by about two orders of magnitude. The bottom line: avoid recursion.

DYZ
  • 55,249
  • 10
  • 64
  • 93
2

There is no need for recursion to solve this. A simple loop is sufficient.

def nest(keys):
    if not keys:
        return {}
    elif len(keys) == 1:
        return key[0]
    root = level = {}
    for key in keys[:-1]:
        level[key] = {}
        level = level[key]
    level[key] = keys[-1]
    return root

Alternatively, you can build the dict inside-out:

def nest(keys):
    if not keys:
        return {}
    current = keys[-1]
    for key in keys[-2::-1]:  # iterate keys in reverse
        current = {key: current}
    return current
MisterMiyagi
  • 44,374
  • 10
  • 104
  • 119
  • 1
    For the first option, if you give it a list with one element, you get `UnboundLocalError: local variable 'key' referenced before assignment`. You could fix it by adding a guard clause: `if len(keys) == 1: raise ValueError("'keys' cannot have exactly one element")` – wjandrea Jul 23 '21 at 23:18
  • @wjandrea Thanks for spotting this. I've changed it to behave like the second one, treating the innermost key as a primitive. – MisterMiyagi Jul 24 '21 at 10:27
1

That's a nice case to use the recursive logic until the length of your list is 1. For example:

Update: Fix empty list error.

A = [1, 2, 3, 4, 5]

def rec_dict(listA):
    try:
        if len(listA) <= 1:
            listA = listA[0]
        else:
            listA = {listA.pop(0): rec_dict(listA)}
    except IndexError:
        pass
    return listA


print(rec_dict(A))

Which returns:

{1: {2: {3: {4: 5}}}}
Georgios Livanos
  • 506
  • 3
  • 17
0

I want to propose another option using a different approach:

Although it's not recommend to use ast.literal_eval, I think in this case can be useful. Additionally, You can help yourself using re.sub() + re.findall() in order to find the matches and to do the replacements correctly. The idea here is using the string representation of the list.

import ast
import re

lst = [1, 2, 3, 4, 5]
lst_str = str(lst).replace("[","{").replace("]","}")

pattern_commas = re.compile(", ")
matches = re.findall(pattern_commas, lst_str)
n = len(matches) - 1
output = re.sub(pattern_commas, ':{', lst_str, n).replace(",",":") + '}' * n
dictionary = ast.literal_eval(output)
print(dictionary)

Output:

{1: {2: {3: {4: 5}}}}

EDIT: Before using this answer, please read @wjandrea's comment. This doesn't work for all inputs.

wjandrea
  • 28,235
  • 9
  • 60
  • 81
Carmoreno
  • 1,271
  • 17
  • 29
  • 1
    You must be confusing `literal_eval` and `eval`. There is nothing wrong or dangerous in `literal_eval`. – DYZ Jul 23 '21 at 20:02
  • 1
    However, even if you replace the last comma with a colon, there are a ton of inputs this won't work for, like `lst = [4, [5]]` -> expected `{4: [5]}`, actual `{4: {5}}`; or `['a, b', 0]` -> expected `{'a, b': 0}`, actual `SyntaxError: unmatched '}'` at `{'a:{b': 0}}`. So for that reason @DYZ I would actually advise against this method. It reminds me of something [S. Lott wrote](/a/1834754/4518341): 'It violates the "Fundamental Principle of Software". Your source is not the sum total of what's executable.' – wjandrea Jul 23 '21 at 23:46
  • 1
    Also FWIW, you don't need to use regex for this. You can use `str.count()` and `str.replace()` – wjandrea Jul 23 '21 at 23:46
  • @wjandrea You are right! I'm going to update my answer, thanks you – Carmoreno Jul 24 '21 at 00:20