简体   繁体   中英

Multiple criteria search in a list in Python

dataset:

data = [{'name':'kelly', 'attack':5, 'defense':10, 'country':'Germany'}, 
        {'name':'louis', 'attack':21, 'defense': 12, 'country':'france'}, 
        {'name':'ann', 'attack':43, 'defense':9, 'country':'Germany'}]

header = ['name', 'attack', 'defense', 'country']

filter_options = {'attack':4, 'defense':7, 'country':'Germany'}

I would like to write a function whereby data is the argument and filter_options is the parameters of the function. ie func(data, filter_options)

The filter_options will filter by exact match for string type values, and/or filter continuous variables specified value that is greater than or equal to the dictionary key parameter. ie my answer should be

answer = [{'name':'kelly', 'attack':5, 'defense':10, 'country':'Germany'},
          {'name':'ann', 'attack':43, 'defense':9, 'country':'Germany'}]

my current code:

search_key_list = [key for key in filter_options.keys()]
header_index_list = [header.index(i) for i in search_key_list if i in header]

answer = []
for i in header_index_list:
    for d in data:
        if type(filter_options[header[i]]) == int or type(filter_options[header[i]]) == float:
            if data[header[i]]>filter_options[header[i]]:
                answer.append(d)
        elif type((filter_options[header[i]])) == str:
            if data[header[i]] == filter_options[header[i]]:
                answer.append(d)

The code is wrong because it is not considering the multiple criteria. It is looking at one criteria, checking which sublist fits the criteria, append the sublist to the answer list and then moving on to the next criteria.

How can I correct this? Or what other codes will work?

You need to check all "filters" and only append it if all of the filters match the dataset:

data = [{'name':'kelly', 'attack':5, 'defense':10, 'country':'Germany'}, 
        {'name':'louis', 'attack':21, 'defense': 12, 'country':'france'}, 
        {'name':'ann', 'attack':43, 'defense':9, 'country':'Germany'}]

header = ['name', 'attack', 'defense', 'country']

filter_options = {'attack':4, 'defense':7, 'country':'Germany'}


def filter_data(data, filter_options):
    answer = []
    for data_dict in data:
        for attr, value in filter_options.items():  # or iteritems
            if isinstance(value, (int, float)):     # isinstance is better than "type(x) == int"!
                if data_dict[attr] < value:         # check if it's NOT a match
                    break                           # stop comparing that dictionary
            elif isinstance(value, str):
                if data_dict[attr] != value:
                    break
        # If there was no "break" during the loop the "else" of the loop will
        # be executed
        else:
            answer.append(data_dict)
    return answer


>>> filter_data(data, filter_options)
[{'attack': 5, 'country': 'Germany', 'defense': 10, 'name': 'kelly'},
 {'attack': 43, 'country': 'Germany', 'defense': 9, 'name': 'ann'}]

The trick here is that it checks if it's smaller (in case it's an integer) or unequal (for strings) and then immediately stops comparing that dictionary and when the loop wasn't break ed and only then it appends the dictionary.


Another way without using an else clause for the loop would be:

def is_match(single_data, filter_options):
    for attr, value in filter_options.items():
        if isinstance(value, (int, float)):
            if single_data[attr] < value:
                return False
        elif isinstance(value, str):
            if single_data[attr] != value:
                return False
    return True

def filter_data(data, filter_options):
    answer = []
    for data_dict in data:
        if is_match(data_dict, filter_options):
            answer.append(data_dict)
    return answer

filter_data(data, filter_options)

You could also use a generator function instead of manual appends (based on the first approach):

def filter_data(data, filter_options):
    for data_dict in data:
        for attr, value in filter_options.items():
            if isinstance(value, (int, float)):
                if data_dict[attr] < value: 
                    break          
            elif isinstance(value, str):
                if data_dict[attr] != value:
                    break
        else:
            yield data_dict
    return answer

However that requires casting it a list afterwards:

>>> list(filter_data(data, filter_options))

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM