Ian Clark
Ian Clark

Reputation: 9347

Filter a list of dictionaries to always return a single dictionary, given a default key value to lookup

I'm aware that there are 1001 ways of solving this solution, I'm asking the community to get an understanding of what the most Pythonic approach seems to be.

Lets say I have a list of dictionaries taking the format:

colours = [{"color": "green", "owner": "Mark"},
           {"color": "blue", "owner": "Luke"},
           {"color": "red", "owner": "John"}]

Overlooking the obvious fact that the list should be a dictionary of dictionaries, I'm wanting to retrieve a single dictionary from the list given a user input for the color key in the dictionary, but using a default value if the color is not matched (lets say "green" in this example).

As such I'm seeking a function:

def get_with_default(colour, colours, default):

That given the colours list would return:

>>> get_with_default("blue", colours, "green") # Valid dictionary
{"color": "blue", "owner": "Luke"}
>>> get_with_default("black", colours, "green") # Colour doesn't exist
{"color": "green", "owner": "Mark"}

Update (thanks Martijn), the default value would be hard-coded and known to be in the list, however the other key/value pairs in that dictionary are unknown/dynamic (so I know that 'green' is a key in a dictionary, but I don't know who 'owns' green in this simplified case

Upvotes: 1

Views: 857

Answers (3)

tailor_raj
tailor_raj

Reputation: 1057

You can also use list comprehension to achieve this.

def get_with_defaults(color,colours,default='green'):
    res = [col for col in colours if col['color']==color]   
    if not res:
        return [col for col in colours if col['color']==default]
    return res

get_with_defaults('blue',colours)
[{'color': 'blue', 'owner': 'Luke'}]
>>> get_with_defaults('black',colours)
[{'color': 'green', 'owner': 'Mark'}]

Output in dictionary.

def get_with_defaults(color,colours,default='green'):
    res = [col for col in colours if col['color']==color]   
    if not res:
        return [col for col in colours if col['color']==default][0]
    return res[0]

get_with_defaults('blue',colours)
{'color': 'blue', 'owner': 'Luke'}
>>> get_with_defaults('black',colours)
{'color': 'green', 'owner': 'Mark'}

Upvotes: 0

Martijn Pieters
Martijn Pieters

Reputation: 1122352

next() is the most pythonic function to achieve just that:

def get_with_default(colour, colours, default):
    search = (d for d in colours if d['color'] in (colour, default))
    match_or_default = next(search)
    if match_or_default['color'] != default or default == colour:
        return match_or_default
    return next(search, match_or_default)

next() loops over the first argument until that produces a result, then returns that. If the first argument is exhausted, StopIteration is raised instead, unless a second argument is given, a default, in which case that value is returned instead of raising the exception.

By giving it a generator expression that embodies the search, you efficiently scan the colours list for the first match. If that turns out to be the default, then we continue to scan until we find the other match, or reach the end of the list.

Demo:

>>> get_with_default("blue", colours, "green")
{'color': 'blue', 'owner': 'Luke'}
>>> get_with_default("black", colours, "green")
{'color': 'green', 'owner': 'Mark'}

The above method is quite efficient in that it only has to scan the input list once, and the scan stops as soon as a match is found.

Note that this function will raise StopIteration if the default is not present either:

>>> get_with_default("black", colours, "purple")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in get_with_default
StopIteration

You could instead return None in that case by giving the first next() call a default return value as well:

match_or_default = next(search, None)

Upvotes: 4

TerryA
TerryA

Reputation: 59984

Not the best, but nice and readable:

def get_with_default(colour, L, default=''):
    temp = None
    for d in L:
        if d['color'] == colour:
            return d
        elif d['color'] == default:
            temp = d
    return temp

When testing:

>>> get_with_default('blue', colours, 'green')
{'color': 'blue', 'owner': 'Luke'}
>>> get_with_default('black', colours, 'green')
{'color': 'green', 'owner': 'Mark'}

Upvotes: 2

Related Questions