Leo E
Leo E

Reputation: 829

Split Python dictionary when multiple values for single key

I have Python dictionaries that sometimes have, for a single key, multiple values indicated by a comma separation (e.g. "value1, value2"). In those instances, I need to separate the dictionary into however many multiple values there are (see bottom of question for example of desired output). There can only be one value per one key in the dictionary, otherwise it needs to be an entirely separate dictionary.

I was trying to iterate through dictionary and use popitem function when value with multiple values found. And then create separate dictionaries and append them. But I run into run time errors. And I suspect there are more elegant solutions possible.

For example

x = {"name":"alice", "age":20,"hobby":"badminton, basketball", "language":"python"}
for eachkey, value in x.items():
    if ", " in value:
        x.popitem()

Get the following error before getting any further :

RuntimeError: dictionary changed size during iteration

This is an example of expected input and output :

Input

x = {"name":"alice", "age":20,"hobby":"badminton, basketball", "language":"python"}

Output

[
    {"name":"alice", "age":20,"hobby":"badminton", "language":"python"},
    {"name":"alice", "age":20,"hobby":"basketball", "language":"python"}
]

Or another example with multiple keys with multiple values :
(edited + included for clarification after @isaactfa answered)

Input

x = {"name":"alice", "age":20,"hobby":"badminton, basketball", "language":"python, go"}

Output

[
    {'name': 'alice', 'age': 20, 'hobby': 'badminton', 'language': 'python'}, 
    {'name': 'alice', 'age': 20, 'hobby': 'badminton', 'language': 'go'}, 
    {'name': 'alice', 'age': 20, 'hobby': 'basketball', 'language': 'python'}, 
    {'name': 'alice', 'age': 20, 'hobby': 'basketball', 'language': 'go'}
]

Upvotes: 1

Views: 4166

Answers (3)

Mykola Zotko
Mykola Zotko

Reputation: 17804

You can use the function product():

from itertools import product

x = {"name":"alice", "age":20,"hobby":"badminton, basketball", "language":"python"}

vals = [v.split(', ') if isinstance(v, str) else [v] for v in x.values()]
print([dict(zip(x, p)) for p in product(*vals)])

Output:

[{'name': 'alice', 'age': 20, 'hobby': 'badminton', 'language': 'python'},
 {'name': 'alice', 'age': 20, 'hobby': 'basketball', 'language': 'python'}]

Upvotes: 2

georg
georg

Reputation: 214949

I'd start by converting the input into a list of lists of key-value tuples (oh well)

def unwrap(d):
    for k, v in d.items():
        if isinstance(v, str):
            yield [(k, v2.strip()) for v2 in v.split(',')]
        else:
            yield [(k, v)]

so that with

d = {
    "name": "alice",
    "age": 20,
    "hobby": "badminton, basketball",
    "language": "python, go"
}

it would return

[
    [('name', 'alice')], 
    [('age', 20)], 
    [('hobby', 'badminton'), ('hobby', 'basketball')], 
    [('language', 'python'), ('language', 'go')]
]

Once this is done, apply itertools.product to convert this to a list of all possible dicts:

result = [
    dict(x)
    for x in product(*unwrap(d))
]

with result being

[
    {'name': 'alice', 'age': 20, 'hobby': 'badminton', 'language': 'python'}, 
    {'name': 'alice', 'age': 20, 'hobby': 'badminton', 'language': 'go'}, 
    {'name': 'alice', 'age': 20, 'hobby': 'basketball', 'language': 'python'}, 
    {'name': 'alice', 'age': 20, 'hobby': 'basketball', 'language': 'go'}
]

Upvotes: 4

isaactfa
isaactfa

Reputation: 6651

EDIT: This does not yield all permutations of dictionaries if more than one key has more than one value. If that is what you need, check out @georg's answer.

ORIGINAL ANSWER:

This should give you the desired output:

def make_dicts(x):
    tuples = [(k, *(s.strip() for s in v.split(","))) if isinstance(v, str) else (k, v) for k, v in x.items()]
    most_values = max(len(t) for t in tuples) - 1
    for i in range(most_values):
        yield {k: v[i % len(v)] for k, *v in tuples}

You can now iterate over make_dicts(x) to retrieve your new dictionaries, or do list(make_dicts(x)) to get them in a list.

The way it works is, it first makes a list of tuples containing the key as the first element and each comma separated value after it. I then find the largest of those tuples, because that is how many new dictionaries will be created (subtracting 1 for the key). For every new dictionary I now just tell it to put the key, and then the next value, wrapping the index with a modulo so keys with only one value get duplicated.

Hope this is what you were looking for.

Upvotes: 1

Related Questions