Rex5
Rex5

Reputation: 767

multiple keys in nested dictionary

Code:

from operator import itemgetter
names = {
    'Bucky': 'Roberts',
    'Tom': 'Roberts',
    'Bernie' : 'Zunks',
    'Jenna' : 'Hayes',
    'Sally': 'Jones',
    'Amanda':'Roberts',
    'Tom':'Williams',
    'Dean':'Hayes',
    'Bernie':'Barbie',
    'Tom':'Jones'
    }
users = []
for k,v in names.items():
    users.append({'fname':k,'lname':v})
print(users)
for x in sorted(users, key=itemgetter('fname')):
    print(x)

Problem: For the last for loop (sorting one) to show all entries of dictionary names, I need all the entries i.e. duplicate keys (one-to-many mapping) also. But 2 Tom(s) and 1 Bernie are missed out while creating nested dictionary users. How can it be avoided using python 3.4?

Upvotes: 2

Views: 3571

Answers (5)

martineau
martineau

Reputation: 123531

Dictionaries cannot have duplicate keys, so the original data would need to be changed into something else...such as a list of full-name strings that contained both the first and last name separated by spaces.

Before Python 3.6, the items in dictionaries are unorderd, so you could instead store the results in a collections.OrderedDict subclass to preserve the order that the keys were inserted if you wished to have it retained (which will continue work in Python 3.6 as well as earlier versions).

Putting everthing together, would result in something like this:

from collections import OrderedDict
from operator import itemgetter
from pprint import pprint

names = ['Bucky Roberts',
         'Tom Roberts',
         'Bernie Zunks',
         'Jenna Hayes',
         'Amanda Roberts',
         'Tom Williams',
         'Dean Hayes',
         'Bernie Barbie',
         'Tom Jones',]

users = OrderedDict()
for name in sorted(names):
    fname, lname = name.split()
    users.setdefault(fname, []).append(lname)

pprint(users)

Output:

OrderedDict([('Amanda', ['Roberts']),
             ('Bernie', ['Zunks', 'Barbie']),
             ('Bucky', ['Roberts']),
             ('Dean', ['Hayes']),
             ('Jenna', ['Hayes']),
             ('Tom', ['Roberts', 'Williams', 'Jones'])])

Upvotes: 2

hygull
hygull

Reputation: 8730

@Rex5, dictionary is defined as an unordered collection of items with unique keys.

Keys should be immutable data items but values can be mutable/immutable both.

So, definition itself is filtering out the duplicated values while creating names dictionary hence there is no chance of having the duplicated copies inside for loops.

Have a look at the below sample example. I have also modified your code example after this.

Focus on keys Sam and Kim.

import json

# Creating list of users (Dictionary) with duplicated keys
users = {
"Sam": "Smith",
"Samuel": "Badri",
"Kim": "Jones",
"Jim": "Hollowen",
"Sam": "Paul",
"Joel": "Brown",
"Kim": "Fillo",
"Sam": "Koelli",
"Tinnu": "Timmon"
}

# Pretty printing the dictionary
# Keys, Sam & Kim will appear only once even after their multiple occurrences
print( json.dumps(users, indent=4))

# {
#    "Kim": "Fillo",
#    "Sam": "Koelli",
#    "Tinnu": "Timmon",
#    "Jim": "Hollowen",
#    "Joel": "Brown",
#    "Samuel": "Badri"
# }

I have also tried to provide an alternative answer that fulfills the need as follows (What I did is I just modified the dictionary and for loop).

from operator import itemgetter
names = {
    'Bucky': 'Roberts',
    'Tom': ['Roberts', 'Williams', 'Jones'],
    'Bernie' : ['Zunks', 'Barbie'],
    'Jenna' : 'Hayes',
    'Sally': 'Jones',
    'Amanda':'Roberts',
    'Dean':'Hayes',
}

users = []
for k,v in names.items():
    if type(v) == type([]):
        for lname in v:
            users.append({'fname': k, 'lname': lname})
    else:
        users.append({'fname':k, 'lname':v})

print(users)

"""
[{'lame': 'Zunks', 'fname': 'Bernie'}, {'lname': 'Barbie', 
'fname': 'Bernie'}, {'lname': 'Jones', 'fname': 'Sally'}, 
{'lname': 'Hayes', 'fname': 'Jenna'}, {'lname': 'Roberts', 
'fname': 'Amanda'}, {'lname': 'Roberts', 'fname': 'Bucky'}, 
{'lname': 'Hayes', 'fname': 'Dean'}, {'lname': 'Roberts', 
'fname': 'Tom'}, {'lname': 'Williams', 'fname': 'Tom'}, 
{'lname': 'Jones', 'fname': 'Tom'}]
"""

for x in sorted(users, key=itemgetter('fname')):
    print(x)

