Saaru Lindestøkke
Saaru Lindestøkke

Reputation: 2564

Is there an easy way to map one dictionary to the other?

I have two dictionaries:

source = {'a': 10, 'b': 20, 'c': 30}
destination = {'x': None, 'y': None, 'z': None, 'qq': 'Some value'}

I would like to map the source dictionary to the destination, based on some pre-set mapping, e.g.:

a -> y
b -> z
c -> x

I could create a function that loops over the destination dictionary and assigns the appropriate value based on a nested if:

for k, v in destination.items():
    if k == 'x':    
        destination[k] = source['c']
    elif k == 'y':
        destination[k] = source['a']
    elif k == 'z':
        destination[k] = source['b']

This doesn't seem very scalable.
Additionally I might have nested keys in both the source and destination, which complicates the loop even more.
An example of a nested source/destination dictionary might be:

source_nest = 
  {
    "a": {
      "f": {
        "k": 4,
        "l": "cat"
      }
    },
    "b": {
      "s": "hit"
    }
  }
destination_nest = 
  {
    "x": None,
    "y": {
      "q": None,
      "r": 100
    },
    "z": None
  }

With an example mapping like:

a/f/k -> y/q
a/f/l -> x
b/s -> z

What would be a more efficient way to do this re-mapping?

If it matters, these dictionaries are representations of JSON files.

Upvotes: 1

Views: 72

Answers (2)

I used the value of the mapping as the key to the source and created a key/value pair dictionary called result

source = {'a': 10, 'b': 20, 'c': 30}
destination = {'x': None, 'y': None, 'z': None, 'qq': 'Some value'}
mapping = {"y": "a", "z": "b", "x": "c"}
result={k:source.get(v) for (k,v) in mapping.items()}

print(result)

output:

{'y': 10, 'z': 20, 'x': 30}

Upvotes: 0

tobias_k
tobias_k

Reputation: 82939

Define the mapping as another dictionary with keys and values inversed, then use a dictionary comprehension with ternary if/else to get the result:

source = {'a': 10, 'b': 20, 'c': 30}
destination = {'x': None, 'y': None, 'z': None, 'qq': 'Some value'}

mapping = {"y": "a", "z": "b", "x": "c"}  # note: key/values inversed
result = {k: source[mapping[k]] if k in mapping else v
          for k, v in destination.items()}
# {'x': 30, 'y': 10, 'z': 20, 'qq': 'Some value'}

Or map source to the destination keys first, then use get with default:

mapping = {"a": "y", "b": "z", "c": "x"}  # note: original order
source_mapped = {mapping.get(k, k): v for k, v in source.items()}
result = {k: source_mapped.get(k, v) for k, v in destination.items()}

For the nested case, you may first have to define two recursive functions, let's call them deep_set and deep_get to set and get values in a nested dictionary using a multi-part key.

def deep_set(d, keys, val):
    first, *rest = keys
    if rest:
        deep_set(d[first], rest, val)
    else:
        d[first] = val

def deep_get(d, keys):
    first, *rest = keys
    if rest:
        return deep_get(d[first], rest)
    else:
        return d[first]

Then you can again define the mapping, iterate the pairs, and replace them in the destination directory. This does not work as well with a dict comprehension, so I'll just modify the destination directory; make sure to create a deep copy first if you still need it.

mapping = [("a/f/k", "y/q"), ("a/f/l", "x"), ("b/s", "z")]
for s, t in mapping:
    val = deep_get(source_nest, s.split("/"))
    deep_set(destination_nest, t.split("/"), val)
# {'x': 'cat', 'y': {'q': 4, 'r': 100}, 'z': 'hit'}

Upvotes: 3

Related Questions