Как создать последний недавно использованный кеш?

Как создать последний недавно использованный кеш?

Предположим, что вы посетили некоторые пункты. Вам нужно разработать структуру данных для хранения этих элементов. Каждый элемент связан с последним временем посещения.

Каждый раз, когда вы посещаете элемент, проверяйте его в структуре данных. Если элемент был в кеше, обновите время его посещения. В противном случае вставьте его в кеш. Размер кеша фиксированный, если он заполнен, удалить самый старый элемент.

Мое решение:

  1. Используйте элемент карты ‹, visitTime >

  2. Инициализация: Отсортируйте карту с помощью f(visitTime) в порядке убывания. О (nlg п)

  3. Если элемент посещен, найдите его на карте с помощью O(lg n).

  4. Если он был на карте, обновите время O(1). Отсортируйте карту O(lg n).

  5. Если нет, вставьте его в карту, а затем отсортируйте. О (lg п)

  6. Если размер карты > фиксированного размера, удалите последний элемент O(1).

Еще одно решение:

  1. Использовать хеш-таблицу ‹ item , visitTime >

  2. Отсортируйте его O (n lgn).

  3. Если элемент посещается, найдите его в таблице с помощью O (1).

  4. Если он был в таблице, обновите время O(1). Отсортируйте таблицу O(n lg n).

  5. Если нет, вставьте его в таблицу, а затем отсортируйте. О(nlgn)

  6. Если размер таблицы > фиксированного размера, удалите последний элемент O(1).

Есть ли лучшие решения? На) ?


person user1002288    schedule 29.11.2011    source источник
comment
Что вы имеете в виду под сортировкой? Почему вы хотите сортировать (и копировать куда?) карту (unordered_)? Линейный поиск найдет самый старый элемент для удаления.   -  person Gene Bushuyev    schedule 29.11.2011
comment
Насколько большой кеш вы рассматриваете здесь? Big-O имеет дело с асимптотической сложностью, но кеш часто достаточно мал, поэтому сложность редко является единственной (а часто даже самой важной) проблемой.   -  person Jerry Coffin    schedule 30.11.2011


Ответы (6)


Если вы используете двусвязный список, вы получите вставку O (1) (после поиска), удаление O (1), поиск O (n).

Предполагая, что вы вставляете новые элементы спереди:

Если кеш не заполнен, просто добавьте его в начало (O(1)).

Если вам нужно обновить элемент, найдите его (O(n)), удалите из связанного списка (O(1)), затем добавьте на передний план (O(1)).

Если вам нужно удалить самый старый элемент, чтобы вставить новый элемент, удалите последний элемент (O (1)) и вставьте его вперед (O (1)) [примечание: в этом случае вам нужно сначала выполнить поиск в списке, чтобы увидеть если элемент еще не находится в кеше, поэтому O (n)].

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

person user1071777    schedule 29.11.2011
comment
Это плохо масштабируется из-за шага поиска O(n). Реализация Python, обсуждаемая в другом ответе, сокращает это до шага O (1). - person Raymond Hettinger; 30.11.2011
comment
Он попросил O(n), вот что я ему дал. Тоже простое решение. - person user1071777; 30.11.2011
comment
Справедливо. Надеюсь, другие люди, которые ищут этот вопрос, будут стремиться выше :-) - person Raymond Hettinger; 30.11.2011
comment
Ниже приведено описание решения O(1), которое можно реализовать на C++. Все, что вам нужно, это один map и один list. - person Dialecticus; 30.11.2011

кеш LRU Python имеет O(1) вставку, удаление и поиск. В его дизайне используется двусвязный список записей (от самых старых к новым) и хэш-таблица для поиска конкретной ссылки.

Вот упрощенная (но быстрая) версия в менее чем 40 строк очень простого Python. Нетрудно перевести решение Python на C++:

class LRU_Cache(object):

    def __init__(self, original_function, maxsize=1000):
        self.original_function = original_function
        self.maxsize = maxsize
        self.mapping = {}

        PREV, NEXT, KEY, VALUE = 0, 1, 2, 3
        self.head = [None, None, None, None]        # oldest
        self.tail = [self.head, None, None, None]   # newest
        self.head[NEXT] = self.tail

    def __call__(self, *key):
        PREV, NEXT, KEY, VALUE = 0, 1, 2, 3
        mapping, head, tail = self.mapping, self.head, self.tail
        sentinel = object()

        link = mapping.get(key, sentinel)
        if link is sentinel:
            value = self.original_function(*key)
            if len(mapping) >= self.maxsize:
                oldest = head[NEXT]
                next_oldest = oldest[NEXT]
                head[NEXT] = next_oldest
                next_oldest[PREV] = head
                del mapping[oldest[KEY]]
            last = tail[PREV]
            link = [last, tail, key, value]
            mapping[key] = last[NEXT] = tail[PREV] = link
        else:
            link_prev, link_next, key, value = link
            link_prev[NEXT] = link_next
            link_next[PREV] = link_prev
            last = tail[PREV]
            last[NEXT] = tail[PREV] = link
            link[PREV] = last
            link[NEXT] = tail
        return value

if __name__ == '__main__':
    p = LRU_Cache(ord, maxsize=3)
    for c in 'abcdecaeaa':
        print(c, p(c))
person Raymond Hettinger    schedule 29.11.2011
comment
впечатляющий. как насчет изменения, если размер › maxsize: =› если размер ›= maxsize:? - person sunqiang; 30.11.2011

Вы можете сделать это на Java с помощью java.util .LinkedHashSet. Это хеш-таблица в сочетании со связанным списком, в котором сохраняется порядок вставки элементов. Вы должны получить (ожидаемое) постоянное время поиска, вставки и удаления, если рассредоточение ключей работает хорошо.

Вы также можете посмотреть WeakHashMap, который реализует автоматизированный механизм, в котором элементы могут быть удалены сборщиком мусора.

person Daniel Lemire    schedule 29.11.2011
comment
+1 По сути, это то, что делает версия Python. Это гораздо лучшее решение, чем двусвязный список без хеш-таблицы. - person Raymond Hettinger; 30.11.2011

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

Редактировать: итератор std::list действителен при добавлении и удалении элементов при условии, что сам итератор элемента, на который ссылается итератор, не удаляется. См. последние строки в разделе Емкость и распределение в Википедии.

person Dialecticus    schedule 29.11.2011
comment
Если элемент списка перемещается, итератор будет изменен. Каждый раз, когда новый элемент вставляется впереди или удаляется из списка, расположение некоторых или даже всех элементов списка может измениться. как сохранить хеш-таблицу, чтобы получить новое местоположение каждого элемента? оно включено). - person user1002288; 30.11.2011
comment
@ user1002288, я обновил свой ответ. Итератор std::list безопасен. - person Dialecticus; 30.11.2011

Вам не нужно сортировать контейнер. Просто добавьте элементы на карту или вектор и пройдитесь по ним линейно, чтобы найти нужный элемент (или самый старый элемент).

Тогда это будет O(n).

person Igor Oks    schedule 29.11.2011

Взгляните на boost::multi_index. Одним из примеров, которые он показывает, является список MRU.

person Chad    schedule 29.11.2011