I have a function that is taking in a list where each element is a vote in JSON format. I am then building and returning a dictionary that looks like this:

{ "District A" : {
"Match A" : {
"Candidate A" : {
0 : 10
1 : 20
}
}
}


Each element in the list could look something like this:

{
"district": "district a",
"list": {
"matches": [
{
"match": "King",
"list": {
"candidates": [
{
"match": "King",
"candidate": "Candidate 1",
"ranking": 0
},
{
"match": "King",
"candidate": "Candidate 2",
"ranking": 1
},
{
"match": "King",
"candidate": "Candidate 3",
"ranking": 2
},
{
"match": "King",
"candidate": "Candidate 4",
"ranking": 3
},
{
"match": "King",
"candidate": "Candidate 5",
"ranking": 4
}
]
}
},
{
"match": "Queen",
"list": {
"candidates": [
{
"match": "Queen",
"candidate": "Candidate 1",
"ranking": 2
},
{
"match": "Queen",
"candidate": "Candidate 2",
"ranking": 0
},
{
"match": "Queen",
"candidate": "Candidate 3",
"ranking": 0
},
{
"match": "Queen",
"candidate": "Candidate 4",
"ranking": 1
},
{
"match": "Queen",
"candidate": "Candidate 5",
"ranking": 0
}
]
}
}
]
}
}


Here is my code that works:

def collate_by_district(votes):

# If the district is in collated_votes
for bout in vote['content']['matches']:
# If the matches is in collated_votes
# Check if the candidate is in collated_votes
for candidate in bout['content']['candidates']:
else:
else:
rankings = {}
if candidate['ranking'] in rankings:
rankings[candidate['ranking']] += 1
else:
rankings[candidate['ranking']] = 1
else:
match = {}
for candidate in bout['content']['candidates']:
rankings = {}
if candidate['ranking'] in rankings:
rankings[candidate['ranking']] += 1
else:
rankings[candidate['ranking']] = 1
match[candidate['candidate']] = rankings
else:
match = {}
for bout in vote['content']['matches']:
candidates = {}
for candidate in bout['content']['candidates']:
rankings = {}
candidates[candidate['candidate']] = rankings
if candidate['ranking'] in rankings:
rankings[candidate['ranking']] += 1
else:
rankings[candidate['ranking']] = 1
match[bout['match']] = candidates



It works, and is fast enough, but it just seems rather unwieldy and hard to read. Is there a better way that all these nested if/then statements?

• Welcome to CodeReview.SE. Would you be able to provide a bit more explanation about what your function is supposed to do and how you call it ? – SylvainD Dec 8 '17 at 21:36

Is there a better way? Yes, absolutely!

There are two tools that ship standard with Python that you need to investigate: collections.Counter and collections.defaultdict.

For your code that looks like this:

if vote['district'] in collated_votes:
else:
match = {}
rankings = {}


All this stuff is exactly what defaultdict is intended to handle: provide a dictionary that, when accessed with a key not currently present in the dictionary, returns a "default" value generated from a no-args factory function.

You can use this with dict as the factory function, or list or even something clever like a functools.partial of defaultdict that generates nested defaultdicts!

You can also pass int as the factory, which constructs an integer (default value = 0) as the default value.

But wait! Instead of using defaultdict(int) you could also use collections.Counter. This implements a bag that counts the number of times each element is added. (It's much like defaultdict(int) but with slightly different semantics.)

Which one you use (defaultdict(int) or Counter()) will depend on your code structure. If you can structure the items as a sequence or generator, the Counter might be the best option - it can take a constructor parameter that slurps them all up. If you have to bounce around from one collection to another because of the way your data is structured, the defaultdict approach might be best.

Something like this:

import functools

from collections import defaultdict

Candidate_factory = functools.partial(defaultdict, int)
Matches_factory = functools.partial(defaultdict, Candidate_factory)
Districts_factory = functools.partial(defaultdict, Matches_factory)

Districts = Districts_factory()

for district in ("District A",):
for match in ("Match 1",):
for candidate in ("Leroy",):
Districts[district][match][candidate] += 1

import pprint
pprint.pprint(Districts)


Which outputs:

\$ python test.py
defaultdict(functools.partial(<class 'collections.defaultdict'>, functools.partial(<class 'collections.defaultdict'>, <class 'int'>)),
{'District A': defaultdict(functools.partial(<class 'collections.defaultdict'>, <class 'int'>),
{'Match 1': defaultdict(<class 'int'>,
{'Leroy': 1})})})

• Thanks for the help. I am going to read up on everything you provided over the weekend, but this looks like it will help me a lot. – chemical Dec 8 '17 at 23:07