# Dictionary Represented Using Lists¶

Accessing nested dictionaries often calls for recursive functions in order to properly parse and/or walk through them. This isn’t always easy to code around. `ListDict` provides an alternative view on nested dictionaries, breaking down the value nested within keys to a simple concept of path and value. This flattens the nesting into a linear list, greatly simplifying the coding around nested dictionaries.

## Concept¶

Consider the following nested dictionary:

```# Nested Dict
# -----------

d = {
'a': {
'b': {
'c': {
'd': 'value',
},
},
},
'x': {
'y': {
'z': 100,
},
},
}

# accessing 'value', means
d['a']['b']['c']['d']
# 'value'

# accessing 100, means
d['x']['y']['z']
# 100
```

Looking at the above code, it’s not hard to generalize the access pattern into path/value, where:

• `d['a']['b']['c']['d']` can be broken down to:

• Path: `['a', 'b', 'c', 'd']`

• Value: `'value'`

• `d['x']['y']['z']` can be broken down to:

• Path: `['x', 'y', 'z']`

• Value: `100`

Where each path value represents a level of dictionary nesting, with the last path key holding the final value at the end of the chain.

Taking advantage of this pattern, `ListDict` takes each nested `dict` and breaks it down into a list of `(path, value)` (`DictItem` namedtuples):

```# Representing Nested Dict using path/value
# -----------------------------------------
#
#   reusing the dictionary 'd' from before

from pyats.datastructures import ListDict

# ListDict format:
#   [(path_x, value_x),
#    (path_y, value_y),
#     ... ]
#
# where path is of the form tuple():
#   (nesting_a, nesting_b, ... , final_key)

ld = ListDict(d)
# [DictItem(path=('x', 'y', 'z'), value=100),
#  DictItem(path=('a', 'b', 'c', 'd'), value='value')]
```

Each item stored within a ListDict corresponds to a path of nested dicts to a stored end value. Same paths always yield the same `dict`, for example:

```# Understanding Paths
# -------------------
#
#   same paths always yield the same dict

# given path ('a', 'b', 'c') and ('a', 'b', 'e')
# notice that the first two position 'a' and 'b' are similar
# and only the last position 'c' and 'e' is different.

# this suggests the following nested datastructure:
suggest = {
'a': {
'b': {
'c': object(),
'e': object(),
},
},
}
```

## Creation¶

A `ListDict` can only be instantiated from another (nested) `dict`.

```# Example
# -------
#
#   Creating ListDict

from pyats.datastructures import ListDict

# reusing 'suggest' variable from previous section
ld = ListDict(suggest)
# [DictItem(path=('a', 'b', 'e'), value=<object object at 0xf7683f40>),
#  DictItem(path=('a', 'b', 'c'), value=<object object at 0xf7683d00>)]
```

The returned datastructure is simply a list, except that the content of the list is always of format path/value (`DictItem` named tuple).

## Access & Reconstruction¶

`ListDict` is an extension (inheriting from) `list`, and thus all known APIs of `list` is expected to continue to work.

```# Example
# -------
#
#   Accessing ListDict

from pyats.datastructures import ListDict

# reusing 'ld' from above
ld
# DictItem(path=('a', 'b', 'e'), value=<object object at 0xf7683f40>)
ld.path
# ('a', 'b', 'e')
ld.value
# <object object at 0xf7683f40>
```

One important property of each `ListDict` is that it is mutable: the content of each instance can be modified, which means when you flatten out a nested dict, you have the ability to add and/or remove content from it as needed.

```# Example
# -------
#
#   modifying and looping ListDict

# continuing from above example...

# appending a new path/value
ld.append((('a', 'b', 'x'), object()))
# [DictItem(path=('a', 'b', 'c'), value=<object object at 0xf7692d00>),
#  DictItem(path=('a', 'b', 'e'), value=<object object at 0xf7692f40>),
#  DictItem(path=('a', 'b', 'x'), value=<object object at 0xf7692ea8>)]

# looping through
for i in ld:
print(i)
# DictItem(path=('a', 'b', 'c'), value=<object object at 0xf7692d00>)
# DictItem(path=('a', 'b', 'e'), value=<object object at 0xf7692f40>)
# DictItem(path=('a', 'b', 'x'), value=<object object at 0xf7692ea8>)

# etc..
```

Hint

the whole point of `ListDict` and breaking information down to path/value is so that users can easily loop through the whole original datastructure and do …stuff… not having to write recursive functions.

At the end, each `ListDict` object can also be re-constructed from its special path/value format, back to its represented `dict` format by calling the `reconstruct()` api.

```# Example
# -------
#
#   reconstructing a dict from ListDict

# continuing from above example...
new_dict = ld.reconstruct()
# {'a': {
#     'b': {
#         'x': <object object at 0xf7716ce8>,
#         'c': <object object at 0xf7716f40>,
#         'e': <object object at 0xf7716ea8>}
#     }
# }

# id(new_dict) is not the same as id(suggest)
id(new_dict) == id(suggest)
# False
```

The creation of a `ListDict` object and reconstructing a `dict` object is the easiest way to take nested `dict` formats, flatten it, operate on it, and return it to original state. However, keep in mind that the process is destructive: the newly created dictionary is a new object.