Знайдіть індекс диктату в списку, зіставивши його значення


131

У мене є список диктів:

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

Як я можу ефективно знайти позицію індексу [0], [1] або [2], порівнюючи на ім’я = 'Том'?

Якби це одновимірний список, я міг би зробити list.index (), але я не знаю, як діяти, шукаючи значення диктів у списку.


6
"list" - це конструктор списку, краще вибрати інше ім'я для списку (навіть у прикладі). А якою має бути відповідь, якщо жоден елемент не знайдеться? підняти виняток? повернути Нічого?
tokland

7
Якщо вам це знадобиться, скористайтеся більш відповідною структурою даних (можливо { 'Jason': {'id': '1234'}, 'Tom': {'id': '1245'}, ...}?)

3
@delnan Тому що це рецепт катастрофи! Якщо що, так і має бути {'1234': {'name': 'Jason'}, ...}. Не те, що допоможе цьому випадку використання.
OJFord

Відповіді:


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'}

2
ІМХО це не так читабельно, а пітонічна - це відповідь Еміля. Оскільки намір насправді не створити генератор (і використання next()цього для мене здається дивним), мета полягає лише в тому, щоб отримати індекс. Також це підвищує StopIteration, тоді як lst.index()метод Python підвищує ValueError.
Бен Хойт

@benhoyt: Мені також не подобається виняток StopIteration, але, хоча ви можете змінити стандартне значення next (), виняток, який він збільшує, виправлено. Пітонічність дещо суб’єктивна, тому я не заперечую її, ймовірно, цикл for-петлі є більш пітонічним. З іншого боку, деякі люди псевдоніми next () для first (), і це, безумовно, звучить краще: по-перше (index for (index, d) in ...).
tokland

first()краще звучить Ви завжди можете спробувати / крім StopIteration та підвищити ValueError, щоб абонент мав послідовність. Крім того, встановіть next()за замовчуванням -1.
Бен Хойт

1
@ gdw2: Я розумію, SyntaxError: Generator expression must be parenthesized if not sole argumentколи це роблю.
аволіва

2
@avoliva додайте дужки приблизно наступним чиномnext((index for (index, d) in enumerate(lst) if d["name"] == "Tom"), None)
HussienK

45

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

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

8
Це здається найбільш читаним та піфонічним. Це також імітує поведінку str.find()приємно. Ви також можете його зателефонувати index()і підняти ValueErrorзамість повернення -1, якщо це було б краще.
Бен Хойт

6
Погоджено - повертаючи -1, коли не знайдено відповідності, ви завжди отримуватимете останній вислів у списку, який, мабуть, не є тим, що ви хочете. Краще повернути None і перевірити наявність матчу в коді виклику.
шейкер

9

Це не буде ефективно, оскільки вам потрібно пройти список, перевіряючи кожен елемент у ньому (O (n)). Якщо ви хочете ефективності, ви можете використовувати диктант . Що стосується питання, ось один із можливих способів його знайти (хоча, якщо ви хочете дотримуватися цієї структури даних, насправді ефективніше використовувати генератор, як написав Brent Newey в коментарях; див. Також відповідь tokland):

>>> 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
Ви можете досягти бажаної ефективності, використовуючи генератор. Дивіться відповідь tokland.
Brent Newey

2
@Brent Newey: Генератор не змінює факту, що вам доведеться пройти весь список, зробивши пошук O (n), як стверджує aeter ... Залежно від того, який довгий цей список, різниця між використанням генератора проти використанням a для циклу чи іншого, що може бути незначним, оскільки різниця між використанням dict та використанням списку може не мати
Дірк

@Brent: Ти маєш рацію, але чи може вона перемогти пошук O (1) у словнику, тим паче, якщо шуканий елемент знаходиться в кінці списку?
етер

1
@Dirk Наступний () виклик генератора припиняється, коли знайдено збіг, тому йому не потрібно проходити весь список.
Brent Newey

@aeter Ви робите справедливу точку. Я мав на увазі можливість зупинитись, коли знайдеться відповідність.
Brent Newey

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'})]

Це рішення знаходить усі словники, що містять «Том» у будь-якому їх значенні.



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 серед інших корисних інструментів.


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.