Найдите индекс dict в списке, сопоставив значение dict

131

У меня есть список диктовок:

list = [{'id':'1234','name':'Jason'},
        {'id':'2345','name':'Tom'},
        {'id':'3456','name':'Art'}]

Как я могу эффективно найти позицию индекса [0], [1] или [2] путем сопоставления по name = 'Tom'?

Если бы это был одномерный список, я мог бы выполнить list.index (), но я не уверен, как действовать дальше, выполняя поиск значений dicts в списке.

Ensnare
источник
6
«список» - это конструктор списка, лучше выбрать другое имя для списка (даже в примере). И какой должна быть реакция, если элемент не найден? возбудить исключение? возврат Нет?
Tokland
7
Если вам это будет очень нужно, используйте более подходящую структуру данных (возможно { 'Jason': {'id': '1234'}, 'Tom': {'id': '1245'}, ...}?)
3
@delnan Потому что это рецепт катастрофы! Во всяком случае, так и должно быть {'1234': {'name': 'Jason'}, ...}. Не то, чтобы это помогло этому варианту использования.
OJFord 09

Ответы:

145
tom_index = next((index for (index, d) in enumerate(lst) if d["name"] == "Tom"), None)
# 1

Если вам нужно многократно выполнять выборку по имени, вы должны проиндексировать их по имени (используя словарь), таким образом, операции получения будут выполняться за O (1) раз. Идея:

def build_dict(seq, key):
    return dict((d[key], dict(d, index=index)) for (index, d) in enumerate(seq))

info_by_name = build_dict(lst, key="name")
tom_info = info_by_name.get("Tom")
# {'index': 1, 'id': '2345', 'name': 'Tom'}
tokland
источник
2
IMHO, это не так читаемо, или Pythonic - это ответ @ Emile. Поскольку на самом деле цель не состоит в создании генератора (и использование next()для этого кажется мне странным), цель - просто получить индекс. Кроме того, это вызывает StopIteration, тогда как lst.index()метод Python вызывает ValueError.
Бен Хойт
@benhoyt: Мне тоже не нравится исключение StopIteration, но, хотя вы можете изменить значение по умолчанию next (), исключение, которое оно вызывает, исправлено. Питоничность несколько субъективна, поэтому я не буду с ней спорить, вероятно, цикл for более питоничен. С другой стороны, некоторые люди используют псевдоним next () для first (), и это определенно звучит лучше: first (index for (index, d) in ...).
Tokland
first()звучит лучше. Вы всегда можете попробовать / кроме StopIteration и поднять ValueError, чтобы вызывающий абонент имел согласованность. В качестве альтернативы установите next()значение по умолчанию на -1.
Бен Хойт
1
@ gdw2: Я понимаю, SyntaxError: Generator expression must be parenthesized if not sole argumentкогда это делаю.
avoliva
2
@avoliva добавляет скобки вокруг следующего, как next((index for (index, d) in enumerate(lst) if d["name"] == "Tom"), None)
показано
45

Простая читаемая версия

def find(lst, key, value):
    for i, dic in enumerate(lst):
        if dic[key] == value:
            return i
    return -1
Emile
источник
8
Это кажется наиболее читаемым и питоническим. Он также str.find()красиво имитирует поведение . Вы также можете вызвать его index()и поднять ValueErrorвместо возврата -1, если это предпочтительнее.
Бен Хойт
6
Согласовано - возвращая -1, когда совпадений не найдено, вы всегда будете получать последний диктат в списке, что, вероятно, не то, что вам нужно. Лучше вернуть None и проверить наличие совпадения в вызывающем коде.
шейкер
9

Это не будет эффективно, так как вам нужно пройти по списку, проверяя каждый элемент в нем (O (n)). Если вам нужна эффективность, вы можете использовать dict of dicts . Что касается вопроса, вот один из возможных способов его найти (хотя, если вы хотите придерживаться этой структуры данных, на самом деле более эффективно использовать генератор, как написал Брент Ньюи в комментариях; см. Также ответ Токланда):

