avatarjb stevenard

Free AI web copilot to create summaries, insights and extended knowledge, download it at here

11941

Abstract

Space Complexity: To achieve this performance, there is some data duplication, but the space complexity remains linear O(n).</p><h2 id="6484">Test-cases:</h2><div id="87e5"><pre><span class="hljs-keyword">public</span> func <span class="hljs-title function_ invoke__">testLRU</span>() { let lruCache = LRUCache<String,String>(size: <span class="hljs-number">3</span>); lruCache.<span class="hljs-title function_ invoke__">setValue</span>(<span class="hljs-string">"1"</span>,<span class="hljs-attr">for</span>: <span class="hljs-string">"test1"</span>); lruCache.<span class="hljs-title function_ invoke__">setValue</span>(<span class="hljs-string">"2"</span>,<span class="hljs-attr">for</span>: <span class="hljs-string">"test2"</span>); lruCache.<span class="hljs-title function_ invoke__">setValue</span>(<span class="hljs-string">"3"</span>,<span class="hljs-attr">for</span>: <span class="hljs-string">"test3"</span>); _ = lruCache.<span class="hljs-title function_ invoke__">getValue</span>(<span class="hljs-attr">for</span>: <span class="hljs-string">"test1"</span>) _ = lruCache.<span class="hljs-title function_ invoke__">getValue</span>(<span class="hljs-attr">for</span>: <span class="hljs-string">"test2"</span>) _ = lruCache.<span class="hljs-title function_ invoke__">getValue</span>(<span class="hljs-attr">for</span>: <span class="hljs-string">"test3"</span>) <span class="hljs-title function_ invoke__">assert</span>(<span class="hljs-string">"1"</span> == lruCache.<span class="hljs-title function_ invoke__">getValue</span>(<span class="hljs-attr">for</span>: <span class="hljs-string">"test1"</span>), <span class="hljs-string">"mainLRU 1 not equal"</span>); <span class="hljs-title function_ invoke__">assert</span>(<span class="hljs-string">"2"</span> == lruCache.<span class="hljs-title function_ invoke__">getValue</span>(<span class="hljs-attr">for</span>: <span class="hljs-string">"test2"</span>), <span class="hljs-string">"mainLRU 2 not equal"</span>); <span class="hljs-title function_ invoke__">assert</span>(<span class="hljs-string">"3"</span> == lruCache.<span class="hljs-title function_ invoke__">getValue</span>(<span class="hljs-attr">for</span>: <span class="hljs-string">"test3"</span>), <span class="hljs-string">"mainLRU 3 not equal"</span>);

lruCache.<span class="hljs-title function_ invoke__">setValue</span>(<span class="hljs-string">"1"</span>,<span class="hljs-attr">for</span>: <span class="hljs-string">"test1"</span>); lruCache.<span class="hljs-title function_ invoke__">setValue</span>(<span class="hljs-string">"2"</span>,<span class="hljs-attr">for</span>: <span class="hljs-string">"test2"</span>); lruCache.<span class="hljs-title function_ invoke__">setValue</span>(<span class="hljs-string">"3"</span>,<span class="hljs-attr">for</span>: <span class="hljs-string">"test3"</span>); _ = lruCache.<span class="hljs-title function_ invoke__">getValue</span>(<span class="hljs-attr">for</span>: <span class="hljs-string">"test1"</span>) lruCache.<span class="hljs-title function_ invoke__">setValue</span>(<span class="hljs-string">"4"</span>,<span class="hljs-attr">for</span>: <span class="hljs-string">"test4"</span>); _ = lruCache.<span class="hljs-title function_ invoke__">getValue</span>(<span class="hljs-attr">for</span>: <span class="hljs-string">"test1"</span>) <span class="hljs-title function_ invoke__">assert</span>(lruCache.<span class="hljs-title function_ invoke__">getValue</span>(<span class="hljs-attr">for</span>: <span class="hljs-string">"test1"</span>) == <span class="hljs-string">"1"</span>, <span class="hljs-string">"mainLRU 4 not equal"</span>);

_ = lruCache.<span class="hljs-title function_ invoke__">getValue</span>(<span class="hljs-attr">for</span>: <span class="hljs-string">"test2"</span>) <span class="hljs-title function_ invoke__">assert</span>(lruCache.<span class="hljs-title function_ invoke__">getValue</span>(<span class="hljs-attr">for</span>: <span class="hljs-string">"test2"</span>) == nil, <span class="hljs-string">"mainLRU 5 not equal"</span>); }</pre></div><h2 id="4c39">Follow-up:</h2><p id="5470">Write a Least Frequent Used (LFU).</p><h2 id="f316">Hints:</h2><p id="fd9d">- You still need to care about the order but also need to store the frequency,</p><p id="7e61">- Think about keeping buckets for the same frequency,</p><p id="fc40">- You need to know the least current frequency.</p><h2 id="75b3">Solution:</h2><div id="bd84"><pre><span class="hljs-keyword">class</span> <span class="hljs-title class_">LFUCache</span><<span class="hljs-title class_">Key</span>: <span class="hljs-title class_">Hashable</span>, <span class="hljs-title class_">Value</span>> { <span class="hljs-comment">// 1.</span> <span class="hljs-keyword">typealias</span> <span class="hljs-type">Element</span> <span class="hljs-operator">=</span> (key: <span class="hljs-type">Key</span>, value: <span class="hljs-type">Value</span>, frequency: <span class="hljs-type">Int</span>)

<span class="hljs-comment">// 2.</span>
<span class="hljs-keyword">let</span> size: <span class="hljs-type">Int</span>
<span class="hljs-keyword">var</span> frequencyMap <span class="hljs-operator">=</span> [<span class="hljs-type">Int</span>: <span class="hljs-type">DoubleLinkedList</span>&lt;<span class="hljs-type">Element</span>&gt;]()
<span class="hljs-keyword">var</span> cache <span class="hljs-operator">=</span> [<span class="hljs-type">Key</span>: <span class="hljs-type">DoubleLinkedListNode</span>&lt;<span class="hljs-type">Element</span>&gt;]()
<span class="hljs-keyword">var</span> currentFrequency: <span class="hljs-type">Int</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>

<span class="hljs-keyword">init</span>(<span class="hljs-params">size</span>: <span class="hljs-type">Int</span>) {
    <span class="hljs-keyword">self</span>.size <span class="hljs-operator">=</span> size
}

<span class="hljs-comment">// 3.</span>
<span class="hljs-keyword">func</span> <span class="hljs-title function_">getValue</span>(<span class="hljs-params">for</span> <span class="hljs-params">key</span>: <span class="hljs-type">Key</span>) -&gt; <span class="hljs-type">Value</span>? {
    <span class="hljs-comment">// 4.</span>
    <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> node <span class="hljs-operator">=</span> cache[key] <span class="hljs-keyword">else</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
    }
    <span class="hljs-comment">// 5.</span>
    update(key: key, node: node)
    <span class="hljs-keyword">return</span> node.value.value
}

