fix(post): fetch code lazily

This commit is contained in:
Barrett Ruth 2024-11-29 21:27:21 -06:00
parent ee3a3b61a2
commit ed83255604
10 changed files with 209 additions and 172 deletions

View file

@ -115,44 +115,7 @@
operations. The minimum/maximum element must be found via a linear
scan in \(O(n)\) time, certainly far from optimal.
</p>
<pre><code class="language-cpp">#include &lt;algorithm&gt;
#include &lt;deque&gt;
#include &lt;stdexcept&gt;
class ExtremaCircularBuffer {
public:
ExtremaCircularBuffer(size_t capacity) : capacity(capacity) {}
void push_back(double value) {
if (prices.size() == capacity) {
prices.pop_front();
}
prices.push_back(value);
}
void pop_front() {
if (prices.empty()) {
throw std::out_of_range("Cannot pop_front() from empty buffer");
}
prices.pop_front();
}
size_t size() const { return prices.size(); }
double get() const {
if (prices.empty()) {
throw std::out_of_range("Cannot find max() of empty buffer");
}
return *std::max_element(prices.begin(), prices.end());
}
private:
std::deque&lt;double&gt; prices;
size_t capacity;
};</code></pre>
<div class="code" data-file="naive.cpp"</div>
</div>
<h3>optimizing the approach</h3>
<div class="problem-content">
@ -174,9 +137,7 @@ private:
<a
target="blank"
href="https://en.cppreference.com/w/cpp/container/map"
><span class="inline-code"
><code>std::map&lt;double, size_t&gt;</code></span
></a
>std::map</a
>
allows us to do all of this.
</p>
@ -184,62 +145,7 @@ private:
Now, we can access extrema instantly. Insertion and deletion take
\(O(log(n))\) time thanks to the map&mdash;but we can do better.
</p>
<pre><code class="language-cpp">#include &lt;deque&gt;
#include &lt;map&gt;
#include &lt;stdexcept&gt;
class ExtremaCircularBuffer {
public:
ExtremaCircularBuffer(size_t capacity) : capacity(capacity) {}
void push_back(double value) {
if (prices.size() == capacity) {
double front = prices.front();
if (--sorted_prices[front] == 0)
sorted_prices.erase(front);
prices.pop_front();
}
prices.push_back(value);
++sorted_prices[value];
}
void pop_front() {
if (prices.empty()) {
throw std::out_of_range("Cannot pop_front() from empty buffer");
}
double front = prices.front();
if (--sorted_prices[front] == 0)
sorted_prices.erase(front);
prices.pop_front();
}
size_t size() const { return prices.size(); }
double get_max() const {
if (prices.empty()) {
throw std::out_of_range("Cannot find max() of empty buffer");
}
return sorted_prices.rbegin()-&gt;first;
}
double get_min() const {
if (prices.empty()) {
throw std::out_of_range("Cannot find min() of empty buffer");
}
return sorted_prices.begin()-&gt;first;
}
private:
std::deque&lt;double&gt; prices;
std::map&lt;double, size_t&gt; sorted_prices;
size_t capacity;
};</code></pre>
<div class="code" data-file="map.cpp"></div>
</div>
<h3>monotonic <s>queues</s> deques</h3>
<div class="problem-content">
@ -298,71 +204,7 @@ private:
once; across a sequence of \(n\) operations, \(n\) total \(O(1)\)
operations will be executed).
</p>
<pre><code class="language-cpp">#include &lt;deque&gt;
#include &lt;stdexcept&gt;
#include &lt;utility&gt;
class ExtremaCircularBuffer {
public:
explicit ExtremaCircularBuffer(size_t capacity) : capacity(capacity) {}
void push_back(double value) {
if (prices.size() == capacity) {
double front_value = prices.front();
pop_max(front_value);
prices.pop_front();
}
prices.push_back(value);
push_max(value);
}
void pop_front() {
if (prices.empty()) {
throw std::out_of_range("Cannot pop_front() from empty buffer");
}
double front_value = prices.front();
pop_max(front_value);
prices.pop_front();
}
size_t size() const { return prices.size(); }
double get_max() const {
if (prices.empty()) {
throw std::out_of_range("Cannot find max() of empty buffer");
}
return maxs.front().first;
}
private:
void push_max(double value) {
size_t popped = 0;
while (!maxs.empty() &amp;&amp; maxs.back().first &lt; value) {
popped += maxs.back().second + 1;
maxs.pop_back();
}
maxs.emplace_back(value, popped);
}
void pop_max(double value) {
size_t popped = maxs.front().second;
if (popped == 0) {
maxs.pop_front();
} else {
--maxs.front().second;
}
}
std::deque&lt;double&gt; prices;
std::deque&lt;std::pair&lt;double, size_t&gt;&gt; maxs;
size_t capacity;
};</code></pre>
<div class="code" data-file="monotonic.cpp"></div>
<h3>further improvements</h3>
<ol>
<li>

View file

