Найдите k элементов dict, значения которых ближе всего к определенному значению

1

Скажем, мы хотим найти 2 предмета, которые имеют значение, самое близкое к 10:

A = {'abc': 12.3, 'def': 17.3, 'dsfsf': 18, 'ppp': 3.2, "jlkljkjlk": 9.23}

Он работает с:

def nearest(D, centre, k=10):
    return sorted([[d, D[d], abs(D[d] - centre)] for d in D], key=lambda e: e[2])[:k]

print(nearest(A, centre=10, k=2))

[['jlkljkjlk', 9.23, 0.7699999999999996], ['abc', 12.3, 2.3000000000000007]]

Но есть ли встроенный способ Python для этого и/или более оптимизированная версия, когда dict имеет гораздо больший размер (сотни тысяч элементов)?

  • 0
    Вы можете использовать разбиение с NumPy
  • 0
    @MadPhysicist Для этого потребуется вручную разделить dict на список для строк и на пустой массив для значений, а затем сделать все вручную, или вы имеете в виду, что для этого есть готовая функция?
Показать ещё 5 комментариев
Теги:
sorting
dictionary
nearest-neighbor

3 ответа

7

Если вы не против использования Pandas:

import pandas as pd
closest = (pd.Series(A) - 10).abs().sort_values()[:2]
#jlkljkjlk    0.77
#abc          2.30
closest.to_dict()
#{'jlkljkjlk': 0.7699999999999996, 'abc': 2.3000000000000007}
  • 0
    Приятно! Есть ли что-то похожее в numpy (чтобы избежать другой зависимости)?
  • 0
    Numpy не подходит для работы с гетерогенными массивами, и в вашем случае массив неоднороден (string и float).
Показать ещё 1 комментарий
3

Вы можете использовать heapq.nsmallest():

from heapq import nsmallest
A = {'abc': 12.3, 'def': 17.3, 'dsfsf': 18, 'ppp': 3.2, 'jlkljkjlk': 9.23}
def nearest(D, centre, k=10):
    return [[x, D[x], abs(D[x] - centre)] for x in nsmallest(k, D, key=lambda x: abs(D[x] - centre))]

print(nearest(A, centre=10, k=2))
# [['jlkljkjlk', 9.23, 0.7699999999999996], ['abc', 12.3, 2.3000000000000007]]

Что касается временной сложности, это выполняется в O(n log(k)) времени вместо O(n log(n)) решения на основе сортировки словаря.

  • 1
    О, у нас уже есть самое nsmallest : D Я удалю свой ответ. Здесь хорошо отметить, что это будет O (n log (k)) вместо O (n log (n)).
  • 0
    @slider: Спасибо, обновлено :)
2

Учитывая, что вам нужно выполнять поиск довольно часто, мы можем сделать это алгоритмом O (log n), сначала сохраняя данные в отсортированном списке:

from operator import itemgetter

ks = sorted(A.items(), key=itemgetter(1))
vs = list(map(itemgetter(1), ks))

Затем для каждого элемента мы можем использовать точку bisect.bisect_left для определения точки вставки. Затем мы можем проверить два окружающих значения, проверить самое маленькое и вернуть соответствующий ключ. Также возможно, что

from bisect import bisect_left
from operator import itemgetter

def closests(v):
    idx = bisect_left(vs, v)
    i, j = max(0, idx-1), min(idx+2, len(ks))
    part = ks[i:j]
    return sorted([[*pi, abs(pi[-1]-v)] for pi in part], key=itemgetter(-1))[:2]

Вышеупомянутое может выглядеть не как улучшение, но здесь мы всегда будем оценивать не более трех элементов в sorted(..), а bisect_left будет оценивать логарифмическое число элементов.

Например:

>>> closests(1)
[['ppp', 3.2, 2.2], ['jlkljkjlk', 9.23, 8.23]]
>>> closests(3.2)
[['ppp', 3.2, 0.0], ['jlkljkjlk', 9.23, 6.03]]
>>> closests(5)
[['ppp', 3.2, 1.7999999999999998], ['jlkljkjlk', 9.23, 4.23]]
>>> closests(9.22)
[['jlkljkjlk', 9.23, 0.009999999999999787], ['abc', 12.3, 3.08]]
>>> closests(9.24)
[['jlkljkjlk', 9.23, 0.009999999999999787], ['abc', 12.3, 3.0600000000000005]]

Таким образом, фаза "загрузки" принимает O (n log n) (с n числом элементов). Затем, если мы обобщим вышеуказанный метод для извлечения k элементов (путем увеличения среза), для выполнения поиска потребуется O (log n + k log k).

  • 0
    Это только получение 2 предметов. Для k элементов это фактически O(k log(n)) (из-за part = ks[i:j] ). Кроме того, вы игнорируете начальную сортировку, но согласились, что если вам нужно выполнить много поисков, это хорошая инвестиция.
  • 1
    @slider: нет, это было бы O (k log k + log n) :) и действительно, здесь сложность времени заключается в поиске , а не в «загрузке».
Показать ещё 7 комментариев

Ещё вопросы

Сообщество Overcoder
Наверх
Меню