<span class="hljs-comment">// 6.</span>
<span class="hljs-keyword">func</span> <span class="hljs-title function_">setValue</span>(<span class="hljs-keyword">_</span> <span class="hljs-params">value</span>: <span class="hljs-type">Value</span>, <span class="hljs-params">for</span> <span class="hljs-params">key</span>: <span class="hljs-type">Key</span>) {
    <span class="hljs-keyword">guard</span> size <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span> <span class="hljs-keyword">else</span> { <span class="hljs-keyword">return</span> }

    <span class="hljs-comment">// 7.</span>
    <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> node <span class="hljs-operator">=</span> cache[key] {
        node.value.value <span class="hljs-operator">=</span> value
        update(key: key, node: node)
        <span class="hljs-keyword">return</span>
    }

    <span class="hljs-keyword">if</span> cache.count <span class="hljs-operator">==</span> size,
       <span class="hljs-keyword">let</span> node <span class="hljs-operator">=</span> frequencyMap[currentFrequency]<span class="hljs-operator">?</span>.removeFirst() {
        <span class="hljs-comment">// 8.</span>
        cache.removeValue(forKey: node.key)
        <span class="hljs-keyword">if</span> frequencyMap[currentFrequency]<span class="hljs-operator">?</span>.isEmpty <span class="hljs-operator">??</span> <span class="hljs-literal">false</span> {
            <span class="hljs-comment">// 9.</span>
            frequencyMap.removeValue(forKey: currentFrequency)
        }
    }

    <span class="hljs-comment">// 10.</span>
    currentFrequency <span class="hljs-operator">=</span> <span class="hljs-number">1</span>
    <span class="hljs-keyword">let</span> list <span class="hljs-operator">=</span> frequencyMap[currentFrequency] <span class="hljs-operator">??</span> <span class="hljs-type">DoubleLinkedList</span>&lt;<span class="hljs-type">Element</span>&gt;()
    <span class="hljs-keyword">let</span> node <span class="hljs-operator">=</span> list.append((key: key, value: value, frequency: currentFrequency))
    frequencyMap[currentFrequency] <span class="hljs-operator">=</span> list
    cache[key] <span class="hljs-operator">=</span> node
}

