DEV Community: Anton Yarkov The latest articles on DEV Community by Anton Yarkov (@optiklab). https://dev.to/optiklab https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F218717%2F934a5703-25a9-4820-b0d6-56f565725cf8.jpeg DEV Community: Anton Yarkov https://dev.to/optiklab en The low-cost path to AI Mastery: building a Wiki Navigator with pure Similarity Search Anton Yarkov Wed, 01 Oct 2025 15:31:16 +0000 https://dev.to/optiklab/the-low-cost-path-to-ai-mastery-building-a-wiki-navigator-with-pure-similarity-search-dak https://dev.to/optiklab/the-low-cost-path-to-ai-mastery-building-a-wiki-navigator-with-pure-similarity-search-dak <p>The world of Artificial Intelligence (AI) and Large Language Models (LLMs) often conjures images of immense computing power, proprietary platforms, and colossal GPU clusters. This perception can create a high barrier to entry, discouraging curious developers from exploring the fundamentals.</p> <p>I recently embarked on a project—a sophisticated yet simple <a href="proxy.php?url=https://github.com/tacticaxyz/tactica.faq.similaritysearch" rel="noopener noreferrer">AI-powered chatbot</a> I call the Wiki Navigator—that proves this complexity is often unnecessary for learning the essentials. By focusing on core concepts like <strong>tokenization, vector embeddings, and cosine similarity</strong>, I built a functional RAG (Retrieval Augmented Generation) search solution that operates across 9,000 documents in the Chromium open-source codebase. It took me a few hours to run and next day I was able to re-use the same codebase to train Chat bot on open-source books about the Rust programming language to have useful help during my Rust learning journey.</p> <p>The main revelation? <strong>You don't need to dive too deep with huge GPU cards to learn how the essentials of LLM and AI work</strong>. It is a supremely rewarding and practical experience to learn by doing, immediately yielding results without incurring significant expense.</p> <h2> The magic of Vector Embeddings </h2> <p>Our Wiki Navigator functions not by generating novel text, but by reliably retrieving contextual replies and relevant links from source documentation, preventing hallucination by strictly following the links in the wiki. It is essentially a contextual search engine powered by Retrieval Augmented Generation (RAG).</p> <p>The core concept is surprisingly straightforward:</p> <ol> <li> <strong>Preparation (Training Phase):</strong> Convert all your documents (like Q&amp;A pairs and wiki content) into a digital representation known as <strong>vector embeddings</strong> (watch <a href="proxy.php?url=https://www.youtube.com/watch?v=LPZh9BOjkQs" rel="noopener noreferrer">this great explanation</a> if you didn't yet). This process, which can take an hour or so for large corpora, creates a vector index.</li> <li> <strong>Querying (Query Phase):</strong> When a user submits a question, that query is also converted into a vector embedding.</li> <li> <strong>Comparison:</strong> The system compares the query vector against the document vectors using the <strong>Cosine Similarity</strong> operation to find the closest matches. If we found two vectors near to each other - that most likely means match in terms of the context (though, as we can see later, not always).</li> </ol> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvuz7wn5gwoqf1m9hd2lx.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvuz7wn5gwoqf1m9hd2lx.jpg" alt="Principal diagram" width="800" height="518"></a></p> <p>This simple process works effectively for tasks like navigating documentation and finding relevant resources.</p> <h2> Ensuring algorithmic parity </h2> <p>While many articles focus on the theory of similarity search, the real fun lies in implementing it. Interestingly enough, to run simplistic MVP you take NO AI MODEL, which makes it possible to be deployed statically, running entirely in the browser, making it perfect for hosting on platforms like GitHub Pages. This static deployment requires the training application (C#) and the client application (JavaScript) to share identical algorithms for tokenization and vector calculation, ensuring smooth operation and consistent results.</p> <p>The training pipeline, which prepares the context database, is built in C# (located in <code>TacTicA.FaqSimilaritySearchBot.Training/Program.cs</code>). During training, data is converted into embeddings using services like the <code>SimpleEmbeddingService</code> (hash-based, in case of NO AI model for static web site deployment), the <code>TfIdfEmbeddingService</code> (TF-IDF/Keyword-Based Similarity - an extended version of trainer), or the sophisticated <code>OnnxEmbeddingService</code> (based on the pre-trained <code>all-MiniLM-L6-v2</code> transformer model, which would require you to run some good back-end with AI model loaded into RAM).</p> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhkwdg1obnqzvd6cbb840.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhkwdg1obnqzvd6cbb840.jpg" alt="Deployment scheme" width="800" height="722"></a></p> <p>In this article I mainly focus on the first option - simplistic hash-based approach, while I do also have an AI-Model-based solution running in production on <a href="proxy.php?url=https://tactica.xyz/#/rust-similarity-search" rel="noopener noreferrer">tactica.xyz</a>. This is full-fledged React application running all comparisons on the back-end, but the fundamental concepts stay the same.</p> <p>The core mathematical utilities that define <a href="proxy.php?url=https://huggingface.co/learn/llm-course/en/chapter2/4" rel="noopener noreferrer">tokenization</a> and vector operations reside in C# within <code>TacTicA.FaqSimilaritySearchBot.Shared/Utils/VectorUtils.cs</code>. To ensure the client-side browser application running in JavaScript via <code>TacTicA.FaqSimilaritySearchBot.Web/js/chatbot.js</code> (or <code>TacTicA.FaqSimilaritySearchBot.WebOnnx/js/chatbot.js</code> for the AI-model based one) can process new user queries identically to C# training algorithm, we must replicate those crucial steps.</p> <p>It is also critical to make sure that all calcuations produce same outputs in both C# and JavaScript, during both training and running, which might take additional efforts, but still pretty straightforward. For example these two:</p> <p>From <a href="proxy.php?url=https://github.com/tacticaxyz/tactica.faq.similaritysearch/blob/14d18641c057cf81c610b314f037b3d0c08d577d/src/TacTicA.FaqSimilaritySearchBot.Training/Services/SimpleEmbeddingService.cs#L151" rel="noopener noreferrer">SimpleEmbeddingService.cs</a>:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight csharp"><code> <span class="c1">// This method is taken from chatbot.js to be very similar to let Simple Embedding Service work at all!</span> <span class="k">private</span> <span class="n">Func</span><span class="p">&lt;</span><span class="kt">double</span><span class="p">&gt;</span> <span class="nf">SeededRandom</span><span class="p">(</span><span class="kt">double</span> <span class="n">initialSeed</span><span class="p">)</span> <span class="p">{</span> <span class="kt">double</span> <span class="n">seed</span> <span class="p">=</span> <span class="n">initialSeed</span><span class="p">;</span> <span class="k">return</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span> <span class="n">seed</span> <span class="p">=</span> <span class="p">(</span><span class="n">seed</span> <span class="p">*</span> <span class="m">9301.0</span> <span class="p">+</span> <span class="m">49297.0</span><span class="p">)</span> <span class="p">%</span> <span class="m">233280.0</span><span class="p">;</span> <span class="k">return</span> <span class="n">seed</span> <span class="p">/</span> <span class="m">233280.0</span><span class="p">;</span> <span class="p">};</span> <span class="p">}</span> </code></pre> </div> <p>From <a href="proxy.php?url=https://github.com/tacticaxyz/tactica.faq.similaritysearch/blob/14d18641c057cf81c610b314f037b3d0c08d577d/src/TacTicA.FaqSimilaritySearchBot.Web/js/chatbot.js#L501" rel="noopener noreferrer">chatbot.js</a>:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code> <span class="c1">// Seeded random number generator</span> <span class="nf">seededRandom</span><span class="p">(</span><span class="nx">seed</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="nx">seed</span> <span class="o">=</span> <span class="p">(</span><span class="nx">seed</span> <span class="o">*</span> <span class="mi">9301</span> <span class="o">+</span> <span class="mi">49297</span><span class="p">)</span> <span class="o">%</span> <span class="mi">233280</span><span class="p">;</span> <span class="k">return</span> <span class="nx">seed</span> <span class="o">/</span> <span class="mi">233280</span><span class="p">;</span> <span class="p">};</span> <span class="p">}</span> </code></pre> </div> <h3> C# training example: vector utility </h3> <p>In the C# training application, the <code>VectorUtils</code> class is responsible for calculating cosine similarity, which is the heart of the comparison operation:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight csharp"><code><span class="c1">// Excerpt from TacTicA.FaqSimilaritySearchBot.Shared/Utils/VectorUtils.cs</span> <span class="c1">// This function calculates how 'similar' two vectors (embeddings) are.</span> <span class="k">public</span> <span class="k">static</span> <span class="kt">double</span> <span class="nf">CalculateCosineSimilarity</span><span class="p">(</span><span class="kt">float</span><span class="p">[]</span> <span class="n">vectorA</span><span class="p">,</span> <span class="kt">float</span><span class="p">[]</span> <span class="n">vectorB</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// [C# Implementation Detail: Normalization and dot product calculation </span> <span class="c1">// to determine similarity score between 0.0 and 1.0]</span> <span class="c1">// ... actual calculation happens here ...</span> <span class="c1">// return similarityScore; </span> <span class="p">}</span> </code></pre> </div> <p>Running training set will take a hour, because we are NOT using GPU's, parallelization or any other fancy staff. Because we are learning the basics and do not want overcomplicate things for now:</p> <p><a href="proxy.php?url=https://asciinema.org/a/736921" rel="noopener noreferrer"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fasciinema.org%2Fa%2F736921.svg" alt="Watch it" width="1836" height="765"></a></p> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fysivoko3r5ngclxtozg5.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fysivoko3r5ngclxtozg5.jpg" alt="Training demo" width="800" height="491"></a></p> <h3> JavaScript client example: real-time search </h3> <p>The client application must then perform the same calculation in real time for every user query against the pre-computed index. The system relies on <strong>fast in-memory vector search</strong> using this very simplistic algorithm.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="c1">// Excerpt from TacTicA.FaqSimilaritySearchBot.Web/js/chatbot.js</span> <span class="c1">// This function is executed when the user submits a query.</span> <span class="kd">function</span> <span class="nf">performSimilaritySearch</span><span class="p">(</span><span class="nx">queryVector</span><span class="p">,</span> <span class="nx">documentIndex</span><span class="p">)</span> <span class="p">{</span> <span class="kd">let</span> <span class="nx">bestMatch</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span> <span class="kd">let</span> <span class="nx">maxSimilarity</span> <span class="o">=</span> <span class="mf">0.0</span><span class="p">;</span> <span class="c1">// Convert user query to vector (if using the simple hash/TF-IDF approach)</span> <span class="c1">// or use ONNX runtime for transformer model encoding.</span> <span class="c1">// Iterate through all pre-calculated document vectors</span> <span class="k">for </span><span class="p">(</span><span class="kd">const</span> <span class="p">[</span><span class="nx">docId</span><span class="p">,</span> <span class="nx">docVector</span><span class="p">]</span> <span class="k">of</span> <span class="nb">Object</span><span class="p">.</span><span class="nf">entries</span><span class="p">(</span><span class="nx">documentIndex</span><span class="p">))</span> <span class="p">{</span> <span class="c1">// Ensure the JS implementation of Cosine Similarity is identical to C#!</span> <span class="kd">const</span> <span class="nx">similarity</span> <span class="o">=</span> <span class="nf">calculateCosineSimilarity</span><span class="p">(</span><span class="nx">queryVector</span><span class="p">,</span> <span class="nx">docVector</span><span class="p">);</span> <span class="k">if </span><span class="p">(</span><span class="nx">similarity</span> <span class="o">&gt;</span> <span class="nx">maxSimilarity</span><span class="p">)</span> <span class="p">{</span> <span class="nx">maxSimilarity</span> <span class="o">=</span> <span class="nx">similarity</span><span class="p">;</span> <span class="nx">bestMatch</span> <span class="o">=</span> <span class="nx">docId</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="c1">// Apply the configured threshold (default 0.90) for FAQ matching.</span> <span class="k">if </span><span class="p">(</span><span class="nx">maxSimilarity</span> <span class="o">&gt;=</span> <span class="nx">CONFIG</span><span class="p">.</span><span class="nx">SimilarityThreshold</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// [Action: Return FAQ Response with Citation-Based Responses]</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="c1">// [Action: Trigger RAG Fallback for Full Document Corpus Search]</span> <span class="p">}</span> <span class="k">return</span> <span class="nx">bestMatch</span><span class="p">;</span> <span class="p">}</span> </code></pre> </div> <p>By ensuring that the underlying <strong>vector utilities</strong> are functionally identical in both C# and JavaScript, we guarantee that the query result will be consistent, regardless of whether the embedding was calculated during the training phase or the real-time query phase.</p> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fugfes6dsv9un5upjaubt.gif" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fugfes6dsv9un5upjaubt.gif" alt="Chat-bot demo in gif" width="80" height="77"></a></p> <h2> Beyond the Simple Lookup </h2> <p>Our bot is far more sophisticated than a simple keyword search. It is engineered with a three-phase architecture to handle complex queries:</p> <ol> <li> <strong>Phase 1: Context Database Preparation.</strong> This is the initial training where Q&amp;A pairs and document chunks are converted to vectors and stored in an index.</li> <li> <strong>Phase 2: User Query Processing.</strong> When a query is received, the system first attempts <strong>Smart FAQ Matching</strong> using the configured similarity threshold (default: 0.90). If the confidence score is high, it returns a precise answer.</li> <li> <strong>Phase 3: General Knowledge Retrieval (RAG Fallback).</strong> If the FAQ match confidence is low, the system activates RAG Fallback, searching the full document corpus, performing Top-K retrieval, and generating synthesized answers with source attribution.</li> </ol> <p>This sophisticated fallback mechanism ensures that every answer is <strong>citation-based</strong>, providing sources and confidence scores. Depending on the use cases you can switch ON or OFF citations as the quality of response hugely depends on the amount of Questions &amp; Answers pairs you used during training. Low amount of Q&amp;A would make this bot find irrelevant citations more frequently. Thus, if you simply don't have enough Q&amp;A - bot still can be useful by returning valid URL links, but not citations. With good amount of Q&amp;A you can notice the quality of answers higher and higher.</p> <h2> The nuances of Similarity Search </h2> <p>This hands-on exploration immediately exposes fascinating, practical insights that often remain hidden in theoretical papers.</p> <p>For instance, comparing approaches side-by-side reveals that the bot can operate both with an AI model (using the transformer-based ONNX embedding) and even without it, leveraging pure hash-based embeddings. While the hash-based approach is simple, the efficacy of embeddings, even theoretically, is limited, as discussed in the paper "<a href="proxy.php?url=https://arxiv.org/abs/2508.21038" rel="noopener noreferrer">On the Theoretical Limitations of Embedding-Based Retrieval</a>".</p> <p>Furthermore, working directly with cosine similarity illuminates concepts like <strong>"Cosine Similarity Abuse"</strong>—a fun, practical demonstration of how one can deliberately trick non-intelligent AI systems. This is only scratch of a surface in the bigger "Prompt Injection" problem (<a href="proxy.php?url=https://www.kaggle.com/competitions/openai-gpt-oss-20b-red-teaming/writeups/visual-prompt-injections-medical-malpractice-and-s#3275057" rel="noopener noreferrer">example good reading</a>) that truly puts a serious threat for the users of AI and software engineers who builts AI for production use.</p> <h2> Your next AI project starts now </h2> <p>Building a robust, functional bot that <a href="proxy.php?url=https://tactica.xyz/#/chromium-similarity-search" rel="noopener noreferrer">handles 9,000 documents across a complex project like Chromium</a> requires technical diligence, but it does <strong>not</strong> require massive infrastructure. This project proves that the fundamental essentials of LLM and AI—tokenization, vectorization, and similarity comparison—are perfectly accessible to anyone willing to dive into the code.</p> <p>The Wiki Navigator serves as a powerful demonstration of what is possible with similarity search on your own internal or corporate data.</p> <p>I encourage you to explore the open-source code and see how quickly you can achieve tangible results:</p> <ul> <li> Source Code: <code>https://github.com/tacticaxyz/tactica.faq.similaritysearch</code> </li> <li> Chromium Demo: <code>https://tactica.xyz/#/chromium-similarity-search</code> </li> <li> Rust Demo: <code>https://tactica.xyz/#/rust-similarity-search</code> </li> </ul> <p>This is just the beginning. Future explorations can dive deeper into topics like advanced vector search techniques, leveraging languages like Rust in AI tooling, and optimizing AI for browser-based applications. Start building today!</p> ai similaritysearch chatbot vectordatabase Algorithmic Alchemy: Exploiting Graph Theory in the Foreign Exchange Anton Yarkov Thu, 05 Oct 2023 09:13:59 +0000 https://dev.to/optiklab/algorithmic-alchemy-exploiting-graph-theory-in-the-foreign-exchange-399k https://dev.to/optiklab/algorithmic-alchemy-exploiting-graph-theory-in-the-foreign-exchange-399k <p>If you're familiar with the FinTech startup industry, you may have heard of Revolut, a well-known FinTech giant based in London, UK. Founded in 2015, Revolut has garnered substantial investments and become one of the fastest-growing startups in the UK, providing banking services to many European citizens.</p> <p>While banking operations are often shrouded in mystery when it comes to how they generate revenue, some key figures about Revolut for the years 2020 and 2021 have shed some light on their income sources:</p> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F943b2le304ve7auplzrd.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F943b2le304ve7auplzrd.jpg" alt="Revolut revenue drama" width="790" height="780"></a></p> <p>As illustrated, a significant portion of this neobank's revenue comes from Foreign Exchange (FX), wealth management (including cryptocurrencies), and card services. Notably, in 2021, FX became the most profitable sector.</p> <p>A friend of mine, who is also a software engineer, once shared an intriguing story about his technical interview at Revolut's Software Engineering department a few years back. He was tasked with developing an algorithm to identify the most profitable way to convert two currencies using one or multiple intermediate currencies. In other words, they were looking for a strategy for Currency Arbitrage.</p> <blockquote> <p><em>Currency Arbitrage</em> is a trading strategy wherein a currency trader leverages different spreads offered by brokers for a particular currency pair through multiple trades.</p> </blockquote> <p>It was explicitly mentioned in the task that the algorithm's foundation must be rooted in graph theory.</p> <h2> FX Basics </h2> <p>FX, or Foreign Exchange, plays a pivotal role in global trade, underpinning the functioning of our interconnected world. It's evident that FX also plays a substantial role in making banks some of the wealthiest organizations.</p> <p>The profit generated from foreign exchange is primarily the difference or spread between the buying (BID) and selling (ASK) prices. While this difference might appear minuscule per transaction, it can accumulate into millions of dollars in profits given the volume of daily operations. This allows some companies to thrive solely on these highly automated financial operations.</p> <p>In the realm of FX (Foreign Exchange), we always work with pairs of currencies, such as EUR/USD. In most cases, these exchanges are bidirectional (i.e., EUR/USD and USD/EUR), and the exchange rate value differs in each direction.</p> <p>An <em>Arbitrage Pair</em> represents a numerical ratio between the values of two currencies (EUR and US Dollar, for example), determining the exchange rate between them.</p> <p>Potentially, we can use multiple intermediate currencies for profitable trading, known as a <em>sure bet</em>.</p> <blockquote> <p><em>Arbitrage sure bet</em> is a set of pairs to be used in a circular manner. <a href="proxy.php?url=https://en.wikipedia.org/wiki/Arbitrage_betting" rel="noopener noreferrer">Read more</a></p> </blockquote> <p>Many providers employ mathematical modeling and analysis to secure their own profits and prevent others from profiting off them. Hence, the term potentially is emphasized here.</p> <blockquote> <p><em>Sure bet length</em> refers to the number of pairs that constitute a set of potential arbitrage opportunities.</p> </blockquote> <p>In the real world, exchange rates can vary among different banks or exchange platforms. It's not uncommon for tourists to traverse a city to find the best possible rate. With computer software, this process can be accomplished within milliseconds when you have access to a list of providers.</p> <p>In practical profitable trades, multiple steps might involve conversions through various currencies across different exchange platforms. In other words, the Arbitrage Circle can be quite extensive.</p> <blockquote> <p><em>Arbitrage Circle</em> entails acquiring a currency, transferring it to another platform, conducting an exchange for other currencies, and ultimately returning to the original currency.</p> </blockquote> <p>The exchange rate between two currencies via one or more intermediate currencies is calculated as <em>the product of exchange rates of these intermediate transactions</em>.</p> <h2> An example </h2> <p>For example, let's imagine we want to buy Swiss Franks for US Dollar, then exchange Franks to Japanese Yens, and then sell Yens for US Dollar again. In Autumn, 2023, we have following exchange rates:</p> <ol> <li>We can buy 0.91 CHF (Swiss Frank) for 1 USD.</li> <li>We can buy 163.16 Japanese Yens for 1 CHF.</li> <li>We can buy 0.0067 USD for 1 Japanese Yen. </li> </ol> <p>Let's present it with a table:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>1 USD | 1 CHF | 1 YEN 0.91 CHF | 163.16 YEN | 0.0067 USD <span class="nt">----------------</span>|-------------------|-------------- 1.098901099 | 0.006128953 | 149.2537313 </code></pre> </div> <p>Now, we need to find a product of those values. A sequence of transactions <em>becomes profitable when this product yields a value less than one</em>:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>1.098901099 <span class="k">*</span> 0.006128953 <span class="k">*</span> 149.2537313 <span class="o">=</span> 1.005240803 </code></pre> </div> <p>As we can see the result is a larger than one, so it looks like we lost 0.05% of our money. But how many exactly? We can sort it out like this:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>0.91 CHF <span class="k">*</span> 163.16 <span class="o">(</span>YEN per 1 CHF<span class="o">)</span> <span class="k">*</span> 0.0067 <span class="o">(</span>USD per 1 YEN<span class="o">)</span> <span class="o">=</span> 0.99478652 US Dollars </code></pre> </div> <p>So, after selling 1 US Dollar in the beginning we have got 0.994 - less than 1 US Dollar in the end.</p> <blockquote> <p>In simpler terms, Arbitrage Cycle is profitable when one unit of currency can be obtained for less than one unit of the same currency. </p> </blockquote> <p>Let's imagine we have found an opportunity to take 0.92 CHF per 1 US Dollar in the initial transaction, instead of 0.91 CHF:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>1 USD | 1 CHF | 1 YEN 0.92 CHF | 163.16 YEN | 0.0067 USD <span class="nt">----------------</span>|-------------------|-------------- 1.086956522 | 0.006128953 | 149.2537313 </code></pre> </div> <p>A product will be less than 1:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>1.086956522 <span class="k">*</span> 0.006128953 <span class="k">*</span> 149.2537313 <span class="o">=</span> 0.994314272 </code></pre> </div> <p>Which means, in the real currencies it will give us more than 1 US Dollar:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>0.92 CHF <span class="k">*</span> 163.16 <span class="o">(</span>YEN per 1 CHF<span class="o">)</span> <span class="k">*</span> 0.0067 <span class="o">(</span>USD per 1 YEN<span class="o">)</span> <span class="o">=</span> 1.00571824 US Dollars </code></pre> </div> <p>Wuolah, <em>we got some PROFIT!</em> Now, let's see how to automate this using graphs analysis.</p> <p>So, the formula to check for profits or losses in an Arbitrage Circle of 3 Arbitrage Pairs would look like this:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>USD/CHF <span class="k">*</span> CHF/YEN <span class="k">*</span> YEN/USD &lt; 1.0 </code></pre> </div> <h2> Graph representation </h2> <p>To automate those processes we can use graphs. The tables mentioned earlier can be naturally transformed into a matrix representation of a graph, where nodes represent currencies and edges represent bidirectional exchanges. </p> <p>Hence, it is straightforward to represent two pairs exchange in matrix like this:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>EUR USD 1 1 EUR 1 1 USD </code></pre> </div> <p>Depending on the number of pairs involved, our matrix can expand:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>EUR USD YEN CHF 1 1 1 1 EUR 1 1 1 1 USD 1 1 1 1 YEN 1 1 1 1 CHF </code></pre> </div> <p>Consequently, our table can become considerably larger, even for just two currencies, if we take into account more exchange platforms and resources. </p> <p>To address real currency arbitrage problems, a complete graph that encompasses all relationships for currency quotes is often utilized. A three-currency exchange table might appear as follows:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code> USD CHF YEN <span class="o">{</span> 1.0, 1.10, 0.0067 <span class="o">}</span> USD <span class="o">{</span> 0.91, 1.0, 0.0061 <span class="o">}</span> CHF <span class="o">{</span> 148.84, 163.16, 1.0 <span class="o">}</span> YEN </code></pre> </div> <p>We can employ a simple <a href="proxy.php?url=https://github.com/optiklab/negative-cycles-in-a-graph/blob/main/pathFindingBase.h" rel="noopener noreferrer">graph data structure</a> to represent our currency pairs in memory:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight cpp"><code><span class="k">class</span> <span class="nc">GraphNode</span> <span class="p">{</span> <span class="nl">public:</span> <span class="n">string</span> <span class="n">Name</span><span class="p">;</span> <span class="p">};</span> <span class="k">class</span> <span class="nc">Graph</span> <span class="p">{</span> <span class="nl">public:</span> <span class="n">vector</span><span class="o">&lt;</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">double</span><span class="o">&gt;&gt;</span> <span class="n">Matrix</span><span class="p">;</span> <span class="n">vector</span><span class="o">&lt;</span><span class="n">GraphNode</span><span class="o">&gt;</span> <span class="n">Nodes</span><span class="p">;</span> <span class="p">};</span> </code></pre> </div> <p>Now, we only need to find out how to traverse this graph and find the most profitable circle. But there is still one problem...</p> <h2> Math saves us, again </h2> <p>Classical graph algorithms are not well-suited for working with the <em>product of edge lengths</em> because they are designed to find paths defined as <em>the sum of these lengths</em> (see implementations of any well-known classic path-finding algorithms <a href="proxy.php?url=https://antonyarkov.substack.com/p/universal-implementation-of-bfs-dfs" rel="noopener noreferrer">BFS, DFS, Dijkstra or even A-Star</a>).</p> <p>However, to circumvent this limitation, there is a mathematical way to transition from a product to a sum: logarithms. If a product appears under a logarithm, it can be converted into a sum of logarithms.</p> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc22a4hsi09f0j69evwcn.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc22a4hsi09f0j69evwcn.jpg" alt=" " width="523" height="63"></a></p> <p>On the right side of this equation, the desired number is less than one, indicating that the logarithm of this number must be less than zero:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>LogE<span class="o">(</span>USD/CHF<span class="o">)</span> <span class="k">*</span> LogE<span class="o">(</span>CHF/YEN<span class="o">)</span> <span class="k">*</span> LogE<span class="o">(</span>YEN/USD<span class="o">)</span> &lt; 0.0 </code></pre> </div> <p>This simple mathematical trick allows us to shift from searching for a cycle with an edge length <em>product</em> less than one to <em>searching for a cycle where the sum of the edge lengths is less than zero</em>.</p> <p>Our matrix values converted to a LogE(x) and rounded with 2 digits after the point, now look like this:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code> USD CHF YEN <span class="o">{</span> 0.0, 0.1, <span class="nt">-5</span>.01 <span class="o">}</span> USD <span class="o">{</span> <span class="nt">-0</span>.09, 0.0, <span class="nt">-5</span>.1 <span class="o">}</span> CHF <span class="o">{</span> 5.0, 5.09, 0.0 <span class="o">}</span> YEN </code></pre> </div> <p>Now this problem becomes more solvable using classical graph algorithms. What we need is to traverse the graph looking for most profitable path of exchange.</p> <h2> Graph algorithms </h2> <p>Every algorithm has its limitations. I mentioned some of them in <a href="proxy.php?url=https://antonyarkov.substack.com/p/universal-implementation-of-bfs-dfs" rel="noopener noreferrer">my previous article</a>.</p> <p>We cannot apply classical BFS, DFS or even Dijkstra here because our graph may contain negative weights, which may lead to <em>Negative Cycles</em> while it traverses the graph. Negative cycles pose a challenge to the algorithm since it continually finds better solutions on each iteration.</p> <p>To address this issue, the Bellman-Ford algorithm simply limits the number of iterations. It traverses each edge of the graph in a cycle and applies relaxation for all edges no more than V-1 times (where V is a number of nodes).</p> <p>As such, the Bellman-Ford algorithm lies at the heart of this Arbitrage system, as it enables the discovery of paths between two nodes in the graph that meet two essential criteria: they contain negative weights and are not part of negative cycles.</p> <p>While this algorithm is theoretically straightforward (and you can find billion videos about it), practical implementation for our needs requires some effort. Let's dig into it.</p> <h2> Bellman-Ford algorithm implementation </h2> <blockquote> <p>As the aim of this article is computer science, I will use imaginary exchange rates that has nothing to do with the real ones.</p> </blockquote> <p>For a smoother introduction to the algorithm, let's use a graph that doesn't contain negative cycles at all:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight cpp"><code><span class="n">graph</span><span class="p">.</span><span class="n">Nodes</span><span class="p">.</span><span class="n">push_back</span><span class="p">({</span> <span class="s">"USD"</span> <span class="p">});</span> <span class="n">graph</span><span class="p">.</span><span class="n">Nodes</span><span class="p">.</span><span class="n">push_back</span><span class="p">({</span> <span class="s">"CHF"</span> <span class="p">});</span> <span class="n">graph</span><span class="p">.</span><span class="n">Nodes</span><span class="p">.</span><span class="n">push_back</span><span class="p">({</span> <span class="s">"YEN"</span> <span class="p">});</span> <span class="n">graph</span><span class="p">.</span><span class="n">Nodes</span><span class="p">.</span><span class="n">push_back</span><span class="p">({</span> <span class="s">"GBP"</span> <span class="p">});</span> <span class="n">graph</span><span class="p">.</span><span class="n">Nodes</span><span class="p">.</span><span class="n">push_back</span><span class="p">({</span> <span class="s">"CNY"</span> <span class="p">});</span> <span class="n">graph</span><span class="p">.</span><span class="n">Nodes</span><span class="p">.</span><span class="n">push_back</span><span class="p">({</span> <span class="s">"EUR"</span> <span class="p">});</span> <span class="c1">// Define exchange rates for pairs of currencies below</span> <span class="c1">// USD CHF YEN GBP CNY EUR</span> <span class="n">graph</span><span class="p">.</span><span class="n">Matrix</span> <span class="o">=</span> <span class="p">{</span> <span class="p">{</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">0.41</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="mf">0.29</span> <span class="p">},</span> <span class="c1">// USD</span> <span class="p">{</span> <span class="n">INF</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">0.51</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="mf">0.32</span><span class="p">,</span> <span class="n">INF</span> <span class="p">},</span> <span class="c1">// CHF</span> <span class="p">{</span> <span class="n">INF</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">0.50</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="n">INF</span> <span class="p">},</span> <span class="c1">// YEN</span> <span class="p">{</span> <span class="mf">0.45</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="o">-</span><span class="mf">0.38</span> <span class="p">},</span> <span class="c1">// GBP</span> <span class="p">{</span> <span class="n">INF</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="mf">0.32</span><span class="p">,</span> <span class="mf">0.36</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="n">INF</span> <span class="p">},</span> <span class="c1">// CNY</span> <span class="p">{</span> <span class="n">INF</span><span class="p">,</span> <span class="o">-</span><span class="mf">0.29</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="mf">0.21</span><span class="p">,</span> <span class="mf">0.0</span> <span class="p">}</span> <span class="p">};</span> <span class="c1">// EUR</span> </code></pre> </div> <p>The code example below finds a path between two nodes using the Bellman-Ford algorithm when the graph lacks negative cycles:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight cpp"><code><span class="n">vector</span><span class="o">&lt;</span><span class="kt">double</span><span class="o">&gt;</span> <span class="n">_shortestPath</span><span class="p">;</span> <span class="n">vector</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span> <span class="n">_previousVertex</span><span class="p">;</span> <span class="kt">void</span> <span class="nf">FindPath</span><span class="p">(</span><span class="n">Graph</span><span class="o">&amp;</span> <span class="n">graph</span><span class="p">,</span> <span class="kt">int</span> <span class="n">start</span><span class="p">)</span> <span class="p">{</span> <span class="kt">int</span> <span class="n">verticesNumber</span> <span class="o">=</span> <span class="n">graph</span><span class="p">.</span><span class="n">Nodes</span><span class="p">.</span><span class="n">size</span><span class="p">();</span> <span class="n">_shortestPath</span><span class="p">.</span><span class="n">resize</span><span class="p">(</span><span class="n">verticesNumber</span><span class="p">,</span> <span class="n">INF</span><span class="p">);</span> <span class="n">_previousVertex</span><span class="p">.</span><span class="n">resize</span><span class="p">(</span><span class="n">verticesNumber</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">);</span> <span class="n">_shortestPath</span><span class="p">[</span><span class="n">start</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="c1">// For each vertex, apply relaxation for all the edges V - 1 times.</span> <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">k</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">k</span> <span class="o">&lt;</span> <span class="n">verticesNumber</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span> <span class="n">k</span><span class="o">++</span><span class="p">)</span> <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">from</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">from</span> <span class="o">&lt;</span> <span class="n">verticesNumber</span><span class="p">;</span> <span class="n">from</span><span class="o">++</span><span class="p">)</span> <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">to</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">to</span> <span class="o">&lt;</span> <span class="n">verticesNumber</span><span class="p">;</span> <span class="n">to</span><span class="o">++</span><span class="p">)</span> <span class="k">if</span> <span class="p">(</span><span class="n">_shortestPath</span><span class="p">[</span><span class="n">to</span><span class="p">]</span> <span class="o">&gt;</span> <span class="n">_shortestPath</span><span class="p">[</span><span class="n">from</span><span class="p">]</span> <span class="o">+</span> <span class="n">graph</span><span class="p">.</span><span class="n">Matrix</span><span class="p">[</span><span class="n">from</span><span class="p">][</span><span class="n">to</span><span class="p">])</span> <span class="p">{</span> <span class="n">_shortestPath</span><span class="p">[</span><span class="n">to</span><span class="p">]</span> <span class="o">=</span> <span class="n">_shortestPath</span><span class="p">[</span><span class="n">from</span><span class="p">]</span> <span class="o">+</span> <span class="n">graph</span><span class="p">.</span><span class="n">Matrix</span><span class="p">[</span><span class="n">from</span><span class="p">][</span><span class="n">to</span><span class="p">];</span> <span class="n">_previousVertex</span><span class="p">[</span><span class="n">to</span><span class="p">]</span> <span class="o">=</span> <span class="n">from</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> </code></pre> </div> <p>Running this code for the Chinese Yuan fills the <em>_previousVertex</em> array and yields results like this:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>Path from 4 to 0 is : 4<span class="o">(</span>CNY<span class="o">)</span> 3<span class="o">(</span>GBP<span class="o">)</span> 0<span class="o">(</span>USD<span class="o">)</span> Path from 4 to 1 is : 4<span class="o">(</span>CNY<span class="o">)</span> 3<span class="o">(</span>GBP<span class="o">)</span> 5<span class="o">(</span>EUR<span class="o">)</span> 1<span class="o">(</span>CHF<span class="o">)</span> Path from 4 to 2 is : 4<span class="o">(</span>CNY<span class="o">)</span> 3<span class="o">(</span>GBP<span class="o">)</span> 5<span class="o">(</span>EUR<span class="o">)</span> 1<span class="o">(</span>CHF<span class="o">)</span> 2<span class="o">(</span>YEN<span class="o">)</span> Path from 4 to 3 is : 4<span class="o">(</span>CNY<span class="o">)</span> 3<span class="o">(</span>GBP<span class="o">)</span> Path from 4 to 4 is : 4<span class="o">(</span>CNY<span class="o">)</span> Path from 4 to 5 is : 4<span class="o">(</span>CNY<span class="o">)</span> 3<span class="o">(</span>GBP<span class="o">)</span> 5<span class="o">(</span>EUR<span class="o">)</span> </code></pre> </div> <p>As you can observe, it identifies optimal paths between CNY and various other currencies.</p> <blockquote> <p>And again, I will not focus on finding only one best one, as it is relatively simple task and not the goal of the article.</p> </blockquote> <p>The above implementation works well in ideal cases but falls short when dealing with graphs containing negative cycles.</p> <h2> Detecting negative cycles </h2> <p>What we truly need is the ability to identify whether a graph contains negative cycles and, if so, pinpoint the problematic segments. This knowledge allows us to mitigate these issues and ultimately discover profitable chains.</p> <p>The number of iterations doesn't always have to reach precisely V - 1. A solution is deemed found if, on the (N+1)-th cycle, no better path than the one on the N-th cycle is discovered. Thus, there's room for slight optimization.</p> <p>The code mentioned earlier can be enhanced to not only find paths but also detect whether the graph contains negative cycles, including the optimization I mentioned:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight cpp"><code><span class="n">vector</span><span class="o">&lt;</span><span class="kt">double</span><span class="o">&gt;</span> <span class="n">_shortestPath</span><span class="p">;</span> <span class="n">vector</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span> <span class="n">_previousVertex</span><span class="p">;</span> <span class="kt">bool</span> <span class="nf">ContainsNegativeCycles</span><span class="p">(</span><span class="n">Graph</span><span class="o">&amp;</span> <span class="n">graph</span><span class="p">,</span> <span class="kt">int</span> <span class="n">start</span><span class="p">)</span> <span class="p">{</span> <span class="kt">int</span> <span class="n">verticesNumber</span> <span class="o">=</span> <span class="n">graph</span><span class="p">.</span><span class="n">Nodes</span><span class="p">.</span><span class="n">size</span><span class="p">();</span> <span class="n">_shortestPath</span><span class="p">.</span><span class="n">resize</span><span class="p">(</span><span class="n">verticesNumber</span><span class="p">,</span> <span class="n">INF</span><span class="p">);</span> <span class="n">_previousVertex</span><span class="p">.</span><span class="n">resize</span><span class="p">(</span><span class="n">verticesNumber</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">);</span> <span class="n">_shortestPath</span><span class="p">[</span><span class="n">start</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="c1">// For each vertex, apply relaxation for all the edges V - 1 times.</span> <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">k</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">k</span> <span class="o">&lt;</span> <span class="n">verticesNumber</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span> <span class="n">k</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="n">updated</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span> <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">from</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">from</span> <span class="o">&lt;</span> <span class="n">verticesNumber</span><span class="p">;</span> <span class="n">from</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">to</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">to</span> <span class="o">&lt;</span> <span class="n">verticesNumber</span><span class="p">;</span> <span class="n">to</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="n">_shortestPath</span><span class="p">[</span><span class="n">to</span><span class="p">]</span> <span class="o">&gt;</span> <span class="n">_shortestPath</span><span class="p">[</span><span class="n">from</span><span class="p">]</span> <span class="o">+</span> <span class="n">graph</span><span class="p">.</span><span class="n">Matrix</span><span class="p">[</span><span class="n">from</span><span class="p">][</span><span class="n">to</span><span class="p">])</span> <span class="p">{</span> <span class="n">_shortestPath</span><span class="p">[</span><span class="n">to</span><span class="p">]</span> <span class="o">=</span> <span class="n">_shortestPath</span><span class="p">[</span><span class="n">from</span><span class="p">]</span> <span class="o">+</span> <span class="n">graph</span><span class="p">.</span><span class="n">Matrix</span><span class="p">[</span><span class="n">from</span><span class="p">][</span><span class="n">to</span><span class="p">];</span> <span class="n">_previousVertex</span><span class="p">[</span><span class="n">to</span><span class="p">]</span> <span class="o">=</span> <span class="n">from</span><span class="p">;</span> <span class="n">updated</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">updated</span><span class="p">)</span> <span class="c1">// No changes in paths, means we can finish earlier.</span> <span class="k">break</span><span class="p">;</span> <span class="p">}</span> <span class="c1">// Run one more relaxation step to detect which nodes are part of a negative cycle. </span> <span class="k">if</span> <span class="p">(</span><span class="n">updated</span><span class="p">)</span> <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">from</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">from</span> <span class="o">&lt;</span> <span class="n">verticesNumber</span><span class="p">;</span> <span class="n">from</span><span class="o">++</span><span class="p">)</span> <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">to</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">to</span> <span class="o">&lt;</span> <span class="n">verticesNumber</span><span class="p">;</span> <span class="n">to</span><span class="o">++</span><span class="p">)</span> <span class="k">if</span> <span class="p">(</span><span class="n">_shortestPath</span><span class="p">[</span><span class="n">to</span><span class="p">]</span> <span class="o">&gt;</span> <span class="n">_shortestPath</span><span class="p">[</span><span class="n">from</span><span class="p">]</span> <span class="o">+</span> <span class="n">graph</span><span class="p">.</span><span class="n">Matrix</span><span class="p">[</span><span class="n">from</span><span class="p">][</span><span class="n">to</span><span class="p">])</span> <span class="c1">// A negative cycle has occurred if we can find a better path beyond the optimal solution.</span> <span class="k">return</span> <span class="nb">true</span><span class="p">;</span> <span class="k">return</span> <span class="nb">false</span><span class="p">;</span> <span class="p">}</span> </code></pre> </div> <p>And now we play with a more intricate graph that includes negative cycles:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight cpp"><code><span class="n">graph</span><span class="p">.</span><span class="n">Nodes</span><span class="p">.</span><span class="n">push_back</span><span class="p">({</span> <span class="s">"USD"</span> <span class="p">});</span> <span class="c1">// 1 (Index = 0)</span> <span class="n">graph</span><span class="p">.</span><span class="n">Nodes</span><span class="p">.</span><span class="n">push_back</span><span class="p">({</span> <span class="s">"CHF"</span> <span class="p">});</span> <span class="n">graph</span><span class="p">.</span><span class="n">Nodes</span><span class="p">.</span><span class="n">push_back</span><span class="p">({</span> <span class="s">"YEN"</span> <span class="p">});</span> <span class="n">graph</span><span class="p">.</span><span class="n">Nodes</span><span class="p">.</span><span class="n">push_back</span><span class="p">({</span> <span class="s">"GBP"</span> <span class="p">});</span> <span class="n">graph</span><span class="p">.</span><span class="n">Nodes</span><span class="p">.</span><span class="n">push_back</span><span class="p">({</span> <span class="s">"CNY"</span> <span class="p">});</span> <span class="n">graph</span><span class="p">.</span><span class="n">Nodes</span><span class="p">.</span><span class="n">push_back</span><span class="p">({</span> <span class="s">"EUR"</span> <span class="p">});</span> <span class="n">graph</span><span class="p">.</span><span class="n">Nodes</span><span class="p">.</span><span class="n">push_back</span><span class="p">({</span> <span class="s">"XXX"</span> <span class="p">});</span> <span class="n">graph</span><span class="p">.</span><span class="n">Nodes</span><span class="p">.</span><span class="n">push_back</span><span class="p">({</span> <span class="s">"YYY"</span> <span class="p">});</span> <span class="c1">// 8 (Index = 7)</span> <span class="c1">// USD CHF YEN GBP CNY EUR XXX YYY</span> <span class="n">graph</span><span class="p">.</span><span class="n">Matrix</span> <span class="o">=</span> <span class="p">{</span> <span class="p">{</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="n">INF</span> <span class="p">},</span> <span class="c1">// USD</span> <span class="p">{</span> <span class="n">INF</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="mf">4.0</span><span class="p">,</span> <span class="mf">4.0</span><span class="p">,</span> <span class="n">INF</span> <span class="p">},</span> <span class="c1">// CHF</span> <span class="p">{</span> <span class="n">INF</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="n">INF</span> <span class="p">},</span> <span class="c1">// YEN</span> <span class="p">{</span> <span class="n">INF</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="n">INF</span> <span class="p">},</span> <span class="c1">// GBP</span> <span class="p">{</span> <span class="n">INF</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="o">-</span><span class="mf">3.0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="n">INF</span> <span class="p">},</span> <span class="c1">// CNY</span> <span class="p">{</span> <span class="n">INF</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">5.0</span><span class="p">,</span> <span class="mf">3.0</span> <span class="p">},</span> <span class="c1">// EUR</span> <span class="p">{</span> <span class="n">INF</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">4.0</span> <span class="p">},</span> <span class="c1">// XXX</span> <span class="p">{</span> <span class="n">INF</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="n">INF</span><span class="p">,</span> <span class="mf">0.0</span> <span class="p">}</span> <span class="p">};</span> <span class="c1">// YYY</span> </code></pre> </div> <p>Our program simply halts and displays a message:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>Graph contains negative cycle. </code></pre> </div> <p>We was able to indicate the problem, however, we need to navigate around problematic segments of the graph.</p> <h2> Avoiding negative cycles </h2> <p>To accomplish this, we'll mark vertices that are part of negative cycles with a constant value, NEG_INF:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight cpp"><code><span class="kt">bool</span> <span class="nf">FindPathsAndNegativeCycles</span><span class="p">(</span><span class="n">Graph</span><span class="o">&amp;</span> <span class="n">graph</span><span class="p">,</span> <span class="kt">int</span> <span class="n">start</span><span class="p">)</span> <span class="p">{</span> <span class="kt">int</span> <span class="n">verticesNumber</span> <span class="o">=</span> <span class="n">graph</span><span class="p">.</span><span class="n">Nodes</span><span class="p">.</span><span class="n">size</span><span class="p">();</span> <span class="n">_shortestPath</span><span class="p">.</span><span class="n">resize</span><span class="p">(</span><span class="n">verticesNumber</span><span class="p">,</span> <span class="n">INF</span><span class="p">);</span> <span class="n">_previousVertex</span><span class="p">.</span><span class="n">resize</span><span class="p">(</span><span class="n">verticesNumber</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">);</span> <span class="n">_shortestPath</span><span class="p">[</span><span class="n">start</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">k</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">k</span> <span class="o">&lt;</span> <span class="n">verticesNumber</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span> <span class="n">k</span><span class="o">++</span><span class="p">)</span> <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">from</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">from</span> <span class="o">&lt;</span> <span class="n">verticesNumber</span><span class="p">;</span> <span class="n">from</span><span class="o">++</span><span class="p">)</span> <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">to</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">to</span> <span class="o">&lt;</span> <span class="n">verticesNumber</span><span class="p">;</span> <span class="n">to</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="n">graph</span><span class="p">.</span><span class="n">Matrix</span><span class="p">[</span><span class="n">from</span><span class="p">][</span><span class="n">to</span><span class="p">]</span> <span class="o">==</span> <span class="n">INF</span><span class="p">)</span> <span class="c1">// Edge not exists</span> <span class="p">{</span> <span class="k">continue</span><span class="p">;</span> <span class="p">}</span> <span class="k">if</span> <span class="p">(</span><span class="n">_shortestPath</span><span class="p">[</span><span class="n">to</span><span class="p">]</span> <span class="o">&gt;</span> <span class="n">_shortestPath</span><span class="p">[</span><span class="n">from</span><span class="p">]</span> <span class="o">+</span> <span class="n">graph</span><span class="p">.</span><span class="n">Matrix</span><span class="p">[</span><span class="n">from</span><span class="p">][</span><span class="n">to</span><span class="p">])</span> <span class="p">{</span> <span class="n">_shortestPath</span><span class="p">[</span><span class="n">to</span><span class="p">]</span> <span class="o">=</span> <span class="n">_shortestPath</span><span class="p">[</span><span class="n">from</span><span class="p">]</span> <span class="o">+</span> <span class="n">graph</span><span class="p">.</span><span class="n">Matrix</span><span class="p">[</span><span class="n">from</span><span class="p">][</span><span class="n">to</span><span class="p">];</span> <span class="n">_previousVertex</span><span class="p">[</span><span class="n">to</span><span class="p">]</span> <span class="o">=</span> <span class="n">from</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="kt">bool</span> <span class="n">negativeCycles</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span> <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">k</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">k</span> <span class="o">&lt;</span> <span class="n">verticesNumber</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span> <span class="n">k</span><span class="o">++</span><span class="p">)</span> <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">from</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">from</span> <span class="o">&lt;</span> <span class="n">verticesNumber</span><span class="p">;</span> <span class="n">from</span><span class="o">++</span><span class="p">)</span> <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">to</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">to</span> <span class="o">&lt;</span> <span class="n">verticesNumber</span><span class="p">;</span> <span class="n">to</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="n">graph</span><span class="p">.</span><span class="n">Matrix</span><span class="p">[</span><span class="n">from</span><span class="p">][</span><span class="n">to</span><span class="p">]</span> <span class="o">==</span> <span class="n">INF</span><span class="p">)</span> <span class="c1">// Edge not exists</span> <span class="p">{</span> <span class="k">continue</span><span class="p">;</span> <span class="p">}</span> <span class="k">if</span> <span class="p">(</span><span class="n">_shortestPath</span><span class="p">[</span><span class="n">to</span><span class="p">]</span> <span class="o">&gt;</span> <span class="n">_shortestPath</span><span class="p">[</span><span class="n">from</span><span class="p">]</span> <span class="o">+</span> <span class="n">graph</span><span class="p">.</span><span class="n">Matrix</span><span class="p">[</span><span class="n">from</span><span class="p">][</span><span class="n">to</span><span class="p">])</span> <span class="p">{</span> <span class="n">_shortestPath</span><span class="p">[</span><span class="n">to</span><span class="p">]</span> <span class="o">=</span> <span class="n">NEG_INF</span><span class="p">;</span> <span class="n">_previousVertex</span><span class="p">[</span><span class="n">to</span><span class="p">]</span> <span class="o">=</span> <span class="o">-</span><span class="mi">2</span><span class="p">;</span> <span class="n">negativeCycles</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="k">return</span> <span class="n">negativeCycles</span><span class="p">;</span> <span class="p">}</span> </code></pre> </div> <p>Now, if we encounter NEG_INF in the _shortestPath array, we can display a message and skip that segment while still identifying optimal solutions for other currencies. For example, with Node 0 (representing USD):<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>Graph contains negative cycle. Path from 0 to 0 is : 0<span class="o">(</span>USD<span class="o">)</span> Path from 0 to 1 is : 0<span class="o">(</span>USD<span class="o">)</span> 1<span class="o">(</span>CHF<span class="o">)</span> Path from 0 to 2 is : Infinite number of shortest paths <span class="o">(</span>negative cycle<span class="o">)</span><span class="nb">.</span> Path from 0 to 3 is : Infinite number of shortest paths <span class="o">(</span>negative cycle<span class="o">)</span><span class="nb">.</span> Path from 0 to 4 is : Infinite number of shortest paths <span class="o">(</span>negative cycle<span class="o">)</span><span class="nb">.</span> Path from 0 to 5 is : 0<span class="o">(</span>USD<span class="o">)</span> 1<span class="o">(</span>CHF<span class="o">)</span> 5<span class="o">(</span>EUR<span class="o">)</span> Path from 0 to 6 is : 0<span class="o">(</span>USD<span class="o">)</span> 1<span class="o">(</span>CHF<span class="o">)</span> 6<span class="o">(</span>XXX<span class="o">)</span> Path from 0 to 7 is : 0<span class="o">(</span>USD<span class="o">)</span> 1<span class="o">(</span>CHF<span class="o">)</span> 5<span class="o">(</span>EUR<span class="o">)</span> 7<span class="o">(</span>YYY<span class="o">)</span> </code></pre> </div> <p>Whoala! Our code was able to identify a number of profitable chains despite the fact that our data was "a bit dirty".</p> <p>All the code examples mentioned above including test data is shared with you on <a href="proxy.php?url=https://github.com/optiklab/negative-cycles-in-a-graph" rel="noopener noreferrer">my GitHub</a>.</p> <h2> Even little fluctuations matter </h2> <p>Let's now consolidate what we've learned. Given a list of exchange rates for three currencies, we can easily detect negative cycles:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight cpp"><code><span class="n">graph</span><span class="p">.</span><span class="n">Nodes</span><span class="p">.</span><span class="n">push_back</span><span class="p">({</span> <span class="s">"USD"</span> <span class="p">});</span> <span class="c1">// 1 (Index = 0)</span> <span class="n">graph</span><span class="p">.</span><span class="n">Nodes</span><span class="p">.</span><span class="n">push_back</span><span class="p">({</span> <span class="s">"CHF"</span> <span class="p">});</span> <span class="n">graph</span><span class="p">.</span><span class="n">Nodes</span><span class="p">.</span><span class="n">push_back</span><span class="p">({</span> <span class="s">"YEN"</span> <span class="p">});</span> <span class="c1">// 3 (Index = 2)</span> <span class="c1">// LogE(x) table: USD CHF YEN</span> <span class="n">graph</span><span class="p">.</span><span class="n">Matrix</span> <span class="o">=</span> <span class="p">{</span> <span class="p">{</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">0.489</span><span class="p">,</span> <span class="o">-</span><span class="mf">0.402</span> <span class="p">},</span> <span class="c1">// USD</span> <span class="p">{</span> <span class="o">-</span><span class="mf">0.489</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="o">-</span><span class="mf">0.891</span> <span class="p">},</span> <span class="c1">// CHF</span> <span class="p">{</span> <span class="mf">0.402</span><span class="p">,</span> <span class="mf">0.89</span><span class="p">,</span> <span class="mf">0.0</span> <span class="p">}</span> <span class="p">};</span> <span class="c1">// YEN</span> <span class="n">from</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">FindPathsAndNegativeCycles</span><span class="p">(</span><span class="n">graph</span><span class="p">,</span> <span class="n">from</span><span class="p">);</span> </code></pre> </div> <p>Result:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>Graph contains negative cycle. Path from 0 to 0 is: Infinite number of shortest paths <span class="o">(</span>negative cycle<span class="o">)</span><span class="nb">.</span> Path from 0 to 1 is: Infinite number of shortest paths <span class="o">(</span>negative cycle<span class="o">)</span><span class="nb">.</span> Path from 0 to 2 is: Infinite number of shortest paths <span class="o">(</span>negative cycle<span class="o">)</span><span class="nb">.</span> </code></pre> </div> <p>However, even slight changes in the exchange rates (i.e., adjustments to the matrix) can lead to significant differences:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight cpp"><code><span class="c1">// LogE(x) table: USD CHF YEN</span> <span class="n">graph</span><span class="p">.</span><span class="n">Matrix</span> <span class="o">=</span> <span class="p">{</span> <span class="p">{</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">0.490</span><span class="p">,</span> <span class="o">-</span><span class="mf">0.402</span> <span class="p">},</span> <span class="c1">// USD</span> <span class="p">{</span> <span class="o">-</span><span class="mf">0.489</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="o">-</span><span class="mf">0.891</span> <span class="p">},</span> <span class="c1">// CHF</span> <span class="p">{</span> <span class="mf">0.403</span><span class="p">,</span> <span class="mf">0.891</span><span class="p">,</span> <span class="mf">0.0</span> <span class="p">}</span> <span class="p">};</span> <span class="c1">// YEN</span> <span class="n">from</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">FindPathsAndNegativeCycles</span><span class="p">(</span><span class="n">graph</span><span class="p">,</span> <span class="n">from</span><span class="p">);</span> </code></pre> </div> <p>Look, we have found one profitable chain:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>Path from 0 to 0 is : 0<span class="o">(</span>USD<span class="o">)</span> Path from 0 to 1 is : 0<span class="o">(</span>USD<span class="o">)</span> 2<span class="o">(</span>YEN<span class="o">)</span> 1<span class="o">(</span>CHF<span class="o">)</span> Path from 0 to 2 is : 0<span class="o">(</span>USD<span class="o">)</span> 2<span class="o">(</span>YEN<span class="o">)</span> </code></pre> </div> <p>We can apply these concepts to much larger graphs, involving multiple currencies:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight cpp"><code><span class="n">graph</span><span class="p">.</span><span class="n">Nodes</span><span class="p">.</span><span class="n">push_back</span><span class="p">({</span> <span class="s">"USD"</span> <span class="p">});</span> <span class="c1">// 1 (Index = 0)</span> <span class="n">graph</span><span class="p">.</span><span class="n">Nodes</span><span class="p">.</span><span class="n">push_back</span><span class="p">({</span> <span class="s">"CHF"</span> <span class="p">});</span> <span class="n">graph</span><span class="p">.</span><span class="n">Nodes</span><span class="p">.</span><span class="n">push_back</span><span class="p">({</span> <span class="s">"YEN"</span> <span class="p">});</span> <span class="n">graph</span><span class="p">.</span><span class="n">Nodes</span><span class="p">.</span><span class="n">push_back</span><span class="p">({</span> <span class="s">"GBP"</span> <span class="p">});</span> <span class="n">graph</span><span class="p">.</span><span class="n">Nodes</span><span class="p">.</span><span class="n">push_back</span><span class="p">({</span> <span class="s">"CNY"</span> <span class="p">});</span> <span class="c1">// 5 (Index = 4)</span> <span class="c1">// LogE(x) table: USD CHF YEN GBP CNY</span> <span class="n">graph</span><span class="p">.</span><span class="n">Matrix</span> <span class="o">=</span> <span class="p">{</span> <span class="p">{</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">0.490</span><span class="p">,</span> <span class="o">-</span><span class="mf">0.402</span><span class="p">,</span> <span class="mf">0.7</span><span class="p">,</span> <span class="mf">0.413</span> <span class="p">},</span> <span class="c1">// USD</span> <span class="p">{</span> <span class="o">-</span><span class="mf">0.489</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="o">-</span><span class="mf">0.891</span><span class="p">,</span> <span class="mf">0.89</span><span class="p">,</span> <span class="mf">0.360</span> <span class="p">},</span> <span class="c1">// CHF</span> <span class="p">{</span> <span class="mf">0.403</span><span class="p">,</span> <span class="mf">0.891</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">0.91</span><span class="p">,</span> <span class="mf">0.581</span> <span class="p">},</span> <span class="c1">// YEN</span> <span class="p">{</span> <span class="mf">0.340</span><span class="p">,</span> <span class="mf">0.405</span><span class="p">,</span> <span class="mf">0.607</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">0.72</span> <span class="p">},</span> <span class="c1">// GBP</span> <span class="p">{</span> <span class="mf">0.403</span><span class="p">,</span> <span class="mf">0.350</span><span class="p">,</span> <span class="mf">0.571</span><span class="p">,</span> <span class="mf">0.71</span><span class="p">,</span> <span class="mf">0.0</span> <span class="p">}</span> <span class="p">};</span> <span class="c1">// CNY</span> <span class="n">from</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">runDetectNegativeCycles</span><span class="p">(</span><span class="n">graph</span><span class="p">,</span> <span class="n">from</span><span class="p">);</span> </code></pre> </div> <p>As a result, we might find multiple candidates to get profit:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>Path from 0 to 0 is : 0<span class="o">(</span>USD<span class="o">)</span> Path from 0 to 1 is : 0<span class="o">(</span>USD<span class="o">)</span> 2<span class="o">(</span>YEN<span class="o">)</span> 1<span class="o">(</span>CHF<span class="o">)</span> Path from 0 to 2 is : 0<span class="o">(</span>USD<span class="o">)</span> 2<span class="o">(</span>YEN<span class="o">)</span> Path from 0 to 3 is : 0<span class="o">(</span>USD<span class="o">)</span> 2<span class="o">(</span>YEN<span class="o">)</span> 3<span class="o">(</span>GBP<span class="o">)</span> Path from 0 to 4 is : 0<span class="o">(</span>USD<span class="o">)</span> 2<span class="o">(</span>YEN<span class="o">)</span> 4<span class="o">(</span>CNY<span class="o">)</span> </code></pre> </div> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr6ytb67fjyevu9f7myxd.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr6ytb67fjyevu9f7myxd.jpg" alt=" " width="719" height="492"></a></p> <p>There are two important factors, though:</p> <ol> <li>Time is a critical factor in implementing arbitrage processes, primarily due to the rapid fluctuations in currency prices. As a result, the lifespan of a sure bet is exceedingly brief.</li> <li>Platforms levy commissions for each transaction.</li> </ol> <p>Therefore, <em>minimizing time costs</em> and <em>reducing commissions</em> are paramount, achieved by limiting the length of the sure bet.</p> <p>Empirical experience suggests that an acceptable sure bet length typically ranges from 2 to 3 pairs. Beyond this, the computational requirements escalate and trading platforms impose larger commissions.</p> <p>Thus, to make an income is not enough to have such technologies, but you also need access to the low-level commissions. Usually, only large financial institutions have such a resource in their hands.</p> <h2> Automation using smart contracts </h2> <p>I've delved into the logic of FX operations and how to derive profits from them, but I haven't touched upon the technologies used to execute these operations. While this topic slightly veers off-course, I couldn't omit mentioning smart contracts.</p> <p>Using smart contracts is one of the most innovative ways to conduct FX operations today. Smart contracts enable real-time FX operations without delays or human intervention (except for the creation of the smart contract).</p> <p>Solidity is the specialized programming language for creating smart contracts that automate financial operations involving cryptocurrencies. The world of smart contracts is dynamic and subject to rapid technological changes and evolving regulations. It's an area with considerable hype and significant risks related to wallets and legal compliance.</p> <p>While there are undoubtedly talented individuals and teams profiting from this field, there are also regulatory bodies striving to ensure market rules are upheld.</p> <h2> Why are we looking into this? </h2> <p>Despite the complexity, obscurity, and unpredictability of global economics, Foreign Exchange remains a hidden driving force in the financial world. It's a crucial element that enables thousands of companies and millions of individuals worldwide to collaborate, provide services, and mutually benefit one another in a peaceful manner, transcending borders.</p> <p>Of course, various factors, such as politics, regulations, and central banks, influence exchange rates and FX efficiency. These complexities make the financial landscape intricate. Yet, it's essential to believe that these complexities serve a greater purpose for the common good.</p> <p>Numerous scientific papers delve into the existence and determination of exchange rates in the global economy, to mention a few:</p> <ul> <li><a href="proxy.php?url=https://www.aeaweb.org/articles?id=10.1257/aer.104.7.1942" rel="noopener noreferrer">Importers, Exporters and Exchange Rate Disconnect</a></li> <li><a href="proxy.php?url=https://www.aeaweb.org/articles?id=10.1257/aer.100.1.304" rel="noopener noreferrer">Currency choice and exchange rate pass-through</a></li> <li><a href="proxy.php?url=https://repositoriodigital.bcentral.cl/xmlui/handle/20.500.12580/7504" rel="noopener noreferrer">Exchange rate puzzles and policies</a></li> </ul> <p>These papers shed light on some fundamental mechanisms of Foreign Exchanges, which is still hard to understand and fit into one model.</p> <p>Though, playing with code and trying to find a solution for a practical problem helped me to get a little more clue on it. I hope you enjoied this little exploration trip as much as I am.</p> <p>Stay tuned!</p> <h2> Links </h2> <ul> <li><a href="proxy.php?url=https://github.com/optiklab/negative-cycles-in-a-graph/" rel="noopener noreferrer">Source code with all these examples</a></li> <li>Sedgewick R. - Algorithms in C, Part 5: Graph Algorithms</li> <li><a href="proxy.php?url=https://www.youtube.com/watch?v=24HziTZ8_xo" rel="noopener noreferrer">Bellman Ford Algorithm Code Implementation</a></li> <li><a href="proxy.php?url=https://github.com/williamfiset/Algorithms/blob/master/src/main/java/com/williamfiset/algorithms/graphtheory/BellmanFordAdjacencyMatrix.java" rel="noopener noreferrer">William Fiset's GitHub examples - Bellman Ford On Adjacency Matrix</a></li> <li><a href="proxy.php?url=https://github.com/williamfiset/Algorithms/blob/master/src/main/java/com/williamfiset/algorithms/graphtheory/BellmanFordEdgeList.java" rel="noopener noreferrer">William Fiset's GitHub examples - Bellman Ford On Edge List</a></li> </ul> graphs algorithms bellman ford Crafting Mazes with Graph Theory Anton Yarkov Wed, 06 Sep 2023 12:03:59 +0000 https://dev.to/optiklab/crafting-mazes-2dia https://dev.to/optiklab/crafting-mazes-2dia <p>In our <a href="proxy.php?url=https://antonyarkov.substack.com/p/exploring-path-finding-algorithms" rel="noopener noreferrer">previous post</a>, we delved into problems of pathfinding in graphs, which are inherently connected to solving mazes.</p> <p>When I set out to create a maze map for the <a href="proxy.php?url=https://github.com/optiklab/path-algorithms-on-a-grid-map" rel="noopener noreferrer">Wall-E project</a>, I initially expected to find a quick and easy way to accomplish this task. However, I quickly found myself immersed in the vast and fascinating world of mazes and labyrinths.</p> <p>I was unaware of the breadth and depth of this topic before. I discovered that mazes can be classified in seven different ways, each with numerous variations and countless algorithms for generating them.</p> <blockquote> <p>Surprisingly, I couldn't find any algorithmic books that comprehensively covered this topic, and even the <a href="proxy.php?url=https://en.wikipedia.org/wiki/Maze_generation_algorithm" rel="noopener noreferrer">Wikipedia-page</a> didn't provide a systematic overview. Fortunately, I stumbled upon a fantastic resource that covers <a href="proxy.php?url=https://www.astrolog.org/labyrnth/algrithm.htm" rel="noopener noreferrer">various maze types and algorithms</a>, which I highly recommend exploring.</p> </blockquote> <p>I embarked on a journey to learn about the different classifications of mazes, including dimensional and hyperdimensional variations, perfect mazes versus unicursal labyrinths, planar and sparse mazes, and more.</p> <h2> How to create a maze </h2> <p>My primary goal was to generate a 2D map representing a maze.</p> <p>While it would have been enticing to implement various maze generation algorithms to compare them, I also wanted a more efficient approach. The quickest solution I found involved randomly selecting connected cells. That's precisely what I did with <a href="proxy.php?url=https://github.com/optiklab/mazerandom" rel="noopener noreferrer">mazerandom</a>. This one-file application creates a grid table of 20 x 20 cells and then randomly connects them using a Depth-First Search (DFS) traversal. In other words, we're simply carving passages in the grid.</p> <p>If you were to do this manually on paper, it would look something like this:</p> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F40x7weidjpuu2j7lmt8d.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F40x7weidjpuu2j7lmt8d.jpg" alt="1" width="800" height="265"></a></p> <p>To achieve this algorithmically, we apply Depth-First Search to the grid of cells. Let's take a look at how it's done in the <a href="proxy.php?url=https://github.com/optiklab/mazerandom/blob/bf61b13b50bfdde4356ef635f89ad5b0965529e9/Main.cpp#L100" rel="noopener noreferrer">Main.cpp</a>.</p> <p>As usual, we represent the grid of cells as an array of arrays, and we use a stack for DFS:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight cpp"><code><span class="n">vector</span><span class="o">&lt;</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;&gt;</span> <span class="n">maze_cells</span><span class="p">;</span> <span class="c1">// A grid 20x20</span> <span class="n">stack</span><span class="o">&lt;</span><span class="n">Coord</span><span class="o">&gt;</span> <span class="n">my_stack</span><span class="p">;</span> <span class="c1">// Stack to traverse the grid by DFS</span> <span class="n">my_stack</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">Coord</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">));</span> <span class="c1">// Starting from very first cell</span> </code></pre> </div> <p>We visit every cell in the grid and push its neighbors onto the stack for deep traversal:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight cpp"><code><span class="p">...</span> <span class="k">while</span> <span class="p">(</span><span class="n">visitedCells</span> <span class="o">&lt;</span> <span class="n">HORIZONTAL_CELLS</span> <span class="o">*</span> <span class="n">VERTICAL_CELLS</span><span class="p">)</span> <span class="p">{</span> <span class="n">vector</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span> <span class="n">neighbours</span><span class="p">;</span> <span class="c1">// Step 1: Create an array of neighbour cells that were not yet visited (from North, East, South and West).</span> <span class="c1">// North is not visited yet?</span> <span class="k">if</span> <span class="p">((</span><span class="n">maze_cells</span><span class="p">[</span><span class="n">offset_x</span><span class="p">(</span><span class="mi">0</span><span class="p">)][</span><span class="n">offset_y</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">)]</span> <span class="o">&amp;</span> <span class="n">CELL_VISITED</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="n">neighbours</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span> <span class="p">}</span> <span class="c1">// East is not visited yet?</span> <span class="k">if</span> <span class="p">((</span><span class="n">maze_cells</span><span class="p">[</span><span class="n">offset_x</span><span class="p">(</span><span class="mi">1</span><span class="p">)][</span><span class="n">offset_y</span><span class="p">(</span><span class="mi">0</span><span class="p">)]</span> <span class="o">&amp;</span> <span class="n">CELL_VISITED</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="n">neighbours</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span> <span class="p">}</span> <span class="p">...</span> <span class="c1">// Do the same for West and South...</span> </code></pre> </div> <p>The most complex logic involves marking the node as reachable (i.e., no wall in between) with CELL_PATH_S, CELL_PATH_N, CELL_PATH_W, or CELL_PATH_E:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight cpp"><code><span class="p">...</span> <span class="c1">// If we have at least one unvisited neighbour </span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">neighbours</span><span class="p">.</span><span class="n">empty</span><span class="p">())</span> <span class="p">{</span> <span class="c1">// Choose random neighbor to make it available</span> <span class="kt">int</span> <span class="n">next_cell_dir</span> <span class="o">=</span> <span class="n">neighbours</span><span class="p">[</span><span class="n">rand</span><span class="p">()</span> <span class="o">%</span> <span class="n">neighbours</span><span class="p">.</span><span class="n">size</span><span class="p">()];</span> <span class="c1">// Create a path between the neighbour and the current cell</span> <span class="k">switch</span> <span class="p">(</span><span class="n">next_cell_dir</span><span class="p">)</span> <span class="p">{</span> <span class="k">case</span> <span class="mi">0</span><span class="p">:</span> <span class="c1">// North</span> <span class="c1">// Mark it as visited. Mark connection between North and South in BOTH directions.</span> <span class="n">maze_cells</span><span class="p">[</span><span class="n">offset_x</span><span class="p">(</span><span class="mi">0</span><span class="p">)][</span><span class="n">offset_y</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">)]</span> <span class="o">|=</span> <span class="n">CELL_VISITED</span> <span class="o">|</span> <span class="n">CELL_PATH_S</span><span class="p">;</span> <span class="n">maze_cells</span><span class="p">[</span><span class="n">offset_x</span><span class="p">(</span><span class="mi">0</span><span class="p">)][</span><span class="n">offset_y</span><span class="p">(</span><span class="mi">0</span><span class="p">)]</span> <span class="o">|=</span> <span class="n">CELL_PATH_N</span><span class="p">;</span> <span class="c1">// </span> <span class="n">my_stack</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">Coord</span><span class="p">(</span><span class="n">offset_x</span><span class="p">(</span><span class="mi">0</span><span class="p">),</span> <span class="n">offset_y</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">)));</span> <span class="k">break</span><span class="p">;</span> <span class="k">case</span> <span class="mi">1</span><span class="p">:</span> <span class="c1">// East</span> <span class="c1">// Mark it as visited. Mark connection between East and West in BOTH directions.</span> <span class="n">maze_cells</span><span class="p">[</span><span class="n">offset_x</span><span class="p">(</span><span class="mi">1</span><span class="p">)][</span><span class="n">offset_y</span><span class="p">(</span><span class="mi">0</span><span class="p">)]</span> <span class="o">|=</span> <span class="n">CELL_VISITED</span> <span class="o">|</span> <span class="n">CELL_PATH_W</span><span class="p">;</span> <span class="n">maze_cells</span><span class="p">[</span><span class="n">offset_x</span><span class="p">(</span><span class="mi">0</span><span class="p">)][</span><span class="n">offset_y</span><span class="p">(</span><span class="mi">0</span><span class="p">)]</span> <span class="o">|=</span> <span class="n">CELL_PATH_E</span><span class="p">;</span> <span class="n">my_stack</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">Coord</span><span class="p">(</span><span class="n">offset_x</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span> <span class="n">offset_y</span><span class="p">(</span><span class="mi">0</span><span class="p">)));</span> <span class="k">break</span><span class="p">;</span> <span class="p">...</span> <span class="c1">// Do the same for West and South...</span> <span class="p">}</span> <span class="n">visitedCells</span><span class="o">++</span><span class="p">;</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="n">my_stack</span><span class="p">.</span><span class="n">pop</span><span class="p">();</span> <span class="p">}</span> <span class="p">...</span> </code></pre> </div> <p>Finally, it calls the <a href="proxy.php?url=https://github.com/optiklab/mazerandom/blob/bf61b13b50bfdde4356ef635f89ad5b0965529e9/Main.cpp#L27" rel="noopener noreferrer">drawMaze</a> method to draw the maze on the screen using the SFML library. It draws a wall between two cells if the current cell isn't marked with CELL_PATH_S, CELL_PATH_N, CELL_PATH_W, or CELL_PATH_E.</p> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpfqdlylevlgbdztq6u0h.gif" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpfqdlylevlgbdztq6u0h.gif" alt="2" width="560" height="590"></a></p> <p>However, this maze doesn't guarantee a solution. In many cases, it will generate a map with no clear path between two points. While this randomness might be interesting, I wanted something more structured.</p> <p>The only way to ensure a solution for the maze is to use a predetermined structure that connects every part of the maze in some way.</p> <h2> Creating a Maze Using Graph Theory </h2> <p>Well-known maze generation algorithms rely on graphs. Each cell is a node in the graph, and every node must have at least one connection to other nodes.</p> <p>As mentioned earlier, mazes come in many forms. Some, called "unicursal" mazes, act as labyrinths with only one entrance, which also serves as the exit. Others may have multiple solutions. However, the process of generation often starts with creating a "perfect" maze.</p> <p>A "perfect" maze, also known as a simply-connected maze, lacks loops, closed circuits, and inaccessible areas. From any point within it, there is precisely one path to any other point. The maze has a single, solvable solution.</p> <p>If we use a graph as the internal representation of our maze, constructing a <a href="proxy.php?url=https://en.wikipedia.org/wiki/Spanning_tree" rel="noopener noreferrer">Spanning Tree</a> ensures that there is a path from the start to the end.</p> <p>In computer science terms, such a maze can be described as a <a href="proxy.php?url=https://en.wikipedia.org/wiki/Spanning_tree" rel="noopener noreferrer">Spanning Tree</a> over the set of cells or vertices.</p> <p>Multiple Spanning Trees may exist, but the goal is to ensure at least one solution from the start to the end, as shown in the example below:</p> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fao0rd96r6fyevkhjrsqr.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fao0rd96r6fyevkhjrsqr.jpg" alt="3" width="616" height="613"></a></p> <p>The image above depicts only one solution, but there are actually multiple paths. No cell is isolated and impossible to reach. So, how do we achieve this?</p> <p>I discovered a well-designed <a href="proxy.php?url=https://github.com/razimantv/mazegenerator" rel="noopener noreferrer">mazegenerator</a> codebase by <a href="proxy.php?url=https://github.com/razimantv" rel="noopener noreferrer">@razimantv</a> that accomplishes this, generating mazes in SVG file format.</p> <p>Therefore, I forked the repository and based my solution on it. Kudos to <a href="proxy.php?url=https://github.com/razimantv" rel="noopener noreferrer">@razimantv</a> for the elegant OOP design, which allowed me to customize the results to create visually appealing images using the SFML library or generate a text file with the necessary map description for my <a href="proxy.php?url=https://github.com/optiklab/path-algorithms-on-a-grid-map" rel="noopener noreferrer">Wall-E project</a>.</p> <p>I refactored the code to remove unnecessary components and focus exclusively on rectangular mazes.</p> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgoiqkt9ojbwm0uqd913s.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgoiqkt9ojbwm0uqd913s.jpg" alt="4" width="527" height="315"></a></p> <p>However, I retained support for various algorithms to build a spanning tree.</p> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq8o801brnme155s7p8wt.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq8o801brnme155s7p8wt.jpg" alt="5" width="800" height="220"></a></p> <p>I also added comments throughout the codebase for easier comprehension, so I don't need to explain it in every detail here. The main pipeline can be found in <a href="proxy.php?url=https://github.com/optiklab/mazegenerator/blob/main/maze/mazebaze.cpp" rel="noopener noreferrer">\mazegenerator\maze\mazebaze.cpp</a>:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight cpp"><code><span class="cm">/** * \param algorithm Algorithm that is used to generate maze spanning tree. */</span> <span class="kt">void</span> <span class="n">MazeBase</span><span class="o">::</span><span class="n">GenerateMaze</span><span class="p">(</span><span class="n">SpanningtreeAlgorithmBase</span><span class="o">*</span> <span class="n">algorithm</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// Generates entire maze spanning tree</span> <span class="k">auto</span> <span class="n">spanningTreeEdges</span> <span class="o">=</span> <span class="n">algorithm</span><span class="o">-&gt;</span><span class="n">SpanningTree</span><span class="p">(</span><span class="n">_verticesNumber</span><span class="p">,</span> <span class="n">_edgesList</span><span class="p">);</span> <span class="c1">// Find a solution of a maze based on Graph DFS.</span> <span class="n">_Solve</span><span class="p">(</span><span class="n">spanningTreeEdges</span><span class="p">);</span> <span class="c1">// Build a maze by removing unnecessary edges.</span> <span class="n">_RemoveBorders</span><span class="p">(</span><span class="n">spanningTreeEdges</span><span class="p">);</span> <span class="p">}</span> </code></pre> </div> <p>I introduced visualization using the SFML graphics library, thanks to a straightforward <em>Draw</em> function.</p> <p>While DFS is the default algorithm for creating a Spanning Tree, there are multiple algorithms available as options.</p> <p>The result is a handy utility that generates rectangular "perfect" mazes and displays them on the screen:</p> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhxjync9xvqh34g102k4s.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhxjync9xvqh34g102k4s.jpg" alt="6" width="632" height="662"></a></p> <p>As you can see, it contains exactly one input and one output at the left top and right bottom corners. The code still generates SVG file, which is a nice addition (though, it is the core function of the original codebase).</p> <p>Now, I can proceed with my experiments in the <a href="proxy.php?url=https://github.com/optiklab/path-algorithms-on-a-grid-map" rel="noopener noreferrer">Wall-E project</a>, and I leave you here, hoping that you're inspired to explore this fascinating world of mazes and embark on your own journey.</p> <p>Stay tuned!</p> maze labirint trees graphs Universal implementation of BFS, DFS, Dijkstra and A-Star algorithms Anton Yarkov Sat, 05 Aug 2023 21:50:29 +0000 https://dev.to/optiklab/universal-implementation-of-bfs-dfs-dijkstra-and-a-star-algorithms-m4k https://dev.to/optiklab/universal-implementation-of-bfs-dfs-dijkstra-and-a-star-algorithms-m4k <p>It turns out that well-known algorithms like <a href="proxy.php?url=https://en.wikipedia.org/wiki/Breadth-first_search" rel="noopener noreferrer">BFS</a>, <a href="proxy.php?url=https://en.wikipedia.org/wiki/Depth-first_search" rel="noopener noreferrer">DFS</a>, <a href="proxy.php?url=https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm" rel="noopener noreferrer">Dijkstra</a>, and <a href="proxy.php?url=https://en.wikipedia.org/wiki/A*_search_algorithm" rel="noopener noreferrer">A-Star</a> are essentially variations of the same algorithm. </p> <p>In other words, it is possible to implement a universal data structure that can switch between these algorithms without requiring changes to its core components. While there are some limitations to consider, exploring this approach is interesting.</p> <p>You can find all the working code for these algorithms on my GitHub repository <a href="proxy.php?url=https://github.com/optiklab/path-algorithms-in-a-graph" rel="noopener noreferrer">here</a>. I recommend experimenting with the code while reading this article since practical experience enhances learning more than just theoretical understanding.</p> <h2> Graph representation </h2> <p>Let's consider a graph with 25 nodes arranged in a 5x5 grid, where we aim to find a path from Node 0 in the top left corner to Node 24 in the bottom right corner:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight plaintext"><code>( 0 ) - ( 1 ) - ( 2 ) - ( 3 ) - ( 4 ) | | | | | ( 5 ) - ( 6 ) - ( 7 ) - ( 8 ) - ( 9 ) | | | | | ( 10 ) - ( 11 ) - ( 12 ) - ( 13 ) - ( 14 ) | | | | | ( 15 ) - ( 16 ) - ( 17 ) - ( 18 ) - ( 19 ) | | | | | ( 20 ) - ( 21 ) - ( 22 ) - ( 23 ) - ( 24 ) </code></pre> </div> <p>Each of the mentioned algorithms is capable of achieving this, but they have their own limitations:</p> <ul> <li>Both BFS and DFS algorithms operate on unweighted graphs, disregarding edge weights. Although they can find any path, they do not guarantee the optimal path.</li> <li>Both Dijkstra's and A-Star algorithms work on weighted graphs but should not be used with graphs containing negative weights. A-Star is usually faster due to its optimization, which incorporates Euclidean coordinates during path finding.</li> </ul> <blockquote> <p>In this article, I do not cover the basic concepts, hoping that you are already familiar with them. If the terminology mentioned above seems daunting to you, you should probably learn the basics as well. However, playing around with these code examples can still be exciting.</p> </blockquote> <p>To account for these limitations, let's assign imaginary coordinates to each node (X, Y):<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight plaintext"><code>(0, 0) - (0, 1) - (0, 2) - (0, 3) - (0, 4) | | | | | (1, 0) - (1, 1) - (1, 2) - (1, 3) - (1, 4) | | | | | (2, 0) - (2, 1) - (2, 2) - (2, 3) - (2, 4) | | | | | (3, 0) - (3, 1) - (3, 2) - (3, 3) - (3, 4) | | | | | (4, 0) - (4, 1) - (4, 2) - (4, 3) - (4, 4) </code></pre> </div> <p>Finally, lets assign some weight for every edge in the graph:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight plaintext"><code>(0, 0) -1- (0, 1) -1- (0, 2) -1- (0, 3) -2- (0, 4) | | | | | 2 1 1 2 2 | | | | | (1, 0) -2- (1, 1) -1- (1, 2) -2- (1, 3) -1- (1, 4) | | | | | 2 1 1 1 1 | | | | | (2, 0) -1- (2, 1) -1- (2, 2) -1- (2, 3) -2- (2, 4) | | | | | 2 1 1 1 2 | | | | | (3, 0) -2- (3, 1) -2- (3, 2) -1- (3, 3) -2- (3, 4) | | | | | 2 1 1 2 2 | | | | | (4, 0) -2- (4, 1) -1- (4, 2) -2- (4, 3) -2- (4, 4) </code></pre> </div> <p>In C++, this structure may be represented as follows:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight cpp"><code><span class="k">class</span> <span class="nc">GraphNode</span> <span class="p">{</span> <span class="nl">public:</span> <span class="kt">int</span> <span class="n">X</span><span class="p">;</span> <span class="kt">int</span> <span class="n">Y</span><span class="p">;</span> <span class="p">};</span> <span class="k">class</span> <span class="nc">Graph</span> <span class="p">{</span> <span class="nl">public:</span> <span class="n">vector</span><span class="o">&lt;</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">pair</span><span class="o">&lt;</span><span class="kt">int</span><span class="p">,</span> <span class="kt">int</span><span class="o">&gt;&gt;&gt;</span> <span class="n">Edges</span><span class="p">;</span> <span class="n">vector</span><span class="o">&lt;</span><span class="n">GraphNode</span><span class="o">&gt;</span> <span class="n">Nodes</span><span class="p">;</span> <span class="p">};</span> </code></pre> </div> <p>The edges list in the Graph is represented by an array of arrays, where the index corresponds to the number of the exit node for each edge in the graph. Then, every element contains a pair of values:</p> <ol> <li>The number of the entering node for each edge in the graph.</li> <li>The weight of the edge.</li> </ol> <p>Using this simple construct, we can traverse every node in the graph and obtain all the necessary information about its connections:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight cpp"><code><span class="kt">int</span> <span class="n">toNode</span> <span class="o">=</span> <span class="n">graph</span><span class="p">.</span><span class="n">Edges</span><span class="p">[</span><span class="n">fromNode</span><span class="p">][</span><span class="n">neighbourIndex</span><span class="p">].</span><span class="n">first</span><span class="p">;</span> <span class="kt">int</span> <span class="n">weight</span> <span class="o">=</span> <span class="n">graph</span><span class="p">.</span><span class="n">Edges</span><span class="p">[</span><span class="n">fromNode</span><span class="p">][</span><span class="n">neighbourIndex</span><span class="p">].</span><span class="n">second</span><span class="p">;</span> </code></pre> </div> <p>Now, let's create some custom connections within the graph to observe the effect on how our universal algorithm works. Since this code is not the main focus here, I will provide links to the relevant methods:</p> <ul> <li><a href="proxy.php?url=https://github.com/optiklab/path-algorithms-in-a-graph/blob/b8b0346ce7c6b5d3f54b91966337d622d6b372ab/main.cpp#L247" rel="noopener noreferrer">Generating a list of nodes</a></li> <li><a href="proxy.php?url=https://github.com/optiklab/path-algorithms-in-a-graph/blob/b8b0346ce7c6b5d3f54b91966337d622d6b372ab/main.cpp#L346" rel="noopener noreferrer">Creating custom connections</a></li> </ul> <p>Alternatively, it is also possible to <a href="proxy.php?url=https://github.com/optiklab/path-algorithms-in-a-graph/blob/b8b0346ce7c6b5d3f54b91966337d622d6b372ab/main.cpp#L176" rel="noopener noreferrer">lazily generate all the connections and weights</a> in this graph with even less code. However, this approach might not provide a comprehensive understanding of the actual differences in how the algorithms traverse the graph.</p> <h2> Universal algorithm </h2> <p>At the core of the universal path-finding algorithm lies the universal data structure, which we will refer to as the "Queue" for the purposes of this project. However, it is not a classic FIFO (First-In-First-Out) data structure. Instead, it is a general structure that allows us to implement node queuing during traversal while being able to change the queuing mechanism based on the algorithm being used. The interface for this "Queue" is simple:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight cpp"><code><span class="k">class</span> <span class="nc">pathFindingBase</span> <span class="p">{</span> <span class="nl">public:</span> <span class="k">virtual</span> <span class="kt">void</span> <span class="n">insert</span><span class="p">(</span><span class="kt">int</span> <span class="n">node</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">virtual</span> <span class="kt">int</span> <span class="n">getFirst</span><span class="p">()</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">virtual</span> <span class="kt">bool</span> <span class="n">isEmpty</span><span class="p">()</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="p">};</span> </code></pre> </div> <p>Before we delve into the details of the Queue, let's examine the traversal algorithm itself.</p> <p>Essentially, it closely resembles a typical A-Star or Dijkstra algorithm. First, we need to initialize a set of collections that enable us to:</p> <ul> <li>Maintain a list of nodes that have not been processed yet (colored white), are currently being processed (colored gray), and have been processed/visited (colored black).</li> <li>Keep track of the current distance of the shortest path from the start node to each node in the collection.</li> <li>Store a list of pairs of previous-next nodes that allows us to reconstruct the final path afterward.</li> </ul> <p><a href="proxy.php?url=https://github.com/optiklab/path-algorithms-in-a-graph/blob/ecb090ce3f0fe8395e2c7360873bd9b0fd899d15/main.cpp#L18" rel="noopener noreferrer">main.cpp#L18</a><br> </p> <div class="highlight js-code-highlight"> <pre class="highlight cpp"><code><span class="k">const</span> <span class="kt">int</span> <span class="n">INF</span> <span class="o">=</span> <span class="mi">1000000</span><span class="p">;</span> <span class="k">const</span> <span class="kt">int</span> <span class="n">WHITE</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">const</span> <span class="kt">int</span> <span class="n">GREY</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="k">const</span> <span class="kt">int</span> <span class="n">BLACK</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span> <span class="c1">/// &lt;summary&gt;</span> <span class="c1">/// Universal algorithm to apply Path search using BFS, DFS, Dijkstra, A-Star.</span> <span class="c1">/// &lt;/summary&gt;</span> <span class="n">vector</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span> <span class="n">FindPath</span><span class="p">(</span><span class="n">Graph</span><span class="o">&amp;</span> <span class="n">graph</span><span class="p">,</span> <span class="kt">int</span> <span class="n">start</span><span class="p">,</span> <span class="kt">int</span> <span class="n">finish</span><span class="p">,</span> <span class="kt">int</span> <span class="n">finishX</span><span class="p">,</span> <span class="kt">int</span> <span class="n">finishY</span><span class="p">)</span> <span class="p">{</span> <span class="kt">int</span> <span class="n">verticesNumber</span> <span class="o">=</span> <span class="n">graph</span><span class="p">.</span><span class="n">Nodes</span><span class="p">.</span><span class="n">size</span><span class="p">();</span> <span class="n">vector</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span> <span class="n">nodeColor</span><span class="p">(</span><span class="n">verticesNumber</span><span class="p">,</span> <span class="n">WHITE</span><span class="p">);</span> <span class="c1">// All the nodes are White colored initially</span> <span class="n">vector</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span> <span class="n">shortestPath</span><span class="p">(</span><span class="n">verticesNumber</span><span class="p">,</span> <span class="n">INF</span><span class="p">);</span> <span class="c1">// Current shortest path found from Start to i is some large/INFinite number from the beginning.</span> <span class="n">vector</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span> <span class="n">previousVertex</span><span class="p">(</span><span class="n">verticesNumber</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">);</span> <span class="c1">// Index of the vertex/node that is predecessor of i-th vertex in a shortest path to it.</span> <span class="c1">// We should use pointers here because we want to pass the pointer to a data-structure</span> <span class="c1">// so it may receive all the updates automatically on every step.</span> <span class="n">shared_ptr</span><span class="o">&lt;</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;&gt;</span> <span class="n">ptrShortestPath</span> <span class="o">=</span> <span class="n">make_shared</span><span class="o">&lt;</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;&gt;</span><span class="p">(</span><span class="n">shortestPath</span><span class="p">);</span> <span class="n">shared_ptr</span><span class="o">&lt;</span><span class="n">Graph</span><span class="o">&gt;</span> <span class="n">ptrGraph</span> <span class="o">=</span> <span class="n">make_shared</span><span class="o">&lt;</span><span class="n">Graph</span><span class="o">&gt;</span><span class="p">(</span><span class="n">graph</span><span class="p">);</span> </code></pre> </div> <p>Next, we need to initialize our data structure. By using the code provided in the <a href="proxy.php?url=https://github.com/optiklab/path-algorithms-in-a-graph" rel="noopener noreferrer">GitHub repository</a>, you can simply uncomment the necessary line of code. The code is not designed to select the data structure based on a parameter because I want you to actively experiment with it to gain a better understanding (yes, I'm a tough guy :D).<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight cpp"><code> <span class="c1">//////////////////////////////////////////////////</span> <span class="c1">// TODO</span> <span class="c1">// UNCOMMENT DATA STRUCTURE YOU WANT TO USE:</span> <span class="c1">//dfsStack customQueue;</span> <span class="c1">//bfsQueue customQueue;</span> <span class="c1">//dijkstraQueue customQueue(ptrShortestPath);</span> <span class="c1">//aStarQueue customQueue(finishX, finishY, ptrGraph, ptrShortestPath);</span> <span class="c1">// END OF TODO</span> <span class="c1">/////////////////////////////////////////////////</span> </code></pre> </div> <p>Finally, the algorithm itself. Essentially, it is a combination of all three algorithms with some additional checks. We initialize a "customQueue" and execute the algorithm until it becomes empty. When examining each neighboring node in the graph, we enqueue every node that potentially needs to be traversed next. Then, we call the getFirst() method, which extracts only one node that should be traversed next in the algorithm. </p> <p><a href="proxy.php?url=https://github.com/optiklab/path-algorithms-in-a-graph/blob/ecb090ce3f0fe8395e2c7360873bd9b0fd899d15/main.cpp#L48" rel="noopener noreferrer">main.cpp#L48</a><br> </p> <div class="highlight js-code-highlight"> <pre class="highlight cpp"><code> <span class="n">customQueue</span><span class="p">.</span><span class="n">insert</span><span class="p">(</span><span class="n">start</span><span class="p">);</span> <span class="n">nodeColor</span><span class="p">[</span><span class="n">start</span><span class="p">]</span> <span class="o">=</span> <span class="n">BLACK</span><span class="p">;</span> <span class="n">ptrShortestPath</span><span class="o">-&gt;</span><span class="n">at</span><span class="p">(</span><span class="n">start</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">while</span> <span class="p">(</span><span class="o">!</span><span class="n">customQueue</span><span class="p">.</span><span class="n">isEmpty</span><span class="p">())</span> <span class="c1">// Traverse nodes starting from start node.</span> <span class="p">{</span> <span class="kt">int</span> <span class="n">current</span> <span class="o">=</span> <span class="n">customQueue</span><span class="p">.</span><span class="n">getFirst</span><span class="p">();</span> <span class="k">if</span> <span class="p">(</span><span class="n">current</span> <span class="o">==</span> <span class="n">finish</span><span class="p">)</span> <span class="c1">// If we found finish node, then let's print full path.</span> <span class="p">{</span> <span class="n">vector</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span> <span class="n">path</span><span class="p">;</span> <span class="kt">int</span> <span class="n">cur</span> <span class="o">=</span> <span class="n">finish</span><span class="p">;</span> <span class="n">path</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="n">cur</span><span class="p">);</span> <span class="k">while</span> <span class="p">(</span><span class="n">previousVertex</span><span class="p">[</span><span class="n">cur</span><span class="p">]</span> <span class="o">!=</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="c1">// Recover path node by node.</span> <span class="p">{</span> <span class="n">cur</span> <span class="o">=</span> <span class="n">previousVertex</span><span class="p">[</span><span class="n">cur</span><span class="p">];</span> <span class="n">path</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="n">cur</span><span class="p">);</span> <span class="p">}</span> <span class="n">reverse</span><span class="p">(</span><span class="n">path</span><span class="p">.</span><span class="n">begin</span><span class="p">(),</span> <span class="n">path</span><span class="p">.</span><span class="n">end</span><span class="p">());</span> <span class="c1">// Since we are at the finish node, reverse list to be at start.</span> <span class="k">return</span> <span class="n">path</span><span class="p">;</span> <span class="p">}</span> <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">neighbourIndex</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">neighbourIndex</span> <span class="o">&lt;</span> <span class="n">graph</span><span class="p">.</span><span class="n">Edges</span><span class="p">[</span><span class="n">current</span><span class="p">].</span><span class="n">size</span><span class="p">();</span> <span class="n">neighbourIndex</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="kt">int</span> <span class="n">to</span> <span class="o">=</span> <span class="n">graph</span><span class="p">.</span><span class="n">Edges</span><span class="p">[</span><span class="n">current</span><span class="p">][</span><span class="n">neighbourIndex</span><span class="p">].</span><span class="n">first</span><span class="p">;</span> <span class="kt">int</span> <span class="n">weight</span> <span class="o">=</span> <span class="n">graph</span><span class="p">.</span><span class="n">Edges</span><span class="p">[</span><span class="n">current</span><span class="p">][</span><span class="n">neighbourIndex</span><span class="p">].</span><span class="n">second</span><span class="p">;</span> <span class="k">if</span> <span class="p">(</span><span class="n">nodeColor</span><span class="p">[</span><span class="n">to</span><span class="p">]</span> <span class="o">==</span> <span class="n">WHITE</span><span class="p">)</span> <span class="c1">// If node is not yet visited.</span> <span class="p">{</span> <span class="n">nodeColor</span><span class="p">[</span><span class="n">to</span><span class="p">]</span> <span class="o">=</span> <span class="n">GREY</span><span class="p">;</span> <span class="c1">// Mark node as "in progress".</span> <span class="n">customQueue</span><span class="p">.</span><span class="n">insert</span><span class="p">(</span><span class="n">to</span><span class="p">);</span> <span class="n">previousVertex</span><span class="p">[</span><span class="n">to</span><span class="p">]</span> <span class="o">=</span> <span class="n">current</span><span class="p">;</span> <span class="n">ptrShortestPath</span><span class="o">-&gt;</span><span class="n">at</span><span class="p">(</span><span class="n">to</span><span class="p">)</span> <span class="o">=</span> <span class="n">ptrShortestPath</span><span class="o">-&gt;</span><span class="n">at</span><span class="p">(</span><span class="n">current</span><span class="p">)</span> <span class="o">+</span> <span class="n">weight</span><span class="p">;</span> <span class="c1">// Calculate cost of moving to this node.</span> <span class="p">}</span> <span class="k">else</span> <span class="c1">// Select the most optimal route.</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="n">ptrShortestPath</span><span class="o">-&gt;</span><span class="n">at</span><span class="p">(</span><span class="n">to</span><span class="p">)</span> <span class="o">&gt;</span> <span class="n">ptrShortestPath</span><span class="o">-&gt;</span><span class="n">at</span><span class="p">(</span><span class="n">current</span><span class="p">)</span> <span class="o">+</span> <span class="n">weight</span><span class="p">)</span> <span class="p">{</span> <span class="n">ptrShortestPath</span><span class="o">-&gt;</span><span class="n">at</span><span class="p">(</span><span class="n">to</span><span class="p">)</span> <span class="o">=</span> <span class="n">ptrShortestPath</span><span class="o">-&gt;</span><span class="n">at</span><span class="p">(</span><span class="n">current</span><span class="p">)</span> <span class="o">+</span> <span class="n">weight</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> <span class="n">nodeColor</span><span class="p">[</span><span class="n">current</span><span class="p">]</span> <span class="o">=</span> <span class="n">BLACK</span><span class="p">;</span> <span class="p">}</span> <span class="k">return</span> <span class="p">{};</span> <span class="p">}</span> </code></pre> </div> <p>Up until this point, the implementation does not differ significantly from other examples you may find in books or on the internet. However, here is where the key aspect lies - getFirst() is the method that serves the main purpose as it determines the exact order of node traversal.</p> <h2> BFS queue </h2> <p>Let's take a closer look at the inner workings of our queue data structure. The queue interface for BFS is the simplest one. <a href="proxy.php?url=https://github.com/optiklab/path-algorithms-in-a-graph/blob/ecb090ce3f0fe8395e2c7360873bd9b0fd899d15/bfsQueue.h#L11" rel="noopener noreferrer">bfsQueue.h#L11</a>:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight cpp"><code><span class="cp">#include</span> <span class="cpf">&lt;queue&gt;</span><span class="cp"> #include</span> <span class="cpf">"pathFindingBase.h"</span><span class="cp"> </span> <span class="k">class</span> <span class="nc">bfsQueue</span> <span class="o">:</span> <span class="k">public</span> <span class="n">pathFindingBase</span> <span class="p">{</span> <span class="nl">private:</span> <span class="n">queue</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span> <span class="n">_queue</span><span class="p">;</span> <span class="nl">public:</span> <span class="k">virtual</span> <span class="kt">void</span> <span class="n">insert</span><span class="p">(</span><span class="kt">int</span> <span class="n">node</span><span class="p">)</span> <span class="p">{</span> <span class="n">_queue</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">node</span><span class="p">);</span> <span class="p">}</span> <span class="k">virtual</span> <span class="kt">int</span> <span class="nf">getFirst</span><span class="p">()</span> <span class="p">{</span> <span class="kt">int</span> <span class="n">value</span> <span class="o">=</span> <span class="n">_queue</span><span class="p">.</span><span class="n">front</span><span class="p">();</span> <span class="n">_queue</span><span class="p">.</span><span class="n">pop</span><span class="p">();</span> <span class="k">return</span> <span class="n">value</span><span class="p">;</span> <span class="p">}</span> <span class="k">virtual</span> <span class="kt">bool</span> <span class="nf">isEmpty</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="n">_queue</span><span class="p">.</span><span class="n">empty</span><span class="p">();</span> <span class="p">}</span> <span class="p">};</span> </code></pre> </div> <p>In reality, we could simply replace the custom queue interface here with the standard C++ queue provided by the STL (Standard Template Library). However, the goal here is universality. Now, you only need to uncomment the line in the main method and run this algorithm:<br> <code>//bfsQueue customQueue; // UNCOMMENT TO USE BFS</code></p> <p>As a result, BFS finds the path 24&lt;-19&lt;-14&lt;-9&lt;-8&lt;-7&lt;-6&lt;-1&lt;-0.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight plaintext"><code>(0, 0) - (0, 1) - (0, 2) - (0, 3) - (0, 4) | (1, 4) | (2, 4) | (3, 4) | (4, 4) </code></pre> </div> <p>If we consider weights, the final cost of this path will be 11. However, remember that neither BFS nor DFS consider weights. Instead, they traverse all nodes in the graph <strong>hoping to find the desired node sooner or later</strong>.</p> <h2> DFS queue </h2> <p>DFS doesn't look very different. We only replace the STD queue with a stack. <a href="proxy.php?url=https://github.com/optiklab/path-algorithms-in-a-graph/blob/ecb090ce3f0fe8395e2c7360873bd9b0fd899d15/dfsStack.h#L11" rel="noopener noreferrer">dfsStack.h#L11</a>:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight cpp"><code><span class="cp">#include</span> <span class="cpf">&lt;stack&gt;</span><span class="cp"> #include</span> <span class="cpf">"pathFindingBase.h"</span><span class="cp"> </span> <span class="k">class</span> <span class="nc">dfsStack</span> <span class="o">:</span> <span class="k">public</span> <span class="n">pathFindingBase</span> <span class="p">{</span> <span class="nl">private:</span> <span class="n">stack</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span> <span class="n">_queue</span><span class="p">;</span> <span class="nl">public:</span> <span class="k">virtual</span> <span class="kt">void</span> <span class="n">insert</span><span class="p">(</span><span class="kt">int</span> <span class="n">node</span><span class="p">)</span> <span class="p">{</span> <span class="n">_queue</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">node</span><span class="p">);</span> <span class="p">}</span> <span class="k">virtual</span> <span class="kt">int</span> <span class="nf">getFirst</span><span class="p">()</span> <span class="p">{</span> <span class="kt">int</span> <span class="n">value</span> <span class="o">=</span> <span class="n">_queue</span><span class="p">.</span><span class="n">top</span><span class="p">();</span> <span class="n">_queue</span><span class="p">.</span><span class="n">pop</span><span class="p">();</span> <span class="k">return</span> <span class="n">value</span><span class="p">;</span> <span class="p">}</span> <span class="k">virtual</span> <span class="kt">bool</span> <span class="nf">isEmpty</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="n">_queue</span><span class="p">.</span><span class="n">empty</span><span class="p">();</span> <span class="p">}</span> <span class="p">};</span> </code></pre> </div> <p>DFS finds the path 24&lt;-23&lt;-22&lt;-21&lt;-20&lt;-15&lt;-10&lt;-5&lt;-0 with a cost of 15 (it doesn't prioritize finding the optimal cost). Interestingly, it traverses in the opposite direction compared to BFS:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight cpp"><code><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="o">|</span> <span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="o">|</span> <span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="o">|</span> <span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="o">|</span> <span class="p">(</span><span class="mi">4</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="o">-</span> <span class="p">(</span><span class="mi">4</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span> <span class="o">-</span> <span class="p">(</span><span class="mi">4</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span> <span class="o">-</span> <span class="p">(</span><span class="mi">4</span><span class="p">,</span> <span class="mi">3</span><span class="p">)</span> <span class="o">-</span> <span class="p">(</span><span class="mi">4</span><span class="p">,</span> <span class="mi">4</span><span class="p">)</span> </code></pre> </div> <h2> Dijkstra queue </h2> <p>Now, Dijkstra's algorithm is the most well-known greedy search algorithm in a graph. Despite its known limitations (inability to handle negative paths, cycles, etc.), it remains popular and efficient enough.</p> <p>It is important to note that the getFirst() method in this implementation uses a greedy approach to select nodes for traversal. <a href="proxy.php?url=https://github.com/optiklab/path-algorithms-in-a-graph/blob/ecb090ce3f0fe8395e2c7360873bd9b0fd899d15/dijkstraQueue.h#L17" rel="noopener noreferrer">dijkstraQueue.h#L17</a>:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight cpp"><code><span class="cp">#include</span> <span class="cpf">&lt;queue&gt;</span><span class="cp"> #include</span> <span class="cpf">"pathFindingBase.h"</span><span class="cp"> </span> <span class="k">class</span> <span class="nc">dijkstraQueue</span> <span class="o">:</span> <span class="k">public</span> <span class="n">pathFindingBase</span> <span class="p">{</span> <span class="nl">private:</span> <span class="n">vector</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span> <span class="n">_queue</span><span class="p">;</span> <span class="n">shared_ptr</span><span class="o">&lt;</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;&gt;</span> <span class="n">_shortestPaths</span><span class="p">;</span> <span class="nl">public:</span> <span class="n">dijkstraQueue</span><span class="p">(</span><span class="n">shared_ptr</span><span class="o">&lt;</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;&gt;</span> <span class="n">shortestPaths</span><span class="p">)</span> <span class="o">:</span> <span class="n">_shortestPaths</span><span class="p">(</span><span class="n">shortestPaths</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span> <span class="k">virtual</span> <span class="kt">void</span> <span class="nf">insert</span><span class="p">(</span><span class="kt">int</span> <span class="n">node</span><span class="p">)</span> <span class="p">{</span> <span class="n">_queue</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="n">node</span><span class="p">);</span> <span class="p">}</span> <span class="k">virtual</span> <span class="kt">int</span> <span class="nf">getFirst</span><span class="p">()</span> <span class="p">{</span> <span class="kt">int</span> <span class="n">minimum</span> <span class="o">=</span> <span class="n">INF</span><span class="p">;</span> <span class="kt">int</span> <span class="n">minimumNode</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span> <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">_queue</span><span class="p">.</span><span class="n">size</span><span class="p">();</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="kt">int</span> <span class="n">to</span> <span class="o">=</span> <span class="n">_queue</span><span class="p">[</span><span class="n">i</span><span class="p">];</span> <span class="kt">int</span> <span class="n">newDistance</span> <span class="o">=</span> <span class="n">_shortestPaths</span><span class="o">-&gt;</span><span class="n">at</span><span class="p">(</span><span class="n">to</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="n">minimum</span> <span class="o">&gt;</span> <span class="n">newDistance</span><span class="p">)</span> <span class="c1">// Greedy selection: select node with minimum distance on every step</span> <span class="p">{</span> <span class="n">minimum</span> <span class="o">=</span> <span class="n">newDistance</span><span class="p">;</span> <span class="n">minimumNode</span> <span class="o">=</span> <span class="n">to</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="k">if</span> <span class="p">(</span><span class="n">minimumNode</span> <span class="o">!=</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span> <span class="n">remove</span><span class="p">(</span><span class="n">_queue</span><span class="p">.</span><span class="n">begin</span><span class="p">(),</span> <span class="n">_queue</span><span class="p">.</span><span class="n">end</span><span class="p">(),</span> <span class="n">minimumNode</span><span class="p">);</span> <span class="p">}</span> <span class="k">return</span> <span class="n">minimumNode</span><span class="p">;</span> <span class="p">}</span> <span class="k">virtual</span> <span class="kt">bool</span> <span class="nf">isEmpty</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="n">_queue</span><span class="p">.</span><span class="n">empty</span><span class="p">();</span> <span class="p">}</span> <span class="p">};</span> </code></pre> </div> <p>Dijkstra's algorithm finds the SHORTEST and most OPTIMAL path 24&lt;-19&lt;-18&lt;-13&lt;-12&lt;-7&lt;-6&lt;-1&lt;-0 with a cost of 10:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight plaintext"><code>(0, 0) -1- (0, 1) | 1 | (1, 1) -1- (1, 2) | 1 | (2, 2) -1- (2, 3) | 1 | (3, 3) -1- (3, 4) | 1 | (4, 4) </code></pre> </div> <h2> A-Star </h2> <p>The A-Star algorithm is particularly suited for cases where a path is sought in a Euclidean space with coordinates, such as maps. This is why it is widely used in games. It not only utilizes a "blind" greedy search based on minimal weights but also considers the Euclidean distance to the goal. As a result, it is usually much more efficient than Dijkstra's algorithm in practical scenarios (refer to <a href="proxy.php?url=https://github.com/optiklab/path-algorithms-on-a-grid-map" rel="noopener noreferrer">my other GitHub project</a> for more details). <a href="proxy.php?url=https://github.com/optiklab/path-algorithms-in-a-graph/blob/ecb090ce3f0fe8395e2c7360873bd9b0fd899d15/aStarQueue.h#L18" rel="noopener noreferrer">aStarQueue.h#L18</a>:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight cpp"><code><span class="k">class</span> <span class="nc">aStarQueue</span> <span class="o">:</span> <span class="k">public</span> <span class="n">pathFindingBase</span> <span class="p">{</span> <span class="nl">private:</span> <span class="n">vector</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span> <span class="n">_queue</span><span class="p">;</span> <span class="n">shared_ptr</span><span class="o">&lt;</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;&gt;</span> <span class="n">_shortestPaths</span><span class="p">;</span> <span class="n">shared_ptr</span><span class="o">&lt;</span><span class="n">Graph</span><span class="o">&gt;</span> <span class="n">_graph</span><span class="p">;</span> <span class="kt">int</span> <span class="n">_finishX</span><span class="p">;</span> <span class="kt">int</span> <span class="n">_finishY</span><span class="p">;</span> <span class="c1">/// &lt;summary&gt;</span> <span class="c1">/// Euclidian distance from node start to specified node id.</span> <span class="c1">/// &lt;/summary&gt;</span> <span class="kt">int</span> <span class="n">calcEuristic</span><span class="p">(</span><span class="kt">int</span> <span class="n">id</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="n">sqrt</span><span class="p">(</span> <span class="n">pow</span><span class="p">(</span><span class="n">abs</span><span class="p">(</span> <span class="n">_finishX</span> <span class="o">&gt;</span> <span class="n">_graph</span><span class="o">-&gt;</span><span class="n">Nodes</span><span class="p">[</span><span class="n">id</span><span class="p">].</span><span class="n">X</span> <span class="o">?</span> <span class="n">_finishX</span> <span class="o">-</span> <span class="n">_graph</span><span class="o">-&gt;</span><span class="n">Nodes</span><span class="p">[</span><span class="n">id</span><span class="p">].</span><span class="n">X</span> <span class="o">:</span> <span class="n">_graph</span><span class="o">-&gt;</span><span class="n">Nodes</span><span class="p">[</span><span class="n">id</span><span class="p">].</span><span class="n">X</span> <span class="o">-</span> <span class="n">_finishX</span><span class="p">),</span> <span class="mi">2</span><span class="p">)</span> <span class="o">+</span> <span class="n">pow</span><span class="p">(</span><span class="n">abs</span><span class="p">(</span> <span class="n">_finishY</span> <span class="o">&gt;</span> <span class="n">_graph</span><span class="o">-&gt;</span><span class="n">Nodes</span><span class="p">[</span><span class="n">id</span><span class="p">].</span><span class="n">Y</span> <span class="o">?</span> <span class="n">_finishY</span> <span class="o">-</span> <span class="n">_graph</span><span class="o">-&gt;</span><span class="n">Nodes</span><span class="p">[</span><span class="n">id</span><span class="p">].</span><span class="n">Y</span> <span class="o">:</span> <span class="n">_graph</span><span class="o">-&gt;</span><span class="n">Nodes</span><span class="p">[</span><span class="n">id</span><span class="p">].</span><span class="n">Y</span> <span class="o">-</span> <span class="n">_finishY</span><span class="p">),</span> <span class="mi">2</span><span class="p">));</span> <span class="p">}</span> <span class="k">public</span><span class="o">:</span> <span class="n">aStarQueue</span><span class="p">(</span><span class="kt">int</span> <span class="n">finishX</span><span class="p">,</span> <span class="kt">int</span> <span class="n">finishY</span><span class="p">,</span> <span class="n">shared_ptr</span><span class="o">&lt;</span><span class="n">Graph</span><span class="o">&gt;</span> <span class="n">graph</span><span class="p">,</span> <span class="n">shared_ptr</span><span class="o">&lt;</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;&gt;</span> <span class="n">shortestPaths</span><span class="p">)</span> <span class="o">:</span> <span class="n">_shortestPaths</span><span class="p">(</span><span class="n">shortestPaths</span><span class="p">),</span> <span class="n">_graph</span><span class="p">(</span><span class="n">graph</span><span class="p">)</span> <span class="p">{</span> <span class="n">_finishX</span> <span class="o">=</span> <span class="n">finishX</span><span class="p">;</span> <span class="n">_finishY</span> <span class="o">=</span> <span class="n">finishY</span><span class="p">;</span> <span class="p">}</span> <span class="k">virtual</span> <span class="kt">void</span> <span class="nf">insert</span><span class="p">(</span><span class="kt">int</span> <span class="n">node</span><span class="p">)</span> <span class="p">{</span> <span class="n">_queue</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="n">node</span><span class="p">);</span> <span class="p">}</span> <span class="k">virtual</span> <span class="kt">int</span> <span class="nf">getFirst</span><span class="p">()</span> <span class="p">{</span> <span class="kt">int</span> <span class="n">minimum</span> <span class="o">=</span> <span class="n">INF</span><span class="p">;</span> <span class="kt">int</span> <span class="n">minimumNode</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span> <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">_queue</span><span class="p">.</span><span class="n">size</span><span class="p">();</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="kt">int</span> <span class="n">to</span> <span class="o">=</span> <span class="n">_queue</span><span class="p">[</span><span class="n">i</span><span class="p">];</span> <span class="kt">int</span> <span class="n">newDistance</span> <span class="o">=</span> <span class="n">_shortestPaths</span><span class="o">-&gt;</span><span class="n">at</span><span class="p">(</span><span class="n">to</span><span class="p">);</span> <span class="kt">int</span> <span class="n">euristic</span> <span class="o">=</span> <span class="n">calcEuristic</span><span class="p">(</span><span class="n">to</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="n">minimum</span> <span class="o">&gt;</span> <span class="n">newDistance</span> <span class="o">+</span> <span class="n">euristic</span><span class="p">)</span> <span class="p">{</span> <span class="n">minimum</span> <span class="o">=</span> <span class="n">newDistance</span> <span class="o">+</span> <span class="n">euristic</span><span class="p">;</span> <span class="n">minimumNode</span> <span class="o">=</span> <span class="n">to</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="k">if</span> <span class="p">(</span><span class="n">minimumNode</span> <span class="o">!=</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span> <span class="n">_queue</span><span class="p">.</span><span class="n">erase</span><span class="p">(</span><span class="n">remove</span><span class="p">(</span><span class="n">_queue</span><span class="p">.</span><span class="n">begin</span><span class="p">(),</span> <span class="n">_queue</span><span class="p">.</span><span class="n">end</span><span class="p">(),</span> <span class="n">minimumNode</span><span class="p">),</span> <span class="n">_queue</span><span class="p">.</span><span class="n">end</span><span class="p">());</span> <span class="p">}</span> <span class="k">return</span> <span class="n">minimumNode</span><span class="p">;</span> <span class="p">}</span> <span class="k">virtual</span> <span class="kt">bool</span> <span class="nf">isEmpty</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="n">_queue</span><span class="p">.</span><span class="n">empty</span><span class="p">();</span> <span class="p">}</span> <span class="p">};</span> </code></pre> </div> <p>As a result, we obtain the same results as Dijkstra's algorithm because it provides the most optimal route.</p> <p>It is possible that this example might be too simplistic to demonstrate the real differences in performance among these algorithms. If you are interested in exploring the potential of these algorithms, please refer to <a href="proxy.php?url=https://github.com/optiklab/path-algorithms-on-a-grid-map" rel="noopener noreferrer">my other project</a>, which implements these algorithms efficiently and employs a more visual approach with a wide range of test data.</p> <h2> Downsides </h2> <p>However, there is a problem with our Dijkstra's and A-Star algorithms...<br> The above implementation uses a vector (a dynamic array []) within our universal data structure. On every call to getFirst(), it takes O(N) time to find the required node in the vector. Consequently, assuming that the main algorithm also takes O(N*M) time, where M is the average number of neighbors, the overall complexity could become almost cubic. This would lead to significant performance degradation on large graphs.</p> <p>While this sample is useful for grasping the overall idea that all four algorithms are not fundamentally different, the devil lies in the details. Implementing all four algorithms efficiently using a universal data structure is challenging.</p> <p>For optimal performance (which is typically the primary concern in 99% of cases), more effort should be directed towards optimizations. For example, it makes a lot of sense to use priority queue instead of an array for both Dijkstra's and A-Star algorithms. </p> <p>Speaking about optimizations of A-Star algorithm, it makes a lot of sense to mention a few links that will open a deep world of optimizations: <a href="proxy.php?url=https://lucho1.github.io/JumpPointSearch/" rel="noopener noreferrer">A* Optimizations and Improvements by Lucho Suaya</a> and <a href="proxy.php?url=https://www.gdcvault.com/play/1022094/JPS-Over-100x-Faster-than" rel="noopener noreferrer">JPS+: Over 100x Faster than A* by Steve Rabin</a>.</p> <h2> Final word </h2> <p>The goal of this article was to show how relevant all traversing algorithms are to each other. But example of a graph used in this article is definitely too simplistic to demonstrate the real differences in performance among these algorithms. Therefore, use these examples primarily to gain a conceptual understanding, rather than for production purposes.</p> <p>If you are interested in exploring the potential of these algorithms, please read my next article based on <a href="proxy.php?url=https://github.com/optiklab/path-algorithms-on-a-grid-map" rel="noopener noreferrer">my other project</a>, which implements these algorithms efficiently and employs a more visual approach with a wide range of test data.</p> <p>Stay tuned!</p> bfs dfs dijkstra astar Exploring well-known path finding algorithms with SFML graphics library Anton Yarkov Thu, 03 Aug 2023 18:23:10 +0000 https://dev.to/optiklab/exploring-path-finding-algorithms-for-walle-3j50 https://dev.to/optiklab/exploring-path-finding-algorithms-for-walle-3j50 <p><em>In my last post I have shown a way to unify implementation of the most well-known graph-traversal algorithms. Now let's make it more visually appealing and look into the performance differences.</em></p> <h1> The story behind </h1> <p>A few years ago, Yandex organized a contest called <a href="proxy.php?url=https://contest.yandex.ru/contest/28643/problems" rel="noopener noreferrer">Robots Couriers</a> with an enticing prize: a ticket to a <a href="proxy.php?url=https://taxi.yandex.ru/action/ysdm" rel="noopener noreferrer">closed self-driving conference for professionals</a>. The contest resembled a game, with participants tasked with finding optimal routes on a map and optimizing delivery using robotic couriers.</p> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fubw1ztqrw5t277ubkxbm.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fubw1ztqrw5t277ubkxbm.jpg" alt="Screenshot credits to Yandex Company Blog" width="744" height="278"></a></p> <p>As I delved into the topic, I discovered that despite route finding being a solved problem, it continued to be of interest to the professional game development community. Between 2010 and 2020, engineers <a href="proxy.php?url=https://www.gdcvault.com/play/1022094/JPS-Over-100x-Faster-than" rel="noopener noreferrer">made significant optimizations to the A* algorithm</a>, particularly beneficial for AAA games with massive maps. Reading <a href="proxy.php?url=https://lucho1.github.io/JumpPointSearch/" rel="noopener noreferrer">articles and research papers</a> on these optimizations was an exciting experience.</p> <p>Furthermore, the contest requirements were designed to enable easy assessment of program outputs by the contest's testing system. As a result, there was little emphasis on visualization.</p> <p>I found it intriguing to explore this field and develop a small application that uses well-known graph algorithms to find routes on a grid map. To visualize my findings, I employed the SFML graphics library.</p> <h1> Goal </h1> <p>This project builds upon one of <a href="proxy.php?url=https://antonyarkov.substack.com/p/universal-implementation-of-bfs-dfs" rel="noopener noreferrer">my previous endeavors</a>, where I demonstrated that four well-known path-finding algorithms (BFS, DFS, Dijkstra's, and A*) are not fundamentally different and can be implemented in a universal way. However, it was challenging to showcase significant performance differences among these algorithms in that project.</p> <p>In this article, I aim to use improved test data and design something visually exciting. While the Yandex Contest task mentioned earlier aligns well with my goals, I will not solve their specific problem here since it heavily relies on their test system, which is currently unavailable.</p> <p>Instead, I will extract general ideas for input parameters from that contest and create my own implementation.</p> <h1> Imaginary world </h1> <p>Imagine a technically advanced and innovative city where the future has arrived long ago. In this city, the majority of orders are delivered by courier robots, and it has become a rarity for a person to deliver an order from a cafe. In this task, we invite you to participate in finding optimal routes to deliver orders efficiently.</p> <p>Let's envision the city as an N × N map. For simplicity, we assume that each robot occupies exactly one cell, and each cell can either be passable or not for the robots. In one step, a robot can move in any of the four cardinal directions (up, down, left, or right) if the target cell is free.</p> <p>And I'm ignoring the rest of orginial task:<br> <del>At the beginning of the test, you need to output the number of robots you want to use to deliver orders and their initial coordinates. The construction of each robot will cost Costc rubles.</del></p> <p><del>Next, T iterations of the simulation will be performed. One iteration represents one virtual minute and consists of 60 seconds. At each iteration, your program will be given the number of new orders, and in response, the program should tell you what actions each robot performs (60 actions per robot).</del></p> <p><del>For each successfully delivered order, you will receive max(0, MaxTips - DeliveryTime) dollars in tips, where MaxTips is the maximum number of tips for one order, and DeliveryTime is the time from the moment the order appeared to its delivery in seconds.</del></p> <p><del>The total number of points that you earn in one test is calculated by the formula TotalTips - R × Costc, where TotalTips is the total number of tips earned, R is the number of robots used, Costc is the cost of building one robot. The Costc and MaxTips values are set in each test. If you earned less tips than you spent on making robots, your total points will be 0. You will also receive 0 points for the test if you perform any incorrect action.</del></p> <h2> Input </h2> <p>The program uses standard input to read the parameters. This approach allows us to specify test data of various complexities using input files. </p> <p>The first line of input contains three natural numbers: N (the size of the city map), MaxTips (the maximum number of tips per order), and Costc (the cost of building one robot). I ignore both MaxTips and Costc parameters for my first implementation and maybe will consider that in future.</p> <p>Following that, each of the next N lines contains N characters representing the city map. Each string can consist of two types of characters:</p> <ul> <li>'#' - indicates a cell occupied by an obstacle.</li> <li>'.' - indicates a free space.</li> </ul> <p>Next, you will be provided with two natural numbers: T and D (T ≤ 100,000, D ≤ 10,000,000). T represents the number of interaction iterations, and D represents the total number of orders.</p> <h2> Output </h2> <p>Your task is to visualize the map and the optimal routes using the SFML graphics library.</p> <h1> Modelling the maps </h1> <p>I'm fun of maps represented as a grid of cells. Thus, I prefer to render all the results and map itself as a grid on cell by cell basis.</p> <p>There is also option to execute path search right on grid without using any additional data structure (and I have implemented this as well for the learning purposes: see in code).</p> <p>But because of a grid, it is easy to represent map as a graph using one way or other. I prefer to use adjacency list of cells for most of the algorithms like BFS, Dijkstra's and A-Star. For algorithms like Bellman-Ford it may make sense to use Edges List instead of Adjacency List. That's why if you explore the codebase then you will find all of it and they all are working examples.</p> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb2vq3i0ul9xib0u7opdq.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb2vq3i0ul9xib0u7opdq.jpg" alt="Picture 1. Graphs representation classes." width="448" height="457"></a></p> <p>To split the logic and responsibility I have a <a href="proxy.php?url=https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/main/application/path-algorithms-on-a-grid-map/src/map/navigator.cpp" rel="noopener noreferrer">Navigator</a> entity that is responsible for executing path finding according to the orders and tasks configuration that is specified via <a href="proxy.php?url=https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/main/data/appConfig.json" rel="noopener noreferrer">App Config</a> file and related map files.</p> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3cygk0n97999cmfy5n7a.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3cygk0n97999cmfy5n7a.jpg" alt="Picture 2. Configuration classes." width="204" height="194"></a></p> <p><a href="proxy.php?url=https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/main/data/appConfig.json" rel="noopener noreferrer">App Config</a> looks like this:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="o">{</span> <span class="s2">"font"</span>: <span class="s2">"../../data/arial.ttf"</span>, <span class="s2">"map"</span>: <span class="s2">"../../data/maps/test_29_yandex_weighten_real_map"</span>, <span class="s2">"shadow"</span>: <span class="nb">false</span>, <span class="s2">"map_"</span>: <span class="s2">"../../data/maps/test_08_low_res_simple_map"</span>, <span class="s2">"map__"</span>: <span class="s2">"../../data/maps/test_10"</span>, <span class="s2">"map___"</span>: <span class="s2">"../../data/maps/test_07_partially_blocked_map"</span>, ... </code></pre> </div> <p>Note, that "map_", "map__", etc. are not really configuration properties. They are ignored during application run. Since there is no way to comment part of JSON file, I use underline in the property name so it can stay in config file, but not used.</p> <p><a href="proxy.php?url=https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/main/data/maps/test_12_low_res_complex_map" rel="noopener noreferrer">Map file</a> looks like this:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>25 50 150 <span class="c">#########################</span> <span class="c">#########################</span> <span class="c">#########################</span> <span class="c">###.......#####.......###</span> <span class="c">###.......#####.......###</span> <span class="c">###.......#####.......###</span> <span class="c">###...................###</span> <span class="c">###.......#####.......###</span> <span class="c">###.......#####.......###</span> <span class="c">###...................###</span> <span class="c">######.###########.######</span> <span class="c">######.###########.######</span> <span class="c">######.###########.######</span> <span class="c">###.......#####.......###</span> <span class="c">###.......#####.......###</span> <span class="c">###.......#####.......###</span> <span class="c">###.......#####.......###</span> <span class="c">###.......#####.......###</span> <span class="c">###.......#####.......###</span> <span class="c">###.......#####.......###</span> <span class="c">######.###########.######</span> <span class="c">#########################</span> <span class="c">#########################</span> <span class="c">#########################</span> <span class="c">#########################</span> 2 4 2 6 6 4 20 </code></pre> </div> <p>This is one of the simplest examples that contain either blocked or not blocked cells. I have prepared a lot of various examples of input parameters and test data. Starting from very small parts that let you to debug and learn the code, finishing by a huge piece of map (from the real existing city) that allow us to measure the performance of a Graph algorithm.</p> <h1> How do we draw maps </h1> <p>When map contains only cells with binary state (either blocked or non-blocked), this basically means that any edge of a graph either exists or not.</p> <p>To find a path in the graph we have to represent it efficiently. Like in my previous post, I have used <a href="proxy.php?url=https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/720f43e5bbcad4eefc7d37e908df8f392a8e0761/application/path-algorithms-on-a-grid-map/src/map/graph.h#L10" rel="noopener noreferrer">adjacency list</a> with the relationship as Vector[NodeId]-&gt;points to-&gt;Vector[Neighbour Nodes]:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight cpp"><code><span class="k">typedef</span> <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">shared_ptr</span><span class="o">&lt;</span><span class="n">Cell</span><span class="o">&gt;&gt;&gt;</span> <span class="n">Graph</span><span class="p">;</span> </code></pre> </div> <blockquote> <p>Interestingly enough, when we exploring grids it's not really necessary to use graphs at all. We are capable to traverse grid using BFS/DFS algorithms cell by cell without thinking about edges. See method <a href="proxy.php?url=https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/720f43e5bbcad4eefc7d37e908df8f392a8e0761/application/path-algorithms-on-a-grid-map/src/map/rectangularmap.cpp#L739" rel="noopener noreferrer">_GetPathByBFSOnGrid</a>.</p> </blockquote> <p>First, initialization code reads the file and converts it into <a href="proxy.php?url=https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/720f43e5bbcad4eefc7d37e908df8f392a8e0761/application/path-algorithms-on-a-grid-map/src/map/rectangularmap.cpp#L147" rel="noopener noreferrer">the grid</a> row by row and column by column:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight cpp"><code><span class="kt">bool</span> <span class="n">RectangularMap</span><span class="o">::</span><span class="n">LoadMap</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;</span> <span class="n">filepath</span><span class="p">,</span> <span class="kt">bool</span> <span class="n">shadow</span><span class="p">)</span> <span class="p">{</span> <span class="p">...</span> <span class="c1">// Fill the grid.</span> <span class="n">_verticesNumber</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">row</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">row</span> <span class="o">&lt;</span> <span class="n">_height</span><span class="p">;</span> <span class="n">row</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="p">...</span> <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">col</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">col</span> <span class="o">&lt;</span> <span class="n">_width</span><span class="p">;</span> <span class="n">col</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="kt">int</span> <span class="n">x</span> <span class="o">=</span> <span class="n">col</span><span class="p">;</span> <span class="kt">int</span> <span class="n">y</span> <span class="o">=</span> <span class="n">row</span><span class="p">;</span> <span class="k">if</span> <span class="p">(</span><span class="n">line</span><span class="p">[</span><span class="n">col</span><span class="p">]</span> <span class="o">==</span> <span class="n">BLOCK_CELL</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// Create a shared pointer here to safely pass pointers between the classes.</span> <span class="n">_grid</span><span class="p">[</span><span class="n">row</span><span class="p">][</span><span class="n">col</span><span class="p">]</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">make_shared</span><span class="o">&lt;</span><span class="n">Cell</span><span class="o">&gt;</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">line</span><span class="p">[</span><span class="n">col</span><span class="p">],</span> <span class="n">blockColor</span><span class="p">,</span> <span class="n">shadow</span><span class="p">,</span> <span class="n">_scaleFactor</span><span class="p">);</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="p">...</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> <span class="c1">// Make a graph</span> <span class="n">InitialiseGraph</span><span class="p">();</span> <span class="p">...</span> <span class="p">}</span> </code></pre> </div> <p>Then, it creates an actual graph as <a href="proxy.php?url=https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/720f43e5bbcad4eefc7d37e908df8f392a8e0761/application/path-algorithms-on-a-grid-map/src/map/rectangularmap.cpp#L32" rel="noopener noreferrer">an adjacency list</a>:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight cpp"><code><span class="kt">void</span> <span class="n">RectangularMap</span><span class="o">::</span><span class="n">InitialiseGraph</span><span class="p">()</span> <span class="p">{</span> <span class="n">MapBase</span><span class="o">::</span><span class="n">InitialiseGraph</span><span class="p">();</span> <span class="p">...</span> <span class="n">unordered_set</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span> <span class="n">visited</span><span class="p">;</span> <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">rr</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">rr</span> <span class="o">&lt;</span> <span class="n">_grid</span><span class="p">.</span><span class="n">size</span><span class="p">();</span> <span class="n">rr</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">cc</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">cc</span> <span class="o">&lt;</span> <span class="n">_grid</span><span class="p">[</span><span class="n">rr</span><span class="p">].</span><span class="n">size</span><span class="p">();</span> <span class="n">cc</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="n">_grid</span><span class="p">[</span><span class="n">rr</span><span class="p">][</span><span class="n">cc</span><span class="p">]</span><span class="o">-&gt;</span><span class="n">GetId</span><span class="p">()</span> <span class="o">&gt;</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span> <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">4</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="kt">int</span> <span class="n">r</span> <span class="o">=</span> <span class="n">rr</span> <span class="o">+</span> <span class="n">dr</span><span class="p">[</span><span class="n">i</span><span class="p">];</span> <span class="kt">int</span> <span class="n">c</span> <span class="o">=</span> <span class="n">cc</span> <span class="o">+</span> <span class="n">dc</span><span class="p">[</span><span class="n">i</span><span class="p">];</span> <span class="k">if</span> <span class="p">(</span><span class="n">r</span> <span class="o">&gt;=</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="n">c</span> <span class="o">&gt;=</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="n">r</span> <span class="o">&lt;</span> <span class="n">_width</span> <span class="o">&amp;&amp;</span> <span class="n">c</span> <span class="o">&lt;</span> <span class="n">_height</span> <span class="o">&amp;&amp;</span> <span class="n">_grid</span><span class="p">[</span><span class="n">r</span><span class="p">][</span><span class="n">c</span><span class="p">]</span><span class="o">-&gt;</span><span class="n">GetId</span><span class="p">()</span> <span class="o">&gt;</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="n">_isNegativeWeighten</span><span class="p">)</span> <span class="p">{</span> <span class="p">...</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="n">_adjacencyList</span><span class="p">[</span><span class="n">_grid</span><span class="p">[</span><span class="n">rr</span><span class="p">][</span><span class="n">cc</span><span class="p">]</span><span class="o">-&gt;</span><span class="n">GetId</span><span class="p">()].</span><span class="n">push_back</span><span class="p">(</span><span class="n">_grid</span><span class="p">[</span><span class="n">r</span><span class="p">][</span><span class="n">c</span><span class="p">]);</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> </code></pre> </div> <p>Grid represenation is useful to draw on screen using SFML library. We can draw <a href="proxy.php?url=https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/720f43e5bbcad4eefc7d37e908df8f392a8e0761/application/path-algorithms-on-a-grid-map/src/map/cell.cpp#L37" rel="noopener noreferrer">by creating a geometric objects</a> (this is exactly what I do for small maps):<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight cpp"><code><span class="p">...</span> <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">j</span> <span class="o">=</span> <span class="n">_visibleTopLeftY</span><span class="p">;</span> <span class="n">j</span> <span class="o">&lt;</span> <span class="n">_visibleBottomRightY</span><span class="p">;</span> <span class="n">j</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="n">_visibleTopLeftX</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">_visibleBottomRightX</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="n">_grid</span><span class="p">[</span><span class="n">j</span><span class="p">][</span><span class="n">i</span><span class="p">]</span><span class="o">-&gt;</span><span class="n">Draw</span><span class="p">(</span><span class="n">_window</span><span class="p">,</span> <span class="n">_scaleFactor</span><span class="p">);</span> <span class="p">}</span> <span class="p">}</span> <span class="p">...</span> <span class="n">sf</span><span class="o">::</span><span class="n">RectangleShape</span> <span class="n">tile</span><span class="p">;</span> <span class="n">tile</span><span class="p">.</span><span class="n">setSize</span><span class="p">(</span><span class="n">sf</span><span class="o">::</span><span class="n">Vector2f</span><span class="p">(</span><span class="n">_cellSize</span> <span class="o">-</span> <span class="mi">5</span><span class="p">,</span> <span class="n">_cellSize</span> <span class="o">-</span> <span class="mi">5</span><span class="p">));</span> <span class="n">tile</span><span class="p">.</span><span class="n">setPosition</span><span class="p">(</span><span class="n">sf</span><span class="o">::</span><span class="n">Vector2f</span><span class="p">(</span><span class="n">_x</span> <span class="o">*</span> <span class="n">_cellSize</span><span class="p">,</span> <span class="n">_y</span> <span class="o">*</span> <span class="n">_cellSize</span><span class="p">));</span> <span class="n">tile</span><span class="p">.</span><span class="n">setFillColor</span><span class="p">(</span><span class="n">_color</span><span class="p">);</span> <span class="n">window</span><span class="p">.</span><span class="n">draw</span><span class="p">(</span><span class="n">tile</span><span class="p">);</span> </code></pre> </div> <p>Or we can do it <a href="proxy.php?url=https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/720f43e5bbcad4eefc7d37e908df8f392a8e0761/application/path-algorithms-on-a-grid-map/src/map/mapbaze.cpp#L49" rel="noopener noreferrer">efficiently pixel by pixel</a> for larger maps:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight cpp"><code><span class="n">sf</span><span class="o">::</span><span class="n">Uint8</span><span class="o">*</span> <span class="n">pixels</span> <span class="o">=</span> <span class="k">new</span> <span class="n">sf</span><span class="o">::</span><span class="n">Uint8</span><span class="p">[</span><span class="n">_width</span> <span class="o">*</span> <span class="n">_height</span> <span class="o">*</span> <span class="mi">4</span><span class="p">];</span> <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">j</span> <span class="o">=</span> <span class="n">_visibleTopLeftY</span><span class="p">;</span> <span class="n">j</span> <span class="o">&lt;</span> <span class="n">_visibleBottomRightY</span><span class="p">;</span> <span class="n">j</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="n">_visibleTopLeftX</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">_visibleBottomRightX</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="kt">int</span> <span class="n">index</span> <span class="o">=</span> <span class="p">(</span><span class="n">_grid</span><span class="p">[</span><span class="n">j</span><span class="p">][</span><span class="n">i</span><span class="p">]</span><span class="o">-&gt;</span><span class="n">GetY</span><span class="p">()</span> <span class="o">*</span> <span class="n">_width</span> <span class="o">+</span> <span class="n">_grid</span><span class="p">[</span><span class="n">j</span><span class="p">][</span><span class="n">i</span><span class="p">]</span><span class="o">-&gt;</span><span class="n">GetX</span><span class="p">());</span> <span class="n">sf</span><span class="o">::</span><span class="n">Color</span> <span class="n">color</span> <span class="o">=</span> <span class="n">_grid</span><span class="p">[</span><span class="n">j</span><span class="p">][</span><span class="n">i</span><span class="p">]</span><span class="o">-&gt;</span><span class="n">GetColor</span><span class="p">();</span> <span class="n">pixels</span><span class="p">[</span><span class="n">index</span> <span class="o">*</span> <span class="mi">4</span><span class="p">]</span> <span class="o">=</span> <span class="n">color</span><span class="p">.</span><span class="n">r</span><span class="p">;</span> <span class="n">pixels</span><span class="p">[</span><span class="n">index</span> <span class="o">*</span> <span class="mi">4</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="n">color</span><span class="p">.</span><span class="n">g</span><span class="p">;</span> <span class="n">pixels</span><span class="p">[</span><span class="n">index</span> <span class="o">*</span> <span class="mi">4</span> <span class="o">+</span> <span class="mi">2</span><span class="p">]</span> <span class="o">=</span> <span class="n">color</span><span class="p">.</span><span class="n">b</span><span class="p">;</span> <span class="n">pixels</span><span class="p">[</span><span class="n">index</span> <span class="o">*</span> <span class="mi">4</span> <span class="o">+</span> <span class="mi">3</span><span class="p">]</span> <span class="o">=</span> <span class="n">color</span><span class="p">.</span><span class="n">a</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="n">sf</span><span class="o">::</span><span class="n">Texture</span> <span class="n">texture</span><span class="p">;</span> <span class="n">texture</span><span class="p">.</span><span class="n">create</span><span class="p">(</span><span class="n">_width</span><span class="p">,</span> <span class="n">_height</span><span class="p">);</span> <span class="n">texture</span><span class="p">.</span><span class="n">update</span><span class="p">(</span><span class="n">pixels</span><span class="p">);</span> <span class="n">sf</span><span class="o">::</span><span class="n">Sprite</span> <span class="n">sprite</span><span class="p">;</span> <span class="n">sprite</span><span class="p">.</span><span class="n">setTexture</span><span class="p">(</span><span class="n">texture</span><span class="p">);</span> <span class="n">sprite</span><span class="p">.</span><span class="n">setScale</span><span class="p">(</span><span class="n">cellSize</span><span class="p">,</span> <span class="n">cellSize</span><span class="p">);</span> <span class="n">_window</span><span class="p">.</span><span class="n">draw</span><span class="p">(</span><span class="n">sprite</span><span class="p">);</span> </code></pre> </div> <p>Finally, let's see how a map defined by file <a href="proxy.php?url=https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/main/data/maps/test_25_xmax" rel="noopener noreferrer">test_25_xmax</a> would look like.</p> <p>Originally, file defines this:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight plaintext"><code>..............C................. ..............#................. .............###................ ............#####............... ...........#######.............. ..........##1###2##............. .........###########............ ........##3######4###........... .......###############.......... ......#################......... .....###################........ ....#####################....... .............###................ .............###................ .............###................ </code></pre> </div> <p>And a map renderred with SFML looks like this:</p> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6e9vn4z2ru4xg0hhcbme.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6e9vn4z2ru4xg0hhcbme.jpg" alt="Picture 3. A map consisting of cells in binary state (green or black)." width="762" height="597"></a></p> <p>Because I wanted all of that to be controlled by user with keyboard, I left all the user-behavior logic in the <a href="proxy.php?url=https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/720f43e5bbcad4eefc7d37e908df8f392a8e0761/application/path-algorithms-on-a-grid-map/src/main.cpp#L33" rel="noopener noreferrer">main.cpp</a>. I like to call it “Controller” logic. </p> <p>SFML library makes it very easy to handle keyboard events:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight cpp"><code><span class="k">while</span> <span class="p">(</span><span class="n">window</span><span class="p">.</span><span class="n">isOpen</span><span class="p">())</span> <span class="p">{</span> <span class="n">Event</span> <span class="n">event</span><span class="p">;</span> <span class="k">while</span> <span class="p">(</span><span class="n">window</span><span class="p">.</span><span class="n">pollEvent</span><span class="p">(</span><span class="n">event</span><span class="p">))</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="n">event</span><span class="p">.</span><span class="n">type</span> <span class="o">==</span> <span class="n">Event</span><span class="o">::</span><span class="n">Closed</span><span class="p">)</span> <span class="n">window</span><span class="p">.</span><span class="n">close</span><span class="p">();</span> <span class="k">if</span> <span class="p">(</span><span class="n">event</span><span class="p">.</span><span class="n">type</span> <span class="o">==</span> <span class="n">Event</span><span class="o">::</span><span class="n">KeyPressed</span> <span class="o">&amp;&amp;</span> <span class="n">event</span><span class="p">.</span><span class="n">key</span><span class="p">.</span><span class="n">code</span> <span class="o">==</span> <span class="n">Keyboard</span><span class="o">::</span><span class="n">Space</span><span class="p">)</span> <span class="p">{</span> <span class="p">...</span> <span class="n">Do</span> <span class="n">what</span> <span class="n">you</span> <span class="n">need</span> <span class="n">here</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> </code></pre> </div> <p>The main idea is user triggers (press of a SPACE button) reading the map file and renderring the map, and then by a second trigger (second press of a SPACE button) to load routing task and calculate the shortest path between two points on map:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight cpp"><code><span class="p">...</span> <span class="k">if</span> <span class="p">(</span><span class="n">navigator</span><span class="o">-&gt;</span><span class="n">IsReady</span><span class="p">())</span> <span class="p">{</span> <span class="n">navigator</span><span class="o">-&gt;</span><span class="n">Navigate</span><span class="p">();</span> <span class="c1">// Finding route between two points</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="n">map</span><span class="o">-&gt;</span><span class="n">IsReady</span><span class="p">())</span> <span class="c1">// Second SPACE press runs the routing</span> <span class="p">{</span> <span class="n">skipReRendering</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span> <span class="k">if</span> <span class="p">(</span><span class="n">navigator</span><span class="o">-&gt;</span><span class="n">LoadTasks</span><span class="p">(</span><span class="n">filepath</span><span class="p">))</span> <span class="p">{</span> <span class="n">navigator</span><span class="o">-&gt;</span><span class="n">SetMap</span><span class="p">(</span><span class="n">map</span><span class="p">);</span> <span class="p">}</span> <span class="p">}</span> <span class="k">else</span> <span class="c1">// Load and draw map</span> <span class="p">{</span> <span class="n">drawLoading</span><span class="p">(</span><span class="n">window</span><span class="p">,</span> <span class="n">font</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">map</span><span class="o">-&gt;</span><span class="n">LoadMap</span><span class="p">(</span><span class="n">filepath</span><span class="p">,</span> <span class="n">shadowed</span><span class="p">))</span> <span class="p">{</span> <span class="k">return</span> <span class="mi">0</span><span class="p">;</span> <span class="p">}</span> <span class="n">drawProcessing</span><span class="p">(</span><span class="n">window</span><span class="p">,</span> <span class="n">font</span><span class="p">);</span> <span class="p">}</span> <span class="p">}</span> <span class="p">...</span> </code></pre> </div> <h1> We need to go deeper </h1> <p>I wanted to play with more Graph algorithms, and they all have their limitations, so I decided to implement also a multi-color maps that can be represented by multi-weighted graphs.</p> <p>Every cell is colored and the color means that edge not only exists, but also applies some weight (or fee, or fine, you name it). So, edge might be blocked, half blocked, not blocked, ... you got the idea. </p> <p>Thus, I have implemented multi-color maps that look more joyful and look like a game-ready (example from file <a href="proxy.php?url=https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/main/data/maps/test_31_multi_weight_graph_map" rel="noopener noreferrer">test_31_multi_weight_graph_map</a>):</p> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fznn4wj3g6jpmfu1hhvi7.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fznn4wj3g6jpmfu1hhvi7.jpg" alt="Picture 4. Multi-color map represented with multi-weighted graph." width="773" height="599"></a></p> <p>Some of the configuration files contain more complex maps from really existing cities, like <a href="proxy.php?url=https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/main/data/maps/test_29_yandex_weighten_real_map" rel="noopener noreferrer">test_29_yandex_weighten_real_map</a>:</p> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjkq10rg3bcgxum4rc02o.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjkq10rg3bcgxum4rc02o.jpg" alt="Picture 8. Testing algorithms." width="800" height="466"></a></p> <p>As a challenge, now we should handle maps with very flexible configuration. <a href="proxy.php?url=https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/main/application/path-algorithms-on-a-grid-map/src/map/rectangularmap.cpp" rel="noopener noreferrer">RectangularMap.cpp</a> essentially contains a lot of logic inside, including all the graph algorithms and even more than needed (because I like to play with things, even if it's not particularly useful for now). </p> <p>I have implemented <a href="proxy.php?url=https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/720f43e5bbcad4eefc7d37e908df8f392a8e0761/application/path-algorithms-on-a-grid-map/src/map/rectangularmap.cpp#L598" rel="noopener noreferrer">BFS#Line 598</a>, <a href="proxy.php?url=https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/720f43e5bbcad4eefc7d37e908df8f392a8e0761/application/path-algorithms-on-a-grid-map/src/map/rectangularmap.cpp#L299" rel="noopener noreferrer">Dijkstra#Line 299</a>, <a href="proxy.php?url=https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/720f43e5bbcad4eefc7d37e908df8f392a8e0761/application/path-algorithms-on-a-grid-map/src/map/rectangularmap.cpp#L356" rel="noopener noreferrer">A-Star#Line 356</a>, <a href="proxy.php?url=https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/720f43e5bbcad4eefc7d37e908df8f392a8e0761/application/path-algorithms-on-a-grid-map/src/map/rectangularmap.cpp#L428" rel="noopener noreferrer">Bellman-Ford#Line 428</a> algorithms and a number of additional "utility" algorithms like Topological Sort, Single Source Path, that are not useful for the current application state (because they work on Directly Acyclic Graphs, which are not type of Graphs I currently use), but I have some ideas to use it in future improvements.</p> <p>I didn't polish all the code and didn't get it ideal enough, but it allows me (and, I hope, will allow you) to play with the code and compare performance metrics.</p> <p>Sorry about some commented lines there and there, maybe some dirty code...it's all way of learning :). To grasp an idea what's inside, I recommend you to review the <a href="proxy.php?url=https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/main/application/path-algorithms-on-a-grid-map/src/map/rectangularmap.h" rel="noopener noreferrer">RectangularMap.h</a>.</p> <p>There is also some fun features like a Focus feature that allows to render only particular part of a map. It changes focus by re-rendering the necessary part using <a href="proxy.php?url=https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/main/application/path-algorithms-on-a-grid-map/src/utils/NotifyVisiblePartChanged.h" rel="noopener noreferrer">Observer pattern</a> when user presses the PgDown or PgUp buttons. It is pretty easy to improve this feature and implement "Zoom" functionality. Take it as a home work, if you like it.</p> <p>Focus feature with map file <a href="proxy.php?url=https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/main/data/maps/test_29_yandex_weighten_real_map" rel="noopener noreferrer">test_29_yandex_weighten_real_map</a> in work:</p> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuulies9rq756cueunshq.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuulies9rq756cueunshq.jpg" alt="Picture 5. Focus feature in work." width="800" height="613"></a></p> <p>Classes diagram looks like this:</p> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3181uohoj2ddsr70vq6j.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3181uohoj2ddsr70vq6j.jpg" alt="Picture 6. Map classes diagram." width="717" height="274"></a></p> <h1> Run and play </h1> <p>I believe, the most joyful part is just running this little application, playing with variations of its configuration and algorithms. You can do a lot of experiments by using various map files as input parameters with different test data as well as change the logic in the code. </p> <p>After start you need to press SPACE an application will render map according to the configuration file and it makes a lot of sense to start exploring from simplest test cases moving forward to the most complex one. </p> <p>Pressing SPACE one more time executes the routing algorithms and finds path between the start and one nearest order. BTW It's not yet done, but it is easy to implement reading all the rest of orders available in map configuration files and execute path finding to all of them.</p> <p>Here is route found on map defined by file <a href="proxy.php?url=https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/main/data/maps/test_18_yandex_super_high_res" rel="noopener noreferrer">test_18_yandex_super_high_res</a>:<br> <a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk5opc8n1lttksnjqnuv1.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk5opc8n1lttksnjqnuv1.jpg" alt="Picture 7. Testing algorithms." width="778" height="597"></a></p> <p>It also capable to find routes in the maps that are simulating existing cities, like <a href="proxy.php?url=https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/main/data/maps/test_29_yandex_weighten_real_map" rel="noopener noreferrer">test_29_yandex_weighten_real_map</a>:</p> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjkq10rg3bcgxum4rc02o.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjkq10rg3bcgxum4rc02o.jpg" alt="Picture 8. Testing algorithms." width="800" height="466"></a></p> <p>Finding efficient paths between two coordinates becomes challenging for algorithms like BFS, but can be easily done by A-star.</p> <p>Based on the cells found in the map configuration files, application will treat map as a weighted or non-weighted graph and will select the right algorithm for it (and you can easily change this as well). It's easy to see the difference between BFS and A-Star performance:</p> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi4tda00jpkwvzsa93vhq.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi4tda00jpkwvzsa93vhq.jpg" alt="Picture 9. Comparing performance of A-Star over BFS." width="800" height="190"></a></p> <h1> Final words </h1> <p>With this I want to leave you alone and let you play with these code examples. I hope you will find it fascinating and will learn a lot from it.</p> <p>Stay tuned!</p> <h1> Links </h1> <ol> <li><a href="proxy.php?url=https://www.gdcvault.com/play/1022094/JPS-Over-100x-Faster-than" rel="noopener noreferrer">JPS+: Over 100x Faster than A* by Steve Rabin</a></li> <li><a href="proxy.php?url=https://lucho1.github.io/JumpPointSearch/" rel="noopener noreferrer">A* Optimizations and Improvements by Lucho Suaya</a></li> <li><a href="proxy.php?url=https://dev.to/optiklab/universal-implementation-of-bfs-dfs-dijkstra-and-a-star-algorithms-m4k">Universal implementation of BFS, DFS, Dijkstra and A-Star algorithms</a></li> <li><a href="proxy.php?url=https://antonyarkov.substack.com/" rel="noopener noreferrer">https://antonyarkov.substack.com/</a></li> </ol> bfs dijkstra astar bellmanford Tip: Debug your Roslyn Source Generator with JetBrains Rider IDE Anton Yarkov Sun, 05 Feb 2023 20:05:06 +0000 https://dev.to/optiklab/tip-debug-your-roslyn-source-generator-with-jetbrains-rider-ide-3a86 https://dev.to/optiklab/tip-debug-your-roslyn-source-generator-with-jetbrains-rider-ide-3a86 <p>.NET Compiler Platform becomes bigger thing, and in the same time, toolset is growing around it. So, it becomes easier to be used.</p> <p>While writing code that parses/analyzes your code is not a simple task, it might be helpful in various ways. One way I just learned (thanks to my colleagues) is a <strong>refactoring</strong>.</p> <p>Usually, code of your source analyzer and/or generator will not be the easiest one to read and maintain. But the idea is it doesn't always need to! The idea is that you have to use source generator as a temporary solution that makes refactoring an easier task for you and your team. And after some refactoring is done, you can simply throw away that code as a legacy one.</p> <p>I think there is a lot of examples on youtube and in the internet, so I don't need to share another "sample". Instead, I will share most recent practical</p> <h2> Tip of the day </h2> <blockquote> <p>To debug C# Source Generator based on Roslyn .NET Compiler Platform in Rider IDE you need to install Rider 2023 EAP (yes, early preview) and then follow instructions from <a href="proxy.php?url=https://github.com/JoanComasFdz/dotnet-how-to-debug-source-generator-vs2022" rel="noopener noreferrer">https://github.com/JoanComasFdz/dotnet-how-to-debug-source-generator-vs2022</a></p> </blockquote> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgyilfzab4phv0d76ikn9.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgyilfzab4phv0d76ikn9.png" alt=" " width="800" height="450"></a></p> <p>If you haven't use Rider IDE for some reason, you should try it. It much more efficient than Visual Studio, especially at big monolithic projects (you know what I mean, if you work at enterprise). Though, debugging source generators was not possible till recently. From know it all yours!</p> dotnet csharp roslyn jetbrainsride API versioning notes Anton Yarkov Sun, 27 Nov 2022 20:59:03 +0000 https://dev.to/optiklab/learn-with-practice-api-versioning-snags-3fp4 https://dev.to/optiklab/learn-with-practice-api-versioning-snags-3fp4 <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frng7f1wv2dbswhx3pjrk.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frng7f1wv2dbswhx3pjrk.jpg" alt=" " width="800" height="229"></a></p> <p>I have finished watching a series of lessons on <a href="proxy.php?url=https://www.youtube.com/watch?v=9Ng00IlBCtw" rel="noopener noreferrer">Designing &amp; Versioning HTTP/REST APIs</a> from well known author <a href="proxy.php?url=https://www.linkedin.com/in/jeffrichter/" rel="noopener noreferrer">Jeffrey Richter</a> who is an author of the best-sellers <a href="proxy.php?url=https://www.amazon.com/CLR-via-4th-Developer-Reference/dp/0735667454/ref=sr_1_1?dchild=1&amp;keywords=jeffrey+richter&amp;qid=1609873118&amp;sr=8-1" rel="noopener noreferrer">CLR via C#</a> and <a href="proxy.php?url=https://www.amazon.com/Windows%C2%AE-Developer-Reference-Christophe-Nasarre-ebook/dp/B00JDMQK9G/ref=sr_1_2?dchild=1&amp;keywords=jeffrey+richter&amp;qid=1609874487&amp;sr=8-2" rel="noopener noreferrer">Windows API via C++</a>.</p> <p>I do definitely recommend it to watch (and read the books as well, if you didn't) for those who design Back-End APIs. And especially for those who think that doing API is simple (probably, impressed by several Hello-World prototypes made during the week-ends).</p> <blockquote> <p>One important thing to note in this article is that it is really important to <strong>combine some practice when you do theoretical learning</strong>. </p> </blockquote> <p>This recommendation will work for developers of any level of experience and do not depend on how well-known the author of the book/course is. It is always easy to watch and read something and put a sign "learned" on it. But actual implementation will always put some impediments and this is where things get really interesting.</p> <p>Now, let's take one piece of advice from the course and let's dive into the topic which is not fully uncovered in the course: on <a href="proxy.php?url=%20https://www.youtube.com/watch?v=bG984_L6dS0&amp;list=PL9XzOCngAkqs4m0XdULJu_78nM3Ok3Q65&amp;index=14&amp;t=734s%20">Versioning HTTP APIs</a> part of course. Details I will cover here are really important for the implementation.</p> <h2> REST API everywhere </h2> <p>Most of the public API's go with REST or RESTful approach. Those gRPC, SOAP, GraphQL and other protocols are good for its own purposes, but REST is covering most of the needs:</p> <ul> <li>It's available for any type of client: old/new browsers, smartphones, low-end computers, Raspberri Pi's and even consoles.</li> <li>It uses well-known HTTP language for all the features that API needs, like handling errors, passing auth tokens (headers), transferring all the types of content, bandwidth-control, etc. etc.</li> <li>Tons of examples in the internet out there,</li> <li>etc.</li> </ul> <p>And, truly saying, for prototypes, local businesses, quick and cheap development it is all you need. All other protocols have been created to solve much more specific needs like scaling, handling low-bandwidth channels, enterprise solutions, etc.</p> <p>But this simplicity and low entry threshold plays a bad role when it comes to product quality of your <strong>Public API</strong>. Very few non-senior developers know that Public API's must be <strong>backward compatible</strong> and sometimes even <strong>forward compatible</strong>. Tiny portion of developers know how to achieve it. </p> <h2> What's wrong with Public APIs? </h2> <p>API versioning plays crucial role in making your API really desirable for your happy clients. Since the time you have published your API for the first time, things never will be fun for you.</p> <p>You have to maintain all the base of the clients from version 1.0-pre-alpha till the version 99999...9.0 unless you have been smart enough to publish your Public API Policy of Use. Didn't you? :D</p> <p>In this policy you can recommend all your clients to update on every release. But it's still just a recommendation.</p> <p>You can also not guarantee stability of your old versions, but that really depends on your customer relationships.</p> <p>In any way, you have to label your API version and make sure that clients potentially understand how it works based on documentation and policies that you have provided.</p> <h2> Just follow someone's recommendations in API versioning. What can go wrong? </h2> <p>API version is a label that should tell your clients what to expect. There are various ways of doing so, to mention a few:</p> <ul> <li><a href="proxy.php?url=http://api.tactica.xyz/products?api-version=0.1-alpha" rel="noopener noreferrer">http://api.tactica.xyz/products?api-version=0.1-alpha</a></li> <li><a href="proxy.php?url=http://api.tactica.xyz/products?api-version=1.0" rel="noopener noreferrer">http://api.tactica.xyz/products?api-version=1.0</a></li> <li><a href="proxy.php?url=http://api.tactica.xyz/products?api-version=2023-01-01" rel="noopener noreferrer">http://api.tactica.xyz/products?api-version=2023-01-01</a></li> <li><a href="proxy.php?url=http://api.tactica.xyz/v0.9-beta/products" rel="noopener noreferrer">http://api.tactica.xyz/v0.9-beta/products</a></li> <li><a href="proxy.php?url=http://api.tactica.xyz/v1.0/products" rel="noopener noreferrer">http://api.tactica.xyz/v1.0/products</a></li> </ul> <p>As you can see, there are many ways to tell your API version to the client. And before you take the first approach in the list, I should tell you that it's not always "up to you". Let's review why you may want to use one or another way.</p> <p>Basically, there is only 2 approaches that differ a lot:</p> <ol> <li>Put your API version in the URL</li> <li>Put your API version in the parameters</li> </ol> <p>In various sources I've seen approach #1 marked as outdated. However, there are still reasons to keep using it. Let's review those.</p> <p>Why to pass Version in the URL</p> <ul> <li>Most of the API Management systems do use "API version in the URI" as a default approach, such as <a href="proxy.php?url=https://www.axway.com/en/products/amplify-api-management-platform" rel="noopener noreferrer">Axway</a>, <a href="proxy.php?url=https://www.mulesoft.com/platform/enterprise-integration" rel="noopener noreferrer">MuleSoft Anypoint</a>, <a href="proxy.php?url=https://cloud.google.com/apigee" rel="noopener noreferrer">Google Apigee</a>, <a href="proxy.php?url=https://boomi.com/platform/api-management" rel="noopener noreferrer">Boomi</a>, <a href="proxy.php?url=https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-developer-portal.html" rel="noopener noreferrer">AWS API portal</a>, <a href="proxy.php?url=https://docs.nginx.com/nginx-controller/api-management/" rel="noopener noreferrer">Nginx API Management</a>.</li> <li>Services co-located behind a DNS endpoint MUST use the same versioning mechanism, so if you already have something (maybe legacy) versioned this way, you better stick with it.</li> </ul> <p>Why to pass Version as a parameter:</p> <ul> <li>We can guarantee the stability of their REST API's URL paths, even through future versions of the API.</li> <li>You can always return a DEFAULT resource without a version even specified. etc.</li> <li>Easily change from -preview or -beta to the released and thus, make your development a bit more agile and iterative.</li> <li>Gives MORE transparency to the clients -&gt; more information on the date of release</li> <li>Split out API BEHAVIOUR from RESOURCE.</li> <li>Support <a href="proxy.php?url=https://github.com/Microsoft/api-guidelines/blob/master/Guidelines.md#12-versioning" rel="noopener noreferrer">Group versioning</a> feature that allows developers to look up a single version number and use it across multiple endpoints. Group version numbers are well known, and services SHOULD reject any unrecognized values.</li> </ul> <p>Phew. Quite a few notes, ha?</p> <p>Fun thing is that nothing of that is actually mentioned in the course. That's why I wanted to bring this example of the need for questions, which appear right after you start practicing.</p> <h2> More to consider for Public API </h2> <p>Approach to API Versioning should not be afterthought. Each time you want to change a mandatory parameter, or payload formats, replace some old error codes or change a behavior...consider adding new API methods and publishing new versions.</p> <p>It would be even better if you embed version numbers in the data structure. You may not need that in the beginning, but you will thank yourself when you want to scale (event-driven architecture, etc.).</p> <h2> Conclusion </h2> <p>Following those rules will make your Public API clients happier and your life a bit easier (but a lot, still).</p> <p>As you may notice, in general, there are many "up to you's" in the API versioning, starting from policies and right to the technical details. But that is not usually part of any book or a course and this is something you have to solve on your own. </p> <p>Next time, before finishing your training course, take your time to practice a little bit. I'm sure you will find many places to raise a hand and discuss with your experienced colleagues or a community.</p> <h2> Resources </h2> <p><a href="proxy.php?url=https://github.com/Microsoft/api-guidelines/blob/master/Guidelines.md#12-versioning" rel="noopener noreferrer">Microsoft API guidelines</a><br> <a href="proxy.php?url=https://sachinsu.github.io/posts/restapiversioning/" rel="noopener noreferrer">ASP.NET versioning examples</a></p> emptystring GraphQL trade offs Anton Yarkov Sat, 12 Nov 2022 19:17:34 +0000 https://dev.to/optiklab/graphql-trade-offs-ipk https://dev.to/optiklab/graphql-trade-offs-ipk <p><strong><em>It's very easy to fall in love with technology sold by professional marketers. However, software engineering is hard because there are no one solution to fit every case.</em></strong></p> <p><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--eEFkdpFH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cqcp79afuekis0lotxwk.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--eEFkdpFH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cqcp79afuekis0lotxwk.png" alt="Image description" width="499" height="484"></a></p> <p>GraphQL is under the focus for a couple of years already. Before you add this nice-looking abbreviation to your resume, I would like to share summarized points and thoughts on it based on production experience. There is a fresh good material from famous Alex Xu I would like you to watch first, so I don't need to get into details here and do not repeat here.</p> <p><iframe width="710" height="399" src="proxy.php?url=https://www.youtube.com/embed/yWzKJPw_VzM"> </iframe> </p> <p>While I fully support concerns mentioned in the video, there are a few things I would like to add:</p> <h4> GraphQL is not going to replace REST or SOAP. </h4> <p>It's just one more way to build an API in a bit different but definitely not "<em>the best one because the new one</em>". I would even say it's more like a SOAP way for even more specific business-cases. Below I will give more details on that point by showing parallels in between.</p> <h4> GraphQL is created to solve a few specific problems </h4> <p>It is covering the cases where:</p> <ol> <li><p>You're doing business where most of the users are running from "smartphones". Such users are frequently switching networks/ISPs while they go and may work with unreliable or poor connection. Basically, GraphQL allows you to execute less number of requests from the mobile application to the API. However, <em>the devil is in the details.</em></p></li> <li><p>You can easily fit all the data (that you need to use on the client/UI) into a single data model (or data graph). It might be because you work in the Big Data, or there are uniform way of represent the information from various sources.<br> GraphQL allows you to optimize your development team throughput by letting your Front-End team think on what they want to get from the API, instead of your back-end team propose their data-model or guess on what is the most optimal API and data model. It's not very efficient to wait for API change (by Back-End engineers) every time you need to change the UI side. Thus, UI developers are happy because they can play with data and find various ways to use it or show it. However, are they knowledgeable enough to do this safely? This <em>benefit comes with a cost.</em></p></li> </ol> <h4> GraphQL brings trade offs </h4> <p>It requires you to be ready for a few things, though:</p> <ol> <li>You have to generate the schema of the API, just like in case of SOAP. That's nothing new and may look normal when you are alone responsible for both Back-End and Front-End (building a Hello World). And it's OK while your team fits in one room (or shares 2 pizza :) ) and can easily speak to each other very quickly. However, if your team is large and/or updates API objects frequently, you can quickly get tired of this process. Every time back-end engineers update API and regenerate schema, they have to connect with some peers that need to pick-up their updates. They have to spend more effort on building and maintaining additional CI automation steps, as well as communicate better and more frequently using chats, messengers, etc. Clearly, your <strong>development process becomes more tightly coupled and fragile</strong>. It doesn't enable so much flexibility in experimentation as REST.</li> <li>It takes control of your <strong>API performance</strong> out of the hands of your back-end development team and into the clients hands (UI/Client development team). Using its strong typing GraphQL enables so-called Client‐specified queries. In worse cases it leads to <a href="proxy.php?url=https://stackoverflow.com/questions/97197/what-is-the-n1-selects-problem-in-orm-object-relational-mapping">N+1 problem</a>. Flexibility that GraphQL provides to the Client team comes with the possibility to make unpredictably slow database queries. Of course this can be mitigated by manual or automated testing, but worth keeping in mind. And definitely it might not be acceptable for some cases.</li> </ol> <h4> On GraphQL schema-stitching </h4> <p>To resolve a problem of coupling there is an idea of schema-stitching. In simple words, you can take two or more GraphQL schemas, and merge them altogether for the client's use, while generating them separately. Schema Federation is a way to go.</p> <p>The problem, though, is that even this idea was far from a perfect solution. That's why companies building complicated tooling around GraphQL and, specifically, Apollo recently (as of summer 2022) introduced <a href="proxy.php?url=https://www.apollographql.com/blog/announcement/backend/the-supergraph-a-new-way-to-think-about-graphql/">Feration v2.0 and an idea of supergraph</a>. </p> <p>To my opinion, this all a sign of problems at the core of the protocol. It adds unnecessary cognitive load and architecture complexity to the world of software sandcastles, which become even more fragile.</p> <h4> The End </h4> <p>Clearly, GraphQL narrows down the number of use cases it solves and is <em>not a silver bullet</em>. You have to evaluate trade-offs mentioned in the video and listed above before you get amazed on the benefits it provides.</p> <h4> References </h4> <p><a href="proxy.php?url=http://spec.graphql.org/">GraphQL Specification</a><br> <a href="proxy.php?url=https://github.com/graphql/graphql-over-http/blob/main/spec/GraphQLOverHTTP.md">GraphQL over HTTP</a><br> <a href="proxy.php?url=https://www.giorgi.dev/dotnet/introducing-graphqlinq-strongly-typed-graphql-queries-with-linq-to-graphql/">Introducing GraphQLinq strongly typed GraphQL queries with LINQ to GraphQL</a><br> <a href="proxy.php?url=https://www.youtube.com/watch?v=HuN94qNwQmM">GraphQL API with .NET 5 and Hot Chocolate</a><br> <a href="proxy.php?url=https://www.youtube.com/watch?v=ZQL7tL2S0oQ">Short GraphQL example in Node.js and Express JS framework</a><br> <a href="proxy.php?url=https://www.youtube.com/watch?v=anW5Qpuh5kI">Introduction to GraphQL</a><br> <a href="proxy.php?url=https://www.youtube.com/watch?v=6duKEokTZ44">Creating a GraphQL back-end with .NET GraphQL library</a><br> <a href="proxy.php?url=https://www.youtube.com/watch?v=Hh8L6I2BV7k">Getting started with GraphQL and HotChocolate</a><br> <a href="proxy.php?url=https://www.youtube.com/watch?v=1VEMqTw8xSo">GraphQL schema design from the creator of HotChocolate GraphQL library</a><br> <a href="proxy.php?url=https://www.youtube.com/watch?v=q-5MUqLAEFs&amp;list=WL&amp;index=98">GraphQL and ASP NET Core - .NET Oxford - November 2019</a><br> <a href="proxy.php?url=https://www.youtube.com/watch?v=bRnu7xvU1_Y&amp;list=WL&amp;index=82">Building Modern APIs with GraphQL</a><br> <a href="proxy.php?url=https://www.youtube.com/watch?v=Hh8L6I2BV7k&amp;list=WL&amp;index=84&amp;t=3s">Getting started with GraphQL and HotChocolate</a><br> <a href="proxy.php?url=https://www.youtube.com/watch?v=XoM1zHZug68&amp;list=WL&amp;index=86&amp;t=24s">GraphQL Part 2 - Setting up project</a><br> <a href="proxy.php?url=https://www.youtube.com/watch?v=0Aqy8h0W3RQ&amp;list=WL&amp;index=86&amp;t=1460s">Beyond REST with GraphQL in .Net core - Irina Scurtu</a><br> <a href="proxy.php?url=https://www.youtube.com/watch?v=JiiELR_UiDo&amp;list=WL&amp;index=88&amp;t=2s">GraphQL Server with .NET Core Tutorial</a><br> <a href="proxy.php?url=https://www.youtube.com/watch?v=1VEMqTw8xSo&amp;list=WL&amp;index=89">GraphQL Schema Design</a><br> <a href="proxy.php?url=https://blog.jeremylikness.com/blog/graphql-for-dotnet-developers/">GraphQL for dotnet developers</a><br> <a href="proxy.php?url=https://www.youtube.com/watch?v=YgRmgHPTXr4&amp;list=WL&amp;index=79">Владимир Цукур — GraphQL — API по-новому</a><br> <a href="proxy.php?url=https://www.youtube.com/watch?v=kZs7CXrtT-s&amp;list=WL&amp;index=81">GraphQL #0 Введение</a><br> <a href="proxy.php?url=https://github.com/nodkz/conf-talks#apolloclient-%D0%B8%D0%BB%D0%B8-relay-%D1%81-%D1%84%D1%80%D0%B0%D0%B3%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D0%BC%D0%B8-%D0%B2%D0%BE%D0%BB%D0%BE%D1%81%D0%B0%D1%82%D1%8B%D0%B9-graphql-holyjs-piter-2019">Статьи и презентации про GraphQL</a><br> <a href="proxy.php?url=https://www.youtube.com/watch?v=OezyScvU9-c&amp;list=WL&amp;index=78">Redux не нужен. GraphQL и Apollo Client</a><br> <a href="proxy.php?url=https://www.youtube.com/watch?v=VQWkY2HpZnA&amp;list=WL&amp;index=89&amp;t=4063s">Разработка GraphQL API на ASP.NET Core // Бесплатный урок OTUS</a><br> <a href="proxy.php?url=https://www.youtube.com/watch?v=GMJNSBur-lM&amp;list=WL&amp;index=81">Apollo GraphQL with React. Полный курс</a><br> <a href="proxy.php?url=https://github.com/nodkz/conf-talks/tree/master/articles/swagger">Why Swagger is dead</a></p> graphql api backend Writing self-documented code with low cognitive complexity Anton Yarkov Sun, 11 Jul 2021 15:06:44 +0000 https://dev.to/optiklab/writing-self-documented-code-with-low-cognitive-complexity-3k2l https://dev.to/optiklab/writing-self-documented-code-with-low-cognitive-complexity-3k2l <p>In this article, I will share practical and straightforward advice on how to stop (holy wars) arguing about code quality and find measurable arguments about the necessity of refactoring, simplification, adding comments or documentation for the code. While I’m going to refer to exact commercial tools in the second half of the article, I should say that I’m not affiliated anyhow with the tool's authors. The tools are available via free community licenses as well as commercial licenses. </p> <p><strong>The goal</strong> of this article is not the tool itself but to tell you about useful metrics that will allow your team to write self-documented code, produce better software and improve the programmers life.</p> <h1> Self-documented code </h1> <p>I frequently get the answer "Go read the code" when I ask developers to provide documentation or explain their code. I'm sure I'm not alone. Many developers feel their code self-documented by default. Not many people understand that creating self-documented code is a <strong>complicated design task</strong>.</p> <p>Why is that? Let's take a look into the way we read code:</p> <ol> <li>First, we are trying to figure out the aim of this code: <strong>WHAT</strong> was the task and the goal (and real experts also try to dig into <strong>WHY</strong>).</li> <li>Next, knowing <strong>WHAT</strong>, we are reading the code to understand <strong>HOW</strong> the author achieves this.</li> </ol> <p>While it's possible to do vice versa, it's tough to do in any production solution. Production code tends to be complex due to additional requirements to integrate with other system components like monitoring, logging, or security; to be resilient, scalable, configurable; to support multiple platforms, versions, etc. </p> <blockquote> <p>Some people claim that SQL and HTML answer both <strong>HOW</strong> and <strong>WHAT</strong> at the same time. I will let myself disregard this comment here and concentrate on general-purpose languages.</p> </blockquote> <p>Doing vice-versa-analysis, software engineers should figure out what the purpose of this code is, <strong>WHAT</strong> it mainly does, and (finally) <strong>WHAT</strong> it missing. That is usually called Mental Model. Whenever how simple or complex it is, there is always some Mental Model underlying the code (even the bad one). It might be a domain model or any other way to express the thinking process. There are many concrete rules to follow to make your code more clean, readable, and understandable. As we know, there are many books has been written on this topic. But if to sum up all of this, there is only one way to write self-documented code: the developer should write the code to uncover the Mental Model and express important model parts while hiding unnecessary implementation details. Very frequently, developers focus on implementation details like frameworks, databases, protocols, and languages, and it makes the task of understanding the model very hard. </p> <p>Questions HOW and WHAT are orthogonal because there are several ways to achieve the same goal. Imagine climbers analyze the better way to reach the mountain peak by different paths. They consider many various aspects, summing their own experience and common knowledge about the mountain relief, weather and air conditions, time of the year, the level of readiness of the group, etc. Finally, they select the optimal path to climb. Optimal path doesn't explain all of these aspects but allows the group to put the flag on the peak. </p> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fabvc5v3d6up210ic25yr.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fabvc5v3d6up210ic25yr.jpg" alt="Ways to climb the mountain" width="800" height="168"></a></p> <p>As I see it, the Mental model shows the explicit dependency of the self-documented code from the author's design skills that allow him to make code more readable.</p> <blockquote> <p>Mental model answers to a question the <strong>WHAT</strong>, while code is telling the <strong>HOW</strong>.</p> </blockquote> <p>As I see it, the Mental model shows the explicit dependency of the self-documented code from the <strong>author's design skills</strong> that allow him to make code more readable.</p> <h1> Measuring readability of the code </h1> <p>Frederick Brooks, in his famous paper <a href="proxy.php?url=https://en.wikipedia.org/wiki/No_Silver_Bullet" rel="noopener noreferrer">No Silver Bullet – Essence and Accident in Software Engineering</a> specified two types of Complexity:</p> <ul> <li> <em>Essential complexity</em> – is caused by the problem to be solved, and nothing can remove it</li> <li> <em>Accidental complexity</em> – relates to the problems which engineers create and can fix.</li> </ul> <p>Many years have passed, but we still cannot measure it precisely. The well-known metric <strong>Cyclomatic Complexity</strong> (invented in 1976) tightly relates to the lines of code. While it's an excellent way to measure code coverage, it is not a way to measure ComplexityComplexity. Here is the problem showcase:</p> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F15kp94yn004vs0dsbv8d.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F15kp94yn004vs0dsbv8d.jpg" alt="Problem in measuring Cyclomatic Complexity " width="800" height="268"></a></p> <p>As you can see, Cyclomatic Complexity shows the same digits for the code from left and right. However, from the developer's viewpoint, left and right pieces of code are not identically complex. The left one is harder to read and to understand. We may believe the code is finding Sum of Prime Numbers which is a famously known problem. But an experienced developer will never think it solves the task until he verifies that it's true:</p> <ul> <li>Is the name of a method clearly states what the code is doing?</li> <li>Is code achieving the mission?</li> <li>Is code missing some use cases, and what are they? (i.e., what are the limitations of the code?)</li> <li>Etc.</li> </ul> <p>Now, imagine how hard it is to understand something more specific to the domains not very famous to others. Sonar Source released the <strong>Cognitive Complexity</strong> metric in 2017, and not many people know about it. However, I believe this is groundbreaking work that has to be widely adopted. As we can see, it works perfectly for the described example:<br> <a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4yxb58gmdbw7x8848y57.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4yxb58gmdbw7x8848y57.jpg" alt="Alt Text" width="680" height="384"></a></p> <p>You can find all the details in their <a href="proxy.php?url=https://redirect.sonarsource.com/doc/cognitive-complexity.html" rel="noopener noreferrer">paper</a>, and on <a href="proxy.php?url=https://www.youtube.com/watch?v=x5V2nvxco90" rel="noopener noreferrer">youtube</a>. But telling short, the metric is based on three rules:</p> <ol> <li>Ignore structures that allow multiple statements to be readably shorthanded into one. </li> <li>Increment (add one) for each break in the linear flow of the code <ul> <li>Loop structures: <strong>for, while, do while</strong>, ...</li> <li>Conditionals, ternary operations, <strong>if, #if, #ifdef</strong> </li> </ul> </li> <li>Increment when flow-breaking structures are nested</li> </ol> <p>You can find this metric using the static code analysis tools produced by Sonar Source (SonarQube, SonarCloud, and its freely available SonarLint IDE extension). SonarQube is available in <a href="proxy.php?url=https://www.sonarsource.com/plans-and-pricing" rel="noopener noreferrer">free community edition</a>.</p> <p>In Sonar Cloud look into <strong>Project -&gt; Issues -&gt; Rules -&gt; Cognitive Complexity</strong></p> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Funcq5hpbh0kjkep1dge5.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Funcq5hpbh0kjkep1dge5.jpg" alt="How find Cognitive Complexity in Sonar Cloud" width="800" height="275"></a></p> <p>It is easy to find the full report with the line-by-line explanation of the penalty assignment:</p> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm73ow3acfm0fme7kkv02.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm73ow3acfm0fme7kkv02.jpg" alt="Detailed line by line penalty assignment" width="800" height="442"></a></p> <p>The <strong>default thresholds</strong> for code quality are: </p> <ol> <li>Cognitive Complexity</li> <li>15 (most of the languages) </li> <li>25 (C-family languages) </li> <li>Cyclomatic Complexity = 10 (all languages) </li> </ol> <p>It's essential to know both Cyclomatic and Cognitive Complexity thresholds since one metric might be larger than the other and vice versa. Let’s take a look into a simple production example (here is how to find it: <strong>Sonar Cloud -&gt; Measures -&gt; select Complexity filter</strong>): </p> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fut4d4lfinufipdqzck3e.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fut4d4lfinufipdqzck3e.jpg" alt="Cyclomatic and Cognitive complexity comparison" width="800" height="460"></a></p> <p>You can find the total complexity measurement for the group of files (folder) on the left side, and here we can see twice times difference: 134 against 64. You can see file-by-file differences as well. <em>LoggerHelper file isn't so bad in Cyclomatic Complexity, but there are ways to improve its Cognitive Complexity</em>. And for other files, we may see a controversial picture: Cyclomatic Complexity is bigger than Cognitive one. </p> <h1> Outcomes </h1> <p>It looks like we have a way to measure code complexity, and I wish more tools to implement this, but we already can start using this quickly and straightforwardly. The Cognitive Complexity metric still doesn't tell us how good code is expressing the mental model, but it is already excellent data for you to start moving towards good software. Using these metrics, you can start building a transparent dialogue between development and business on necessary resources and roadmaps for better code and product quality: </p> <ul> <li>Measure cognitive Complexity Complexity in all parts of your codebase to assess how hard it is to introduce new developers, implement and deliver new changes, etc.</li> <li>Use measurable goals for planning your development cycles and any activities for improving your code, like refactorings.</li> <li>Prioritize improvements for the most critical parts of your codebase.</li> <li>See the places that should cover with additional documentation.</li> <li>Stop arguing and holy waring, conflicting, and stressing with colleagues on code quality.</li> <li>Make the life of your colleagues more fruitful (everyone wants to achieve results on their task as quickly as possible and then meet with friends and family).</li> </ul> <p>I hope I shared some exciting food for you to start digging into this and use <strong>Cognitive Complexity</strong> in everyday programmer's life.</p> codequality design oop complexity A pragmatic guide to starting Communities of Practice in the organization Anton Yarkov Tue, 18 May 2021 17:25:54 +0000 https://dev.to/optiklab/a-pragmatic-guide-to-starting-communities-of-practice-in-the-organization-25n3 https://dev.to/optiklab/a-pragmatic-guide-to-starting-communities-of-practice-in-the-organization-25n3 <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq742ja5iw440enbn9cpc.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq742ja5iw440enbn9cpc.jpg" alt="Graphics made by Max Degtyarev (https://www.behance.net/maxdwork)" width="727" height="433"></a><em>Graphics made by Max Degtyarev (<a href="proxy.php?url=https://www.behance.net/maxdwork" rel="noopener noreferrer">https://www.behance.net/maxdwork</a>)</em></p> <h2> What is a community of practice? </h2> <blockquote> <p>A community of practice (CoP) is a group of people who share a concern or a passion for something they do and learn how to do it better as they interact regularly. Unlike a regular team, which is held together by a shared task, a community of practice is held together by the “learning value” members find in their interactions, and usually contains members of multiple existing teams.</p> </blockquote> <p>As an example, Automation Quality Assurance engineers spread across multiple scrum teams should define their community of practice to regularly interact with each other with a goal of maintaining and improving automation best practices across teams in your organization. As counter-examples, a scrum team itself, the whole <a href="proxy.php?url=https://www.scaledagileframework.com/agile-release-train/" rel="noopener noreferrer">Agile Release Train</a>, or the whole department are not communities of practice.</p> <p>Three key pillars of a community of practice:</p> <p><strong>Domain</strong> is the shared domain of interest. It is a concern, a set of problems, or a passion about a topic. Members of communities of practice are all committed to their designated domain to improve their craft, collaborate with other employees, and strategize in solving relevant hurdles within a company.</p> <p><strong>Community</strong> is where members interact and learn from each other about how they can improve their work and tackle discoveries in their chosen domains. Members engage in joint activities and discussions, help each other, and share information.</p> <p><strong>Practice</strong> is where the members share knowledge, discover methods, learn cases, and exchange tools that can help the members solve common problems. This is a shared repertoire of resources: experience, stories, tools, ways of addressing recurring problems, etc.</p> <p>Communities of practice can be organized around any one of three aspects of work:</p> <ul> <li>Role</li> <li>Business problem</li> <li>Process</li> </ul> <h2> Why to use communities of practice? </h2> <p>Communities of practice (CoPs) connect people with common goals and interests for the purpose of sharing resources, strategies, innovations, and support. CoPs support the transmission and expansion of knowledge and expertise for leaders, learners, and professionals in any field or discipline. CoPs contribute to a more connected and collaborative global community in your field of expertise.</p> <p>Members of the community can brainstorm new tools and processes, and try or explore new ways of communication/organization/development that still incorporates the company’s objectives. Communities of practice are not only limited to solving problems and meeting the company’s objectives. CoPs can also help you:</p> <ul> <li>Reuse assets that can lower expenses,</li> <li>Discuss and disseminate developments in the field, and</li> <li>Identify and resolve gaps within the company.</li> </ul> <p>Three characteristics or qualities define a practice:</p> <ol> <li>Joint Enterprise: The members of a CoP are there to accomplish something on an ongoing basis. They have some kind of work in common and they clearly see the larger purpose of that work. They have a mission.</li> <li>Mutual Engagement: The members of a CoP interact with one another not just in the course of doing their work but to clarify that work, to define how it is done, and even to change how it is done.</li> <li>Shared Repertoire: The members of a CoP not only have work in common, but also methods, tools, techniques, and even language, stories, and behavior patterns unique to their domain.</li> </ol> <p>Two indicators stand out from all the rest:</p> <ul> <li>People have a strong sense of identity tied to the community (e.g., as technicians, salespeople, researchers and so on).</li> <li>The practice itself is not fully captured in formal procedures. People learn how to do what they do and come to be seen as competent (or not) by doing it in concert with others.</li> </ul> <blockquote> <p>Thus, the main responsibility of communities of practice is to build a community of highly engaged and collaborative colleagues to effectively grow, train, and coach each other in the domain or field, find ways to solve identical problems, and unify approaches, tools, and methods used across the organization.</p> </blockquote> <h2> Key concepts for successul implementation of CoPs </h2> <h3> Facilitator’s role is essential </h3> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkiogwqsab7nr5pq4g2bg.JPG" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkiogwqsab7nr5pq4g2bg.JPG" alt="Alt Text" width="273" height="260"></a></p> <p>Our practical experience shows that most thriving CoPs have several attributes in common:</p> <ul> <li>Someone becomes a <strong>Facilitator</strong> and dedicates some time to organizing meetings and notes, engaging in conversations, driving learning and brainstorming activities, and keeping up communication inside and outside the group.</li> <li>Each community of practice decides whether they are private or public, allowing anyone to join as a listener or contributor at any time. Still, all CoPs maintain transparent and <strong>regular external communication</strong> with executive leadership (CTO, VP, CEO, etc.) and/or engineering management (Engineering Managers, Scrum Masters, Architects) about the results (even partial) of the work they have done, lessons they learnt, problems they solved. This communication might be informal (chat, email, a voice call, etc.), and it’s up to members to decide who is responsible for it. But if no one is assigned, it’s the <strong>Facilitator’s</strong> responsibility.</li> <li>When only one person takes on the <strong>Facilitator role</strong>, it usually takes up to 20% of that person’s time to fulfill all the needs of the CoP. </li> </ul> <p><strong>The Facilitator’s role</strong> is essential for CoPs to be productive, as it helps to maintain healthy communication, synchronize across teams, drive engagement and motivation, and ultimately helps get important things done. <strong>It is also beneficial for person acting as a Facilitator.</strong> The work can help grow or pivot your career by gaining leadership experience, improving soft skills, and learning more about the company, teams, people, tools, and other accompanying staff. </p> <h3> Facilitating patterns to use </h3> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flrbfe10u59dc33oa9quo.JPG" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flrbfe10u59dc33oa9quo.JPG" alt="Alt Text" width="256" height="253"></a></p> <ol> <li>You should provide the infrastructure needed by the community to meet their objectives and be productive. This infrastructure may be information, supplies, or allotted time for discussion/interaction and so forth. A web page with links to relevant resources might be useful, but the real action in a CoP is in the interactions among members. Start small and evolve.</li> <li>Keeping things simple and informal, since all members of a community of practice have their professional obligations to the company and providing them minimal responsibilities is highly recommended. Do not force them to overwork; let their creativity and ideas flow naturally. Give them the privilege of expressing their thoughts. Requiring CoP members to attend several meeting in just one day makes them feel exhausted and unproductive. Levying demands and imposing strong expectations can quickly convert a CoP into a project team focused on tasks and deliverables. The team will drive toward satisfying the boss — instead of producing and sharing new knowledge.</li> <li>The success of a CoP hinges on trust between and among its members.</li> <li>Do stay focused on the primary purposes of a CoP: to learn from each other through sharing and collaborating.</li> <li>Appreciating and identifying the efforts of the members of the communities of practice will motivate them. Remember the reason you gathered them into a CoP in the first place. A simple gift certificate from a related event in their domain or allowing them to have at least one free day to discuss and execute their ideas makes them feel more valued in the company.</li> </ol> <h3> Best practices for organizing communities of practice </h3> <ul> <li>Use a “light hand”. Mandates to “launch” CoPs may create resistance to what could be viewed as the next corporate program to wait out.</li> <li>Send a continuing message reinforcing the business value of CoPs.</li> <li>Provide information to others about what CoPs are, how they operate, how to support and encourage them — and how to avoid undercutting them.</li> <li>Encourage appropriate professionals to form CoPs that focus on key business issues at the unit, sector, process, function, or company level.</li> <li>Seek out and subtly promote a few exemplar CoPs. Point to solid results and value added — but don’t overdo it.</li> <li>Spend time with a few existing CoPs to learn first-hand how they operate.</li> <li>Leverage outside events (e.g., bring attendees together afterward and de-brief the sessions attended).</li> </ul> <h2> Work items </h2> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi6ye370nc1ckngd7zw68.JPG" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi6ye370nc1ckngd7zw68.JPG" alt="Alt Text" width="286" height="270"></a></p> <p>It’s not a surprise that members of a CoP may generate some amount of work to do. But members of a CoP usually have their daily jobs in the Scrum teams. And it might be hard to prioritize one another. It’s helpful to go through the following steps to execute an additional amount of work desired by members of a CoP:</p> <ol> <li>Decide whether some members should do this work as part of their main daily job in the Scrum team, or as an additional activity outside the group.</li> <li>All the work finished as part of the team’s backlogs should be demoed in the usual cadence (i.e., at the end of iteration) and should not be treated as something different. It should also be welcoming to demo anything CoP did as an additional activity inside a CoP.</li> </ol> <h3> Work inside vs. outside the CoP </h3> <blockquote> <p>The rule of thumb is to decide based on work estimate: if this work item is Epic-sized, then it is a candidate for the Scrum team backlog. Otherwise (Story-sized), it can be an additional activity.</p> </blockquote> <p>Usually, Epic-sized work items should be transparently discussed with Scrum team members (including QA, PO, SM, and devs). A member of CoP should convince team to take the item into a team backlog, plan, execute and deliver this work.</p> <p>Story-sized work items can be completed relatively quickly and should not require the allocation of many resources or affect any Scrum team commitments. Thus, this work might be considered lightweight.</p> <h3> The ways to demo the work done by the CoP </h3> <p>How to demo results of CoP’s work:</p> <ul> <li>Present results during a regular demo ceremony</li> <li>Write and publish brief articles or results descriptions in company or unit communication vehicles</li> <li>Create special communications: your CoP might periodically produce and distribute its own newsletter or blog</li> <li>Invite others to special briefings where your CoP members share their learning and results</li> <li>Publish articles in external journals or magazines and then distribute them internally (after clearing through proper company channels)</li> </ul> <h2> Why exert the effort to market your CoP’s results? </h2> <p>Several reasons:</p> <ul> <li>To generate enthusiasm among current members</li> <li>To ensure continued resources and support from your sponsor(s)</li> <li>To stimulate interest in joining from high-potential prospective members</li> <li>To promote interest on the part of your colleagues in finding out what the members of your CoP have learned and, as a result, to share what they have learned with your CoP</li> <li>To better leverage the knowledge created and the learnings generated by your CoP</li> </ul> <h2> Lifecycle </h2> <p>Last but not least, to understand is that like any other group of people, a community of practice has <strong>phases</strong> or <strong>lifecycle stages</strong>. First, we define a new CoP. Then we have to find the best way to collaborate (including communication, learning, and working together) and run it. Later, at some point, the group may feel the need <strong>to transform to another kind of CoP</strong> or to shut down forever. Those phases are meant to be flexible, so any phase can be skipped or repeated based on your community of practice’s needs.</p> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6m0enczrcyuq9a50usj6.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6m0enczrcyuq9a50usj6.png" alt="CoP lifecycle phases" width="800" height="483"></a><em>Figure 1. CoP lifecycle phases</em></p> <p>Usually, groups of people need some time to pass each stage, whether fast or slow. It’s not expected that every community of practice will immediately move quickly. The four-stage Bruce Tuckman model of team development — Forming, Storming, Norming, and Performing — can be applied to CoPs. It’s normal that things change going forward and a shutdown decision may be made by the CoP members themselves. It might be helpful to consider transforming instead of shutting down, to ensure that your CoP is adaptive to changes in funding or goals.</p> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9x3nbif9wq0xbletlakw.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9x3nbif9wq0xbletlakw.png" alt="Bruce Tuckman’s model of team development" width="517" height="319"></a><em>Figure 2. Bruce Tuckman’s model of team development</em></p> <h2> Checklists </h2> <h3> I want to start a new community of practice. What should I do? </h3> <ul> <li>☑ Start by creating a Community of Practice wiki-like page in the organization web portal. The page should cover following attributes:</li> <ul> <li>☐ The facilitator is identified (i.e. who drives creation, leads this CoP, facilitates necessary changes)</li> <li>☐ The sponsor is identified (i.e. who can approve creation of this CoP based on proposal)</li> <li>☐ Aspect of work selected (a role, business problem, or process to focus on)</li> <li>☐ Roles identified</li> <li>☐ Value/benefits</li> <li>☐ Sponsorship/support (where resources or support will be needed)</li> <li>☐ Interactions</li> <li>☐ Outcomes</li> <li>☐ List members</li> </ul> <li>☐ Proposal presented to the sponsor and approvals are received</li> <li>☐ First meetings scheduled</li> <ul> <li>☐ Initial agreements with members received</li> <li>☐ Agenda is set for the initial interaction</li> <ul> <li>☐ Issues/interests listed</li> <li>☐ Problems listed</li> <li>☐ Goals/outcomes listed</li> </ul> <li>☐ Interaction modes selected and agreed</li> <ul> <li>☐ Email distribution groups</li> <li>☐ 1-1 meetings</li> <li>☐ Scheduled/unscheduled</li> <li>☐ Conference calls/group meetings</li> <li>☐ Videoconferencing</li> </ul> </ul> <li>☐ Run the following reccurring practices / events using Best Practices covered above</li> <ul> <li>☐ Meetings</li> <li>☐ Processes</li> <li>☐ Interactions</li> </ul> <li>☐ Success is measured periodically and adjusted</li> </ul> <h3> How can I measure the success of my community of practice? </h3> <p>CoPs don’t just happen; it takes hard work to form and sustain them. Regardless of your role – Sponsor, Champion, Facilitator, Practice Leader, Information Integrator, or Member – all members of your CoP should take some responsibility for marketing and promoting their CoP. Each member individually, and your CoP collectively, will want to market the value of your CoP. This means generating interest in your CoP and demonstrating its value. Both members and non-members need to know the value of their CoP: what real benefits accrue to the members and the company from the investment of time, energy, and resources in the CoP? </p> <p>Check if your CoP is successful with this checklist of indicators below. Complete alignment with the checklist is not expected, but the more more boxes you check, the closer to ideal your CoP is:</p> <ul> <li>☑ Members of the CoP share a fairly broad consensus about who is "in" and who is "out"</li> <ul> <li>☐ A shared, evolving language (e.g., special terms, jargon, "shortcuts" such as acronyms, etc.)</li> <li>☐ Perspectives reflected in language that suggest a common way of viewing the world (e.g., shared analogies, examples, explanations, etc.)</li> <li>☐ Shared ways of doing things together (i.e., common practices and beliefs about best practices)</li> <li>☐ A widespread and shared awareness of each others’ competencies, strengths, shortcomings and contributions</li> </ul> <li>☐ Members of the CoP share experiences and know-how</li> <ul> <li>☐ Continuing mutual relationships – regular, work-related interactions</li> <li>☐ Common tools, methods, techniques and artifacts such as forms, job aids, etc.</li> <li>☐ Capture/codify new know-how</li> </ul> <li>☐ Members of the CoP experiment with new ideas and novel approaches</li> <ul> <li>☐ A rapid flow of information between and among members</li> <li>☐ Quick diffusion of innovation among members (e.g., rapid transfer of best practices)</li> </ul> <li>☐ Members of the CoP discuss common issues and interests, collaborate in solving problems</li> <ul> <li>☐ Conversations come quickly to the point (i.e., no lengthy lead-ins)</li> <li>☐ Problems are quickly framed (i.e., a common understanding the milieu in which they all operate)</li> <li>☐ Group analyze causes and contributing factors</li> </ul> <li>☐ Members of the CoP evaluate actions and effects</li> <ul> <li>☐ Learning</li> <li>☐ Resolving issues</li> <li>☐ Solving business problems</li> </ul> </ul> <h1> Where to learn more </h1> <ul> <li><a href="proxy.php?url=https://www.scaledagileframework.com/communities-of-practice/" rel="noopener noreferrer">https://www.scaledagileframework.com/communities-of-practice/</a></li> <li><a href="proxy.php?url=https://teachingcommons.lakeheadu.ca/communities-practice-quick-start-guide" rel="noopener noreferrer">https://teachingcommons.lakeheadu.ca/communities-practice-quick-start-guide</a></li> <li><a href="proxy.php?url=https://www.organisationalmastery.com/communities-of-practice/" rel="noopener noreferrer">https://www.organisationalmastery.com/communities-of-practice/</a></li> <li><a href="proxy.php?url=https://www.nickols.us/CoPStartUpKit.pdf" rel="noopener noreferrer">https://www.nickols.us/CoPStartUpKit.pdf</a></li> <li><a href="proxy.php?url=https://ktdrr.org/resources/rush/copmanual/CoP_Manual.pdf" rel="noopener noreferrer">https://ktdrr.org/resources/rush/copmanual/CoP_Manual.pdf</a></li> <li><a href="proxy.php?url=https://www.talent.wisc.edu/home/LinkClick.aspx?fileticket=B6rgxakCMtI%3D&amp;portalid=0" rel="noopener noreferrer">https://www.talent.wisc.edu/home/LinkClick.aspx?fileticket=B6rgxakCMtI%3D&amp;portalid=0</a></li> </ul> Working example of SAML Single Sign-On integration using C# Anton Yarkov Tue, 16 Feb 2021 21:32:22 +0000 https://dev.to/optiklab/working-example-of-saml-single-sign-on-integration-using-c-39mb https://dev.to/optiklab/working-example-of-saml-single-sign-on-integration-using-c-39mb <h2> What is Single Sign-On at all? </h2> <p>Suppose you have a web application that people are using to do one thing X, but you are doing it great. For example, it would be a web store allowing to order a custom T-shirt printing by uploading some funny and pretty images found in the internet.</p> <p>You are looking for ways to extend its functionality by adding some more capabilities for your users, but you don't want to lose the focus on this thing that you are doing best of all. </p> <p>One way would be to integrate your web application with other services, such as a cool image provider, a trends meter, a delivery service, etc. It would allow your users to seamlessly move between different services (for example: looking for a cool image, checking trends, and uploading this image to your custom printing service) and do multiple things using only your web application.</p> <p><a href="proxy.php?url=https://en.wikipedia.org/wiki/Single_sign-on" rel="noopener noreferrer">Single Sign-On</a> simplifies authentication flow and allows your users to sign in only once (on your web application) with their login/password (or even more innovative mechanisms, like <a href="proxy.php?url=https://fidoalliance.org/fido2/" rel="noopener noreferrer">FIDO2</a>, if you use such) and use more integrated services by simple clicks without the need to log in again and again. It allows you to extend the functionality of your product by adding more value with relatively low investments.</p> <h2> SAML </h2> <p><a href="proxy.php?url=https://en.wikipedia.org/wiki/Security_Assertion_Markup_Language" rel="noopener noreferrer">SAML</a> is one of the standard ways of doing Single Sign-On. For a long time, extensive enterprise services use this mechanism as one of the most secure and proven methods to exchange sensitive authentication and authorization information, like logins, passwords, emails, user accounts, etc. But it's not that complicated to add this solution between smaller businesses and enable cool integrations.</p> <p>SAML is one of the most secure way to integrate with third-party among many other options. It allows parties to use asymmetric encryption (<a href="proxy.php?url=https://en.wikipedia.org/wiki/RSA_(cryptosystem)" rel="noopener noreferrer">RSA</a>) based on secure X.509 certificates. As of 2021, the standard is in <a href="proxy.php?url=https://en.wikipedia.org/wiki/SAML_2.0" rel="noopener noreferrer">version 2</a>.</p> <p>I should note that it might be better for you to trust one of many existing boxed solutions with a proven history and from vendors responsible for their code and overall solution. However, as a developer, I know that businesses may need much more flexibility and customization in their products, so I want to provide more details and working example using C#, so you can easily reuse this with your ASP.NET application.</p> <h2> SAML Workflow </h2> <p>Usually, the "classic" SAML workflow includes 3 parties: </p> <ul> <li>Service Provider - this is a third-party service you want to integrate with</li> <li>Identity Provider - this is some (enterprise) trused authentication service, that is able to proof the user identify and tell the Service Provider that "he is OK!".</li> <li>User Agent - a browser with your Web Store opened by a user</li> </ul> <p>This "classic" scenario is suitable for the enterprise, and we are not going to stop for long here. You can see the workflow diagram below and it is explained in all details on <a href="proxy.php?url=https://en.wikipedia.org/wiki/Security_Assertion_Markup_Language" rel="noopener noreferrer">Wikipedia</a>.</p> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fclwvw3t4jiliq61i9wxn.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fclwvw3t4jiliq61i9wxn.png" alt="Classic SAML" width="800" height="601"></a></p> <p>However, we are a small business, right? So, usually, we don't have complicated agreements with Enterprise-level Identity Providers, but we still need to integrate with third-party. Let's see how it should look like for us.</p> <h2> First step of the integration </h2> <p>The first thing is an agreement with some service provider to have an integration with your service. This time we focus on SAML protocol specifically, but I know about tenish more custom integration algorithms used by many small and medium-sized services. They use custom HTTP API, REST, WCF, XML, and JSON-based data formats, JWTokens and OAuth, and sometimes even combine all things. Whatever option is used, they need to provide you with some configuration parameters to establish an initial test connection.</p> <p>For SAML-based integrations, they will ask you to provide so-called "metadata information." That's a small XML file that contains information about your service. This file is being used by service providers representatives to automatically create a certification (aka test) endpoint that would allow you to test the connection, resolve all the roadblocks and certify everything before you go public.</p> <p>The critical part here is the XML content of this file that must be signed with your private X.509 certificate (aka private key, in terms of RSA). Of course, you have to buy one. Some providers allow using self-signed credentials for testing purposes. That's a matter of your agreement with the service provider.</p> <p>I've prepared some code that is open source and publicly available on my GitHub account. <a href="proxy.php?url=https://github.com/optiklab/SAML-integration-utilities/tree/main/src/SamlIntegration.MetadataFileGenerator" rel="noopener noreferrer">This small console application</a> allows you to generate a Metadata.xml file and properly sign it with the private key.</p> <p>Now, you should send your Public X.509 Certificate (<a href="proxy.php?url=https://findanyanswer.com/how-do-i-install-a-pem-certificate-in-windows" rel="noopener noreferrer">as a *.crt file, or PEM text</a>) and this Metadata.xml via secure channel.</p> <p>As soon as service provider replies with some connection endpoints, the first part might be over.</p> <h2> Integration workflow </h2> <p>You have a web application that implements some authentication workflow and allows users to sign in with their login and password. It means that you are the Identity Provider, and by doing this, you can eliminate some redundant steps and make a process simpler. So, imagine that we combine Identity Provider and your Web Store into one entity, like this:</p> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkimn86te4zrrht4c43qk.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkimn86te4zrrht4c43qk.png" alt="Simple SAML scenario" width="658" height="508"></a></p> <p>It simplifies the overall picture. However, it's not that simple under the hood. What actually should happen in the end is we should have an XML document called <em>SAMLResponse</em> prepared and encoded in Base64. You have to send this document via an HTTP POST request to the service provider to authenticate the user and automatically redirect his browser to the third-party target service — see detailed steps in the diagram below.</p> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ceq2qkg7vorbefggucv.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ceq2qkg7vorbefggucv.png" alt="Under the hood of SAML authentication" width="800" height="408"></a></p> <p>Again, I've prepared <a href="proxy.php?url=https://github.com/optiklab/SAML-integration-utilities" rel="noopener noreferrer">a set of utility classes written in C#</a>, so you can easily see how to execute those steps in your web application.</p> <p>Below, you can find my comments on every important aspect of that process. But I believe, it will be much more easy to follow the steps in class <a href="proxy.php?url=https://github1s.com/optiklab/SAML-integration-utilities/blob/main/src/SamlIntegration.Utilities/SamlIntegrationSteps.cs" rel="noopener noreferrer">SamlIntegrationSteps.cs</a> -&gt; method <em>BuildEncodedSamlResponse(...)</em>.</p> <p><strong>Step 1.</strong> Creating SAML assertion XML is not harder than create a usual XML document. But you have to follow the SAML specification and documentation provided by your Service Provider to use the appropriate field names. Working example is here <a href="proxy.php?url=https://github1s.com/optiklab/SAML-integration-utilities/blob/main/src/SamlIntegration.Utilities/Helpers/SamlAssertionAlgorithms.cs" rel="noopener noreferrer">SamlAssertionAlgorithms.cs</a>.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight csharp"><code><span class="n">AssertionType</span> <span class="n">assertion</span> <span class="p">=</span> <span class="k">new</span> <span class="n">AssertionType</span> <span class="p">{</span> <span class="n">ID</span> <span class="p">=</span> <span class="p">...</span><span class="n">Assertion</span> <span class="n">Id</span> <span class="p">...,</span> <span class="n">IssueInstant</span> <span class="p">=</span> <span class="p">...</span><span class="n">UTC</span> <span class="n">Time</span><span class="p">...,</span> <span class="n">Version</span> <span class="p">=</span> <span class="s">"2.0"</span><span class="p">,</span> <span class="n">Issuer</span> <span class="p">=</span> <span class="k">new</span> <span class="n">NameIDType</span> <span class="p">{</span> <span class="n">Value</span> <span class="p">=</span> <span class="s">"...your Web Store URL here..."</span> <span class="p">},</span> <span class="n">Subject</span> <span class="p">=</span> <span class="k">new</span> <span class="n">SubjectType</span> <span class="p">{</span> <span class="n">Items</span> <span class="p">=</span> <span class="k">new</span> <span class="kt">object</span><span class="p">[]</span> <span class="p">{</span> <span class="k">new</span> <span class="n">NameIDType</span> <span class="p">{</span> <span class="n">Format</span> <span class="p">=</span> <span class="s">"urn:oasis:names:tc:SAML:2.0:nameid-format:emailAddress"</span><span class="p">,</span> <span class="n">Value</span> <span class="p">=</span> <span class="n">userData</span><span class="p">.</span><span class="nf">GetUserEmail</span><span class="p">()</span> <span class="p">},</span> <span class="k">new</span> <span class="n">SubjectConfirmationType</span> <span class="p">{</span> <span class="n">Method</span> <span class="p">=</span> <span class="s">"urn:oasis:names:tc:SAML:2.0:cm:bearer"</span><span class="p">,</span> <span class="n">SubjectConfirmationData</span> <span class="p">=</span> <span class="k">new</span> <span class="n">SubjectConfirmationDataType</span> <span class="p">{</span> <span class="n">NotOnOrAfter</span> <span class="p">=</span> <span class="p">...</span><span class="n">UTC</span> <span class="n">Time</span><span class="p">....</span><span class="nf">AddMinutes</span><span class="p">(</span><span class="m">3</span><span class="p">),</span> <span class="n">NotOnOrAfterSpecified</span> <span class="p">=</span> <span class="k">true</span><span class="p">,</span> <span class="n">Recipient</span> <span class="p">=</span> <span class="n">settings</span><span class="p">.</span><span class="n">Recipient</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> <span class="p">},</span> <span class="n">Conditions</span> <span class="p">=</span> <span class="k">new</span> <span class="n">ConditionsType</span> <span class="p">{</span> <span class="n">NotBefore</span> <span class="p">=</span> <span class="p">...</span><span class="n">UTC</span> <span class="n">Time</span><span class="p">...,</span> <span class="n">NotBeforeSpecified</span> <span class="p">=</span> <span class="k">true</span><span class="p">,</span> <span class="n">NotOnOrAfter</span> <span class="p">=</span> <span class="p">...</span><span class="n">UTC</span> <span class="n">Time</span><span class="p">....</span><span class="nf">AddMinutes</span><span class="p">(</span><span class="m">3</span><span class="p">),</span> <span class="n">NotOnOrAfterSpecified</span> <span class="p">=</span> <span class="k">true</span><span class="p">,</span> <span class="n">Items</span> <span class="p">=</span> <span class="p">...</span><span class="n">conditions</span> <span class="n">that</span> <span class="n">you</span> <span class="n">need</span><span class="p">...</span> <span class="p">},</span> <span class="n">Items</span> <span class="p">=</span> <span class="k">new</span> <span class="n">StatementAbstractType</span><span class="p">[]</span> <span class="p">{</span> <span class="k">new</span> <span class="n">AttributeStatementType</span> <span class="p">{</span> <span class="c1">// ReSharper disable once CoVariantArrayConversion</span> <span class="n">Items</span> <span class="p">=</span> <span class="p">...</span><span class="n">attributes</span> <span class="n">array</span><span class="p">...</span> <span class="p">},</span> <span class="k">new</span> <span class="n">AuthnStatementType</span> <span class="p">{</span> <span class="n">AuthnInstant</span> <span class="p">=</span> <span class="p">...</span><span class="n">UTC</span> <span class="n">Time</span><span class="p">...,</span> <span class="n">SessionIndex</span> <span class="p">=</span> <span class="p">...</span><span class="n">Assertion</span> <span class="n">Id</span> <span class="p">...,</span> <span class="n">AuthnContext</span> <span class="p">=</span> <span class="k">new</span> <span class="n">AuthnContextType</span> <span class="p">{</span> <span class="n">ItemsElementName</span> <span class="p">=</span> <span class="k">new</span> <span class="p">[]</span> <span class="p">{</span> <span class="n">ItemsChoiceType5</span><span class="p">.</span><span class="n">AuthnContextClassRef</span> <span class="p">},</span> <span class="n">Items</span> <span class="p">=</span> <span class="k">new</span> <span class="kt">object</span><span class="p">[]</span> <span class="p">{</span> <span class="s">"urn:federation:authentication:windows"</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> <span class="p">};</span> </code></pre> </div> <p><strong>Step 2.</strong> Signing of the SAML assertion can be done as described in official <a href="proxy.php?url=https://docs.microsoft.com/en-us/dotnet/standard/security/how-to-sign-xml-documents-with-digital-signatures" rel="noopener noreferrer">Microsoft docs</a>. Working example is here <a href="proxy.php?url=https://github1s.com/optiklab/SAML-integration-utilities/blob/main/src/SamlIntegration.Utilities/Helpers/SamlAssertionAlgorithms.cs" rel="noopener noreferrer">SamlAssertionAlgorithms.cs</a>.</p> <p><strong>Step 3.</strong> Encryption of the SAML assertion is implemented in <a href="proxy.php?url=https://github1s.com/optiklab/SAML-integration-utilities/blob/main/src/SamlIntegration.Utilities/Helpers/SamlAssertionAlgorithms.cs" rel="noopener noreferrer">SamlAssertionAlgorithms.cs</a> using System.Security.Cryptography.Xml.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight csharp"><code><span class="n">X509Certificate2</span> <span class="n">x509</span> <span class="p">=</span> <span class="p">...</span><span class="k">get</span> <span class="n">certificate</span><span class="p">...</span> <span class="n">xmlElement</span><span class="p">.</span><span class="n">SigningKey</span> <span class="p">=</span> <span class="n">x509</span><span class="p">.</span><span class="n">PrivateKey</span><span class="p">;</span> <span class="n">xmlElement</span><span class="p">.</span><span class="n">SignedInfo</span><span class="p">.</span><span class="n">CanonicalizationMethod</span> <span class="p">=</span> <span class="n">SamlSignedXml</span><span class="p">.</span><span class="n">XmlDsigExcC14NTransformUrl</span><span class="p">;</span> <span class="c1">// Create a reference to be signed. </span> <span class="n">Reference</span> <span class="n">reference</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Reference</span> <span class="p">{</span> <span class="n">Uri</span> <span class="p">=</span> <span class="s">"#"</span> <span class="p">+</span> <span class="n">referenceValue</span> <span class="p">};</span> <span class="n">reference</span><span class="p">.</span><span class="nf">AddTransform</span><span class="p">(</span><span class="k">new</span> <span class="nf">XmlDsigEnvelopedSignatureTransform</span><span class="p">());</span> <span class="n">reference</span><span class="p">.</span><span class="nf">AddTransform</span><span class="p">(</span><span class="k">new</span> <span class="nf">XmlDsigExcC14NTransform</span><span class="p">());</span> <span class="c1">// Add the reference to the SignedXml object. </span> <span class="n">xmlElement</span><span class="p">.</span><span class="nf">AddReference</span><span class="p">(</span><span class="n">reference</span><span class="p">);</span> <span class="c1">// Add an RSAKeyValue KeyInfo (optional; helps recipient find key to validate). </span> <span class="n">KeyInfo</span> <span class="n">keyInfo</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">KeyInfo</span><span class="p">();</span> <span class="n">keyInfo</span><span class="p">.</span><span class="nf">AddClause</span><span class="p">(</span><span class="k">new</span> <span class="nf">KeyInfoX509Data</span><span class="p">(</span><span class="n">certificate</span><span class="p">));</span> <span class="n">xmlElement</span><span class="p">.</span><span class="n">KeyInfo</span> <span class="p">=</span> <span class="n">keyInfo</span><span class="p">;</span> <span class="c1">// Compute the signature. </span> <span class="n">xmlElement</span><span class="p">.</span><span class="nf">ComputeSignature</span><span class="p">();</span> <span class="c1">// Put the sign as the first child of main Request tag.</span> <span class="n">xmlAssertion</span><span class="p">?.</span><span class="nf">InsertAfter</span><span class="p">(</span><span class="n">xmlElement</span><span class="p">,</span> <span class="n">xmlAssertion</span><span class="p">.</span><span class="n">ChildNodes</span><span class="p">[</span><span class="m">0</span><span class="p">]);</span> </code></pre> </div> <p><strong>Step 4.</strong> Adding the assertion document to the SAMLResponse is just about adding XML:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight csharp"><code><span class="n">XmlDocument</span> <span class="n">encryptedAssertion</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">XmlDocument</span><span class="p">();</span> <span class="c1">// Add namespaces</span> <span class="n">XmlDeclaration</span> <span class="n">xmlDeclaration</span> <span class="p">=</span> <span class="n">encryptedAssertion</span><span class="p">.</span><span class="nf">CreateXmlDeclaration</span><span class="p">(</span><span class="s">"1.0"</span><span class="p">,</span> <span class="s">"UTF-8"</span><span class="p">,</span> <span class="k">null</span><span class="p">);</span> <span class="n">XmlElement</span> <span class="n">encryptedRoot</span> <span class="p">=</span> <span class="n">encryptedAssertion</span><span class="p">.</span><span class="n">DocumentElement</span><span class="p">;</span> <span class="n">encryptedAssertion</span><span class="p">.</span><span class="nf">InsertBefore</span><span class="p">(</span><span class="n">xmlDeclaration</span><span class="p">,</span> <span class="n">encryptedRoot</span><span class="p">);</span> <span class="c1">// Form Assertion element</span> <span class="n">XmlElement</span> <span class="n">encryptedAssertionElement</span> <span class="p">=</span> <span class="n">encryptedAssertion</span><span class="p">.</span><span class="nf">CreateElement</span><span class="p">(</span><span class="s">"saml"</span><span class="p">,</span> <span class="s">"EncryptedAssertion"</span><span class="p">,</span> <span class="s">"urn:oasis:names:tc:SAML:2.0:assertion"</span><span class="p">);</span> <span class="n">encryptedAssertion</span><span class="p">.</span><span class="nf">AppendChild</span><span class="p">(</span><span class="n">encryptedAssertionElement</span><span class="p">);</span> <span class="c1">// Add encrypted content</span> <span class="kt">var</span> <span class="n">encryptedDataNode</span> <span class="p">=</span> <span class="n">encryptedAssertion</span><span class="p">.</span><span class="nf">ImportNode</span><span class="p">(</span><span class="n">encryptedData</span><span class="p">.</span><span class="nf">GetXml</span><span class="p">(),</span> <span class="k">true</span><span class="p">);</span> <span class="n">encryptedAssertionElement</span><span class="p">.</span><span class="nf">AppendChild</span><span class="p">(</span><span class="n">encryptedDataNode</span><span class="p">);</span> <span class="c1">// Form a document</span> <span class="kt">var</span> <span class="n">root</span> <span class="p">=</span> <span class="n">xmlDocument</span><span class="p">.</span><span class="n">DocumentElement</span><span class="p">;</span> <span class="kt">var</span> <span class="n">node</span> <span class="p">=</span> <span class="n">root</span><span class="p">.</span><span class="n">OwnerDocument</span><span class="p">.</span><span class="nf">ImportNode</span><span class="p">(</span><span class="n">encryptedAssertionElement</span><span class="p">,</span> <span class="k">true</span><span class="p">);</span> <span class="n">root</span><span class="p">.</span><span class="nf">RemoveChild</span><span class="p">(</span><span class="n">xmlAssertionSource</span> <span class="p">??</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">InvalidOperationException</span><span class="p">());</span> <span class="n">root</span><span class="p">.</span><span class="nf">AppendChild</span><span class="p">(</span><span class="n">node</span><span class="p">);</span> </code></pre> </div> <p><strong>Step 5.</strong> Signing the SAMLResponse is about almost the same as siging the Assertion. Working example is here <a href="proxy.php?url=https://github1s.com/optiklab/SAML-integration-utilities/blob/main/src/SamlIntegration.Utilities/Helpers/SamlResponseAlgorithms.cs" rel="noopener noreferrer">SamlResponseAlgorithms.cs</a>.</p> <h2> The End </h2> <p>Some service providers may avoid specific steps and do not require the signature or encryption. In this case, you may remove these parts of the code. Overall, feel free to use my examples in your apps, and I hope this will help you and your application engage users and grow the customer base.</p> <p><strong>Cheers!</strong></p> <h2> Related links </h2> <p><a href="proxy.php?url=https://www.samltool.com/generic_sso_res.php" rel="noopener noreferrer">Examples of SAML responses</a><br> <a href="proxy.php?url=https://medium.com/optiklab/tips-tricks-on-api-documentation-cd7e46334055" rel="noopener noreferrer">Tips&amp;Tricks on documenting custom API</a>.</p> sso saml integration api Five facts about security to know in 2021 Anton Yarkov Wed, 13 Jan 2021 10:17:40 +0000 https://dev.to/optiklab/5-facts-about-security-30d4 https://dev.to/optiklab/5-facts-about-security-30d4 <p>With COVID-19 everybody went to remote work, and 2020 became a year with significant security risks at organizations' infrastructure, software, and hardware tools. I expect this trend to grow even more in 2021; it's going to affect not only organizations but also personal security while you at home.<br> Below you can find several facts that are might be essential and not evident for those who are non-experts in security. Please, keep those facts in mind in 2021:</p> <ol> <li><p>It’s impossible to make unbreakable encryption, so security is about to focus on making the job of brute-force decryption too pricey to be cost-effective and achievable in the adequate timing.<br> The recommendation is to continually track updates in the world of encryption and hashing algorithms: your company and products should use the latest &amp; greatest versions of security tools, security libraries, and configuration. For example, you should not use encryption algorithm 3DES and instead use at least <a href="proxy.php?url=https://en.wikipedia.org/wiki/Advanced_Encryption_Standard" rel="noopener noreferrer">AES/Rijndael</a>, but even better is to use multi-layer encryption using signatures and/or <a href="proxy.php?url=https://en.wikipedia.org/wiki/RSA_(cryptosystem)" rel="noopener noreferrer">RSA with public/private keys</a>. It would not help if you still use hashing algorithms like MD5, SHA1, or SHA2. If that's the fact, then immediately jump to <a href="proxy.php?url=https://docs.microsoft.com/en-us/dotnet/framework/migration-guide/retargeting/4.7.2-4.8#workflow-xaml-checksums-for-symbols-changed-from-sha1-to-sha256" rel="noopener noreferrer">SHA256</a> or <a href="proxy.php?url=https://www.openwall.com/presentations/Passwords13-Energy-Efficient-Cracking/Passwords13-Energy-Efficient-Cracking.pdf" rel="noopener noreferrer">bcrypt/scrypt</a> (assess your choice carefully - those two methods are not cheap in terms of either CPU or Memory consumption), or both like <a href="proxy.php?url=https://dropbox.tech/security/how-dropbox-securely-stores-your-passwords" rel="noopener noreferrer">Dropbox did</a>. Your entire infrastructure should rely on the latest versions of TLS, <a href="proxy.php?url=https://en.wikipedia.org/wiki/Transport_Layer_Security#TLS_1.3" rel="noopener noreferrer">not TLS 1.0 or TLS 1.1</a>.</p></li> <li><p>Nobody cares about security until it hurts. So, white hat hackers are those who hurt you with relatively low cost.<br> The recommendation is that you pay the official companies that can provide you external security audit. It is much, much cheaper than to get down after an actual hacking attack.</p></li> <li><p>In most cases, people are the weakest part of your security infrastructure.<br> The recommendation is to allocate some focus and time on teaching people, controlling their access to company resources, providing appropriate hardware and software. You and your colleagues should not use software that is not updating within the last year.</p></li> <li><p>Security does not mean you should hide everything. Actually, using Open source solutions allows you to use highly secure and well-reviewed solutions instead of crappy custom made solutions. I would not say there are no stable proprietary solutions, but the facts they are hidden make it challenging to compare.<br> Regardless the type of licensing recommendation is to keep expertise in the software that you use. In other words, you should have staff who are experts in the selected tools, libraries, devices, etc.</p></li> <li><p>Using latest version of Linux does not mean having full security turned on by default. You can use guides like <a href="proxy.php?url=https://madaidans-insecurities.github.io/guides/linux-hardening.html" rel="noopener noreferrer">this</a> or <a href="proxy.php?url=https://www.cisecurity.org/benchmark/distribution_independent_linux/" rel="noopener noreferrer">this</a> to make sure you are up to date with your operating system's latest security configuration.</p></li> </ol> <p>In general, security is mostly about making yourself a difficult target. It’s like that joke where you go hiking with your friends and a bear attacks you. You do not need to be faster than the bear; you simply need to be faster than your slowest friend.</p> <p>Stay safe!</p> <p><em>Picture taken from <a href="proxy.php?url=http://anneke.me/assets/" rel="noopener noreferrer">open source assets</a></em></p> security encryption hashing ciso