forked from TheAlgorithms/Java
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathLFUCache.java
180 lines (168 loc) · 5.93 KB
/
LFUCache.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
package com.thealgorithms.datastructures.caches;
import java.util.HashMap;
import java.util.Map;
/**
* The {@code LFUCache} class implements a Least Frequently Used (LFU) cache.
* An LFU cache evicts the least frequently used item when the cache reaches its capacity.
* It keeps track of how many times each item is used and maintains a doubly linked list
* for efficient addition and removal of items based on their frequency of use.
*
* @param <K> The type of keys maintained by this cache.
* @param <V> The type of mapped values.
*
* <p>
* Reference: <a href="https://en.wikipedia.org/wiki/Least_frequently_used">LFU Cache - Wikipedia</a>
* </p>
*
* @author Akshay Dubey (https://github.com/itsAkshayDubey)
*/
public class LFUCache<K, V> {
/**
* The {@code Node} class represents an element in the LFU cache.
* Each node contains a key, a value, and a frequency count.
* It also has pointers to the previous and next nodes in the doubly linked list.
*/
private class Node {
private final K key;
private V value;
private int frequency;
private Node previous;
private Node next;
/**
* Constructs a new {@code Node} with the specified key, value, and frequency.
*
* @param key The key associated with this node.
* @param value The value stored in this node.
* @param frequency The frequency of usage of this node.
*/
Node(K key, V value, int frequency) {
this.key = key;
this.value = value;
this.frequency = frequency;
}
}
private Node head;
private Node tail;
private final Map<K, Node> cache;
private final int capacity;
private static final int DEFAULT_CAPACITY = 100;
/**
* Constructs an LFU cache with the default capacity.
*/
public LFUCache() {
this(DEFAULT_CAPACITY);
}
/**
* Constructs an LFU cache with the specified capacity.
*
* @param capacity The maximum number of items that the cache can hold.
* @throws IllegalArgumentException if the specified capacity is less than or equal to zero.
*/
public LFUCache(int capacity) {
if (capacity <= 0) {
throw new IllegalArgumentException("Capacity must be greater than zero.");
}
this.capacity = capacity;
this.cache = new HashMap<>();
}
/**
* Retrieves the value associated with the given key from the cache.
* If the key exists, the node's frequency is increased and the node is repositioned
* in the linked list based on its updated frequency.
*
* @param key The key whose associated value is to be returned.
* @return The value associated with the key, or {@code null} if the key is not present in the cache.
*/
public V get(K key) {
Node node = cache.get(key);
if (node == null) {
return null;
}
removeNode(node);
node.frequency += 1;
addNodeWithUpdatedFrequency(node);
return node.value;
}
/**
* Inserts or updates a key-value pair in the cache.
* If the key already exists, the value is updated and its frequency is incremented.
* If the cache is full, the least frequently used item is removed before inserting the new item.
*
* @param key The key associated with the value to be inserted or updated.
* @param value The value to be inserted or updated.
*/
public void put(K key, V value) {
if (cache.containsKey(key)) {
Node node = cache.get(key);
node.value = value;
node.frequency += 1;
removeNode(node);
addNodeWithUpdatedFrequency(node);
} else {
if (cache.size() >= capacity) {
cache.remove(this.head.key); // Evict least frequently used item
removeNode(head);
}
Node node = new Node(key, value, 1);
addNodeWithUpdatedFrequency(node);
cache.put(key, node);
}
}
/**
* Adds a node to the linked list in the correct position based on its frequency.
* The linked list is ordered by frequency, with the least frequently used node at the head.
*
* @param node The node to be inserted into the list.
*/
private void addNodeWithUpdatedFrequency(Node node) {
if (tail != null && head != null) {
Node temp = this.head;
while (temp != null) {
if (temp.frequency > node.frequency) {
if (temp == head) {
node.next = temp;
temp.previous = node;
this.head = node;
break;
} else {
node.next = temp;
node.previous = temp.previous;
temp.previous.next = node;
temp.previous = node;
break;
}
} else {
temp = temp.next;
if (temp == null) {
tail.next = node;
node.previous = tail;
node.next = null;
tail = node;
break;
}
}
}
} else {
tail = node;
head = tail;
}
}
/**
* Removes a node from the doubly linked list.
* This method ensures that the pointers of neighboring nodes are properly updated.
*
* @param node The node to be removed from the list.
*/
private void removeNode(Node node) {
if (node.previous != null) {
node.previous.next = node.next;
} else {
this.head = node.next;
}
if (node.next != null) {
node.next.previous = node.previous;
} else {
this.tail = node.previous;
}
}
}