>>> L = [{'id':'1234','name':'Jason'},
...         {'id':'2345','name':'Tom'},
...         {'id':'3456','name':'Art'}]
>>> [i for i,_ in enumerate(L) if _['name'] == 'Tom'][0]
1
aeter
источник
1
Вы можете добиться желаемой эффективности, используя генератор. См. Ответ Токланда.
Брент Ньюи
2
@Brent Newey: Генератор не меняет того факта, что вы должны пройти весь список, делая поиск O (n), как утверждает aeter ... В зависимости от того, как долго этот список, разница между использованием генератора и использованием цикл for или что-то еще может быть незначительным, где разница между использованием dict и использованием списка может не быть
Дирк
@Brent: Вы правы, но может ли он превзойти поиск O (1) в словаре, более того, если искомый элемент находится в конце списка?
aeter
1
@Dirk Вызов генератора next () останавливается, когда найдено совпадение, поэтому ему не нужно проходить через весь список.
Брент Ньюи
@aeter Вы верно подметили. Я имел в виду возможность остановиться при обнаружении совпадения.
Брент Ньюи
2

Вот функция, которая находит позицию индекса словаря, если она существует.

dicts = [{'id':'1234','name':'Jason'},
         {'id':'2345','name':'Tom'},
         {'id':'3456','name':'Art'}]

def find_index(dicts, key, value):
    class Null: pass
    for i, d in enumerate(dicts):
        if d.get(key, Null) == value:
            return i
    else:
        raise ValueError('no dict with the key and value combination found')

print find_index(dicts, 'name', 'Tom')
# 1
find_index(dicts, 'name', 'Ensnare')
# ValueError: no dict with the key and value combination found
Мартино
источник
2

Кажется наиболее логичным использовать комбинацию фильтр / индекс:

names=[{}, {'name': 'Tom'},{'name': 'Tony'}]
names.index(filter(lambda n: n.get('name') == 'Tom', names)[0])
1

И если вы думаете, что совпадений может быть несколько:

[names.index(n) for item in filter(lambda n: n.get('name') == 'Tom', names)]
[1]
Майкл лосось
источник
2

Ответ, предложенный @faham, является хорошим однострочным, но он не возвращает индекс в словарь, содержащий значение. Вместо этого он возвращает сам словарь. Вот простой способ получить: список индексов один или несколько, если их больше одного, или пустой список, если их нет:

list = [{'id':'1234','name':'Jason'},
        {'id':'2345','name':'Tom'},
        {'id':'3456','name':'Art'}]

[i for i, d in enumerate(list) if 'Tom' in d.values()]

Вывод:

>>> [1]

Что мне нравится в этом подходе, так это то, что с помощью простого редактирования вы можете получить список индексов и словарей в виде кортежей. Это проблема, которую мне нужно было решить, и я нашел эти ответы. Далее я добавил повторяющееся значение в другой словарь, чтобы показать, как это работает:

list = [{'id':'1234','name':'Jason'},
        {'id':'2345','name':'Tom'},
        {'id':'3456','name':'Art'},
        {'id':'4567','name':'Tom'}]

[(i, d) for i, d in enumerate(list) if 'Tom' in d.values()]

Вывод:

>>> [(1, {'id': '2345', 'name': 'Tom'}), (3, {'id': '4567', 'name': 'Tom'})]

Это решение находит все словари, содержащие слово «Том» в любом из их значений.

stanely
источник
1

Один лайнер!?

elm = ([i for i in mylist if i['name'] == 'Tom'] or [None])[0]
faham
источник
0

Для данной итерации more_itertools.locateвыдает позиции элементов, которые удовлетворяют предикату.

import more_itertools as mit


iterable = [
    {"id": "1234", "name": "Jason"},
    {"id": "2345", "name": "Tom"},
    {"id": "3456", "name": "Art"}
]

list(mit.locate(iterable, pred=lambda d: d["name"] == "Tom"))
# [1]

more_itertools- это сторонняя библиотека, которая реализует рецепты itertools среди других полезных инструментов.

pylang
источник
0
def search(itemID,list):
     return[i for i in list if i.itemID==itemID]
Рохан Кумара
источник