@ -0,0 +1,56 @@
#include <deque>
#include <map>
#include <stdexcept>
class ExtremaCircularBuffer {
public:
ExtremaCircularBuffer(size_t capacity) : capacity(capacity) {}
void push_back(double value) {
if (prices.size() == capacity) {
double front = prices.front();
if (--sorted_prices[front] == 0)
sorted_prices.erase(front);
prices.pop_front();
}
prices.push_back(value);
++sorted_prices[value];
}
void pop_front() {
if (prices.empty()) {
throw std::out_of_range("Cannot pop_front() from empty buffer");
}
double front = prices.front();
if (--sorted_prices[front] == 0)
sorted_prices.erase(front);
prices.pop_front();
}
size_t size() const { return prices.size(); }
double get_max() const {
if (prices.empty()) {
throw std::out_of_range("Cannot find max() of empty buffer");
}
return sorted_prices.rbegin()->first;
}
double get_min() const {
if (prices.empty()) {
throw std::out_of_range("Cannot find min() of empty buffer");
}
return sorted_prices.begin()->first;
}
private:
std::deque<double> prices;
std::map<double, size_t> sorted_prices;
size_t capacity;
};

View file

@ -0,0 +1,65 @@
#include <deque>
#include <stdexcept>
#include <utility>
class ExtremaCircularBuffer {
public:
explicit ExtremaCircularBuffer(size_t capacity) : capacity(capacity) {}
void push_back(double value) {
if (prices.size() == capacity) {
double front_value = prices.front();
pop_max(front_value);
prices.pop_front();
}
prices.push_back(value);
push_max(value);
}
void pop_front() {
if (prices.empty()) {
throw std::out_of_range("Cannot pop_front() from empty buffer");
}
double front_value = prices.front();
pop_max(front_value);
prices.pop_front();
}
size_t size() const { return prices.size(); }
double get_max() const {
if (prices.empty()) {
throw std::out_of_range("Cannot find max() of empty buffer");
}
return maxs.front().first;
}
private:
void push_max(double value) {
size_t popped = 0;
while (!maxs.empty() && maxs.back().first < value) {
popped += maxs.back().second + 1;
maxs.pop_back();
}
maxs.emplace_back(value, popped);
}
void pop_max(double value) {
size_t popped = maxs.front().second;
if (popped == 0) {
maxs.pop_front();
} else {
--maxs.front().second;
}
}
std::deque<double> prices;
std::deque<std::pair<double, size_t>> maxs;
size_t capacity;
};

View file

@ -0,0 +1,38 @@
#include <algorithm>
#include <deque>
#include <stdexcept>
class ExtremaCircularBuffer {
public:
ExtremaCircularBuffer(size_t capacity) : capacity(capacity) {}
void push_back(double value) {
if (prices.size() == capacity) {
prices.pop_front();
}
prices.push_back(value);
}
void pop_front() {
if (prices.empty()) {
throw std::out_of_range("Cannot pop_front() from empty buffer");
}
prices.pop_front();
}
size_t size() const { return prices.size(); }
double get() const {
if (prices.empty()) {
throw std::out_of_range("Cannot find max() of empty buffer");
}
return *std::max_element(prices.begin(), prices.end());
}
private:
std::deque<double> prices;
size_t capacity;
};

View file

@ -41,7 +41,7 @@ pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
background: #f6f8fa;
background: #f4f4f4;
}
:not(pre) > code[class*="language-"] {
padding: .1em .3em;
@ -129,4 +129,4 @@ pre[class*="language-"] > code[class*="language-"] {
}
.token.entity {
cursor: help;
}
}

View file

@ -19,9 +19,7 @@ const postMapping = new Map([
[
"Algorithms",
[
{ name: "leetcode daily", link: "leetcode-daily" },
{ name: "extrema circular buffer", link: "extrema-circular-buffer" },
{ name: "two pointers", link: "two-pointers" },
],
],
]);

View file

@ -20,3 +20,39 @@ document.addEventListener("DOMContentLoaded", () => {
document.querySelectorAll(".post-article h2").forEach(setStyle);
document.querySelectorAll(".post-article h3").forEach(setStyle);
});
document.addEventListener("DOMContentLoaded", () => {
document.querySelectorAll(".code").forEach(loadCode);
});
async function loadCode(e) {
const file = e.dataset.file;
const language = file.substring(file.lastIndexOf(".") + 1);
let [_, __, topic, post] = window.location.pathname.split("/");
post = post.substring(0, post.lastIndexOf("."));
try {
const path = `/public/code/${topic}/${post}/${file}`;
const response = await fetch(path);
if (!response.ok) {
const errorText = await response.text();
throw new Error(
`Failed to fetch ${path}: ${response.status} ${response.statusText}\n${errorText}`,
);
}
const code = await response.text();
const pre = document.createElement("pre");
const codeElement = document.createElement("code");
codeElement.className = `language-${language}`;
codeElement.textContent = code;
pre.appendChild(codeElement);
e.appendChild(pre);
Prism.highlightElement(codeElement);
} catch (error) {
console.error(error);
}
}

View file

@ -42,7 +42,6 @@ footer {
a {
color: inherit;
text-decoration: none;
}
li {
@ -79,7 +78,3 @@ li {
.terminal-container {
font-family: "Courier New", monospace;
}
.inline-code {
display: inline-block;
}

View file

@ -56,6 +56,10 @@ ul {
flex: 1;
}
a {
text-decoration: none;
}
.post {
margin-bottom: 25px;
}

View file

@ -51,7 +51,6 @@ li {
:not(pre) > code {
font-family: "Courier New", Courier, monospace;
background-color: #f4f4f4;
padding: 2px 4px;
margin: 0 5px;
border: 1px solid #e1e1e1;
@ -60,6 +59,10 @@ li {
white-space: nowrap;
}
code {
background: #f4f4f4 !important;
}
.post-title::before {
content: "";
position: absolute;