<span class="hljs-comment">// 11.</span>
<span class="hljs-keyword">func</span> <span class="hljs-title function_">update</span>(<span class="hljs-params">key</span>: <span class="hljs-type">Key</span>, <span class="hljs-params">node</span>: <span class="hljs-type">DoubleLinkedListNode</span>&lt;<span class="hljs-type">Element</span>&gt;) {
    <span class="hljs-comment">// 12.</span>
    <span class="hljs-keyword">let</span> frequency <span class="hljs-operator">=</span> node.value.frequency
    <span class="hljs-keyword">_</span> <span class="hljs-operator">=</span> frequencyMap[frequency]<span class="hljs-operator">?</span>.remove(node)

    <span class="hljs-keyword">if</span> frequencyMap[frequency]<span class="hljs-operator">?</span>.isEmpty <span class="hljs-operator">??</span> <span class="hljs-literal">false</span> {
        <span class="hljs-comment">// 13.</span>
        frequencyMap.removeValue(forKey: frequency)
        <span class="hljs-keyword">if</span> currentFrequency <span class="hljs-operator">==</span> frequency {
            currentFrequency <span class="hljs-operator">=</span> frequency <span class="hljs-operator">+</span> <span class="hljs-number">1</span>
        }
    }

    <span class="hljs-comment">// 14.</span>
    <span class="hljs-keyword">let</span> list <span class="hljs-operator">=</span> frequencyMap[frequency <span class="hljs-operator">+</span> <span class="hljs-number">1</span>] <span class="hljs-operator">??</span> <span class="hljs-type">DoubleLinkedList</span>&lt;<span class="hljs-type">Element</span>&gt;()
    <span class="hljs-keyword">let</span> newNode <span class="hljs-operator">=</span> list.append((key: key, value: node.value.value, frequency: frequency <span class="hljs-operator">+</span> <span class="hljs-number">1</span>))
    frequencyMap[frequency <span class="hljs-operator">+</span> <span class="hljs-number">1</span>] <span class="hljs-operator">=</span> list
    cache[key] <span class="hljs-operator">=</span> newNode
}

}</pre></div><h2 id="1d7f">Explanations:</h2><p id="8d37">1. Let’s define a new type (tuple) to store the key, the value, and its frequency.</p><div id="e302"><pre><span class="hljs-keyword">typealias</span> <span class="hljs-type">Element</span> <span class="hljs-operator">=</span> (key: <span class="hljs-type">Key</span>, value: <span class="hljs-type">Value</span>, frequency: <span class="hljs-type">Int</span>)</pre></div><p id="0646">2. Because we need to access in O(1), we need to have a dictionary for the key and the frequency; as we need to keep the order, we need a sequence, as we want to reorder also in O(1). We will use a dictionary with the frequency as the key and a double-linked list with

Options

