Как реализовать кеш в Python, который эффективно поддерживает операции как со словарем, так и с кучей?

Существует ли структура данных Python, которая легко объединяет словарь (с вложенными словарями или списками в качестве значений) и кучу, позволяя сортировать на основе определенного значения внутри вложенных структур?

cache = {"key1": {"time": time1, "info": "key1 info"}, "key2": {"time": time2, "info": "key2 info"}, ...}

или:

cache = {"key1": [time1, "key1 info"], "key2": [time2, "key2 info"], ...}

здесь time1, time2, ... — время вставки или обновления записи.

Цель состоит в том, чтобы реализовать эффективный кеш, проверяя существование ключа, проверяя актуальность значения (поскольку оно устаревает с течением времени) и удаляя самый старый ключ, когда кеш заполнен. Словарь должен поддерживать операции с кучей либо с использованием вложенного ключа «время», либо с использованием нулевого элемента списка.

Рассматриваемые текущие варианты:

  1. Формирование кучи из словаря (недостаток — дорогостоящая операция O(n^2)).
  2. Реализация класса с раздельно хранимой кучей и словарем (недостаток — сложность синхронизации данных в куче и словаре).
  3. Простая итерация по словарю за O(n). Этот вариант предпочтителен из-за его простоты, но может быть неоптимальным.

Есть ли более эффективное решение или другой подход, позволяющий избежать создания собственной структуры данных?

всегда ли часть времени является текущим временем или, иначе говоря: всегда ли вставки/обновления элементов dict имеют хронологическое время? Или есть обновления, которые "меняют историю"?

trincot 09.03.2024 15:33

@trincot, спасибо за ответ и вопрос. временная часть всегда является текущим временем (время вставки или обновления). Но я не понял, что касается хронологического времени. Что ты имеешь в виду?

maskalev 09.03.2024 16:52

Я имею в виду: может ли когда-нибудь случиться, что вы добавите в dict пару time,info, которая равна [5, «info1»], а затем (позже) добавите пару, которая будет [4, «info2»]], так что где второй раз меньше первого? Я понимаю, что это не так и значения времени никогда не уменьшаются, а «хронологические» (увеличиваются временные метки).

trincot 09.03.2024 16:59

Я не могу изменить историю, конечно

maskalev 09.03.2024 17:01

Что конкретно вам нужно из кучи? Вставка Python dict уже заказана, поэтому получить самый старый или самый новый элемент не составит труда.

MisterMiyagi 09.03.2024 17:24

сейчас напишу ответ...

MisterMiyagi 09.03.2024 17:36

@МистерМияги, ты! 1. Я не знал, что, поскольку порядок LIFO в Python 3.7 гарантирован в методе popitem() dict 2. Кажется, что OrderedDict и его popitem(last=False) - это то, что мне нужно (docs.python.org/3/ библиотека/…) Пожалуйста, не останавливайтесь, если пишете ответ!

maskalev 09.03.2024 17:44
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
7
166
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В Python dict уже упорядочена вставка (в старых версиях используйте collections.OrderedDict), аналогично куче, отсортированной по времени. Это означает, что самый старый вставленный элемент всегда находится впереди. Вместо обновления значений удалите и вставьте элемент, чтобы принудительно переместить обновленные элементы назад.

Чтобы использовать dict в качестве временного кеша, используйте следующий подход. Вы можете выделить его в отдельный подкласс, предоставить вспомогательные функции или добавить встроенный код. Подкласс удобнее и позволяет избежать неправильного использования, но включает в себя множество специальных методов, поэтому я покажу более простые вспомогательные функции и предположу, что время жизни (ttl) задано.

  • Предметы должны быть формы "key": (time, value). Выгодно иметь неизменяемые значения (т. е. tuple или NamedTuple), чтобы предотвратить аннулирование ограничения действительного time.
  • При вставке удалите все предыдущие элементы того же ключа. Это заставляет элемент быть вставлен в последнюю позицию.
    def set(cache, key, value):
         cache.pop(key, None)  # clear the previous position if any
         cache[key] = (time.monotonic(), value))
    
  • При доступе просто проверьте время. Вероятно, вы захотите напрямую удалить устаревшие ключи доступа для повышения эффективности.
    def get(cache, key):
         key_time, value = cache[key]
         if key_time < time.monotonic() + ttl:  # check timestamp validity
             del cache[key]
             raise KeyError(key)
         return value
    
  • Чтобы удалить самый старый элемент, просто возьмите первый ключ и удалите его.
    def free(cache):
         if cache:
            oldest_key = next(iter(cache))
            del cache[oldest_key]
    
  • Чтобы удалить все устаревшие элементы, просто выполните итерацию до тех пор, пока не будет найден первый ключ, который все еще действителен.
    def clean(cache):
         outdated, deadline = [], time.monotonic() + ttl
         for key, (key_time, _) in cache.items():
             if key_time < deadline:
                 outdated.append(key)
             else:  # all following keys are valid as well
                 break
         for key in outdated:
             del cache[key]
    

Другие вопросы по теме