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

#python

#python

Вопрос:

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

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

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

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

Комментарии:

1. «list» — это конструктор списка, вам лучше выбрать другое имя для списка (даже в примере). И каким должен быть ответ, если элемент не найден? вызвать исключение? вернуть None?

2. Если вам это очень понадобится, используйте более подходящую структуру данных (возможно { 'Jason': {'id': '1234'}, 'Tom': {'id': '1245'}, ...} ?)

3. @delnan Потому что это путь к катастрофе! Во всяком случае, так и должно быть {'1234': {'name': 'Jason'}, ...} . Не то, чтобы это помогло бы в данном случае использования.

Ответ №1:

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

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

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

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

people_by_name = build_dict(lst, key="name")
tom_info = people_by_name.get("Tom")
# {'index': 1, 'id': '2345', 'name': 'Tom'}
  

Комментарии:

1. ИМХО, это не так читаемо, или Pythonic — это ответ @Emile. Поскольку на самом деле целью является не создание генератора (и использование next() для этого кажется мне странным), цель состоит в том, чтобы просто получить индекс. Кроме того, это вызывает StopIteration, тогда как метод Python lst.index() вызывает ValueError .

2. @benhoyt: Мне тоже не нравится исключение StopIteration, но, хотя вы можете изменить значение по умолчанию next (), исключение, которое оно вызывает, исправлено. pythonicity несколько субъективен, поэтому я не буду оспаривать это, вероятно, цикл for более pythonic. С другой стороны, некоторые люди используют псевдоним next() для first(), и это определенно звучит лучше: first(индекс для (index, d) в …).

3. first() действительно звучит лучше. Вы всегда можете попробовать / за исключением StopIteration и повысить ValueError, чтобы вызывающий объект имел согласованность. В качестве альтернативы установите next() значение по умолчанию равным -1.

4. @gdw2: я получаю SyntaxError: Generator expression must be parenthesized if not sole argument при этом.

5. @avoliva добавьте круглую скобку вокруг next следующим образом next((index for (index, d) in enumerate(lst) if d["name"] == "Tom"), None)

Ответ №2:

Простая для чтения версия

 def find(lst, key, value):
    for i, dic in enumerate(lst):
        if dic[key] == value:
            return i
    return -1
  

Комментарии:

1. Это кажется наиболее читаемым и Pythonic. Это также хорошо имитирует поведение str.find() . Вы также могли бы вызвать его index() и получить ValueError вместо возврата -1, если бы это было предпочтительнее.

2. Согласовано — возвращая значение -1, когда совпадение не найдено, вы всегда будете получать последний dict в списке, что, вероятно, не то, что вы хотите. Лучше вернуть None и проверить наличие совпадения в вызывающем коде.

Ответ №3:

Это не будет эффективным, так как вам нужно пройтись по списку, проверяя каждый элемент в нем (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
  

Комментарии:

1. Вы можете добиться желаемой эффективности, используя генератор. Смотрите ответ токланда.

2. @Брент Ньюи: Генератор не меняет того факта, что вам нужно пройти весь список, выполнив поиск O (n), как утверждает aeter… В зависимости от длины этого списка разница между использованием генератора и использованием цикла for или чего-либо еще может быть незначительной, в то время как разница между использованием dict и использованием списка может быть нет

3. @Brent: Вы правы, но может ли это превзойти поиск O (1) в словаре, более того, если искомый элемент находится в конце списка?

4. @Dirk Вызов next() генератора останавливается, когда найдено совпадение, поэтому ему не нужно проходить весь список.

5. @aeter Вы справедливо заметили. Я имел в виду возможность остановки при обнаружении совпадения.

Ответ №4:

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

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

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

 [names.index(item) for item in filter(lambda n: n.get('name') == 'Tom', names)]
[1]
  

Ответ №5:

Ответ, предложенный @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'})]
  

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

Ответ №6:

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

 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
  

Ответ №7:

Одна строка!?

 elm = ([i for i in mylist if i['name'] == 'Tom'] or [None])[0]
  

Ответ №8:

Мне нужно было более общее решение для учета возможности наличия нескольких словарей в списке, имеющих ключевое значение, и простая реализация с использованием понимания списка:

 dict_indices = [i for i, d in enumerate(dict_list) if d[dict_key] == key_value] 
  

Ответ №9:

Для данной итерации 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 среди других полезных инструментов.

Ответ №10:

 def search(itemID,list):
     return[i for i in list if i.itemID==itemID]
  

Ответ №11:

мой ответ лучше использовать в одном словаре

 food_time_dict = {"Lina": 312400, "Tom": 360054, "Den": 245800}
print(list(food_time_dict.keys()).index("Lina"))
  

Я запрашиваю ключи из словаря, затем перевожу список, если он не добавлен, появится ошибка, тогда я использую его как список. но в вашем коде:

 lists = [{'id': '1234', 'name': 'Jason'},
         {'id': '2345', 'name': 'Tom'},
         {'id': '3456', 'name': 'Art'}]
    
    
def dict_in_lists_index(lists, search):  # function for convenience
    j = 0  # [j][i]
    for i in lists:
        try:  # try our varible search if not found in list
            return f"[{j}][{list(i.values()).index(search)}]"
            # small decor
        except ValueError: # error was ValueError
            pass # aa... what must was what you want to do
        j  = 1 # not found? ok j  
    return "Not Found"
    
    
def dict_cropped_index(lists, search):
    for i in lists:
        try:
            return list(i.values()).index(search)
        except ValueError:
            pass
    return "Not Found"
    
    
print(dict_in_lists_index(lists, 'Tom')) # and end
print(dict_cropped_index(lists, 'Tom')) # now for sure end
  

Ответ №12:

Следующее вернет индекс для первого соответствующего элемента:

 ['Tom' == i['name'] for i in list].index(True)
  

Комментарии:

1. Это должно было быть 'Tom' == i['name']