our tuple as the value. We will also use a dictionary with the key and the double-linked list node as the value.</p><div id="30a9"><pre><span class="hljs-keyword">let</span> size: <span class="hljs-type">Int</span> <span class="hljs-keyword">var</span> frequencyMap <span class="hljs-operator">=</span> <span class="hljs-type">Int</span>: <span class="hljs-type">DoubleLinkedList</span><<span class="hljs-type">Element</span>> <span class="hljs-keyword">var</span> cache <span class="hljs-operator">=</span> <span class="hljs-type">Key</span>: <span class="hljs-type">DoubleLinkedListNode</span><<span class="hljs-type">Element</span>> <span class="hljs-keyword">var</span> currentFrequency: <span class="hljs-type">Int</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span></pre></div><p id="886e">3. Let’s create the get method.</p><div id="5d4d"><pre><span class="hljs-keyword">func</span> <span class="hljs-title function_">getValue</span>(<span class="hljs-params">for</span> <span class="hljs-params">key</span>: <span class="hljs-type">Key</span>) -> <span class="hljs-type">Value</span>? {</pre></div><p id="bf3e">4. If our cache does not have this key let’s return nil.</p><div id="f754"><pre><span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> node <span class="hljs-operator">=</span> cache[key] <span class="hljs-keyword">else</span> { <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span> }</pre></div><p id="7b23">5. Let’s “update” the key/node (this function is explained later), and return the stored value (the node’s value’s value).</p><div id="0bb0"><pre>update(key: key, node: node) <span class="hljs-keyword">return</span> node.value.value</pre></div><p id="0057">6. Let’s create the set method.</p><div id="aad6"><pre><span class="hljs-keyword">func</span> <span class="hljs-title function_">setValue</span>(<span class="hljs-keyword"></span> <span class="hljs-params">value</span>: <span class="hljs-type">Value</span>, <span class="hljs-params">for</span> <span class="hljs-params">key</span>: <span class="hljs-type">Key</span>) {</pre></div><p id="8e0e">7. If our cache has this key, let’s update the node’s value’s value, “update” the key/node and return.</p><div id="fa6d"><pre><span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> node <span class="hljs-operator">=</span> cache[key] { node.value.value <span class="hljs-operator">=</span> value update(key: key, node: node) <span class="hljs-keyword">return</span> }</pre></div><p id="14d5">8. If we have reached the capacity limit, let’s remove the first element from the double-linked list at our current frequency and remove its key from the cache.</p><div id="0dad"><pre><span class="hljs-keyword">if</span> cache.count <span class="hljs-operator">==</span> size, <span class="hljs-keyword">let</span> node <span class="hljs-operator">=</span> frequencyMap[currentFrequency]<span class="hljs-operator">?</span>.removeFirst() { cache.removeValue(forKey: node.key)</pre></div><p id="db5e">9. If the double-linked list at the current frequency is empty, let’s remove the current frequency from the frequency map.</p><div id="1e03"><pre><span class="hljs-keyword">if</span> frequencyMap[currentFrequency]<span class="hljs-operator">?</span>.isEmpty <span class="hljs-operator">??</span> <span class="hljs-literal">false</span> { frequencyMap.removeValue(forKey: currentFrequency) }</pre></div><p id="d06b">10. Let’s reset the current frequency at 1 (this is a new entry), get or create the double linked list for frequency 1, append to it the new tuple, and update the cache and the frequency map.</p><div id="b569"><pre>currentFrequency <span class="hljs-operator">=</span> <span class="hljs-number">1</span> <span class="hljs-keyword">let</span> list <span class="hljs-operator">=</span> frequencyMap[currentFrequency] <span class="hljs-operator">??</span> <span class="hljs-type">DoubleLinkedList</span><<span class="hljs-type">Element</span>>() <span class="hljs-keyword">let</span> node <span class="hljs-operator">=</span> list.append((key: key, value: value, frequency: currentFrequency)) frequencyMap[currentFrequency] <span class="hljs-operator">=</span> list cache[key] <span class="hljs-operator">=</span> node</pre></div><p id="e4c7">11. Let’s create the “update key/node”.</p><div id="6b3f"><pre><span class="hljs-keyword">func</span> <span class="hljs-title function_">update</span>(<span class="hljs-params">key</span>: <span class="hljs-type">Key</span>, <span class="hljs-params">node</span>: <span class="hljs-type">DoubleLinkedListNode</span><<span class="hljs-type">Element</span>>) {</pre></div><p id="dad0">12. Remove the given node from its list.</p><div id="4a31"><pre><span class="hljs-keyword">let</span> frequency <span class="hljs-operator">=</span> node.value.frequency <span class="hljs-keyword"></span> <span class="hljs-operator">=</span> frequencyMap[frequency]<span class="hljs-operator">?</span>.remove(node)</pre></div><p id="5ee4">13. If its list becomes empty remove the list from the frequency map and increment the current frequency if the same frequency.</p><div id="8c40"><pre><span class="hljs-keyword">if</span> frequencyMap[frequency]<span class="hljs-operator">?</span>.isEmpty <span class="hljs-operator">??</span> <span class="hljs-literal">false</span> { frequencyMap.removeValue(forKey: frequency) <span class="hljs-keyword">if</span> currentFrequency <span class="hljs-operator">==</span> frequency { currentFrequency <span class="hljs-operator">=</span> frequency <span class="hljs-operator">+</span> <span class="hljs-number">1</span> } }</pre></div><p id="ac3b">14. Let’s get or create the double linked list for frequency its frequency + 1, append to it the new tuple, and update the cache and the frequency map.</p><div id="14a6"><pre><span class="hljs-keyword">let</span> list <span class="hljs-operator">=</span> frequencyMap[frequency <span class="hljs-operator">+</span> <span class="hljs-number">1</span>] <span class="hljs-operator">??</span> <span class="hljs-type">DoubleLinkedList</span><<span class="hljs-type">Element</span>>() <span class="hljs-keyword">let</span> newNode <span class="hljs-operator">=</span> list.append((key: key, value: node.value.value, frequency: frequency <span class="hljs-operator">+</span> <span class="hljs-number">1</span>)) frequencyMap[frequency <span class="hljs-operator">+</span> <span class="hljs-number">1</span>] <span class="hljs-operator">=</span> list cache[key] <span class="hljs-operator">=</span> newNode</pre></div><h2 id="320d">Complexity:</h2><p id="2f98">- Time Complexity: As per requirement, both get and set methods are constant in time complexity O(1),</p><p id="8cba">- Space Complexity: To achieve this performance, there is some data duplication, but the space complexity remains linear O(n).</p><h2 id="92b3">Test-cases:</h2><div id="a70c"><pre><span class="hljs-keyword">public</span> func <span class="hljs-title function_ invoke__">testLFU</span>() { let lfuCache = LFUCache<String,String>(size: <span class="hljs-number">3</span>); lfuCache.<span class="hljs-title function_ invoke__">setValue</span>(<span class="hljs-string">"1"</span>,<span class="hljs-attr">for</span>: <span class="hljs-string">"test1"</span>); lfuCache.<span class="hljs-title function_ invoke__">setValue</span>(<span class="hljs-string">"2"</span>,<span class="hljs-attr">for</span>: <span class="hljs-string">"test2"</span>); lfuCache.<span class="hljs-title function_ invoke__">setValue</span>(<span class="hljs-string">"3"</span>,<span class="hljs-attr">for</span>: <span class="hljs-string">"test3"</span>); _ = lfuCache.<span class="hljs-title function_ invoke__">getValue</span>(<span class="hljs-attr">for</span>: <span class="hljs-string">"test1"</span>) _ = lfuCache.<span class="hljs-title function_ invoke__">getValue</span>(<span class="hljs-attr">for</span>: <span class="hljs-string">"test2"</span>) _ = lfuCache.<span class="hljs-title function_ invoke__">getValue</span>(<span class="hljs-attr">for</span>: <span class="hljs-string">"test3"</span>) <span class="hljs-title function_ invoke__">assert</span>(<span class="hljs-string">"1"</span> == lfuCache.<span class="hljs-title function_ invoke__">getValue</span>(<span class="hljs-attr">for</span>: <span class="hljs-string">"test1"</span>), <span class="hljs-string">"mainLFU 1 not equal"</span>); <span class="hljs-title function_ invoke__">assert</span>(<span class="hljs-string">"2"</span> == lfuCache.<span class="hljs-title function_ invoke__">getValue</span>(<span class="hljs-attr">for</span>: <span class="hljs-string">"test2"</span>), <span class="hljs-string">"mainLFU 2 not equal"</span>); <span class="hljs-title function_ invoke__">assert</span>(<span class="hljs-string">"3"</span> == lfuCache.<span class="hljs-title function_ invoke__">getValue</span>(<span class="hljs-attr">for</span>: <span class="hljs-string">"test3"</span>), <span class="hljs-string">"mainLFU 3 not equal"</span>);

lfuCache.<span class="hljs-title function_ invoke__">setValue</span>(<span class="hljs-string">"1"</span>,<span class="hljs-attr">for</span>: <span class="hljs-string">"test1"</span>); lfuCache.<span class="hljs-title function_ invoke__">setValue</span>(<span class="hljs-string">"2"</span>,<span class="hljs-attr">for</span>: <span class="hljs-string">"test2"</span>); lfuCache.<span class="hljs-title function_ invoke__">setValue</span>(<span class="hljs-string">"3"</span>,<span class="hljs-attr">for</span>: <span class="hljs-string">"test3"</span>); _ = lfuCache.<span class="hljs-title function_ invoke__">getValue</span>(<span class="hljs-attr">for</span>: <span class="hljs-string">"test1"</span>) lfuCache.<span class="hljs-title function_ invoke__">setValue</span>(<span class="hljs-string">"4"</span>,<span class="hljs-attr">for</span>: <span class="hljs-string">"test4"</span>); _ = lfuCache.<span class="hljs-title function_ invoke__">getValue</span>(<span class="hljs-attr">for</span>: <span class="hljs-string">"test1"</span>) <span class="hljs-title function_ invoke__">assert</span>(lfuCache.<span class="hljs-title function_ invoke__">getValue</span>(<span class="hljs-attr">for</span>: <span class="hljs-string">"test1"</span>) == <span class="hljs-string">"1"</span>, <span class="hljs-string">"mainLFU 4 not equal"</span>);

_ = lfuCache.<span class="hljs-title function_ invoke__">getValue</span>(<span class="hljs-attr">for</span>: <span class="hljs-string">"test2"</span>) <span class="hljs-title function_ invoke__">assert</span>(lfuCache.<span class="hljs-title function_ invoke__">getValue</span>(<span class="hljs-attr">for</span>: <span class="hljs-string">"test2"</span>) == nil, <span class="hljs-string">"mainLFU 5 not equal"</span>); }</pre></div><h2 id="3c4e">Follow-up:</h2><p id="f2fd">No more follow-up question.</p><p id="4404"><a href="https://readmedium.com/algorithms-questions-429c4d84a98f"><< More Interview Questions</a> | <a href="https://readmedium.com/learn-data-structures-and-algorithms-with-swift-5-6-d9f36a4027dd">Data Structures and Algorythms >></a></p><div id="c879" class="link-block"> <a href="https://medium.com/@jbstevenard/membership"> <div> <div> <h2>Join Medium with my referral link — jb stevenard</h2> <div><h3>Read every story from jb stevenard (and thousands of other writers on Medium). Your membership fee directly supports jb…</h3></div> <div><p>medium.com</p></div> </div> <div> <div style="background-image: url(https://miro.readmedium.com/v2/resize:fit:320/0*TEb_Qg9xmrjfkxhH)"></div> </div> </div> </a> </div></article></body>

26. Least Recently Used (LRU) / Least Frequent Used (LFU)

Question: Write a Least Recently Used (LRU) class where you can retrieve the value by key and also set a new value for a key; both methods should execute on constant time O(1).

Hints:

- You will need to keep the order of the access (read and write),

- You will need to have direct access to all values,

- To achieve this, you will need two data structures, one double-linked list for the order and one dictionary to get the values.

Solution:

class LRUCache<Key: Hashable, Value> {
    // 1.
    typealias Element = (key: Key, value: Value)

    // 2.
    let size: Int
    let orderList = DoubleLinkedList<Element>()
    var cache = [Key: DoubleLinkedListNode<Element>]()

    init(size: Int) {
        self.size = size
    }

    // 3.
    func getValue(for key: Key) -> Value? {
        // 4.
        guard let node = cache[key] else {
            return nil
        }

        // 5.
        orderList.moveUp(node: node)
        return node.value.value
    }

    // 6.
    func setValue(_ value: Value, for key: Key) {
        guard size > 0 else { return }
        // 7.
        let element = Element(key: key, value: value)

        if let node = cache[key] {
            // 8.
            node.value = element
            orderList.moveUp(node: node)
            return
        }

        if cache.count == size {
            // 9.
            if let key = orderList.removeFirst()?.key {
                cache.removeValue(forKey: key)
            }
        }

        // 10.
        let node = orderList.append(element)
        cache[key] = node
    }
}

extension DoubleLinkedList {
    // 11.
    func moveUp(node: DoubleLinkedListNode<T>) {
        // 12.
        guard node !== tail else {
            return
        }
        // 13.
        if head === node {
            head = node.next
        }

        // 14.
        node.next?.previous = node.previous
        node.previous?.next = node.next
        node.next = nil

        // 15.
        tail?.next = node
        node.previous = tail
        tail = node
    }
}

Explanations:

1. Let’s define a new type (tuple) to store the key and value.

typealias Element = (key: Key, value: Value)

2. Because we need to access in O(1), we need to have a dictionary; as we need to keep the order, we require a sequence, as we want to be able to reorder also in O(1). we will use a double-linked list with our tuple as value and a dictionary with the key as key and the double-linked list node as value.

let size: Int
let orderList = DoubleLinkedList<Element>()
var cache = [Key: DoubleLinkedListNode<Element>]()

3. Let’s create the get method.

func getValue(for key: Key) -> Value? {

4. If our dictionary does not have this key let’s return nil.

guard let node = cache[key] else {
  return nil
}

5. Let’s “move up” (this function is explained later) the key associated node and return the stored value (the node’s value’s value)..

orderList.moveUp(node: node)
return node.value.value

6. Let’s create the set method

func setValue(_ value: Value, for key: Key) {

7. We create a new tuple with the key and the value.

let element = Element(key: key, value: value)

8. If a node exists in our dictionary for the given key, let’s update its value and “move up” the node.

if let node = cache[key] {
  node.value = element
  orderList.moveUp(node: node)
  return
}

9. If we have reached the capacity limit, let’s remove the first element from the double-linked list and remove its key from the dictionary.

if cache.count == size {
  if let key = orderList.removeFirst()?.key {
    cache.removeValue(forKey: key)
  }
}

10. Append the new tuple into the linked list, and set the new node into the dictionary too.

let node = orderList.append(element)
cache[key] = node

11. Let’s create the “move up” method (taking any node from the list and send it to the tail).

func moveUp(node: DoubleLinkedListNode<T>) {

12. If the node is already the tail, nothing to do.

guard node !== tail else {
  return
}

13. If the given node is the current head, make the current node’s next node the new head.

if head === node {
  head = node.next
}

14. To remove the node from the list, connect its previous with its next node.

node.next?.previous = node.previous
node.previous?.next = node.next
node.next = nil

15. Let’s connect the current tail with the given node and make it the new tail.

tail?.next = node
node.previous = tail
tail = node

Complexity:

- Time Complexity: As per requirement, both get and set methods are constant in time complexity O(1),

- Space Complexity: To achieve this performance, there is some data duplication, but the space complexity remains linear O(n).

Test-cases:

public func testLRU() {
  let lruCache = LRUCache<String,String>(size: 3);
  lruCache.setValue("1",for: "test1");
  lruCache.setValue("2",for: "test2");
  lruCache.setValue("3",for: "test3");
  _ = lruCache.getValue(for: "test1")
  _ = lruCache.getValue(for: "test2")
  _ = lruCache.getValue(for: "test3")
  assert("1" == lruCache.getValue(for: "test1"), "mainLRU 1 not equal");
  assert("2" == lruCache.getValue(for: "test2"), "mainLRU 2 not equal");
  assert("3" == lruCache.getValue(for: "test3"), "mainLRU 3 not equal");

  lruCache.setValue("1",for: "test1");
  lruCache.setValue("2",for: "test2");
  lruCache.setValue("3",for: "test3");
  _ = lruCache.getValue(for: "test1")
  lruCache.setValue("4",for: "test4");
  _ = lruCache.getValue(for: "test1")
  assert(lruCache.getValue(for: "test1") == "1", "mainLRU 4 not equal");

  _ = lruCache.getValue(for: "test2")
  assert(lruCache.getValue(for: "test2") == nil, "mainLRU 5 not equal");
}

Follow-up:

Write a Least Frequent Used (LFU).

Hints:

- You still need to care about the order but also need to store the frequency,

- Think about keeping buckets for the same frequency,

- You need to know the least current frequency.

Solution:

class LFUCache<Key: Hashable, Value> {
    // 1.
    typealias Element = (key: Key, value: Value, frequency: Int)

    // 2.
    let size: Int
    var frequencyMap = [Int: DoubleLinkedList<Element>]()
    var cache = [Key: DoubleLinkedListNode<Element>]()
    var currentFrequency: Int = 0

    init(size: Int) {
        self.size = size
    }

    // 3.
    func getValue(for key: Key) -> Value? {
        // 4.
        guard let node = cache[key] else {
            return nil
        }
        // 5.
        update(key: key, node: node)
        return node.value.value
    }

    // 6.
    func setValue(_ value: Value, for key: Key) {
        guard size > 0 else { return }

        // 7.
        if let node = cache[key] {
            node.value.value = value
            update(key: key, node: node)
            return
        }

        if cache.count == size,
           let node = frequencyMap[currentFrequency]?.removeFirst() {
            // 8.
            cache.removeValue(forKey: node.key)
            if frequencyMap[currentFrequency]?.isEmpty ?? false {
                // 9.
                frequencyMap.removeValue(forKey: currentFrequency)
            }
        }

        // 10.
        currentFrequency = 1
        let list = frequencyMap[currentFrequency] ?? DoubleLinkedList<Element>()
        let node = list.append((key: key, value: value, frequency: currentFrequency))
        frequencyMap[currentFrequency] = list
        cache[key] = node
    }

    // 11.
    func update(key: Key, node: DoubleLinkedListNode<Element>) {
        // 12.
        let frequency = node.value.frequency
        _ = frequencyMap[frequency]?.remove(node)

        if frequencyMap[frequency]?.isEmpty ?? false {
            // 13.
            frequencyMap.removeValue(forKey: frequency)
            if currentFrequency == frequency {
                currentFrequency = frequency + 1
            }
        }

        // 14.
        let list = frequencyMap[frequency + 1] ?? DoubleLinkedList<Element>()
        let newNode = list.append((key: key, value: node.value.value, frequency: frequency + 1))
        frequencyMap[frequency + 1] = list
        cache[key] = newNode
    }
}

Explanations:

1. Let’s define a new type (tuple) to store the key, the value, and its frequency.

typealias Element = (key: Key, value: Value, frequency: Int)

2. Because we need to access in O(1), we need to have a dictionary for the key and the frequency; as we need to keep the order, we need a sequence, as we want to reorder also in O(1). We will use a dictionary with the frequency as the key and a double-linked list with our tuple as the value. We will also use a dictionary with the key and the double-linked list node as the value.

let size: Int
var frequencyMap = [Int: DoubleLinkedList<Element>]()
var cache = [Key: DoubleLinkedListNode<Element>]()
var currentFrequency: Int = 0

3. Let’s create the get method.

func getValue(for key: Key) -> Value? {

4. If our cache does not have this key let’s return nil.

guard let node = cache[key] else {
  return nil
}

5. Let’s “update” the key/node (this function is explained later), and return the stored value (the node’s value’s value).

update(key: key, node: node)
return node.value.value

6. Let’s create the set method.

func setValue(_ value: Value, for key: Key) {

7. If our cache has this key, let’s update the node’s value’s value, “update” the key/node and return.

if let node = cache[key] {
  node.value.value = value
  update(key: key, node: node)
  return
}

8. If we have reached the capacity limit, let’s remove the first element from the double-linked list at our current frequency and remove its key from the cache.

if cache.count == size,
let node = frequencyMap[currentFrequency]?.removeFirst() {
cache.removeValue(forKey: node.key)

9. If the double-linked list at the current frequency is empty, let’s remove the current frequency from the frequency map.

if frequencyMap[currentFrequency]?.isEmpty ?? false {
  frequencyMap.removeValue(forKey: currentFrequency)
}

10. Let’s reset the current frequency at 1 (this is a new entry), get or create the double linked list for frequency 1, append to it the new tuple, and update the cache and the frequency map.

currentFrequency = 1
let list = frequencyMap[currentFrequency] ?? DoubleLinkedList<Element>()
let node = list.append((key: key, value: value, frequency: currentFrequency))
frequencyMap[currentFrequency] = list
cache[key] = node

11. Let’s create the “update key/node”.

func update(key: Key, node: DoubleLinkedListNode<Element>) {

12. Remove the given node from its list.

let frequency = node.value.frequency
_ = frequencyMap[frequency]?.remove(node)

13. If its list becomes empty remove the list from the frequency map and increment the current frequency if the same frequency.

if frequencyMap[frequency]?.isEmpty ?? false {
  frequencyMap.removeValue(forKey: frequency)
  if currentFrequency == frequency {
    currentFrequency = frequency + 1
  }
}

14. Let’s get or create the double linked list for frequency its frequency + 1, append to it the new tuple, and update the cache and the frequency map.

let list = frequencyMap[frequency + 1] ?? DoubleLinkedList<Element>()
let newNode = list.append((key: key, value: node.value.value, frequency: frequency + 1))
frequencyMap[frequency + 1] = list
cache[key] = newNode

Complexity:

- Time Complexity: As per requirement, both get and set methods are constant in time complexity O(1),

- Space Complexity: To achieve this performance, there is some data duplication, but the space complexity remains linear O(n).

Test-cases:

public func testLFU() {
  let lfuCache = LFUCache<String,String>(size: 3);
  lfuCache.setValue("1",for: "test1");
  lfuCache.setValue("2",for: "test2");
  lfuCache.setValue("3",for: "test3");
  _ = lfuCache.getValue(for: "test1")
  _ = lfuCache.getValue(for: "test2")
  _ = lfuCache.getValue(for: "test3")
  assert("1" == lfuCache.getValue(for: "test1"), "mainLFU 1 not equal");
  assert("2" == lfuCache.getValue(for: "test2"), "mainLFU 2 not equal");
  assert("3" == lfuCache.getValue(for: "test3"), "mainLFU 3 not equal");

  lfuCache.setValue("1",for: "test1");
  lfuCache.setValue("2",for: "test2");
  lfuCache.setValue("3",for: "test3");
  _ = lfuCache.getValue(for: "test1")
  lfuCache.setValue("4",for: "test4");
  _ = lfuCache.getValue(for: "test1")
  assert(lfuCache.getValue(for: "test1") == "1", "mainLFU 4 not equal");

  _ = lfuCache.getValue(for: "test2")
  assert(lfuCache.getValue(for: "test2") == nil, "mainLFU 5 not equal");
}

Follow-up:

No more follow-up question.

<< More Interview Questions | Data Structures and Algorythms >>

Data Structures
Algorithms
Coding
Programming
Interview Questions
Recommended from ReadMedium