8

Поиск в списке словарей в Python

7

Описание проблемы:

У меня есть массив словарей, который выглядит следующим образом:

[
  {"name": "Tom", "age": 10},
  {"name": "Mark", "age": 5},
  {"name": "Pam", "age": 7}
]

Мне нужно выполнить поиск по имени, чтобы найти словарь, где name равен "Pam". Как мне это сделать и получить соответствующий словарь:

{"name": "Pam", "age": 7}

Я работаю с Python и не могу понять, как правильно осуществить этот поиск. Можете ли вы помочь с примером кода?

5 ответ(ов)

3

Это, на мой взгляд, самый питоничный способ:

people = [
    {'name': "Tom", 'age': 10},
    {'name': "Mark", 'age': 5},
    {'name': "Pam", 'age': 7}
]

filter(lambda person: person['name'] == 'Pam', people)

Результат (возвращается как список в Python 2):

[{'age': 7, 'name': 'Pam'}]

Примечание: В Python 3 возвращается объект фильтра. Таким образом, решение для Python 3 будет выглядеть так:

list(filter(lambda person: person['name'] == 'Pam', people))
1

Ответ @Frédéric Hamidi отличный. В Python 3.x синтаксис для метода .next() немного изменился. Поэтому потребуется небольшая модификация:

>>> dicts = [
     { "name": "Tom", "age": 10 },
     { "name": "Mark", "age": 5 },
     { "name": "Pam", "age": 7 },
     { "name": "Dick", "age": 12 }
 ]
>>> next(item for item in dicts if item["name"] == "Pam")
{'age': 7, 'name': 'Pam'}

Как упомянул в комментариях @Matt, вы можете задать значение по умолчанию следующим образом:

>>> next((item for item in dicts if item["name"] == "Pam"), False)
{'name': 'Pam', 'age': 7}
>>> next((item for item in dicts if item["name"] == "Sam"), False)
False
>>>
0

Я протестировал различные методы обхода списка словарей и возврата тех словарей, где ключ x имеет определённое значение.

Результаты:

  • Скорость: списковое включение > генераторное выражение >> обычная итерация по списку >>> фильтр.
  • Все методы масштабируются линейно с количеством словарей в списке (увеличение размера списка в 10 раз → увеличение времени выполнения в 10 раз).
  • Количество ключей в словаре не оказывает значительного влияния на скорость при большом количестве ключей (тысячи). Посмотрите на этот график, который я составил: ссылка на график (названия методов смотрите ниже).

Все тесты проводились на Python 3.6.4, W7x64.

from random import randint
from timeit import timeit

list_dicts = []
for _ in range(1000):     # количество словарей в списке
    dict_tmp = {}
    for i in range(10):   # количество ключей для каждого словаря
        dict_tmp[f"key{i}"] = randint(0,50)
    list_dicts.append( dict_tmp )

def a():
    # обычная итерация по всем элементам
    for dict_ in list_dicts:
        if dict_["key3"] == 20:
            pass

def b():
    # использование 'генератора'
    for dict_ in (x for x in list_dicts if x["key3"] == 20):
        pass

def c():
    # использование 'списка'
    for dict_ in [x for x in list_dicts if x["key3"] == 20]:
        pass

def d():
    # использование 'фильтра'
    for dict_ in filter(lambda x: x['key3'] == 20, list_dicts):
        pass

Результаты:

1.7303 # обычная итерация по списку
1.3849 # генераторное выражение
1.3158 # списковое включение
7.7848 # фильтр
0

Ваш код делает именно то, что вы ожидаете, т.е. ищет человека по имени в списке people. Функция search проходит по каждому элементу списка и сравнивает значение ключа 'name' с переданным аргументом name. Если находит совпадение, то возвращает соответствующий словарь.

Вот ваш код на русском:

people = [
    {'name': "Том", 'age': 10},
    {'name': "Марк", 'age': 5},
    {'name': "Пэм", 'age': 7}
]

def search(name):
    for p in people:
        if p['name'] == name:
            return p

search("Пэм")

Функция search("Пэм") вернет словарь {'name': "Пэм", 'age': 7}, так как именно такой элемент присутствует в списке people. Если вы хотите учесть возможные ситуации, когда имя не найдено, вы можете доработать функцию, добавив обработку этого случая, например:

def search(name):
    for p in people:
        if p['name'] == name:
            return p
    return None  # Вернуть None, если имя не найдено

Таким образом, если вызвать search("Джон"), вы получите None, что указывает на отсутствие результата.

0

Да, я пробовал использовать пакет pandas, и он действительно отлично подходит для таких задач поиска, а также оптимизирован по производительности.

Вот пример использования pandas для работы с данными в виде словарей:

import pandas as pd

listOfDicts = [
    {"name": "Tom", "age": 10},
    {"name": "Mark", "age": 5},
    {"name": "Pam", "age": 7}
]

# Создаем DataFrame, ключи используются в качестве заголовков столбцов.
# Элементы словаря с одинаковым ключом попадают в один и тот же столбец.
df = pd.DataFrame(listOfDicts)

# DataFrame pandas позволяет извлекать конкретные значения вот так:

df2 = df[(df['name'] == 'Pam') & (df['age'] == 7)]

# Альтернативный синтаксис, тот же результат

df2 = df[(df.name == 'Pam') & (df.age == 7)]

Я добавил немного бенчмаркинга ниже, чтобы продемонстрировать более быстрые времена выполнения pandas на больших объемах данных (например, 100k+ записей):

setup_large = 'dicts = [];\
[dicts.extend(({ "name": "Tom", "age": 10 },{ "name": "Mark", "age": 5 },\
{ "name": "Pam", "age": 7 },{ "name": "Dick", "age": 12 })) for _ in range(25000)];\
from operator import itemgetter;import pandas as pd;\
df = pd.DataFrame(dicts);'

setup_small = 'dicts = [];\
dicts.extend(({ "name": "Tom", "age": 10 },{ "name": "Mark", "age": 5 },\
{ "name": "Pam", "age": 7 },{ "name": "Dick", "age": 12 }));\
from operator import itemgetter;import pandas as pd;\
df = pd.DataFrame(dicts);'

method1 = '[item for item in dicts if item["name"] == "Pam"]'
method2 = 'df[df["name"] == "Pam"]'

import timeit
t = timeit.Timer(method1, setup_small)
print('Маленький метод LC: ' + str(t.timeit(100)))
t = timeit.Timer(method2, setup_small)
print('Маленький метод Pandas: ' + str(t.timeit(100)))

t = timeit.Timer(method1, setup_large)
print('Большой метод LC: ' + str(t.timeit(100)))
t = timeit.Timer(method2, setup_large)
print('Большой метод Pandas: ' + str(t.timeit(100)))

# Маленький метод LC: 0.000191926956177
# Маленький метод Pandas: 0.044392824173
# Большой метод LC: 1.98827004433
# Большой метод Pandas: 0.324505090714

Как видно из результатов, pandas демонстрирует явное преимущество по времени выполнения при работе с большими наборами данных.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь