Reputation: 1261
I have a dict setup like so:
deck = [{
'name': 'drew',
'lvl': 23,
'items': ['sword', 'axe', 'mana_potion']},
{
'name': 'john',
'lvl': 23,
'items': ['sword', 'mace', 'health_potion']}]
This is a basic example of what it looks like, I need a way to filter (copy only the {characters}) that match certain values, such as I want only characters that are level 23, or that are carrying a sword.
I was looking at doing something like this:
filtered = filter_deck(deck, 'mace')
def filter_deck(self, deck, filt):
return [{k:v for (k,v) in deck.items() if filt in k}]
and return:
filtered = [{
'name': 'john',
'lvl': 23,
'items': ['sword', 'mace', 'health_potion']}]
I am not sure how to filter either a specific item like k:v or k:[v1,v2,v3] when I do not know if it is a single value, or a list of values, or how to filter multiple values.
I am not sure how I can filter character's with multiple keys. Say that I want to sort out characters that are lvl 23, or have items['sword'] or items['mace']. How would I have it sort in a way filter_cards(deck, ['lvl'=23, 'items'=['sword','mace'])
So if any character is lvl 23, or carries a mace or a sword, they are on that list.
Upvotes: 5
Views: 7405
Reputation: 37053
deck
isn't a dict, but a list of dicts, so I presume your search should return a list of the dicts that match your search criteria.
There are two cases to consider: the first is when you want to match a specific value for a particular key - which you will need for attributes like lvl
and name
. The second is where you are looking for a value in the list stored against an attribute. You'll need this, for example, to find out if a player is carrying a particular item.
It would make sense for you to actually know the difference between these - presumably items
is always a list, and lvl
is always an integer? - and you can register the types in a subsidiary dict like this:
is_list = {'name': False,
'lvl': False,
'items': True}
You could make the same decision dynamically if you had to by examining the type()
of an item, but it's not very good practice to have variable data types as it complicates your logic somewhat. Since you add a self
argument to your function I presume it's a method, but that's not really relevant to the question.
Given the presence of is_list
your logic could define a test_card
function that returns True if the card meets your criteria, and a filter_deck
function that iterates over the deck returning only those cards that meet at least one of the criteria. The logic would then look something like this:
deck = [{
'name': 'drew',
'lvl': 23,
'items': ['sword', 'axe', 'mana_potion']},
{'name': 'john',
'lvl': 23,
'items': ['sword', 'mace', 'health_potion']},
{'name': 'somethingelse',
'lvl': 10,
'items': ['health_potion']}]
is_list = {'name': False,
'lvl': False,
'items': True}
def test_card(card, name, values):
if is_list[name]: # values is a list
return any(value in card[name] for value in values)
else: # values is a single value
return card[name] == values
def filter_deck(deck, **filters): # filters is a dict of keyword args
r = []
for d in deck:
#print "Filtering", d, "on", filters
if any(test_card(d, n, v) for n, v in filters.items()):
r.append(d)
return r
print filter_deck(deck, name="john")
print filter_deck(deck, name="drew", lvl=10)
which outputs
[{'lvl': 23, 'name': 'john', 'items': ['sword', 'mace', 'health_potion']}]
[{'lvl': 23, 'name': 'drew', 'items': ['sword', 'axe', 'mana_potion']}, {'lvl': 10, 'name': 'somethingelse', 'items': ['health_potion']}]
You could compress the logic more by using a list comprehension in filter_deck
but that might make the program more difficult to read and understand. I always try for readability first.
Upvotes: 0
Reputation: 30268
You could just use the standard filter
and pass in the filter function, e.g:
filter(lambda x: 'mace' in x['items'], deck)
filter(lambda x: x['lvl'] == 23 or any(i in x['items'] for i in ['sword', 'mace']), deck)
Etc. These return filter generators so you if you want to print them out turn them into a list:
>>> list(filter(lambda x: x['lvl'] == 23 or any(i in x['items'] for i in ['sword', 'mace']), deck))
[{'items': ['sword', 'axe', 'mana_potion'], 'lvl': 23, 'name': 'drew'},
{'items': ['sword', 'mace', 'health_potion'], 'lvl': 23, 'name': 'john'}]
You could also break out the lambda into a standard function:
>>> def lvl_sword_mace(x):
... return x['lvl'] == 23 or any(i in x['items'] for i in ['sword', 'mace'])
...
>>> list(filter(lvl_sword_mace, deck))
[{'items': ['sword', 'axe', 'mana_potion'], 'lvl': 23, 'name': 'drew'},
{'items': ['sword', 'mace', 'health_potion'], 'lvl': 23, 'name': 'john'}]
Upvotes: 3
Reputation: 7883
The correct way to filter dict using list comprehension is by requesting the field that you want to filter against. key in dictionary
is equivalent to key in dictionary.keys()
in python2. The correct syntax is the following:
[card for card in deck if 'mace' in card['items']]
You can also use filter
:
filter(lambda card: 'mace' in card['items'], deck)
If you want to filter against multiple values, you can chain tests using and
and or
to capture the subset your need:
[card for card in deck if 'mace' in card['items'] and card['lvl'] == 23]
If the filter gets bigger create a function.
In this case filter
doesn't provide more value than list comprension. List comprehension are easier to read when they are short, more than their filter
counter part. And for the complex filtering cases, both requires a function to stay readable.
Upvotes: 1
Reputation: 90929
Your deck
is a list (of dictionaries) , it does not have .items()
. so trying to do - deck.items()
would fail.
Also the syntax -
filter_cards(deck, ['lvl'=23, 'items'=['sword','mace'])
is invalid , You should use a dictionary as the second element. Example -
filter_cards(deck, {'lvl':23, 'items':['sword','mace']})
You should use filter()
built-in function, with a function that returns True, if the dictionary contains one of the values. Example -
def filter_func(dic, filterdic):
for k,v in filterdic.items():
if k == 'items':
if any(elemv in dic[k] for elemv in v):
return True
elif v == dic[k]:
return True
return False
def filter_cards(deck, filterdic):
return list(filter(lambda dic, filterdic=filterdic: filter_func(dic, filterdic) , deck))
Demo -
>>> deck = [{
... 'name': 'drew',
... 'lvl': 23,
... 'items': ['sword', 'axe', 'mana_potion']},{
... 'name': 'john',
... 'lvl': 23,
... 'items': ['sword', 'mace', 'health_potion']},{
... 'name': 'somethingelse',
... 'lvl': 10,
... 'items': ['health_potion']}]
>>>
>>>
>>> filter_cards(deck, {'lvl':23, 'items':['sword','mace']})
[{'lvl': 23, 'items': ['sword', 'axe', 'mana_potion'], 'name': 'drew'}, {'lvl': 23, 'items': ['sword', 'mace', 'health_potion'], 'name': 'john'}]
Upvotes: 3