"""
{'lname': 'Roberts', 'fname': 'Amanda'}
{'lname': 'Zunks', 'fname': 'Bernie'}
{'lname': 'Barbie', 'fname': 'Bernie'}
{'lname': 'Roberts', 'fname': 'Bucky'}
{'lname': 'Hayes', 'fname': 'Dean'}
{'lname': 'Hayes', 'fname': 'Jenna'}
{'lname': 'Jones', 'fname': 'Sally'}
{'lname': 'Roberts', 'fname': 'Tom'}
{'lname': 'Williams', 'fname': 'Tom'}
{'lname': 'Jones', 'fname': 'Tom'}
"""

Upvotes: 1

Olivier Melançon
Olivier Melançon

Reputation: 22324

A dict cannot have duplicate keys, you might want to consider another data structure such as a list of tuples.

from operator import itemgetter

names = [
    ('Bucky', 'Roberts'),
    ('Tom', 'Roberts'),
    ('Bernie', 'Zunks'),
    ('Jenna', 'Hayes'),
    ('Sally', 'Jones'),
    ('Amanda','Roberts'),
    ('Tom', 'Williams'),
    ('Dean', 'Hayes'),
    ('Bernie', 'Barbie'),
    ('Tom', 'Jones')
    ]

users = [{'fname': k,'lname': v} for k, v in names]

Upvotes: 3

Srini
Srini

Reputation: 1649

You could also use a defaultdict

Consider the below

from collections import defaultdict

names = defaultdict(list)

names['Bucky'].append('Roberts')

names['Tom'].append('Roberts')

names['Bernie'].append('Zunks')

names['Jenna'].append('Hayes')

names['Sally'].append('Jones')

names['Amanda'].append('Roberts')

names['Tom'].append('Williams')

names['Dean'].append('Hayes')

names['Bernie'].append('Barbie')

names['Tom'].append('Jones')
print names

Outputs:

defaultdict(list,
            {'Amanda': ['Roberts'],
             'Bernie': ['Zunks', 'Barbie'],
             'Bucky': ['Roberts'],
             'Dean': ['Hayes'],
             'Jenna': ['Hayes'],
             'Sally': ['Jones'],
             'Tom': ['Roberts', 'Williams', 'Jones']})

Explanation

names = defaultdict(list) initializes a dictionary that creates an empty list instead of throwing a KeyError when a key that doesn't exist is queried.

Therefore you can just append to new keys as though they were in the dict.

Making the users list can be as below

users = []

for fname in names:
    for lname in names[fname]:
        users.append({'fname': fname, 'lname': lname})

Upvotes: 1

salparadise
salparadise

Reputation: 5825

A few possible improvements, but to your main issue, use the full name and a set as your structure, and split on space to create the other dict (which I am not sure you need):

names = {
    'Bucky Roberts',
    'Tom Roberts',
    'Bernie Zunks',
    'Jenna Hayes',
    'Sally Jones',
    'Amanda Roberts',
    'Tom Williams',
    'Dean Hayes',
    'Bernie Barbie',
    'Tom Jones'
}

users = []
for name in names:
    k, v = name.split()
    users.append({'fname':k,'lname':v})
for x in sorted(users, key=itemgetter('fname')):
    print(x)

Produces:

{'fname': 'Amanda', 'lname': 'Roberts'}
{'fname': 'Bernie', 'lname': 'Barbie'}
{'fname': 'Bernie', 'lname': 'Zunks'}
{'fname': 'Bucky', 'lname': 'Roberts'}
{'fname': 'Dean', 'lname': 'Hayes'}
{'fname': 'Jenna', 'lname': 'Hayes'}
{'fname': 'Sally', 'lname': 'Jones'}
{'fname': 'Tom', 'lname': 'Williams'}
{'fname': 'Tom', 'lname': 'Jones'}
{'fname': 'Tom', 'lname': 'Roberts'}

EDIT: using tuples and adding Betty Sue Johnson:

names = {
    ('Bucky', 'Roberts'),
    ('Betty Sue', 'Johnson'),
    ('Tom', 'Roberts'),
    ('Bernie', 'Zunks'),
    ('Jenna', 'Hayes'),
    ('Sally', 'Jones'),
    ('Amanda', 'Roberts'),
    ('Tom', 'Williams'),
    ('Dean', 'Hayes'),
    ('Bernie', 'Barbie'),
    ('Tom', 'Jones')
}

for each in sorted([{'fname':n[0], 'lname':n[1]} for n in names], key=itemgetter('fname')):
    print(each)

With output:

{'fname': 'Amanda', 'lname': 'Roberts'}
{'fname': 'Bernie', 'lname': 'Zunks'}
{'fname': 'Bernie', 'lname': 'Barbie'}
{'fname': 'Betty Sue', 'lname': 'Johnson'}
{'fname': 'Bucky', 'lname': 'Roberts'}
{'fname': 'Dean', 'lname': 'Hayes'}
{'fname': 'Jenna', 'lname': 'Hayes'}
{'fname': 'Sally', 'lname': 'Jones'}
{'fname': 'Tom', 'lname': 'Roberts'}
{'fname': 'Tom', 'lname': 'Williams'}
{'fname': 'Tom', 'lname': 'Jones'}

Upvotes: 4

Related Questions