<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Anton Yarkov on Medium]]></title>
        <description><![CDATA[Stories by Anton Yarkov on Medium]]></description>
        <link>https://medium.com/@optiklab?source=rss-89b693ce234e------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*tTsDeQgnijDFJU_4WXOmzw.jpeg</url>
            <title>Stories by Anton Yarkov on Medium</title>
            <link>https://medium.com/@optiklab?source=rss-89b693ce234e------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Tue, 23 Jun 2026 03:54:08 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@optiklab/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Developing Low-Cost AI-Based Similarity Search]]></title>
            <link>https://medium.com/optiklab/developing-low-cost-ai-based-similarity-search-38d6d08ec95c?source=rss-89b693ce234e------2</link>
            <guid isPermaLink="false">https://medium.com/p/38d6d08ec95c</guid>
            <category><![CDATA[chatbots]]></category>
            <category><![CDATA[cosine-similarity]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[similarity-search]]></category>
            <category><![CDATA[vector-embeddings]]></category>
            <dc:creator><![CDATA[Anton Yarkov]]></dc:creator>
            <pubDate>Mon, 13 Oct 2025 08:39:51 GMT</pubDate>
            <atom:updated>2025-10-19T17:30:29.509Z</atom:updated>
            <content:encoded><![CDATA[<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="https://github.com/tacticaxyz/tactica.faq.similaritysearch">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><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WrbRJwjTxrEPaZ63M0rcLg.jpeg" /></figure><h3>Deconstructing AI: the magic of Vector Embeddings</h3><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="https://www.youtube.com/watch?v=LPZh9BOjkQs">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><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*VhyqDMR5b04fnZi4m_0Rew.jpeg" /><figcaption>Principal diagram</figcaption></figure><p>This simple process works effectively for tasks like navigating documentation and finding relevant resources.</p><h3>Practicality over theory: ensuring algorithmic parity</h3><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 <a href="https://github.com/tacticaxyz/tactica.faq.similaritysearch/blob/main/src/TacTicA.FaqSimilaritySearchBot.Training/Program.cs">TacTicA.FaqSimilaritySearchBot.Training/Program.cs</a>). During training, data is converted into embeddings using services like the <a href="https://github.com/tacticaxyz/tactica.faq.similaritysearch/blob/main/src/TacTicA.FaqSimilaritySearchBot.Training/Services/SimpleEmbeddingService.cs">SimpleEmbeddingService</a> (hash-based, in case of NO AI model for static web site deployment), the <a href="https://github.com/tacticaxyz/tactica.faq.similaritysearch/blob/main/src/TacTicA.FaqSimilaritySearchBot.Training/Services/TfIdfEmbeddingService.cs">TfIdfEmbeddingService.cs</a> (TF-IDF/Keyword-Based Similarity — an extended version of trainer), or the sophisticated <a href="https://github.com/tacticaxyz/tactica.faq.similaritysearch/blob/main/src/TacTicA.FaqSimilaritySearchBot.Training/Services/OnnxEmbeddingService.cs">OnnxEmbeddingService</a> (based on the pre-trained <a href="https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2">all-MiniLM-L6-v2</a> transformer model, which would require you to run some good back-end with AI model loaded into RAM).</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, for example, on <a href="https://tactica.xyz/#/rust-similarity-search">rust similarity search example</a>. This is full-fledged React application running all comparisons on the back-end, but the fundamental concepts stay the same.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jKkpUMw1hPPAkuQ0yYp3gQ.jpeg" /><figcaption>Deployment scheme</figcaption></figure><p>The core mathematical utilities that define <a href="https://huggingface.co/learn/llm-course/en/chapter2/4">tokenization</a> and vector operations reside in C# within <a href="https://github.com/tacticaxyz/tactica.faq.similaritysearch/blob/main/src/TacTicA.FaqSimilaritySearchBot.Shared/Utils/VectorUtils.cs">TacTicA.FaqSimilaritySearchBot.Shared/Utils/VectorUtils.cs</a>. To ensure the client-side browser application running in JavaScript via <a href="https://github.com/tacticaxyz/tactica.faq.similaritysearch/blob/main/src/TacTicA.FaqSimilaritySearchBot.Web/js/chatbot.js">TacTicA.FaqSimilaritySearchBot.Web/js/chatbot.js</a> (or <a href="https://github.com/tacticaxyz/tactica.faq.similaritysearch/blob/main/src/TacTicA.FaqSimilaritySearchBot.WebOnnx/js/chatbot.js">TacTicA.FaqSimilaritySearchBot.WebOnnx/js/chatbot.js</a> 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="https://github.com/tacticaxyz/tactica.faq.similaritysearch/blob/14d18641c057cf81c610b314f037b3d0c08d577d/src/TacTicA.FaqSimilaritySearchBot.Training/Services/SimpleEmbeddingService.cs#L151">SimpleEmbeddingService.cs</a>:</p><pre>// This method is similar to one from chatbot.js<br>    private Func&lt;double&gt; SeededRandom(double initialSeed)<br>    {<br>        double seed = initialSeed;<br>        return () =&gt;<br>        {<br>            seed = (seed * 9301.0 + 49297.0) % 233280.0;<br>            return seed / 233280.0;<br>        };<br>    }</pre><p>From <a href="https://github.com/tacticaxyz/tactica.faq.similaritysearch/blob/14d18641c057cf81c610b314f037b3d0c08d577d/src/TacTicA.FaqSimilaritySearchBot.Web/js/chatbot.js#L501">chatbot.js</a>:</p><pre>// Seeded random number generator<br>    seededRandom(seed) {<br>        return function() {<br>            seed = (seed * 9301 + 49297) % 233280;<br>            return seed / 233280;<br>        };<br>    }</pre><h3>C# training example: vector utility</h3><p>In the C# training application, the VectorUtils class is responsible for calculating cosine similarity, which is the heart of the comparison operation:</p><pre>// Excerpt from TacTicA.FaqSimilaritySearchBot.Shared/Utils/VectorUtils.cs<br>// This function calculates how &#39;similar&#39; two vectors (embeddings) are.<br>public static double CalculateCosineSimilarity(float[] vectorA, float[] vectorB)<br>{<br>    // [C# Implementation Detail: Normalization and dot product calculation <br>    // to determine similarity score between 0.0 and 1.0]<br>    <br>    // ... actual calculation happens here ...<br>    <br>    // return similarityScore; <br>}</pre><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><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mKTJqNXG8n9-_DbHkx_KHg.jpeg" /><figcaption><a href="https://asciinema.org/a/736921">Watch on asciinema</a></figcaption></figure><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.</p><pre>// Excerpt from TacTicA.FaqSimilaritySearchBot.Web/js/chatbot.js<br>// This function is executed when the user submits a query.<br>function performSimilaritySearch(queryVector, documentIndex) {<br>    let bestMatch = null;<br>    let maxSimilarity = 0.0;<br>    <br>    // Convert user query to vector (if using the simple hash/TF-IDF approach)<br>    // or use ONNX runtime for transformer model encoding.<br>    <br>    // Iterate through all pre-calculated document vectors<br>    for (const [docId, docVector] of Object.entries(documentIndex)) {<br>        <br>        // Ensure the JS implementation of Cosine Similarity is identical to C#!<br>        const similarity = calculateCosineSimilarity(queryVector, docVector); <br>        if (similarity &gt; maxSimilarity) {<br>            maxSimilarity = similarity;<br>            bestMatch = docId;<br>        }<br>    }<br>    // Apply the configured threshold (default 0.90) for FAQ matching.<br>    if (maxSimilarity &gt;= CONFIG.SimilarityThreshold) {<br>        // [Action: Return FAQ Response with Citation-Based Responses]<br>    } else {<br>        // [Action: Trigger RAG Fallback for Full Document Corpus Search]<br>    }<br>    <br>    return bestMatch;<br>}</pre><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><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Y1N3TWB5XiSHlOTG31OwZw.gif" /><figcaption><a href="https://github.com/tacticaxyz/tactica.faq.similaritysearch/blob/main/images/chat-action.gif">Link to gif</a></figcaption></figure><p>As you can see, it doesn’t take long to have a running app.</p><h3>Beyond the Simple Lookup</h3><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><h3>The nuances of Similarity Search</h3><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="https://arxiv.org/abs/2508.21038">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="https://www.kaggle.com/competitions/openai-gpt-oss-20b-red-teaming/writeups/visual-prompt-injections-medical-malpractice-and-s#3275057">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><h3>Your next AI project starts now</h3><p>Building a robust, functional bot that <a href="https://tactica.xyz/#/chromium-similarity-search">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><a href="https://github.com/tacticaxyz/tactica.faq.similaritysearch">Source code</a></li><li><a href="https://tactica.xyz/#/chromium-similarity-search">Chromium similarity search demo</a></li><li><a href="https://tactica.xyz/#/rust-similarity-search">Rust similarity search demo</a></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><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=38d6d08ec95c" width="1" height="1" alt=""><hr><p><a href="https://medium.com/optiklab/developing-low-cost-ai-based-similarity-search-38d6d08ec95c">Developing Low-Cost AI-Based Similarity Search</a> was originally published in <a href="https://medium.com/optiklab">optiklab</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[NFT Wallets Unleashed: A Data Structures and Application Design Journey]]></title>
            <link>https://medium.com/optiklab/nft-wallets-unleashed-a-data-structures-and-application-design-journey-aed3918fe92f?source=rss-89b693ce234e------2</link>
            <guid isPermaLink="false">https://medium.com/p/aed3918fe92f</guid>
            <category><![CDATA[dotnet-core]]></category>
            <category><![CDATA[nft]]></category>
            <category><![CDATA[cli]]></category>
            <category><![CDATA[wallet]]></category>
            <category><![CDATA[blockchain]]></category>
            <dc:creator><![CDATA[Anton Yarkov]]></dc:creator>
            <pubDate>Mon, 29 Jan 2024 22:42:13 GMT</pubDate>
            <atom:updated>2024-01-29T22:42:13.819Z</atom:updated>
            <content:encoded><![CDATA[<p><em>TLDR. Learning NFTs concept by implementing a little efficient CLI application using C# and .NET Core. Application allows to execute NFTs transactions in a simplified way. While doing this we unveil underlying complexities and selecting efficient data structures.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mdxboqtZb3Z74wYwqwdbDg.png" /></figure><h3>NFT Wallets Unleashed: A Data Structures and Application Design Journey</h3><p>Whether or not you’re caught up in the NFT hype, as a software engineer, staying abreast of recent innovations is crucial. It’s always fascinating to delve into the technologies underpinning such trendy features. Typically, I prefer to let the dust settle before jumping in, but now seems like a good time to explore “what NFTs are all about.”</p><h3>Terminology</h3><p><strong>NFT</strong> stands for <strong>Non-fungible tokens</strong>. <strong>Non-fungible tokens</strong> are tokens based on a blockchain that represent ownership of a <strong>digital asset</strong>. <strong>Digital asset</strong> may be anything, from a hand-crafted image, a song, a music, a blog post or entire digital book, or even a single tweet (which is, basically, a publicly available record from a database of the well-known public company). These assets have public value and can be owned by someone.</p><p>Unlike <strong>fungible</strong> tokens, such as Bitcoins or Etheriums, which are replaceable with identical units (they have the same value and one can be exchanged for another), NFTs are unique (cannot be equally exchanged), ensuring the ownership of unique digital assets and enforcing digital copyright and trademark laws. NFTs are based on <strong>blockchain technology</strong>, guaranteeing ownership and facilitating ownership transfer.</p><h3>What we build</h3><p>We’re creating an NFT Wallet prototype using a C# console app with (not that famous yet) .NET CLI SDK. The <a href="https://learn.microsoft.com/en-us/dotnet/standard/commandline/define-commands">System.CommandLine</a> library, although still in beta, is promising and enables the creation of clean and efficient command-line interfaces.</p><p>The minimal requirements for NFT Wallets are as follows:</p><p>1. Keep the records of tokens’ ownership history.</p><p>2. Support Mint transactions (creating tokens).</p><p>3. Support Burn transactions (destroying tokens).</p><p>4. Support Transfer transactions (changing ownership).</p><p>We assume transactions are in JSON format, but for educational purposes, we’ll read them from a formatted JSON (text or file on disk) since we lack a real blockchain network server.</p><h3>Keep it simple</h3><p>To keep things simple, we’ll ignore details like specific blockchain networks, hash-generation algorithms for unique NFTs, and the persistent storage choice (in our prototype we will use an XML file on disk).</p><h3>API</h3><p>Considering the mentioned requirements and limits, we’ll support the following <strong>commands.</strong></p><h4>Read Inline ( — read-inline &lt;json&gt;)</h4><p>Reads a single JSON element or an array of JSON elements representing transactions as an argument.</p><pre>$&gt; program --read-inline &#39;{&quot;Type&quot;: &quot;Burn&quot;, &quot;TokenId&quot;: &quot;0x...&quot;}&#39; <br>$&gt; program --read-inline &#39;[{&quot;Type&quot;: &quot;Mint&quot;, &quot;TokenId&quot;: &quot;0x...&quot;, &quot;Address&quot;: &quot;0x...&quot;}, {&quot;Type&quot;: &quot;Burn&quot;, &quot;TokenId&quot;: &quot;0x...&quot;}]&#39;</pre><h4>Read File ( — read-file &lt;file&gt;)</h4><p>Reads a single JSON element or an array of JSON elements representing transactions from the specified file location.</p><pre>$&gt; program --read-file transactions.json</pre><h4>NFT Ownership ( — nft &lt;id&gt;)</h4><p>Returns ownership information for the NFT with the given ID.</p><pre>$&gt; program --nft 0x...</pre><h4>Wallet Ownership ( — wallet &lt;address&gt;)</h4><p>Lists all NFTs currently owned by the wallet with the given address.</p><pre>$&gt; program --wallet 0x...</pre><h4>Reset ( — reset)</h4><p>Deletes all data previously processed by the program.</p><pre>$&gt; program --reset</pre><h3>NFTs Transactions</h3><p>From wallet transactions perspective, we need to support three types of operations as following.</p><h4>Mint</h4><pre>{ <br>  &quot;Type&quot;: &quot;Mint&quot;, <br>  &quot;TokenId&quot;: string, <br>  &quot;Address&quot;: string <br>}</pre><p>A mint transaction creates a new token in the wallet with the provided address.</p><h4>Burn</h4><pre>{ <br>  &quot;Type&quot;: &quot;Burn&quot;, <br>  &quot;TokenId&quot;: string <br>}</pre><p>A burn transaction destroys the token with the given id.</p><h4>Transfer</h4><pre>{ <br>  &quot;Type&quot;: &quot;Transfer&quot;, <br>  &quot;TokenId&quot;: string, <br>  &quot;From&quot;: string, <br>  &quot;To&quot;: string <br>}</pre><p>A transfer transaction changes ownership of a token by removing the “from” wallet address, and adds it to the “to” wallet address.</p><h3>Transactions operations</h3><p>In the following example of a batch of transactions, we create three new tokens, destroy one and transfer ownership for another one:</p><pre>[<br>	{<br>		&quot;Type&quot;: &quot;Mint&quot;,<br>		&quot;TokenId&quot;: &quot;0xA000000000000000000000000000000000000000&quot;,<br>		&quot;Address&quot;: &quot;0x1000000000000000000000000000000000000000&quot;<br>	},<br>	{<br>		&quot;Type&quot;: &quot;Mint&quot;,<br>		&quot;TokenId&quot;: &quot;0xB000000000000000000000000000000000000000&quot;,<br>		&quot;Address&quot;: &quot;0x2000000000000000000000000000000000000000&quot;<br>	},<br>	{<br>		&quot;Type&quot;: &quot;Mint&quot;,<br>		&quot;TokenId&quot;: &quot;0xC000000000000000000000000000000000000000&quot;,<br>		&quot;Address&quot;: &quot;0x3000000000000000000000000000000000000000&quot;<br>	},<br>	{<br>		&quot;Type&quot;: &quot;Burn&quot;,<br>		&quot;TokenId&quot;: &quot;0xA000000000000000000000000000000000000000&quot;<br>	},<br>	{<br>		&quot;Type&quot;: &quot;Transfer&quot;,<br>		&quot;TokenId&quot;: &quot;0xB000000000000000000000000000000000000000&quot;,<br>		&quot;From&quot;: &quot;0x2000000000000000000000000000000000000000&quot;,<br>		&quot;To&quot;: &quot;0x3000000000000000000000000000000000000000&quot;<br>	}<br>]</pre><p>As seen, tokens are identified by imaginary hex-formatted values. Wallet addresses should be supported by our underlying imaginary blockchain network. Verification of these values is skipped, focusing on the efficiency of operations and storage in our NFTs wallet.</p><h3>Data structure design</h3><p>To support all necessary operations we have to think about efficient execution of a following three types of tasks:</p><ul><li>Persist information about ownership relationship between imaginary NFT token ids and NFT wallet addresses provided.</li><li>Quickly answer what wallet contains a token, by token id.</li><li>Quickly answer what tokens are owned by certain wallet.</li><li>Efficiently change the ownership of the Token between the wallet addresses.</li></ul><p>We begin by creating a class to represent a single transaction.</p><pre>public class Transaction<br>{<br> // Transaction type: Mint, Burn, Transfer, etc. <br> // As a type, we may use enum here as well.<br> [JsonProperty(&quot;Type&quot;, Required = Required.Always)]<br> public string Type { get; set; }<br><br> [JsonProperty(&quot;TokenId&quot;, Required = Required.Always)]<br> public string TokenId { get; set; }<br><br> // Address of the Wallet to own Token Id created (Minted)<br> [JsonProperty(&quot;Address&quot;, Required = Required.Default)]<br> public string Address { get; set; }<br><br> // From Address of the Transfer operation.<br> [JsonProperty(&quot;From&quot;, Required = Required.Default)]<br> public string From { get; set; }<br><br> // To Address of the Transfer operation.<br> [JsonProperty(&quot;To&quot;, Required = Required.Default)]<br> public string To { get; set; }<br>}</pre><p>In the world of NFTs, the owner is represented by a wallet address, and we add a timestamp to track when a new token is created or transferred between wallets.</p><pre>public class OwnershipInfo<br>{<br> [XmlElement(&quot;WalletAddress&quot;)]<br> public string WalletAddress { get; set; }<br><br> [XmlElement(&quot;Timestamp&quot;)]<br> public DateTime Timestamp {  get; set; }<br>}</pre><p>Most efficient algorithms should be executed with O(1), right? Hash-based collections allow us to support GET operations with O(1) efficiency, which means we have to use Dictionary&lt; K, V &gt; for the whole storage. But to make all operations efficient, we have to sacrifice memory as it’s not enough to have only one efficient collection. Instead, we are going to use multiple collections in memory. Let’s look at it piece by piece, first, and then discuss this solution.</p><blockquote>Remember, in the following code we don’t verify tokens ids or wallet addresses.</blockquote><h4>Which wallet owns the token?</h4><p>Since a token can be owned by only one wallet, a direct address-to-address map between<em> Token ID</em> (key) and <em>Wallet Address</em> (value) is used. This allows us to easily support the “ — nft” operation, answering the question of who the owner is.</p><pre>public class TokenStorage<br>{<br> // To easily find owning wallet by NFT token.<br> public Dictionary&lt;string, string&gt; NftTokenWalletMap { get; set; }<br>}<br><br>public async Task&lt;string&gt; FindWalletOwnerAsync(string tokenId)<br>{<br> if (_tokenStorage.NftTokenWalletMap.ContainsKey(tokenId))<br> {<br>  return await Task&lt;string&gt;.FromResult(_tokenStorage.NftTokenWalletMap[tokenId]);<br> }<br><br> return null;<br>}</pre><h4>Which tokens wallet owns?</h4><p>To efficiently list tokens owned by a wallet, a map of <em>Wallet Addresses</em> (key) to lists of their <em>Token IDs</em> (value) is maintained, so we can easily support operation “<em>- - wallet</em>”.</p><pre>public class TokenStorage<br>{<br> // To easily find list of owned Tokens in the wallet.<br> public Dictionary&lt;string, List&lt;string&gt;&gt; WalletNftTokensMap { get; set; }<br>}<br><br>public async Task&lt;List&lt;string&gt;&gt; GetTokensAsync(string walletId)<br>{<br> var result = new List&lt;string&gt;();<br><br> if (_tokenStorage.WalletNftTokensMap.ContainsKey(walletId) &amp;&amp;<br>  _tokenStorage.WalletNftTokensMap[walletId] != null)<br> {<br>  result = _tokenStorage.WalletNftTokensMap[walletId];<br><br>  result.Sort();<br> }<br><br> return await Task.FromResult(result);<br>}</pre><h4>Ownership transfer and history</h4><p>To efficinetly support the history of ownership changes for each token, we need to map <em>Token Id</em> (key) to a list of <em>Owners Wallet Addresses</em> (values). This list must be sorted in a way that we can efficiently take the last one (but still, be able to list all the history, when needed). We also want to efficiently insert new history records (to the end). <strong>Linked List </strong>is what suits well for this history-record data structure: it allows us to insert new records and take the last one with O(1) efficiency.</p><pre>public class TokenStorage<br>{<br> // To easily change the ownership.<br> public Dictionary&lt;string, NFTToken&gt; NftTokenOwnershipMap { get; set; }<br>}<br><br>public class NFTToken<br>{<br> public string TokenId { get; set; }<br><br> /// &lt;summary&gt;<br> /// Allows to efficiently insert new owners.<br> /// &lt;/summary&gt;<br> public LinkedList&lt;OwnershipInfo&gt; OwnershipInfo { get; set; }<br>}</pre><p>With these structures, we can efficiently support minting, burning, and transferring operations on NFTs in <a href="https://github.com/optiklab/Nft.CLI.ConsoleApp.V2/blob/main/src/Nft.App/TransactionsManager.cs">TransactionManager</a>. Follow the comments in code.</p><h4>Mint new token</h4><pre>private bool MintNFTToken(string tokenId, string walletAddress)<br>{<br> // Is token really new/unique?<br> if (!_tokenStorage.NftTokenWalletMap.ContainsKey(tokenId))<br> {<br>  // Do we know such wallet address?<br>  if (!_tokenStorage.WalletNftTokensMap.ContainsKey(walletAddress))<br>  {<br>   // Remember a new wallet address.<br>   _tokenStorage.WalletNftTokensMap.Add(walletAddress, new List&lt;string&gt;());<br>  }<br>  <br>  // Add token to the wallet to Wallet-Token records.<br>  _tokenStorage.WalletNftTokensMap[walletAddress].Add(tokenId);<br>  <br>  // Add Token-Wallet record.<br>  _tokenStorage.NftTokenWalletMap.Add(tokenId, walletAddress);<br><br>  // Create an Ownership entry in history<br>  var nftToken = new NFTToken<br>  {<br>   TokenId = tokenId,<br>   OwnershipInfo = new LinkedList&lt;OwnershipInfo&gt;()<br>  };<br><br>  // Insert the record<br>  nftToken.OwnershipInfo.AddFirst(<br>   new OwnershipInfo<br>   {<br>    WalletAddress = walletAddress,<br>    Timestamp = DateTime.Now<br>   });<br>  _tokenStorage.NftTokenOwnershipMap.Add(tokenId, nftToken);<br><br>  return true;<br> }<br><br> return false;<br>}</pre><h4>Burn token</h4><pre>private void BurnNFTToken(string tokenId)<br>{<br> if (_tokenStorage.NftTokenWalletMap.ContainsKey(tokenId))<br> {<br>  string walletId = _tokenStorage.NftTokenWalletMap[tokenId];<br><br>  _tokenStorage.NftTokenWalletMap.Remove(tokenId);<br><br>  if (_tokenStorage.WalletNftTokensMap.ContainsKey(walletId))<br>  {<br>   _tokenStorage.WalletNftTokensMap.Remove(walletId);<br>  }<br> }<br><br> if (_tokenStorage.NftTokenOwnershipMap.ContainsKey(tokenId))<br> {<br>  _tokenStorage.NftTokenOwnershipMap.Remove(tokenId);<br> }<br>}</pre><h4>Transfer token</h4><pre>private bool ChangeOwnership(string tokenId, string oldWalletAddress, string newWalletAddress)<br>{<br> // Validate that token is actually owned by From<br> if (_tokenStorage.NftTokenWalletMap.ContainsKey(tokenId) &amp;&amp;<br>  _tokenStorage.NftTokenWalletMap[tokenId].Equals(oldWalletAddress))<br> {<br>  // Remove existing Wallet-Token record, it&#39;s not valid anymore.<br>  _tokenStorage.WalletNftTokensMap[oldWalletAddress].Remove(tokenId);<br>  // Add a new one.<br>  if (!_tokenStorage.WalletNftTokensMap.ContainsKey(newWalletAddress))<br>  {<br>   _tokenStorage.WalletNftTokensMap.Add(newWalletAddress, new List&lt;string&gt;());<br>  }<br>  _tokenStorage.WalletNftTokensMap[newWalletAddress].Add(tokenId);<br><br>  // Update a second map that maps back Token to Wallet.<br>  _tokenStorage.NftTokenWalletMap[tokenId] = newWalletAddress;<br><br>  // Now, create a new ownership history record.<br>  NFTToken nftToken = _tokenStorage.NftTokenOwnershipMap[tokenId];<br>  nftToken.OwnershipInfo.AddFirst(<br>   new OwnershipInfo<br>   {<br>    WalletAddress = newWalletAddress,<br>    Timestamp = DateTime.Now<br>   });<br><br>  return true;<br> }<br><br> return false;<br>}</pre><p>Finally, our <a href="https://github.com/optiklab/Nft.CLI.ConsoleApp.V2/blob/main/src/Nft.App/Storage/TokenStorage.cs">token storage</a> data structures will look like this and will support all the necessary operations with O(1) efficiency with additional memory redundancy.</p><pre>public class TokenStorage<br>{<br> public TokenStorage()<br> {<br>  NftTokenWalletMap = new Dictionary&lt;string, string&gt;();<br>  WalletNftTokensMap = new Dictionary&lt;string, List&lt;string&gt;&gt;();<br>  NftTokenOwnershipMap = new Dictionary&lt;string, NFTToken&gt;();<br> }<br><br> // To easily find owning wallet by NFT token.<br> public Dictionary&lt;string, string&gt; NftTokenWalletMap { get; set; }<br><br> // To easily find list of owned Tokens in the wallet.<br> public Dictionary&lt;string, List&lt;string&gt;&gt; WalletNftTokensMap { get; set; }<br><br> // To easily change the ownership.<br> public Dictionary&lt;string, NFTToken&gt; NftTokenOwnershipMap { get; set; }<br>}<br><br>public class NFTToken<br>{<br> public string TokenId { get; set; }<br><br> /// &lt;summary&gt;<br> /// Allows to efficiently insert new owners.<br> /// &lt;/summary&gt;<br> public LinkedList&lt;OwnershipInfo&gt; OwnershipInfo { get; set; }<br>}</pre><h3>Application design</h3><p>Following an Object-Oriented Programming (OOP) design, we create a number of entities:</p><ol><li>All the transactions supported by a <strong>TransactionManager</strong>.</li><li>Every CLI command inherited from a base <strong>Command</strong> with business logic implemented in appropriate <strong>CommandHandler</strong>s.</li><li><strong>ConsoleOutputHandlers</strong> play the role of a View Interface (similar to MVC concept) to print to the Console, which lets us potentially send outputs of the application to the Display, Network, Web etc.</li><li>We do use a NewtonsoftJson library to parse incoming requests as well as a System.Xml to work with our persisting XML-storage file.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BeEVDWsMdMxNYho9wYXKaw.png" /><figcaption>Straightforward CLI application OOP design</figcaption></figure><p>All of this allows us to implement a set of unit tests that you also can find in <a href="https://github.com/optiklab/Nft.CLI.ConsoleApp.V2">the repository</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/806/1*YIx1wNsvW5PCHkx6qPQt6w.png" /><figcaption>Unit tests</figcaption></figure><p>Now, thanks to System.CommandLine library, it’s easy to wire-up all the commands into a little application <a href="https://github.com/optiklab/Nft.CLI.ConsoleApp.V2/blob/main/src/Nft.App/Program.cs?ref=hackernoon.com">as following</a>:</p><pre>class Program<br>{<br>    static async Task&lt;int&gt; Main(string[] args)<br>    {<br>        var root = new RootCommand();<br>        root.Description = &quot;Wallet CLI app to work with NFT tokens.&quot;;<br><br>        root.AddCommand(new ReadFileCommand());<br>        root.AddCommand(new ReadInlineCommand());<br>        root.AddCommand(new WalletCommand());<br>        root.AddCommand(new ResetCommand());<br>        root.AddCommand(new NftCommand());<br><br>        root.Handler = CommandHandler.Create(() =&gt; root.Invoke(args));<br><br>        return await new CommandLineBuilder(root)<br>           .UseHost(_ =&gt; Host.CreateDefaultBuilder(args), builder =&gt; builder<br>                .ConfigureServices(RegisterServices)<br>                .UseCommandHandler&lt;ReadFileCommand, ReadFileCommandHandler&gt;()<br>                .UseCommandHandler&lt;ReadInlineCommand, ReadInlineCommandHandler&gt;()<br>                .UseCommandHandler&lt;WalletCommand, WalletCommandHandler&gt;()<br>                .UseCommandHandler&lt;ResetCommand, ResetCommandHandler&gt;()<br>                .UseCommandHandler&lt;NftCommand, NftCommandHandler&gt;())<br>           .UseDefaults()<br>           .Build()<br>           .InvokeAsync(args);<br>    }<br><br>    private static void RegisterServices(IServiceCollection services)<br>    {<br>        services.AddHttpClient();<br>        services.AddSingleton&lt;IFileSystem, XmlFileSystem&gt;();<br>        services.AddSingleton&lt;ITransactionsManager, TransactionsManager&gt;();<br>        services.AddSingleton&lt;IConsoleOutputHandlers, ConsoleOutputHandlers&gt;();<br>    }<br>}</pre><h3>Run your Wallet</h3><p>Now, we can run our little CLI. It contains a nice little help listing the commands (thanks to <a href="https://learn.microsoft.com/en-us/dotnet/standard/commandline/define-commands">System.CommandLine</a> library):</p><pre>&gt;nft.app.exe -h<br>Description:<br>  Wallet CLI app to work with NFT tokens.<br><br>Usage:<br>  Nft.App [command] [options]<br><br>Options:<br>  --version       Show version information<br>  -?, -h, --help  Show help and usage information<br><br>Commands:<br>  --read-file &lt;filePath&gt;  Reads transactions from the ?le in the speci?ed location.<br>  --read-inline &lt;json&gt;    Reads either a single json element, or an array of json elements representing transactions as<br>                          an argument.<br>  --wallet &lt;Address&gt;      Lists all NFTs currently owned by the wallet of the given address.<br>  --reset                 Deletes all data previously processed by the program.<br>  --nft &lt;tokenId&gt;         Returns ownership information for the nft with the given id.</pre><p>If we read all the transactions from JSON file, then we can find XML wallet storage “WalletDb.xml” after execution is finished.</p><pre>&gt;Nft.App --read-file transactions.json</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vCR_Mle_aLOH5zT4bQ2ODw.png" /><figcaption>Xml Storage container file</figcaption></figure><p>Now, let’s execute following transactions one by one and watch results:</p><pre>&gt;Nft.App --read-file transactions.json <br>Read 5 transaction(s) <br><br>&gt;Nft.App --nft 0xA000000000000000000000000000000000000000<br>Token 0xA000000000000000000000000000000000000000 is not owned by any wallet <br><br>&gt;Nft.App --nft 0xB000000000000000000000000000000000000000<br>Token 0xA000000000000000000000000000000000000000 is owned by 0x3000000000000000000000000000000000000000 <br><br>&gt;Nft.App --nft 0xC000000000000000000000000000000000000000<br>Token 0xC000000000000000000000000000000000000000 is owned by 0x3000000000000000000000000000000000000000 <br><br>&gt;Nft.App --nft 0xD000000000000000000000000000000000000000<br>Token 0xA000000000000000000000000000000000000000 is not owned by any wallet <br><br>&gt;Nft.App --read-inline  &quot;{ \&quot;Type\&quot;: \&quot;Mint\&quot;, \&quot;TokenId\&quot;: \&quot;0xD000000000000000000000000000000000000000\&quot;, \&quot;Address\&quot;: \&quot;0x1000000000000000000000000000000000000000\&quot; }&quot;<br>Read 1 transaction(s) <br><br>&gt;Nft.App --nft 0xD000000000000000000000000000000000000000<br>Token 0xA000000000000000000000000000000000000000 is owned by 0x1000000000000000000000000000000000000000 <br><br>&gt;Nft.App --wallet 0x3000000000000000000000000000000000000000<br>Wallet 0x3000000000000000000000000000000000000000 holds 2 Tokens: <br>0xB000000000000000000000000000000000000000 <br>0xC000000000000000000000000000000000000000 <br><br>&gt;Nft.App -—reset <br>Program was reset <br><br>&gt;Nft.App --wallet 0x3000000000000000000000000000000000000000<br>Wallet 0x3000000000000000000000000000000000000000 holds no Tokens </pre><h3>Outcomes</h3><p>As we can see, we were able to implement all Wallet operations with O(1) efficiency. Unfortunately, it involves trade-offs in memory usage. In production scenarios, considerations for large datasets that may not fit into a single machine’s RAM might lead to compromises. Depending on requirements, sacrificing efficiency for optimized memory usage or vice versa may be necessary.</p><p>While this example demonstrates a compromise for a standalone system, in a production environment, third-party software supporting scalable mappings with redundancy might be preferred. This introduces additional complexity but is crucial for operational efficiency in distributed systems.</p><p>This exploration provides insights into the world of NFTs and the data structures supporting their operations. I hope it was interesting and useful for you.</p><p>Stay tuned for more!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=aed3918fe92f" width="1" height="1" alt=""><hr><p><a href="https://medium.com/optiklab/nft-wallets-unleashed-a-data-structures-and-application-design-journey-aed3918fe92f">NFT Wallets Unleashed: A Data Structures and Application Design Journey</a> was originally published in <a href="https://medium.com/optiklab">optiklab</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Algorithmic Alchemy: Exploiting Graph Theory in the Foreign Exchange]]></title>
            <link>https://medium.com/better-programming/algorithmic-alchemy-exploiting-graph-theory-in-the-foreign-exchange-bba62d90f921?source=rss-89b693ce234e------2</link>
            <guid isPermaLink="false">https://medium.com/p/bba62d90f921</guid>
            <category><![CDATA[algorithms]]></category>
            <category><![CDATA[foreign-exchange]]></category>
            <category><![CDATA[bellman-ford-algorithm]]></category>
            <category><![CDATA[graph]]></category>
            <category><![CDATA[currency-exchange]]></category>
            <dc:creator><![CDATA[Anton Yarkov]]></dc:creator>
            <pubDate>Thu, 05 Oct 2023 20:18:58 GMT</pubDate>
            <atom:updated>2023-10-05T20:18:58.377Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*RE-wKkGZ9Ti7Xwsa" /><figcaption>Photo by <a href="https://unsplash.com/@lukechesser?utm_source=medium&amp;utm_medium=referral">Luke Chesser</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><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><figure><img alt="" src="https://cdn-images-1.medium.com/max/790/1*BTDXV0ieE5qtZSZAo0Y0UQ.jpeg" /><figcaption>Taken from <a href="https://linas.substack.com/p/fintechpulse408">https://linas.substack.com/p/fintechpulse408</a></figcaption></figure><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>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.</em></blockquote><p>The task explicitly mentioned that the algorithm’s foundation must be rooted in graph theory.</p><h3>FX Basics</h3><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 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 their exchange rate.</p><p>We can use multiple intermediate currencies for profitable trading, known as a sure bet.</p><blockquote>Arbitrage sure bet<em> is a set of pairs to be used in a circular manner. </em><a href="https://en.wikipedia.org/wiki/Arbitrage_betting"><em>Read more</em></a></blockquote><p>Many providers employ mathematical modeling and analysis to secure their own profits and prevent others from profiting from them. Hence, the term potentially is emphasized here.</p><blockquote>Sure bet length<em> refers to the number of pairs that constitute a set of potential arbitrage opportunities.</em></blockquote><p>In the real world, exchange rates vary among banks or 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 can access 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>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.</em></blockquote><p>The exchange rate between two currencies via one or more intermediate currencies is calculated as <em>the product of the exchange rates of these intermediate transactions</em>.</p><h3>An Example</h3><p>Let’s imagine we want to buy Swiss Franks for US Dollars, exchange Franks for Japanese Yens, and then sell Yens for US Dollars again. In Autumn 2023, we have the 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:</p><pre>1           USD |   1           CHF |   1       YEN<br>0.91        CHF |   163.16      YEN |   0.0067  USD<br>----------------|-------------------|--------------<br>1.098901099     |   0.006128953     |   149.2537313</pre><p>Now, we need to find a product of those values. A sequence of transactions becomes profitable when this product yields a value of less than one:</p><pre>1.098901099 * 0.006128953 * 149.2537313 = 1.005240803</pre><p>As we can see, the result is larger than one. We lost 0.05% of our money. But how many exactly? We can sort it out like this:</p><pre>0.91 CHF * 163.16 (YEN per 1 CHF) * 0.0067 (USD per 1 YEN) = 0.99478652 USD</pre><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>In simpler terms, Arbitrage Cycle is profitable when one unit of currency can be obtained for less than one unit of the same currency.</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:</p><pre>1           USD |   1           CHF |   1       YEN<br>0.92        CHF |   163.16      YEN |   0.0067  USD<br>----------------|-------------------|--------------<br>1.086956522     |   0.006128953     |   149.2537313</pre><p>A product will be less than 1:</p><pre>1.086956522 * 0.006128953 * 149.2537313 = 0.994314272</pre><p>This means that in the real currencies, it will give us more than 1 US Dollar. Here’s what that looks like:</p><pre>0.92 CHF * 163.16 (YEN per 1 CHF) * 0.0067 (USD per 1 YEN) = 1.00571824 USD</pre><p>Whoa, <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 three Arbitrage Pairs would look like this:</p><pre>USD/CHF * CHF/YEN * YEN/USD &lt; 1.0</pre><h3>Graph Representation</h3><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 the matrix like this:</p><pre>EUR  USD<br> 1    1  EUR <br> 1    1  USD</pre><p>Depending on the number of pairs involved, our matrix can expand:</p><pre>EUR  USD  YEN  CHF  <br> 1    1    1    1  EUR <br> 1    1    1    1  USD<br> 1    1    1    1  YEN<br> 1    1    1    1  CHF</pre><p>Consequently, our table can become considerably larger, even for just two currencies, if we consider more exchange platforms and resources.</p><p>To address real currency arbitrage problems, a complete graph encompassing all currency quote relationships is often utilized. A three-currency exchange table might appear as follows:</p><pre>USD     CHF     YEN<br>{ 1.0,    1.10,   0.0067 }  USD<br>{ 0.91,   1.0,    0.0061 }  CHF<br>{ 148.84, 163.16, 1.0    }  YE</pre><p>We can employ a simple <a href="https://github.com/optiklab/negative-cycles-in-a-graph/blob/main/pathFindingBase.h">graph data structure</a> to represent our currency pairs in memory:</p><pre>class GraphNode<br>{<br>public:<br>    string Name;<br>};<br><br>class Graph<br>{<br>public:<br>    vector&lt;vector&lt;double&gt;&gt; Matrix;<br>    vector&lt;GraphNode&gt; Nodes;<br>};</pre><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><h3>Math Saves Us Again</h3><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="https://antonyarkov.substack.com/p/universal-implementation-of-bfs-dfs">BFS, DFS, Dijkstra, or even A-Star</a>).</p><p>However, logarithms are a mathematical way to transition from a product to a sum to circumvent this limitation. If a product appears under a logarithm, it can be converted into a sum of logarithms.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/523/0*JK3BLicBvk8jEis6.jpg" /><figcaption>Sum of logarithms turns out to be useful concept in applying graphs traversal</figcaption></figure><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:</p><pre>LogE(USD/CHF) * LogE(CHF/YEN) * LogE(YEN/USD) &lt; 0.0</pre><p>This simple mathematical trick allows us to shift from searching for a cycle with an edge length product less than one to searching for a cycle where the sum of the edge lengths is less than zero.</p><p>Our matrix values converted to a LogE(x) and rounded with two digits after the point, now look like this:</p><pre>USD      CHF     YEN<br>{ 0.0,      0.1,     -5.01 }  USD<br>{ -0.09,    0.0,     -5.1  }  CHF<br>{ 5.0,      5.09,    0.0   }  YEN</pre><p>Now, this problem becomes more solvable using classical graph algorithms. What we need is to traverse the graph looking for the most profitable path of exchange.</p><h3>Graph Algorithms</h3><p>Every algorithm has its limitations. I mentioned some of them in <a href="https://antonyarkov.substack.com/p/universal-implementation-of-bfs-dfs">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 Negative Cycles while it traverses the graph. Negative cycles challenge the algorithm since it continually finds better solutions on each iteration.</p><p>To address this issue, the Bellman-Ford algorithm 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 the 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 billions of videos about it), practical implementation for our needs requires some effort. Let’s dig into it.</p><h3>Bellman-Ford Algorithm Implementation</h3><blockquote><em>As the aim of this article is computer science, I will use imaginary exchange rates that has nothing to do with the real ones.</em></blockquote><p>For a smoother introduction to the algorithm, let’s use a graph that doesn’t contain negative cycles at all:</p><pre>graph.Nodes.push_back({ &quot;USD&quot; });<br>graph.Nodes.push_back({ &quot;CHF&quot; });<br>graph.Nodes.push_back({ &quot;YEN&quot; });<br>graph.Nodes.push_back({ &quot;GBP&quot; });<br>graph.Nodes.push_back({ &quot;CNY&quot; });<br>graph.Nodes.push_back({ &quot;EUR&quot; });<br>// Define exchange rates for pairs of currencies below<br>//                 USD    CHF   YEN   GBP   CNY   EUR<br>graph.Matrix = { { 0.0,   0.41, INF,  INF,  INF,  0.29 },  // USD<br>                 { INF,   0.0,  0.51, INF,  0.32, INF },   // CHF<br>                 { INF,   INF,  0.0,  0.50, INF,  INF },   // YEN<br>                 { 0.45,  INF,  INF,  0.0,  INF,  -0.38 }, // GBP<br>                 { INF,   INF,  0.32, 0.36, 0.0,  INF },   // CNY<br>                 { INF, -0.29,  INF,  INF,  0.21, 0.0 } }; // EUR</pre><p>The code example below finds a path between two nodes using the Bellman-Ford algorithm when the graph lacks negative cycles:</p><pre>vector&lt;double&gt; _shortestPath;<br>vector&lt;int&gt; _previousVertex;<br><br>void FindPath(Graph&amp; graph, int start)<br>{<br>    int verticesNumber = graph.Nodes.size();<br>    _shortestPath.resize(verticesNumber, INF);<br>    _previousVertex.resize(verticesNumber, -1);<br>    _shortestPath[start] = 0;<br>    // For each vertex, apply relaxation for all the edges V - 1 times.<br>    for (int k = 0; k &lt; verticesNumber - 1; k++)<br>        for (int from = 0; from &lt; verticesNumber; from++)<br>            for (int to = 0; to &lt; verticesNumber; to++)<br>                if (_shortestPath[to] &gt; _shortestPath[from] + graph.Matrix[from][to])<br>                {<br>                    _shortestPath[to] = _shortestPath[from] + graph.Matrix[from][to];<br>                    _previousVertex[to] = from;<br>                }<br>}</pre><p>Running this code for the Chinese Yuan fills the _previousVertex array and yields results like this:</p><pre>Path from 4 to 0 is : 4(CNY) 3(GBP) 0(USD)<br>Path from 4 to 1 is : 4(CNY) 3(GBP) 5(EUR) 1(CHF)<br>Path from 4 to 2 is : 4(CNY) 3(GBP) 5(EUR) 1(CHF) 2(YEN)<br>Path from 4 to 3 is : 4(CNY) 3(GBP)<br>Path from 4 to 4 is : 4(CNY)<br>Path from 4 to 5 is : 4(CNY) 3(GBP) 5(EUR)</pre><p>As you can observe, it identifies optimal paths between CNY and various other currencies.</p><blockquote><em>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.</em></blockquote><p>The above implementation works well in ideal cases but falls short when dealing with negative-cycle graphs.</p><h3>Detecting Negative Cycles</h3><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:</p><pre>vector&lt;double&gt; _shortestPath;<br>vector&lt;int&gt; _previousVertex;<br><br>bool ContainsNegativeCycles(Graph&amp; graph, int start)<br>{<br>    int verticesNumber = graph.Nodes.size();<br>    _shortestPath.resize(verticesNumber, INF);<br>    _previousVertex.resize(verticesNumber, -1);<br>    _shortestPath[start] = 0;<br>    // For each vertex, apply relaxation for all the edges V - 1 times.<br>    for (int k = 0; k &lt; verticesNumber - 1; k++)<br>    {<br>        updated = false;<br>        for (int from = 0; from &lt; verticesNumber; from++)<br>        {<br>            for (int to = 0; to &lt; verticesNumber; to++)<br>            {<br>                if (_shortestPath[to] &gt; _shortestPath[from] + graph.Matrix[from][to])<br>                {<br>                    _shortestPath[to] = _shortestPath[from] + graph.Matrix[from][to];<br>                    _previousVertex[to] = from;<br>                    updated = true;<br>                }<br>            }<br>        }<br>        if (!updated) // No changes in paths, means we can finish earlier.<br>            break;<br>    }<br>    // Run one more relaxation step to detect which nodes are part of a negative cycle. <br>    if (updated)<br>        for (int from = 0; from &lt; verticesNumber; from++)<br>            for (int to = 0; to &lt; verticesNumber; to++)<br>                if (_shortestPath[to] &gt; _shortestPath[from] + graph.Matrix[from][to])<br>                    // A negative cycle has occurred if we can find a better path beyond the optimal solution.<br>                    return true;<br>    return false;<br>}</pre><p>And now we play with a more intricate graph that includes negative cycles:</p><pre>graph.Nodes.push_back({ &quot;USD&quot; }); // 1 (Index = 0)<br>graph.Nodes.push_back({ &quot;CHF&quot; });<br>graph.Nodes.push_back({ &quot;YEN&quot; });<br>graph.Nodes.push_back({ &quot;GBP&quot; });<br>graph.Nodes.push_back({ &quot;CNY&quot; });<br>graph.Nodes.push_back({ &quot;EUR&quot; });<br>graph.Nodes.push_back({ &quot;XXX&quot; });<br>graph.Nodes.push_back({ &quot;YYY&quot; }); // 8  (Index = 7)<br>//                 USD  CHF  YEN  GBP   CNY  EUR  XXX  YYY<br>graph.Matrix = { { 0.0, 1.0, INF, INF,  INF, INF, INF, INF },   // USD<br>                 { INF, 0.0, 1.0, INF,  INF, 4.0, 4.0, INF },   // CHF<br>                 { INF, INF, 0.0, INF,  1.0, INF, INF, INF },   // YEN<br>                 { INF, INF, 1.0, 0.0,  INF, INF, INF, INF },   // GBP<br>                 { INF, INF, INF, -3.0, 0.0, INF, INF, INF },   // CNY<br>                 { INF, INF, INF, INF,  INF, 0.0, 5.0, 3.0 },   // EUR<br>                 { INF, INF, INF, INF,  INF, INF, 0.0, 4.0 },   // XXX<br>                 { INF, INF, INF, INF,  INF, INF, INF, 0.0 } }; // YYY</pre><p>Our program halts and displays a message:</p><pre>Graph contains negative cycle.</pre><p>We were able to indicate the problem. However, we need to navigate around problematic segments of the graph.</p><h3>Avoiding Negative Cycles</h3><p>To accomplish this, we’ll mark vertices that are part of negative cycles with a constant value, NEG_INF:</p><pre>bool FindPathsAndNegativeCycles(Graph&amp; graph, int start)<br>{<br>    int verticesNumber = graph.Nodes.size();<br>    _shortestPath.resize(verticesNumber, INF);<br>    _previousVertex.resize(verticesNumber, -1);<br>    _shortestPath[start] = 0;<br><br>    for (int k = 0; k &lt; verticesNumber - 1; k++)<br>        for (int from = 0; from &lt; verticesNumber; from++)<br>            for (int to = 0; to &lt; verticesNumber; to++)<br>            {<br>                if (graph.Matrix[from][to] == INF) // Edge not exists<br>                {<br>                    continue;<br>                }<br>                if (_shortestPath[to] &gt; _shortestPath[from] + graph.Matrix[from][to])<br>                {<br>                    _shortestPath[to] = _shortestPath[from] + graph.Matrix[from][to];<br>                    _previousVertex[to] = from;<br>                }<br>            }<br>    bool negativeCycles = false;<br>    for (int k = 0; k &lt; verticesNumber - 1; k++)<br>        for (int from = 0; from &lt; verticesNumber; from++)<br>            for (int to = 0; to &lt; verticesNumber; to++)<br>            {<br>                if (graph.Matrix[from][to] == INF) // Edge not exists<br>                {<br>                    continue;<br>                }<br>                if (_shortestPath[to] &gt; _shortestPath[from] + graph.Matrix[from][to])<br>                {<br>                    _shortestPath[to] = NEG_INF;<br>                    _previousVertex[to] = -2;<br>                    negativeCycles = true;<br>                }<br>            }<br>    return negativeCycles;<br>}</pre><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):</p><pre>Graph contains negative cycle.<br>Path from 0 to 0 is : 0(USD)<br>Path from 0 to 1 is : 0(USD) 1(CHF)<br>Path from 0 to 2 is : Infinite number of shortest paths (negative cycle).<br>Path from 0 to 3 is : Infinite number of shortest paths (negative cycle).<br>Path from 0 to 4 is : Infinite number of shortest paths (negative cycle).<br>Path from 0 to 5 is : 0(USD) 1(CHF) 5(EUR)<br>Path from 0 to 6 is : 0(USD) 1(CHF) 6(XXX)<br>Path from 0 to 7 is : 0(USD) 1(CHF) 5(EUR) 7(YYY)</pre><p>Whoa! Our code identified several profitable chains despite our data being “a bit dirty.”</p><p>All the code examples mentioned above, including test data, are shared with you on <a href="https://github.com/optiklab/negative-cycles-in-a-graph">my GitHub</a>.</p><h3>Even Little Fluctuations Matter</h3><p>Let’s now consolidate what we’ve learned. Given a list of exchange rates for three currencies, we can easily detect negative cycles:</p><pre>graph.Nodes.push_back({ &quot;USD&quot; }); // 1 (Index = 0)<br>graph.Nodes.push_back({ &quot;CHF&quot; });<br>graph.Nodes.push_back({ &quot;YEN&quot; }); // 3 (Index = 2)<br><br>// LogE(x) table:   USD      CHF     YEN<br>graph.Matrix = { { 0.0,    0.489,  -0.402 },   // USD<br>                 { -0.489, 0.0,    -0.891 },   // CHF<br>                 { 0.402,  0.89,   0.0    } }; // YEN<br>from = 0;<br>FindPathsAndNegativeCycles(graph, from);</pre><p>Here’s the result:</p><pre>Graph contains negative cycle.<br>Path from 0 to 0 is: Infinite number of shortest paths (negative cycle).<br>Path from 0 to 1 is: Infinite number of shortest paths (negative cycle).<br>Path from 0 to 2 is: Infinite number of shortest paths (negative cycle).</pre><p>However, even slight changes in the exchange rates (i.e., adjustments to the matrix) can lead to significant differences:</p><pre>// LogE(x) table:   USD      CHF     YEN<br>graph.Matrix = { { 0.0,    0.490,  -0.402 },    // USD<br>                 { -0.489, 0.0,    -0.891 },    // CHF<br>                 { 0.403,  0.891,   0.0    } }; // YEN<br>from = 0;<br>FindPathsAndNegativeCycles(graph, from);</pre><p>Look, we have found one profitable chain:</p><pre>Path from 0 to 0 is : 0(USD)<br>Path from 0 to 1 is : 0(USD) 2(YEN) 1(CHF)<br>Path from 0 to 2 is : 0(USD) 2(YEN)</pre><p>We can apply these concepts to much larger graphs involving multiple currencies:</p><pre>graph.Nodes.push_back({ &quot;USD&quot; }); // 1 (Index = 0)<br>graph.Nodes.push_back({ &quot;CHF&quot; });<br>graph.Nodes.push_back({ &quot;YEN&quot; });<br>graph.Nodes.push_back({ &quot;GBP&quot; });<br>graph.Nodes.push_back({ &quot;CNY&quot; }); // 5  (Index = 4)<br>// LogE(x) table:  USD     CHF     YEN    GBP   CNY<br>graph.Matrix = { { 0.0,    0.490, -0.402, 0.7,  0.413 },   // USD<br>                 { -0.489, 0.0,   -0.891, 0.89, 0.360 },   // CHF<br>                 { 0.403,  0.891,  0.0,   0.91, 0.581 },   // YEN<br>                 { 0.340,  0.405,  0.607, 0.0,  0.72 },    // GBP<br>                 { 0.403,  0.350,  0.571, 0.71, 0.0 } };   // CNY<br>from = 0;<br>runDetectNegativeCycles(graph, from);</pre><p>As a result, we might find multiple candidates to get profit:</p><pre>Path from 0 to 0 is : 0(USD)<br>Path from 0 to 1 is : 0(USD) 2(YEN) 1(CHF)<br>Path from 0 to 2 is : 0(USD) 2(YEN)<br>Path from 0 to 3 is : 0(USD) 2(YEN) 3(GBP)<br>Path from 0 to 4 is : 0(USD) 2(YEN) 4(CNY)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/719/1*6lMJmMYUSHe6vLyMKYLxLg.jpeg" /><figcaption>© <em>Carl Barks The Sport of Tycoons Painting Original Art (1974)</em></figcaption></figure><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, minimizing time costs and reducing commissions 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 two to three 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 low-level commissions. Usually, only large financial institutions have such a resource in their hands.</p><h3>Automation Using Smart Contracts</h3><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 creating 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 has 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, regulatory bodies are striving to uphold market rules.</p><h3>Why Are We Looking Into This?</h3><p>Despite global economics&#39; complexity, obscurity, and unpredictability, 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 peacefully benefit one another, 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 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="https://www.aeaweb.org/articles?id=10.1257/aer.104.7.1942">Importers, Exporters, and Exchange Rate Disconnect</a></li><li><a href="https://www.aeaweb.org/articles?id=10.1257/aer.100.1.304">Currency choice and exchange rate pass-through</a></li><li><a href="https://repositoriodigital.bcentral.cl/xmlui/handle/20.500.12580/7504">Exchange rate puzzles and policies</a></li></ul><p>These papers shed light on some fundamental mechanisms of Foreign Exchanges, which are still hard to understand and fit into one model.</p><p>However, playing with code and finding a solution for a practical problem helped me get more clues on it. I hope you enjoyed this little exploration trip as much as I am.</p><p>Stay tuned!</p><h3>Links</h3><ul><li><a href="https://github.com/optiklab/negative-cycles-in-a-graph/">Source code with all these examples</a></li><li>Sedgewick R. — Algorithms in C, Part 5: Graph Algorithms</li><li><a href="https://www.youtube.com/watch?v=24HziTZ8_xo">Bellman Ford Algorithm Code Implementation</a></li><li><a href="https://github.com/williamfiset/Algorithms/blob/master/src/main/java/com/williamfiset/algorithms/graphtheory/BellmanFordAdjacencyMatrix.java">William Fiset’s GitHub examples — Bellman Ford On Adjacency Matrix</a></li><li><a href="https://github.com/williamfiset/Algorithms/blob/master/src/main/java/com/williamfiset/algorithms/graphtheory/BellmanFordEdgeList.java">William Fiset’s GitHub examples — Bellman Ford On Edge List</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=bba62d90f921" width="1" height="1" alt=""><hr><p><a href="https://medium.com/better-programming/algorithmic-alchemy-exploiting-graph-theory-in-the-foreign-exchange-bba62d90f921">Algorithmic Alchemy: Exploiting Graph Theory in the Foreign Exchange</a> was originally published in <a href="https://betterprogramming.pub">Better Programming</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Crafting Mazes]]></title>
            <link>https://medium.com/better-programming/crafting-mazes-4686cccee99f?source=rss-89b693ce234e------2</link>
            <guid isPermaLink="false">https://medium.com/p/4686cccee99f</guid>
            <category><![CDATA[maze]]></category>
            <category><![CDATA[labyrinth]]></category>
            <category><![CDATA[spanning-tree]]></category>
            <category><![CDATA[graph]]></category>
            <category><![CDATA[c-programming]]></category>
            <dc:creator><![CDATA[Anton Yarkov]]></dc:creator>
            <pubDate>Wed, 27 Sep 2023 18:29:02 GMT</pubDate>
            <atom:updated>2023-09-27T18:29:02.427Z</atom:updated>
            <content:encoded><![CDATA[<h4>Inspired while creating a maze map for the Wall-E project. Follow this tutorial to explore ways to generate mazes using Graph Theory algorithmically</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*xz6fyB9QDfOl8RQq" /><figcaption>Photo by <a href="https://unsplash.com/@benseibel?utm_source=medium&amp;utm_medium=referral">Ben Mathis Seibel</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>In my <a href="https://medium.com/better-programming/exploring-path-finding-algorithms-for-wall-e-98516829633">previous article</a>, we delved into pathfinding problems in graphs, which are inherently connected to solving mazes.</p><p>When I set out to create a maze map for the <a href="https://github.com/optiklab/path-algorithms-on-a-grid-map">Wall-E project</a>, I initially expected to find a quick and easy way to accomplish this task. However, I quickly immersed myself in the vast, 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 ways, each with numerous variations and countless algorithms for generating them.</p><blockquote>Surprisingly, I couldn’t find any algorithmic books that comprehensively covered this topic, and even the <a href="https://en.wikipedia.org/wiki/Maze_generation_algorithm">Wikipedia page</a> didn’t provide a systematic overview. Fortunately, I stumbled upon a fantastic resource that covers <a href="https://www.astrolog.org/labyrnth/algrithm.htm">various maze types and algorithms</a>, which I highly recommend exploring.</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><h3>How To Create a Maze</h3><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="https://github.com/optiklab/mazerandom">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><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fCWEM5xuize5nWzJsuo1Mg.jpeg" /><figcaption>Creating maze by carving passages in the grid.</figcaption></figure><p>To achieve this algorithmically, we apply Depth-First Search to the grid of cells. Let’s look at how it’s done in the <a href="https://github.com/optiklab/mazerandom/blob/bf61b13b50bfdde4356ef635f89ad5b0965529e9/Main.cpp#L100">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:</p><pre>vector &lt; vector &lt; int &gt; &gt; maze_cells; // A grid 20x20<br>stack&lt;Coord&gt; my_stack;      // Stack to traverse the grid by DFS<br>my_stack.push(Coord(0, 0)); // Starting from very first cell</pre><p>We visit every cell in the grid and push its neighbors onto the stack for deep traversal:</p><pre>...<br>while (visitedCells &lt; HORIZONTAL_CELLS * VERTICAL_CELLS)<br>{<br> vector&lt;int&gt; neighbours;<br> // Step 1: Create an array of neighbour cells that were not yet visited (from North, East, South and West).<br> // North is not visited yet?<br> if ((maze_cells[offset_x(0)][offset_y(-1)] &amp; CELL_VISITED) == 0) <br> {<br>  neighbours.push_back(0);<br> }<br> // East is not visited yet?<br> if ((maze_cells[offset_x(1)][offset_y(0)] &amp; CELL_VISITED) == 0) <br> {<br>  neighbours.push_back(1);<br> }<br> ... // Do the same for West and South...</pre><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:</p><pre>...<br> // If we have at least one unvisited neighbour <br> if (!neighbours.empty()) <br> {<br>  // Choose random neighbor to make it available<br>  int next_cell_dir = neighbours[rand() % neighbours.size()];<br><br>  // Create a path between the neighbour and the current cell<br>  switch (next_cell_dir)<br>  {<br>  case 0: // North<br>   // Mark it as visited. Mark connection between North and South in BOTH directions.<br>   maze_cells[offset_x(0)][offset_y(-1)] |= CELL_VISITED | CELL_PATH_S;<br>   maze_cells[offset_x(0)][offset_y(0)] |= CELL_PATH_N;<br>   // <br>   my_stack.push(Coord(offset_x(0), offset_y(-1)));<br>   break;<br><br>  case 1: // East<br>   // Mark it as visited. Mark connection between East and West in BOTH directions.<br>   maze_cells[offset_x(1)][offset_y(0)] |= CELL_VISITED | CELL_PATH_W;<br>   maze_cells[offset_x(0)][offset_y(0)] |= CELL_PATH_E;<br>   my_stack.push(Coord(offset_x(1), offset_y(0)));<br>   break;<br>  ... // Do the same for West and South...<br>  }<br>  visitedCells++;<br> }<br> else<br> {<br>  my_stack.pop();<br> }<br>...</pre><p>Finally, it calls the <a href="https://github.com/optiklab/mazerandom/blob/bf61b13b50bfdde4356ef635f89ad5b0965529e9/Main.cpp#L27">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><figure><img alt="" src="https://cdn-images-1.medium.com/max/560/1*IyMZUdXVkw9vwPlcOii48w.gif" /><figcaption>Maze generation with random passages selection</figcaption></figure><p>However, this maze doesn’t guarantee a solution. It will often 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.</p><h3>Creating a Maze Using Graph Theory</h3><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 generation process often starts with creating a “perfect” maze.</p><p>A “perfect” maze, 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="https://en.wikipedia.org/wiki/Spanning_tree">Spanning Tree</a> ensures a path from the start to the end.</p><p>In computer science terms, such a maze can be described as a <a href="https://en.wikipedia.org/wiki/Spanning_tree">Spanning Tree</a> over a 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><figure><img alt="" src="https://cdn-images-1.medium.com/max/616/1*iI96RjtwhquVQtQyO5Q3mQ.jpeg" /><figcaption>Example of one possible spanning tree within the graph of a maze.</figcaption></figure><p>The image above depicts only one solution, but there are 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="https://github.com/razimantv/mazegenerator">mazegenerator</a> codebase by <a href="https://github.com/razimantv">@razimantv</a> that generates mazes in SVG file format.</p><p>Therefore, I forked the repository and based my solution on it. Kudos to <a href="https://github.com/razimantv">@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="https://github.com/optiklab/path-algorithms-on-a-grid-map">Wall-E project</a>.</p><p>I refactored the code to remove unnecessary components and focus exclusively on rectangular mazes.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/527/1*vfgBZIcDo990N2N8W9OhVQ.jpeg" /></figure><p>However, I retained support for various algorithms to build a spanning tree.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JIBBwlVcZ-u0xW5MwWdi-Q.jpeg" /></figure><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="https://github.com/optiklab/mazegenerator/blob/main/maze/mazebaze.cpp">\mazegenerator\maze\mazebaze.cpp</a>:</p><pre>/**<br> * \param algorithm Algorithm that is used to generate maze spanning tree.<br> */<br>void MazeBase::GenerateMaze(SpanningtreeAlgorithmBase* algorithm)<br>{<br>    // Generates entire maze spanning tree<br> auto spanningTreeEdges = algorithm-&gt;SpanningTree(_verticesNumber, _edgesList);<br><br> // Find a solution of a maze based on Graph DFS.<br> _Solve(spanningTreeEdges);<br> <br> // Build a maze by removing unnecessary edges.<br> _RemoveBorders(spanningTreeEdges);<br>}</pre><p>I introduced visualization using the SFML graphics library, thanks to a straightforward _Draw_ function.</p><p>While DFS is the default algorithm for creating a Spanning Tree, multiple algorithms are available.</p><p>The result is a handy utility that generates rectangular “perfect” mazes and displays them on the screen:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/632/1*1cfbDUlrMAIAgGJBW2pFJA.jpeg" /><figcaption>Maze with exactly one input and one output</figcaption></figure><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 an 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="https://github.com/optiklab/path-algorithms-on-a-grid-map">Wall-E project</a>, and I leave you here, hoping you’re inspired to explore this fascinating world of mazes and embark on your own journey.</p><p>Stay tuned!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4686cccee99f" width="1" height="1" alt=""><hr><p><a href="https://medium.com/better-programming/crafting-mazes-4686cccee99f">Crafting Mazes</a> was originally published in <a href="https://betterprogramming.pub">Better Programming</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Exploring Well-Known Path-Finding Algorithms With SFML Graphics Library]]></title>
            <link>https://medium.com/better-programming/exploring-path-finding-algorithms-for-wall-e-98516829633?source=rss-89b693ce234e------2</link>
            <guid isPermaLink="false">https://medium.com/p/98516829633</guid>
            <category><![CDATA[pathfinding]]></category>
            <category><![CDATA[breadth-first-search]]></category>
            <category><![CDATA[grid-layout]]></category>
            <category><![CDATA[a-star-algorithm]]></category>
            <category><![CDATA[dijkstras-algorithm]]></category>
            <dc:creator><![CDATA[Anton Yarkov]]></dc:creator>
            <pubDate>Thu, 10 Aug 2023 21:44:34 GMT</pubDate>
            <atom:updated>2023-09-18T21:09:40.650Z</atom:updated>
            <content:encoded><![CDATA[<h4>In my last article, I showed you how to unify the implementation of the most well-known graph-traversal algorithms. Now, let’s look into performance differences and ways to make it more visually appealing</h4><h3>Behind the Scenes</h3><p>A few years ago, Yandex organized a contest called <a href="https://contest.yandex.ru/contest/28643/problems">Robots Couriers</a> with an enticing prize: a ticket to a <a href="https://taxi.yandex.ru/action/ysdm">closed self-driving conference for professionals</a>. The contest resembled a game. Participants were tasked to find optimal routes on a map and optimize delivery using robotic couriers.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/744/0*6WnUYp9gxzKPpsJs.png" /><figcaption>Image from Yandex</figcaption></figure><p>As I delved into the topic, I discovered that despite route finding being a solved problem, it continued to interest the professional game development community. Between 2010 and 2020, engineers <a href="https://www.gdcvault.com/play/1022094/JPS-Over-100x-Faster-than">made significant optimizations to the A* algorithm</a>, particularly beneficial for AAA games with massive maps. Reading <a href="https://lucho1.github.io/JumpPointSearch/">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 exploring this field intriguing and developing 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><figure><img alt="" src="https://cdn-images-1.medium.com/max/860/0*I1-ZuMtsExcbkD9e.png" /></figure><h3>Goal</h3><p>This project builds upon one of my previous endeavors, where I demonstrated that four well-known path-finding algorithms (BFS, DFS, Dijkstra’s, and A*) are not fundamentally different and can be implemented universally. However, showcasing significant performance differences among these algorithms in that project was challenging.</p><p>I aim to use improved test data in this article 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 implementation.</p><h3>Imaginary World</h3><p>Imagine a technically advanced and innovative city where the future has arrived long ago. In this city, courier robots deliver most orders, and it has become a rarity for someone 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 precisely 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><blockquote><em>And I’m ignoring the rest of the original task:</em></blockquote><blockquote><em>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 </em><em>Costc rubles.</em></blockquote><blockquote><em>Next, </em><em>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).</em></blockquote><blockquote><em>For each successfully delivered order, you will receive </em><em>max(0, MaxTips — DeliveryTime) dollars in tips, where </em><em>MaxTips is the maximum number of tips for one order, and </em><em>DeliveryTime is the time from when the order appeared to its delivery in seconds.</em></blockquote><blockquote><em>The total number of points you earn in one test is calculated by the formula </em><em>TotalTips — R × Costc, where </em><em>TotalTips is the total number of tips earned, </em><em>R is the number of robots used, and </em><em>Costc is the cost of building one robot. The </em><em>Costc and </em><em>MaxTips values are set in each test. If you earned fewer 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 incorrect actions.</em></blockquote><h4>Input</h4><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 MaxTips and Costc parameters for my first implementation and may consider that in the 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 receive 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><h4>Output</h4><p>Your task is to visualize the map and the optimal routes using the SFML graphics library.</p><h3>Modelling the Maps</h3><p>I’m fond of maps represented as a grid of cells. Thus, I prefer to render all the results and map them as a grid on a cell-by-cell basis.</p><p>There is also an option to execute a path search right on the grid without using any additional data structure (I have implemented this for learning purposes. You can see the results in the code).</p><p>However, because of a grid, it is easy to represent a map as a graph in one way or another. I prefer to use an adjacency list of cells for most algorithms like BFS, Dijkstra’s, and A-Star. For algorithms like Bellman-Ford, using an Edges List instead of an Adjacency List may make sense. That’s why if you explore the codebase, you will find all of it, and they are all working examples.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/448/0*Be8KIxmz9pU--DOc.jpeg" /></figure><p>To split the logic and responsibility, I have a Navigator entity responsible for executing path finding according to the orders and tasks configuration specified via AppConfig file and related map files.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/204/0*kVxK64hVd4MIBD-M.jpeg" /></figure><p><a href="https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/main/data/appConfig.json">App Config</a> looks like this:</p><pre>{<br>    &quot;font&quot;: &quot;../../data/arial.ttf&quot;,<br>    &quot;map&quot;: &quot;../../data/maps/test_29_yandex_weighten_real_map&quot;,<br>    &quot;shadow&quot;: false,<br>    &quot;map_&quot;: &quot;../../data/maps/test_08_low_res_simple_map&quot;,<br>    &quot;map__&quot;: &quot;../../data/maps/test_10&quot;,<br>    &quot;map___&quot;: &quot;../../data/maps/test_07_partially_blocked_map&quot;,<br>    ...</pre><p>Note that “map_”, “map__”, etc., are not configuration properties. They are ignored during the application run. Since there is no way to comment on part of the JSON file, I use underlining in the property name so it can stay in the config file but not be used.</p><p>The <a href="https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/main/data/maps/test_12_low_res_complex_map">map file</a> looks like this:</p><pre>25 50 150<br>#########################<br>#########################<br>#########################<br>###.......#####.......###<br>###.......#####.......###<br>###.......#####.......###<br>###...................###<br>###.......#####.......###<br>###.......#####.......###<br>###...................###<br>######.###########.######<br>######.###########.######<br>######.###########.######<br>###.......#####.......###<br>###.......#####.......###<br>###.......#####.......###<br>###.......#####.......###<br>###.......#####.......###<br>###.......#####.......###<br>###.......#####.......###<br>######.###########.######<br>#########################<br>#########################<br>#########################<br>#########################<br>2 4<br>2<br>6 6 4 20</pre><p>This is one of the simplest examples that contain either blocked or not blocked cells. I have prepared many examples of input parameters and test data. Starting from very small parts that let you debug and learn the code, finishing with a huge piece of map (from the real existing city) that allows us to measure the performance of a Graph algorithm.</p><h3>How Do We Draw Maps</h3><p>When a map contains only cells with a binary state (either blocked or non-blocked), any edge of a graph exists.</p><p>To find a path in the graph, we have to represent it efficiently. Like in my previous article, I have used an <a href="https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/720f43e5bbcad4eefc7d37e908df8f392a8e0761/application/path-algorithms-on-a-grid-map/src/map/graph.h#L10">adjacency list</a> with the relationship as Vector[NodeId]-&gt;points to-&gt;Vector[Neighbour Nodes]:</p><pre>typedef std::vector&lt;std::vector&lt;std::shared_ptr&lt;Cell&gt;&gt;&gt; Graph;</pre><blockquote>Interestingly enough, when exploring grids, it’s not necessary to use graphs at all. We are capable of traversing grids using BFS/DFS algorithms cell by cell without thinking about edges. See the method <a href="https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/720f43e5bbcad4eefc7d37e908df8f392a8e0761/application/path-algorithms-on-a-grid-map/src/map/rectangularmap.cpp#L739">_GetPathByBFSOnGrid</a>.</blockquote><p>First, the initialization code reads the file and converts it into <a href="https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/720f43e5bbcad4eefc7d37e908df8f392a8e0761/application/path-algorithms-on-a-grid-map/src/map/rectangularmap.cpp#L147">the grid</a> row by row and column by column. Here’s what that looks like:</p><pre>bool RectangularMap::LoadMap(const std::string&amp; filepath, bool shadow)<br>{<br>...<br>  // Fill the grid.<br>  _verticesNumber = 0;<br>  for (int row = 0; row &lt; _height; row++)<br>  {<br>    ...<br>    for (int col = 0; col &lt; _width; col++)<br>    {<br>      int x = col;<br>      int y = row;<br>      if (line[col] == BLOCK_CELL)<br>      {<br>        // Create a shared pointer to safely pass pointers between the classes.<br>        _grid[row][col] = std::make_shared&lt;Cell&gt;(x, y, line[col], <br>          blockColor, shadow, _scaleFactor);<br>      }<br>      else<br>      {<br>        ...<br>      }<br>    }<br>  }<br><br>  // Make a graph<br>  InitialiseGraph();<br>...<br>}</pre><p>Then, it creates an actual graph as <a href="https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/720f43e5bbcad4eefc7d37e908df8f392a8e0761/application/path-algorithms-on-a-grid-map/src/map/rectangularmap.cpp#L32">an adjacency list</a>:</p><pre>void RectangularMap::InitialiseGraph()<br>{<br>  MapBase::InitialiseGraph();<br>  ...<br>  unordered_set&lt;int&gt; visited;<br>for (int rr = 0; rr &lt; _grid.size(); rr++)<br>  {<br>    for (int cc = 0; cc &lt; _grid[rr].size(); cc++)<br>    {<br>      if (_grid[rr][cc]-&gt;GetId() &gt; -1)<br>      {<br>        for (int i = 0; i &lt; 4; i++)<br>        {<br>          int r = rr + dr[i];<br>          int c = cc + dc[i];<br>          if (r &gt;= 0 &amp;&amp; c &gt;= 0 &amp;&amp; r &lt; _width &amp;&amp; c &lt; _height &amp;&amp;<br>              _grid[r][c]-&gt;GetId() &gt; -1)<br>          {<br>            if (_isNegativeWeighten)<br>            {<br>              ...<br>            }<br>            else<br>            {<br>              _adjacencyList[_grid[rr][cc]-&gt;GetId()].push_back(_grid[r][c]);<br>            }<br>          }<br>        }<br>      }<br>    }<br>  }<br>}</pre><p>Grid representation is useful to draw on the screen using the SFML library. We can draw it <a href="https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/720f43e5bbcad4eefc7d37e908df8f392a8e0761/application/path-algorithms-on-a-grid-map/src/map/cell.cpp#L37">by creating geometric objects</a> (this is precisely what I do for small maps):</p><pre>...<br>for (int j = _visibleTopLeftY; j &lt; _visibleBottomRightY; j++)<br>{<br>  for (int i = _visibleTopLeftX; i &lt; _visibleBottomRightX; i++)<br>  {<br>    _grid[j][i]-&gt;Draw(_window, _scaleFactor);<br>  }<br>}<br>...<br>sf::RectangleShape tile;<br>tile.setSize(sf::Vector2f(_cellSize - 5, _cellSize - 5));<br>tile.setPosition(sf::Vector2f(_x * _cellSize, _y * _cellSize));<br>tile.setFillColor(_color);<br>window.draw(tile);</pre><p>Or, for larger maps, we can do it <a href="https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/720f43e5bbcad4eefc7d37e908df8f392a8e0761/application/path-algorithms-on-a-grid-map/src/map/mapbaze.cpp#L49">pixel by pixel</a>, which is more efficient. Here’s what that looks like:</p><pre>sf::Uint8* pixels = new sf::Uint8[_width * _height * 4];<br>for (int j = _visibleTopLeftY; j &lt; _visibleBottomRightY; j++)<br>{<br>  for (int i = _visibleTopLeftX; i &lt; _visibleBottomRightX; i++)<br>  {<br>    int index = (_grid[j][i]-&gt;GetY() * _width + _grid[j][i]-&gt;GetX());<br>    sf::Color color = _grid[j][i]-&gt;GetColor();<br>    pixels[index * 4] = color.r;<br>    pixels[index * 4 + 1] = color.g;<br>    pixels[index * 4 + 2] = color.b;<br>    pixels[index * 4 + 3] = color.a;<br>  }<br>}<br>sf::Texture texture;<br>texture.create(_width, _height);<br>texture.update(pixels);<br>sf::Sprite sprite;<br>sprite.setTexture(texture);<br>sprite.setScale(cellSize, cellSize);<br>_window.draw(sprite);</pre><p>Finally, let’s see what a map defined by the file <a href="https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/main/data/maps/test_25_xmax">test_25_xmax</a> would look like.</p><p>Initially, the file’s definitions look like this:</p><pre>..............C.................<br>..............#.................<br>.............###................<br>............#####...............<br>...........#######..............<br>..........##1###2##.............<br>.........###########............<br>........##3######4###...........<br>.......###############..........<br>......#################.........<br>.....###################........<br>....#####################.......<br>.............###................<br>.............###................<br>.............###................</pre><p>And a map rendered with SFML looks like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/762/0*Lye-Q5wzPQxPZFuq.jpeg" /></figure><p>Because I wanted all of that to be controlled by the user with the keyboard, I left all the user-behavior logic in the <a href="https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/720f43e5bbcad4eefc7d37e908df8f392a8e0761/application/path-algorithms-on-a-grid-map/src/main.cpp#L33">main.cpp</a>. I like to call it Controller logic.</p><p>The SFML library makes it easy to handle keyboard events:</p><pre>while (window.isOpen())<br>{<br>  Event event;<br>  while (window.pollEvent(event))<br>  {<br>    if (event.type == Event::Closed)<br>      window.close();<br><br>    if (event.type == Event::KeyPressed &amp;&amp; event.key.code == Keyboard::Space)<br>    {<br>      ... Do what you need here<br>    }<br>  }<br>}</pre><p>The main idea is that pressing the space button will trigger the program to read and render the map file. Then, you can load routing tasks and calculate the shortest path between two points on a map by creating a second trigger (press the space button again). Here’s how to do that:</p><pre>...<br>if (navigator-&gt;IsReady())<br>{<br>  navigator-&gt;Navigate(); // Finding route between two points<br>}<br>else<br>{<br>  if (map-&gt;IsReady()) // Second SPACE press runs the routing<br>  {<br>	skipReRendering = true;<br>	if (navigator-&gt;LoadTasks(filepath))<br>	{<br>	  navigator-&gt;SetMap(map);<br>	}<br>  }<br>  else // Load and draw map<br>  {<br>	drawLoading(window, font);<br>	if (!map-&gt;LoadMap(filepath, shadowed))<br>	{<br>	  return 0;<br>	}<br>	drawProcessing(window, font);<br>  }<br>}<br>...</pre><h3>We Need To Go Deeper</h3><p>I wanted to play with more Graph algorithms, which all have their limitations, so I also implemented multicolor maps that multiweighted graphs can represent.</p><p>Every cell is colored, which means that the edge not only exists but also applies some weight (or fee, or fine, you name it). So, the edge might be blocked, half-blocked, not blocked, etc. You got the idea.</p><p>So now, I have implemented multicolor maps that look joyful — like a game that’s ready to play (example from file <a href="https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/main/data/maps/test_31_multi_weight_graph_map">test_31_multi_weight_graph_map</a>):</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/773/0*b_-vjJVB0I2Gzz_5.jpeg" /></figure><p>Some of the configuration files contain more complex maps from really existing cities, like the <a href="https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/main/data/maps/test_29_yandex_weighten_real_map">test_29_yandex_weighten_real_map</a>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*IrePVCrw2XUDkVS8.jpeg" /></figure><p>As a challenge, we should now handle maps with very flexible configurations. <a href="https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/main/application/path-algorithms-on-a-grid-map/src/map/rectangularmap.cpp">RectangularMap.cpp</a> 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="https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/720f43e5bbcad4eefc7d37e908df8f392a8e0761/application/path-algorithms-on-a-grid-map/src/map/rectangularmap.cpp#L598">BFS#Line 598</a>, <a href="https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/720f43e5bbcad4eefc7d37e908df8f392a8e0761/application/path-algorithms-on-a-grid-map/src/map/rectangularmap.cpp#L299">Dijkstra#Line 299</a>, <a href="https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/720f43e5bbcad4eefc7d37e908df8f392a8e0761/application/path-algorithms-on-a-grid-map/src/map/rectangularmap.cpp#L356">A-Star#Line 356</a>, <a href="https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/720f43e5bbcad4eefc7d37e908df8f392a8e0761/application/path-algorithms-on-a-grid-map/src/map/rectangularmap.cpp#L428">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 the 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, 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 here and there, maybe some dirty code… it’s all a way of learning :). To grasp an idea of what’s inside, I recommend you review the <a href="https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/main/application/path-algorithms-on-a-grid-map/src/map/rectangularmap.h">RectangularMap.h</a>.</p><p>There are also some fun features, like a Focus feature allowing one to render only a particular map part. It changes focus by rerendering the necessary part using the <a href="https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/main/application/path-algorithms-on-a-grid-map/src/utils/NotifyVisiblePartChanged.h">Observer pattern</a> when the user presses the PgDown or PgUp buttons. Improving this feature and implementing “Zoom” functionality is pretty easy. Use it as a homework assignment if you like it.</p><p>Focus feature with a working map file, <a href="https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/main/data/maps/test_29_yandex_weighten_real_map">test_29_yandex_weighten_real_map</a>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*M1JHkR9gERVrKMMh.jpeg" /></figure><p>The Classes diagram looks like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/717/0*sdCfKaJrf5WvUgMV.jpeg" /></figure><h3>Run and Play</h3><p>The most joyful part is running this little application and 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 and changing the code&#39;s logic.</p><p>After starting, you need to press SPACE. An application will render a map according to the configuration file, and it makes a lot of sense to start exploring from the simplest test cases and then transition to the most complex ones.</p><p>Pressing SPACE again executes the routing algorithms and finds the path between the start and the nearest order. By the way, it’s not done yet, but it is easy to implement ways to read all the rest of the orders available in map configuration files and to execute pathfinding to all of them.</p><p>Here is the route found on the map defined by file <a href="https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/main/data/maps/test_18_yandex_super_high_res">test_18_yandex_super_high_res</a>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/778/0*KhxgMG0BuK9xCkop.jpeg" /></figure><p>It is also capable of finding routes in the maps that simulate existing cities, like the <a href="https://github.com/optiklab/path-algorithms-on-a-grid-map/blob/main/data/maps/test_29_yandex_weighten_real_map">test_29_yandex_weighten_real_map</a>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*wi4IHuzYJmh5eEeF.jpeg" /></figure><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, the application will treat the 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><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*3-ZeELugkEz_n1SE.jpeg" /></figure><h3>Final words</h3><p>With this, I want to leave you alone and let you play with these code examples. I hope you find it fascinating and will learn a lot from it.</p><p>Stay tuned!</p><h3>Links</h3><ol><li><a href="https://antonyarkov.substack.com/p/universal-implementation-of-bfs-dfs">Universal implementation of BFS, DFS, Dijkstra, and A-Star algorithms</a></li><li><a href="https://www.gdcvault.com/play/1022094/JPS-Over-100x-Faster-than">JPS+: Over 100x Faster than A* by Steve Rabin</a></li><li><a href="https://lucho1.github.io/JumpPointSearch/">A* Optimizations and Improvements by Lucho Suaya</a></li></ol><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=98516829633" width="1" height="1" alt=""><hr><p><a href="https://medium.com/better-programming/exploring-path-finding-algorithms-for-wall-e-98516829633">Exploring Well-Known Path-Finding Algorithms With SFML Graphics Library</a> was originally published in <a href="https://betterprogramming.pub">Better Programming</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Universal implementation of BFS, DFS, Dijkstra and A-Star algorithms]]></title>
            <link>https://medium.com/better-programming/universal-implementation-of-bfs-dfs-dijkstra-and-a-star-algorithms-996ced18768?source=rss-89b693ce234e------2</link>
            <guid isPermaLink="false">https://medium.com/p/996ced18768</guid>
            <category><![CDATA[depth-first-search]]></category>
            <category><![CDATA[breadth-first-search]]></category>
            <category><![CDATA[pathfinding]]></category>
            <category><![CDATA[dijkstras-algorithm]]></category>
            <category><![CDATA[a-star-algorithm]]></category>
            <dc:creator><![CDATA[Anton Yarkov]]></dc:creator>
            <pubDate>Tue, 08 Aug 2023 14:58:35 GMT</pubDate>
            <atom:updated>2023-08-08T14:58:35.077Z</atom:updated>
            <content:encoded><![CDATA[<p>It turns out that well-known algorithms like <a href="https://en.wikipedia.org/wiki/Breadth-first_search">BFS</a>, <a href="https://en.wikipedia.org/wiki/Depth-first_search">DFS</a>, <a href="https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm">Dijkstra</a>, and <a href="https://en.wikipedia.org/wiki/A*_search_algorithm">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><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Sex876sJbWqQmd_yw2dCYw.png" /></figure><p>You can find all the working code for these algorithms on my GitHub repository <a href="https://github.com/optiklab/path-algorithms-in-a-graph">here</a>. I recommend experimenting with the code while reading this article since practical experience enhances learning more than just theoretical understanding.</p><h3>Graph representation</h3><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:</p><pre>( 0  ) - ( 1  ) - ( 2  ) - ( 3  ) - ( 4  )<br>  |        |        |        |        |<br>( 5  ) - ( 6  ) - ( 7  ) - ( 8  ) - ( 9  )<br>  |        |        |        |        |<br>( 10 ) - ( 11 ) - ( 12 ) - ( 13 ) - ( 14 )<br>  |        |        |        |        |<br>( 15 ) - ( 16 ) - ( 17 ) - ( 18 ) - ( 19 )<br>  |        |        |        |        |<br>( 20 ) - ( 21 ) - ( 22 ) - ( 23 ) - ( 24 )</pre><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><em>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.</em></blockquote><p>To account for these limitations, let’s assign imaginary coordinates to each node (X, Y):</p><p>To account for these limitations, let’s assign imaginary coordinates to each node (X, Y):</p><pre>(0, 0) - (0, 1) - (0, 2) - (0, 3) - (0, 4)<br>   |        |        |        |       |<br>(1, 0) - (1, 1) - (1, 2) - (1, 3) - (1, 4)<br>   |        |        |        |       |<br>(2, 0) - (2, 1) - (2, 2) - (2, 3) - (2, 4)<br>   |        |        |        |       |<br>(3, 0) - (3, 1) - (3, 2) - (3, 3) - (3, 4)<br>   |        |        |        |       |<br>(4, 0) - (4, 1) - (4, 2) - (4, 3) - (4, 4)</pre><p>Finally, lets assign some weight for every edge in the graph:</p><pre>(0, 0) -1- (0, 1) -1- (0, 2) -1- (0, 3) -2- (0, 4)<br>   |          |          |          |         |<br>   2          1          1          2         2<br>   |          |          |          |         |<br>(1, 0) -2- (1, 1) -1- (1, 2) -2- (1, 3) -1- (1, 4)<br>   |          |          |          |         |<br>   2          1          1          1         1<br>   |          |          |          |         |<br>(2, 0) -1- (2, 1) -1- (2, 2) -1- (2, 3) -2- (2, 4)<br>   |          |          |          |         |<br>   2          1          1          1         2<br>   |          |          |          |         |<br>(3, 0) -2- (3, 1) -2- (3, 2) -1- (3, 3) -2- (3, 4)<br>   |          |          |          |         |<br>   2          1          1          2         2<br>   |          |          |          |         |<br>(4, 0) -2- (4, 1) -1- (4, 2) -2- (4, 3) -2- (4, 4)</pre><p>In C++, this structure may be represented as follows:</p><pre>class GraphNode<br>{<br>public:<br>    int X;<br>    int Y;<br>};</pre><pre>class Graph<br>{<br>public:<br>    vector&lt;vector&lt;pair&lt;int, int&gt;&gt;&gt; Edges;<br>    vector&lt;GraphNode&gt; Nodes;<br>};</pre><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:</p><pre>int toNode = graph.Edges[fromNode][neighbourIndex].first;<br>int weight = graph.Edges[fromNode][neighbourIndex].second;</pre><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="https://github.com/optiklab/path-algorithms-in-a-graph/blob/b8b0346ce7c6b5d3f54b91966337d622d6b372ab/main.cpp#L247">Generating a list of nodes</a></li><li><a href="https://github.com/optiklab/path-algorithms-in-a-graph/blob/b8b0346ce7c6b5d3f54b91966337d622d6b372ab/main.cpp#L346">Creating custom connections</a></li></ul><p>Alternatively, it is also possible to <a href="https://github.com/optiklab/path-algorithms-in-a-graph/blob/b8b0346ce7c6b5d3f54b91966337d622d6b372ab/main.cpp#L176">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><h3>Universal algorithm</h3><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:</p><pre>class pathFindingBase<br>{<br>public:<br>  virtual void insert(int node) = 0;<br>  virtual int getFirst() = 0;<br>  virtual bool isEmpty() = 0;<br>};</pre><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="https://github.com/optiklab/path-algorithms-in-a-graph/blob/ecb090ce3f0fe8395e2c7360873bd9b0fd899d15/main.cpp#L18">main.cpp#L18</a>:</p><pre>const int INF = 1000000;<br>const int WHITE = 0;<br>const int GREY = 1;<br>const int BLACK = 2;<br><br>/// &lt;summary&gt;<br>/// Universal algorithm to apply Path search using BFS, DFS, Dijkstra, A-Star.<br>/// &lt;/summary&gt;<br>vector&lt;int&gt; FindPath(Graph&amp; graph, int start, int finish, int finishX, int finishY)<br>{<br>  int verticesNumber = graph.Nodes.size();<br>  // All the nodes are White colored initially<br>  vector&lt;int&gt; nodeColor(verticesNumber, WHITE);<br>  // Current shortest path found from Start to i <br>  // is some large/INFinite number from the beginning.<br>  vector&lt;int&gt; shortestPath(verticesNumber, INF);<br>  // Index of the vertex/node that is predecessor <br>  // of i-th vertex in a shortest path to it.<br>  vector&lt;int&gt; previousVertex(verticesNumber, -1); <br>  // We should use pointers here because we want <br>  // to pass the pointer to a data-structure<br>  // so it may receive all the updates automatically on every step.<br>  auto ptrShortestPath = make_shared&lt;vector&lt;int&gt;&gt;(shortestPath);<br>  shared_ptr&lt;Graph&gt; ptrGraph = make_shared&lt;Graph&gt;(graph);<br>  ...</pre><p>Next, we need to initialize our data structure. By using the code provided in the <a href="https://github.com/optiklab/path-algorithms-in-a-graph">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).</p><pre>//////////////////////////////////////////////////<br>// TODO<br>// UNCOMMENT DATA STRUCTURE YOU WANT TO USE:<br><br>//dfsStack customQueue;<br>//bfsQueue customQueue;<br>//dijkstraPriorityQueue customQueue(ptrShortestPath);<br>//aStarQueue customQueue(finishX, finishY, ptrGraph, ptrShortestPath);<br>// END OF TODO<br>/////////////////////////////////////////////////</pre><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="https://github.com/optiklab/path-algorithms-in-a-graph/blob/ecb090ce3f0fe8395e2c7360873bd9b0fd899d15/main.cpp#L48">main.cpp#L48:</a></p><pre>...<br>  customQueue.insert(start);<br>  nodeColor[start] = BLACK;<br>  ptrShortestPath-&gt;at(start) = 0;<br><br>  // Traverse nodes starting from start node.<br>  while (!customQueue.isEmpty()) <br>  {<br>    int current = customQueue.getFirst();<br>    // If we found finish node, then let&#39;s print full path.<br>    if (current == finish) <br>    {<br>      vector&lt;int&gt; path;<br>      int cur = finish;<br>      path.push_back(cur);<br>      // Recover path node by node.<br>      while (previousVertex[cur] != -1) <br>      {<br>        cur = previousVertex[cur];<br>        path.push_back(cur);<br>      }<br>      // Since we are at the finish node, reverse list to be at start.<br>      reverse(path.begin(), path.end()); <br>      return path;<br>    }<br>    for (int neighbourIndex = 0; <br>         neighbourIndex &lt; graph.Edges[current].size(); <br>         neighbourIndex++)<br>    {<br>      int to = graph.Edges[current][neighbourIndex].first;<br>      int weight = graph.Edges[current][neighbourIndex].second;<br>      if (nodeColor[to] == WHITE) // If node is not yet visited.<br>      {<br>        nodeColor[to] = GREY; // Mark node as &quot;in progress&quot;.<br>        customQueue.insert(to);<br>        previousVertex[to] = current;<br>        // Calculate cost of moving to this node.<br>        ptrShortestPath-&gt;at(to) = ptrShortestPath-&gt;at(current) + weight;<br>      }<br>      else // Select the most optimal route.<br>      {<br>        if (ptrShortestPath-&gt;at(to) &gt; ptrShortestPath-&gt;at(current) + weight)<br>        {<br>          ptrShortestPath-&gt;at(to) = ptrShortestPath-&gt;at(current) + weight;<br>        }<br>      }<br>    }<br>    nodeColor[current] = BLACK;<br>  }<br>  return {};<br>}</pre><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><h3>BFS queue</h3><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="https://github.com/optiklab/path-algorithms-in-a-graph/blob/ecb090ce3f0fe8395e2c7360873bd9b0fd899d15/bfsQueue.h#L11">bfsQueue.h#L11:</a></p><pre>#include &lt;queue&gt;<br>#include &quot;pathFindingBase.h&quot;<br><br>class bfsQueue : public pathFindingBase<br>{<br>private:<br>  queue&lt;int&gt; _queue;<br>public:<br>  virtual void insert(int node)<br>  {<br>    _queue.push(node);<br>  }<br>  virtual int getFirst()<br>  {<br>    int value = _queue.front();<br>    _queue.pop();<br>    return value;<br>  }<br>  virtual bool isEmpty()<br>  {<br>    return _queue.empty();<br>  }<br>};</pre><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>//bfsQueue customQueue; // UNCOMMENT TO USE BFS</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.</p><pre>(0, 0) - (0, 1) - (0, 2) - (0, 3) - (0, 4)<br>                                       |<br>                                    (1, 4)<br>                                       |<br>                                    (2, 4)<br>                                       |<br>                                    (3, 4)<br>                                       |<br>                                    (4, 4)</pre><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><h3>DFS queue</h3><p>DFS doesn’t look very different. We only replace the STD queue with a stack. <a href="https://github.com/optiklab/path-algorithms-in-a-graph/blob/ecb090ce3f0fe8395e2c7360873bd9b0fd899d15/dfsStack.h#L11">dfsStack.h#L11:</a></p><pre>#include &lt;stack&gt;<br>#include &quot;pathFindingBase.h&quot;<br><br>class dfsStack : public pathFindingBase<br>{<br>private:<br>  stack&lt;int&gt; _queue;<br>public:<br>  virtual void insert(int node)<br>  {<br>    _queue.push(node);<br>  }<br>  virtual int getFirst()<br>  {<br>    int value = _queue.top();<br>    _queue.pop();<br>    return value;<br>  }<br>  virtual bool isEmpty()<br>  {<br>    return _queue.empty();<br>  }<br>};</pre><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:</p><pre>(0, 0)<br>   | <br>(1, 0) <br>   |<br>(2, 0)<br>   |<br>(3, 0)<br>   | <br>(4, 0) - (4, 1) - (4, 2) - (4, 3) - (4, 4)</pre><h3>Dijkstra queue</h3><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="https://github.com/optiklab/path-algorithms-in-a-graph/blob/ecb090ce3f0fe8395e2c7360873bd9b0fd899d15/dijkstraQueue.h#L17">dijkstraQueue.h#L17:</a></p><pre>#include &lt;queue&gt;<br>#include &quot;pathFindingBase.h&quot;<br><br>class dijkstraQueue : public pathFindingBase<br>{<br>private:<br>  vector&lt;int&gt; _queue;<br>  shared_ptr&lt;vector&lt;int&gt;&gt; _shortestPaths;<br>public:<br>  dijkstraQueue(shared_ptr&lt;vector&lt;int&gt;&gt; shortestPaths) : _shortestPaths(shortestPaths) { }<br>  virtual void insert(int node)<br>  {<br>    _queue.push_back(node);<br>  }<br>  virtual int getFirst()<br>  {<br>    int minimum = INF;<br>    int minimumNode = -1;<br>    for (int i = 0; i &lt; _queue.size(); i++)<br>    {<br>      int to = _queue[i];<br>      int newDistance = _shortestPaths-&gt;at(to);<br>      if (minimum &gt; newDistance) // Greedy selection: select node with minimum distance on every step<br>      {<br>        minimum = newDistance;<br>        minimumNode = to;<br>      }<br>    }<br>    if (minimumNode != -1)<br>    {<br>      remove(_queue.begin(), _queue.end(), minimumNode);<br>    }<br>    return minimumNode;<br>  }<br>  virtual bool isEmpty()<br>  {<br>    return _queue.empty();<br>  }<br>};</pre><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:</p><pre>(0, 0) -1- (0, 1)<br>             |<br>             1 <br>             |<br>           (1, 1) -1- (1, 2)<br>                         |<br>                         1 <br>                         |<br>                      (2, 2) -1- (2, 3)<br>                                    |<br>                                    1 <br>                                    |<br>                                  (3, 3) -1- (3, 4)<br>                                               |<br>                                               1 <br>                                               |<br>                                             (4, 4)</pre><h3>A-Star</h3><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="https://github.com/optiklab/path-algorithms-on-a-grid-map">my other GitHub project</a> for more details). <a href="https://github.com/optiklab/path-algorithms-in-a-graph/blob/ecb090ce3f0fe8395e2c7360873bd9b0fd899d15/aStarQueue.h#L18">aStarQueue.h#L18</a>:</p><pre>class aStarQueue : public pathFindingBase<br>{<br>private:<br>  vector&lt;int&gt; _queue;<br>  shared_ptr&lt;vector&lt;int&gt;&gt; _shortestPaths;<br>  shared_ptr&lt;Graph&gt; _graph;<br>  int _finishX;<br>  int _finishY;<br><br>  /// &lt;summary&gt;<br>  /// Euclidian distance from node start to specified node id.<br>  /// &lt;/summary&gt;<br>  int calcEuristic(int id)<br>  {<br>    return sqrt(<br>      pow(abs(<br>        _finishX &gt; _graph-&gt;Nodes[id].X ?<br>        _finishX - _graph-&gt;Nodes[id].X :<br>        _graph-&gt;Nodes[id].X - _finishX), 2) +<br>      pow(abs(<br>        _finishY &gt; _graph-&gt;Nodes[id].Y ?<br>        _finishY - _graph-&gt;Nodes[id].Y :<br>        _graph-&gt;Nodes[id].Y - _finishY), 2));<br>  }<br>public:<br>  aStarQueue(int finishX, int finishY, shared_ptr&lt;Graph&gt; graph, shared_ptr&lt;vector&lt;int&gt;&gt; shortestPaths)<br>    :<br>    _shortestPaths(shortestPaths),<br>    _graph(graph)<br>  {<br>    _finishX = finishX;<br>    _finishY = finishY;<br>  }<br>  virtual void insert(int node)<br>  {<br>    _queue.push_back(node);<br>  }<br>  virtual int getFirst()<br>  {<br>    int minimum = INF;<br>    int minimumNode = -1;<br>    for (int i = 0; i &lt; _queue.size(); i++)<br>    {<br>      int to = _queue[i];<br>      int newDistance = _shortestPaths-&gt;at(to);<br>      int euristic = calcEuristic(to);<br>      if (minimum &gt; newDistance + euristic)<br>      {<br>        minimum = newDistance + euristic;<br>        minimumNode = to;<br>      }<br>    }<br>    if (minimumNode != -1)<br>    {<br>      _queue.erase(remove(_queue.begin(), _queue.end(), minimumNode), _queue.end());<br>    }<br>    return minimumNode;<br>  }<br>  virtual bool isEmpty()<br>  {<br>    return _queue.empty();<br>  }<br>};</pre><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="https://github.com/optiklab/path-algorithms-on-a-grid-map">my other project</a>, which implements these algorithms efficiently and employs a more visual approach with a wide range of test data.</p><h3>Downsides</h3><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="https://lucho1.github.io/JumpPointSearch/">A* Optimizations and Improvements by Lucho Suaya</a> and <a href="https://www.gdcvault.com/play/1022094/JPS-Over-100x-Faster-than">JPS+: Over 100x Faster than A* by Steve Rabin</a>.</p><h3>Final word</h3><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="https://github.com/optiklab/path-algorithms-on-a-grid-map">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><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=996ced18768" width="1" height="1" alt=""><hr><p><a href="https://medium.com/better-programming/universal-implementation-of-bfs-dfs-dijkstra-and-a-star-algorithms-996ced18768">Universal implementation of BFS, DFS, Dijkstra and A-Star algorithms</a> was originally published in <a href="https://betterprogramming.pub">Better Programming</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Are there experts in the crypto-industry?]]></title>
            <link>https://medium.com/optiklab/are-cryptocurrencies-the-pyramids-of-the-current-decade-3871ac3154e?source=rss-89b693ce234e------2</link>
            <guid isPermaLink="false">https://medium.com/p/3871ac3154e</guid>
            <category><![CDATA[crypto]]></category>
            <category><![CDATA[crypt]]></category>
            <category><![CDATA[blockchain]]></category>
            <category><![CDATA[cryptocurrency]]></category>
            <category><![CDATA[fraud]]></category>
            <dc:creator><![CDATA[Anton Yarkov]]></dc:creator>
            <pubDate>Thu, 13 Jan 2022 17:44:26 GMT</pubDate>
            <atom:updated>2022-01-28T12:32:21.830Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YDBVtjjteFjjDuVVDJx7IQ.png" /></figure><p>Current crypto-hype in large IT companies makes everyone believe there are experts in the topic. Folks jump into investments thinking they join to the success with someone who knows what they are doing. But it turns out it’s not.</p><p>This leads me to a question: <strong>Are cryptocurrencies the pyramids of the current decade?</strong></p><p>This kind of comparison is not new, but as a developer highly involved in everything that happens in IT and in FinTech specifically, I find more and more parallels between two ideas. For some, they allow earning. But for the majority, they only give hope for a miracle (to become rich, achieve what they want, etc.).</p><p>Neither was the goal of cryptocurrency creation, actually. Technically, the two main big ideas behind them are:</p><ul><li>The idea of ​​storing information about transactions in the form of a chain of records dependent on each other —<strong> Blockchain </strong>(broke a record in the middle, the chain “breaks” and all subsequent records automatically become invalid);</li><li>The idea of ​​confirming each new transaction by performing some valuable computing work in a distributed network of decentralized “workers” — <strong>Proof-of-work</strong>. I must say that there are other ways to confirm transactions, but so far, the idea of ​​”wasting time and electricity” has turned out to be more workable than others;</li></ul><p>That’s it. There was no talk of any earnings or profits. At the same time, cryptocurrencies have existed for about 15 years and still cannot break through a thick layer in FinTech. For a moment, the classic pyramids (the so-called Ponzi scheme) appeared more than 100 years ago, so in comparison with this idea, cryptocurrencies are still young, and you can try to draw parallels and understand what else they can grow into.</p><h3><strong>Unhealthy signals in crypto</strong></h3><p>Cryptocurrencies can have reasonably practical uses. But at the moment, even in regulated environments, they are more often created and used for hype, marketing of individual companies and their leaders.</p><p>What can we say about countries where regulators cannot reach out to potential fraudsters? Here we very often see fraud in its purest form, attempts to “hit the jackpot and hide,” confuse end users, and sometimes even “cut” from the government investments.</p><p>In 2021, we all saw one of the last attempts to do something with cryptocurrencies: NFT — one another game in an attempt to make money on something new and incomprehensible.</p><p>What makes them attractive and remind me of a pyramid:</p><ul><li>The opportunity to “<strong>jump on the train of success</strong>” earlier than others and become rich. It usually comes with arguments of the right moment and the low price initially.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/512/0*SdyJp_culVkcYRm4.png" /></figure><ul><li>Everything is based on “<strong>faith</strong>”: lack of any confirmation of declared quality or performance. Unclear “roots,” (the original companies or the people who created the product). There is no way to talk to the source. And in both cases, the explanations are vague and so abstract (and in both cases, it is intentionally so) that even technically savvy people cannot distinguish a technical Fake from an actual working model. I know it’s true for the world of science as well; however, we should distinguish the absence of proofs (in pyramids and crypto) and technically complex proofs (math, physics, etc.). In the world of cryptocurrencies, the terms are complex, so it is frequently misused, and very few specialists at the moment can talk about “crypto” in a technically correct language.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/950/0*rco3ALAi_XLtfhyL.jpg" /></figure><ul><li>The ability to touch the “<strong>new big idea</strong>” without the necessary knowledge. Opportunities being sold are deliberately reduced to the level of “understanding” by a client who is willing to invest. Everything is explained so that the one thinks he/she understands what it is about.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/0*_zuIXp-cAmJzBLEm.jpg" /></figure><p>In fact, any really big ideas were based on mathematical apparatus and then had to go over a tough time check (before it became clear that the concept is vast). An unprepared person hardly has the opportunity to keep up with them on purpose.</p><h3>Unhealthy trends in 2021</h3><p>Unfortunately, things are only getting worse. Here are some trends of 2021:</p><ul><li>Every self-respecting IT company has open vacancies with the Crypto prefix. That is, more and more products will appear on the market, with the prefix crypto, blockchain, token, etc.</li><li>Media people often jump on the hype wave. Price surges certainly have beneficiaries. For example, Elon Musk in 2021 either hated or praised individual cryptocurrencies, which led to huge jumps in the latter’s price.</li><li>A couple of years earlier, we saw dozens of so-called ICOs, during which funds were raised, and then the organizers evaporated in an unknown direction.</li></ul><p>Everything that happens to cryptocurrencies is someone’s attempt to hit the jackpot. In general, this is nothing more than an idea with no practically helpful solution so far. It may appear, but as long as the scales are in the balance between those “for whom it is profitable” and “for whom it is not profitable,” the latter are winning.</p><p>The financial sector is driven by technology, but it is not based on technology (Boom!). Instead, it is based but on relationships between big companies and “uncle Bob’s,” agreements and contracts, handshakes, and even personal sympathy.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*CuMmpg7uA7tuDBho.jpg" /><figcaption>FinTech is based on agreements</figcaption></figure><p>A company’s success in the FinTech sector depends on negotiating with several intermediaries and issuing a financial tool with the lowest possible commission so that clients are not afraid to use it. We are not always talking about convenience or UX, here it is from case to case. Therefore, FinTech is (mainly) the B2B sector. That is, more companies are working with companies in it.</p><p>FinTech enters the B2C sector and works with end clients, but only to make the client essentially a Business (profitable for itself) and a source of profit. This is why your bank is constantly offering new tools, from insurance and protection to cost control. All this is a concern with a downside (I’m not saying that this is very bad, I state that any coin has a downside).</p><p>Many financial instruments have not changed for decades simply because they are already profitable. For the last ten years (or more), FinTech companies have been bringing their old technologies to Asia and Africa. They took it out as it is, changing nothing. They are applying essentially the same ideas to a new, not yet matured audience. Expanding the scope is good for the end customer, but it’s not always the best solution, so you need to understand that they did not do it out of kindness. That is, someone, benefits greatly from it.</p><p>Are you familiar with the structure of most Forex brokers? The idea is to find people willing to bring all their pennies and with huge leverage (10x, 100x, 1000x, etc.) from the broker to convert them into other currencies in the hope of that the rate will jump up… and there is no certainty that this will happen since trading occurs in short periods: mainly during the afternoon session of the exchange for 8 hours (from 9 am to 5 pm).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8fKHlmLPm9UfUtzaklqTKQ.png" /></figure><p>Today you are lucky, and you are left with nothing the next day.</p><h3>Don’t be tricked</h3><p>See the parallel with cryptocurrencies? Many people buy them only to sell them profitably and convert them back to their currency. And this immediately leads to fraudulent schemes, and states consider it their duty to protect against it. That’s why cryptocurrencies will only become widely-used when there is a way to control them. So that can convert their clients into a controllable source of profit, which is against one of crypto’s main ideas — decentralization (above). So, in anyway, the idea of ​​cryptocurrencies will degenerate into a controlled technology sold to the uninitiated in every corner under the guise of a decentralized blockchain tokens.</p><p>I am sure that many of us had or have relatives who were influenced by the classical pyramids and belief in miracles. With cryptocurrencies, they are even more vulnerable.</p><p>In the article, I draw parallels with what we are already familiar with. Want it or not, cryptocurrencies will stay with us forever, like the pyramids. And it will continue to spread.</p><p>And in order not to unknowingly fall into the trap the next time you decide to replenish the wallet of virtual “bunnies,” be vigilant, watch your feelings and sensations. Ask yourself, why are you doing this? Do you have any practical working needs? Or are you craving a quick profit at this moment? In the latter case, most likely, it will trick you.</p><p>Good luck!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3871ac3154e" width="1" height="1" alt=""><hr><p><a href="https://medium.com/optiklab/are-cryptocurrencies-the-pyramids-of-the-current-decade-3871ac3154e">Are there experts in the crypto-industry?</a> was originally published in <a href="https://medium.com/optiklab">optiklab</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How To Write Self-Documented Code With Low Cognitive Complexity]]></title>
            <link>https://medium.com/better-programming/how-to-write-self-documented-code-with-low-cognitive-complexity-84d3e0bc53ad?source=rss-89b693ce234e------2</link>
            <guid isPermaLink="false">https://medium.com/p/84d3e0bc53ad</guid>
            <category><![CDATA[code-quality-tools]]></category>
            <category><![CDATA[code-quality]]></category>
            <category><![CDATA[domain-driven-design]]></category>
            <category><![CDATA[metrics]]></category>
            <category><![CDATA[static-analysis-tool]]></category>
            <dc:creator><![CDATA[Anton Yarkov]]></dc:creator>
            <pubDate>Sun, 11 Jul 2021 19:52:18 GMT</pubDate>
            <atom:updated>2023-02-15T22:23:50.207Z</atom:updated>
            <content:encoded><![CDATA[<h4>A guide to help your team write self-documented code, produce better software, and improve programmers’ lives</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*pc06vd5vbidkYRnB" /><figcaption>Photo by <a href="https://unsplash.com/@cdr6934?utm_source=medium&amp;utm_medium=referral">Chris Ried</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>In this article, I will share practical and straightforward advice to stop holy wars, arguments about code quality, and the necessity of refactoring, simplifying, and adding comments or documentation for the code. While I will refer to exact commercial tools in the second half of the article, I want to say that I’m not affiliated with the tool’s authors. The tools are available via free community licenses as well as commercial licenses.</p><p>The goal 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 programmers’ lives.</p><h3>Self-Documented Code</h3><p>I frequently get the answer <em>“Go read the code”</em> when I ask developers to provide documentation or explain their code. I’m sure I’m not alone. Many developers feel their code is self-documented by default. Not many people understand that creating self-documented code is a complicated design task.</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: WHAT was the task and the goal (and real experts also try to dig into WHY).</li><li>Next, knowing WHAT, we are reading the code to understand HOW the author achieves this.</li></ol><p>While it’s possible to do vice versa, it’s tough 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. They must also be resilient, scalable, configurable, support multiple platforms, versions, etc.</p><p><em>Some people claim that SQL and HTML answer both HOW and WHAT at the same time. I will disregard this comment here and concentrate on general-purpose languages.</em></p><p>Doing vice-versa-analysis, software engineers should figure out what the purpose of this code is, WHAT it mainly does, and (finally) WHAT it missing. This is usually called Mental Model. No matter how simple or complex, there is always some mental model underlying the code (even the bad one). It might be a domain model or another way to express the thinking process. There are many concrete rules to follow to make your code more clean, readable, and understandable.</p><p>As we know, many books have been written on this topic. But 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, making understanding the model difficult.</p><p>Questions like HOW and WHAT are orthogonal because there are several ways to achieve the same goal. Imagine climbers analyzing the better way to reach the mountain peak by different paths. They consider various aspects, sum up their own experience and common knowledge about the mountain relief, weather and air conditions, time of the year, the group&#39;s readiness level, etc. Finally, they select the optimal path to climb. The optimal path doesn’t explain all these aspects but allows the group to put the flag on the peak.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*cWMmMnSjhE0lDxQG.jpg" /><figcaption><a href="https://www.freepik.com/premium-vector/mountain-paths-progress-success-hiking-path-business-metaphor-journey-climb-peak-route-mission-progressive-career-way-vector-illustration-mountain-goal-progress-career-business_18144991.htm">image via freepik</a></figcaption></figure><p>As I see it, the mental model shows the explicit dependency of the self-documented code from the author’s design skills, allowing them to make the code more readable.</p><blockquote>The mental model answers the WHAT, while the code tells us the HOW.</blockquote><h3>Measuring the Readability of the Code</h3><p>Frederick Brooks, in his famous paper <a href="https://en.wikipedia.org/wiki/No_Silver_Bullet">No Silver Bullet — Essence and Accident in Software Engineering</a> specified two types of complexity:</p><ul><li>Essential complexity — caused by the problem to be solved, and nothing can remove it</li><li>Accidental complexity — relates to the problems engineers create that can be fixed</li></ul><p>Many years have passed, but we still cannot measure it precisely. The well-known metric, Cyclomatic Complexity (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 cyclomatic complexity. Here is the problem showcase:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*PQ525FdvYR2W-Z5u.jpg" /></figure><p>As you can see, Cyclomatic Complexity shows the same digits for the code from left and right. However, from the developer’s viewpoint, the left and right pieces of code are not identically complex. The left one is harder to read and understand. We may believe the code is finding the Sum of Prime Numbers, a famously known problem, but an experienced developer will never think it solves the task until they verify it’s true:</p><ul><li>Does the method’s name clearly state what the code does?</li><li>Does the code achieve the mission?</li><li>Does the code miss some use cases, and what are they? (i.e., what are the limitations of the code?)</li></ul><p>Now, imagine how hard it is to understand something more specific about the domains that aren’t famous to others. Sonar Source released the Cognitive Complexity 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:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*dTcXNmEyfhTSKlD0.jpg" /></figure><p>You can find all the details in <a href="https://redirect.sonarsource.com/doc/cognitive-complexity.html">their paper</a> and on <a href="https://www.youtube.com/watch?v=x5V2nvxco90">YouTube</a>. The metric is based on three rules:</p><ol><li>Ignore structures that allow multiple statements to be shorthanded into one.</li><li>Increment (add one) for each break in the linear flow of the code: loop structures (for, while, do-while), conditionals, ternary operations (if, #if, #ifdef).</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 the <a href="https://www.sonarsource.com/plans-and-pricing">free community edition</a>.</p><p>In Sonar Cloud, look into Project -&gt; Issues -&gt; Rules -&gt; Cognitive Complexity.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*csGjYZxXYSJLDbtH.jpg" /></figure><p>It is easy to find the full report with the line-by-line explanation of the penalty assignment. Here’s how to do that:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*xCA0JsZHBNipIKJW.jpg" /></figure><p>The default thresholds for code quality are:</p><p>Cognitive Complexity</p><ul><li>15 (most of the languages)</li><li>25 (C-family languages)</li></ul><p>Cyclomatic Complexity</p><ul><li>10 (all languages)</li></ul><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 at a simple production example. To find it, you can do the following: Sonar Cloud -&gt; Measures -&gt; select Complexity filter</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/680/0*Gu8OA7hWhPDPneRq.jpg" /></figure><p>You can find the total complexity measurement for the group of files (folder) on the left side. Here, we can see the numbers doubled: 134 against 64. You can see file-by-file differences as well.</p><p>The LoggerHelper file isn’t so bad in Cyclomatic Complexity, but there are ways to improve its Cognitive Complexity. And for other files, we may see a controversial picture — Cyclomatic Complexity is bigger than the Cognitive one.</p><h3>Outcomes</h3><p>It looks like we have a way to measure code complexity, and I wish more tools were available to implement this, but we can already 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 to move toward 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 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 when 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 exciting food for you to dig into this and showed you how Cognitive Complexity can be used in your daily life.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=84d3e0bc53ad" width="1" height="1" alt=""><hr><p><a href="https://medium.com/better-programming/how-to-write-self-documented-code-with-low-cognitive-complexity-84d3e0bc53ad">How To Write Self-Documented Code With Low Cognitive Complexity</a> was originally published in <a href="https://betterprogramming.pub">Better Programming</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Starting Communities of Practice in Your Organization]]></title>
            <link>https://medium.com/optiklab/starting-communities-of-practice-in-your-organization-19a43ae70f6d?source=rss-89b693ce234e------2</link>
            <guid isPermaLink="false">https://medium.com/p/19a43ae70f6d</guid>
            <category><![CDATA[people-management]]></category>
            <category><![CDATA[growth]]></category>
            <category><![CDATA[community]]></category>
            <category><![CDATA[agile-development]]></category>
            <category><![CDATA[learning-and-development]]></category>
            <dc:creator><![CDATA[Anton Yarkov]]></dc:creator>
            <pubDate>Tue, 01 Jun 2021 10:38:49 GMT</pubDate>
            <atom:updated>2021-06-01T10:38:49.715Z</atom:updated>
            <content:encoded><![CDATA[<p><em>Everything you need to know about the concept of Community of Practices and how to engage collaborations in multiple Agile teams across the organization.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/727/1*5Tvb-FXsyOhtBC5-mPdf5A.jpeg" /><figcaption><em>Graphics made by Max Degtyarev (</em><a href="https://www.behance.net/maxdwork"><em>https://www.behance.net/maxdwork</em></a><em>)</em></figcaption></figure><h3>What is a community of practice?</h3><blockquote>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.</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="https://www.scaledagileframework.com/agile-release-train/">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><h3>Why to use communities of practice?</h3><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>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.</blockquote><h3>Key concepts for successul implementation of CoPs</h3><h3>Facilitator’s role is essential</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/273/1*vwrTmF6zf7jFsIzsn_wniw.jpeg" /></figure><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><figure><img alt="" src="https://cdn-images-1.medium.com/max/256/1*equO7t4OelMpH38BJ7QIEQ.jpeg" /></figure><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><h3>Work items</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/286/1*rzmi3LSNcPVpaYkNUTyy_Q.jpeg" /></figure><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>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.</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><h3>Why exert the effort to market your CoP’s results?</h3><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><h3>Lifecycle</h3><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><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*aHkhQJCokmpvzf9m6Tdcow.png" /><figcaption><em>Figure 1. CoP lifecycle phases</em></figcaption></figure><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><figure><img alt="" src="https://cdn-images-1.medium.com/max/517/1*7FFaVw01Q1_u0w8Pz7JLRg.png" /><figcaption><em>Figure 2. Bruce Tuckman’s model of team development</em></figcaption></figure><h3>Checklists</h3><h3>I want to start a new community of practice. What should I do?</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/686/1*GBOtPS53WC8_ogN5l2laEA.jpeg" /><figcaption>Checklist 1. How to start CoP</figcaption></figure><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><figure><img alt="" src="https://cdn-images-1.medium.com/max/669/1*EVBcKXkVkd8lFDnZfOjk0Q.png" /><figcaption>Checlist 2. How to measure success of CoP</figcaption></figure><h3>Where to learn more</h3><ul><li><a href="https://www.scaledagileframework.com/communities-of-practice/">https://www.scaledagileframework.com/communities-of-practice/</a></li><li><a href="https://teachingcommons.lakeheadu.ca/communities-practice-quick-start-guide">https://teachingcommons.lakeheadu.ca/communities-practice-quick-start-guide</a></li><li><a href="https://www.organisationalmastery.com/communities-of-practice/">https://www.organisationalmastery.com/communities-of-practice/</a></li><li><a href="https://www.nickols.us/CoPStartUpKit.pdf">https://www.nickols.us/CoPStartUpKit.pdf</a></li><li><a href="https://ktdrr.org/resources/rush/copmanual/CoP_Manual.pdf">https://ktdrr.org/resources/rush/copmanual/CoP_Manual.pdf</a></li><li><a href="https://www.talent.wisc.edu/home/LinkClick.aspx?fileticket=B6rgxakCMtI%3D&amp;portalid=0">https://www.talent.wisc.edu/home/LinkClick.aspx?fileticket=B6rgxakCMtI%3D&amp;portalid=0</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=19a43ae70f6d" width="1" height="1" alt=""><hr><p><a href="https://medium.com/optiklab/starting-communities-of-practice-in-your-organization-19a43ae70f6d">Starting Communities of Practice in Your Organization</a> was originally published in <a href="https://medium.com/optiklab">optiklab</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Web crawlers make it harder to follow Digital Millennium Copyright Act, so developers have to…]]></title>
            <link>https://medium.com/optiklab/web-crawlers-make-it-harder-to-follow-digital-millennium-copyright-act-so-developers-have-to-43e5e032c?source=rss-89b693ce234e------2</link>
            <guid isPermaLink="false">https://medium.com/p/43e5e032c</guid>
            <category><![CDATA[copyright]]></category>
            <category><![CDATA[web-crawler]]></category>
            <category><![CDATA[data-protection]]></category>
            <category><![CDATA[web]]></category>
            <category><![CDATA[dmca-takedown]]></category>
            <dc:creator><![CDATA[Anton Yarkov]]></dc:creator>
            <pubDate>Mon, 01 Mar 2021 11:53:41 GMT</pubDate>
            <atom:updated>2021-03-11T10:12:16.777Z</atom:updated>
            <content:encoded><![CDATA[<h3>Web Crawlers, the DMCA, and Thinking Ahead</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Yf8qwpxiYx-n7O0_fz5xQA.jpeg" /><figcaption>Image taken from <a href="https://unsplash.com/photos/9XfSFjcwGh0">Unsplash</a>. Copyright © <a href="https://unsplash.com/@markuswinkler">Markus Winkler</a></figcaption></figure><h3>Who should read it</h3><p>This article is for web content makers and owners of the public content platforms, web developers, and anyone who can suddenly publish content that might become a subject of <a href="https://en.wikipedia.org/wiki/Digital_Millennium_Copyright_Act">DMCA </a>claim.<br>A couple of examples are Twitter, GitHub, Vimeo platforms that allow users to publish pictures, videos, and source codes that might appear to violate copyright laws.</p><h3>Disclaimer</h3><p>Of course, when we are talking about Public resources like Twitter, it is not a problem for someone to write a web-crawler smart enough to analyze specific resources and copy/download all the possible content from it (or save the content on the user machine). In this case, your platform/web-site is simply one point in the chain of content distribution and does not know how this content is supposed to be shared after all. Since it is a separate resource with its mission and reasons to work with this information (so they might need or don’t need to satisfy <a href="https://en.wikipedia.org/wiki/Digital_Millennium_Copyright_Act">DMCA </a>rules), I don’t think it’s something you can do with it. <a href="https://en.wikipedia.org/wiki/Web_crawler">Web-crawlers</a> overall may become a massive problem in the <a href="https://en.wikipedia.org/wiki/Digital_Millennium_Copyright_Act">DMCA</a> applicability. On the other side, they play a substantial role as an external cache that allows people to find information lost on the original resource.</p><h3>So, what’s the problem?</h3><p>It’s not that simple when we talk about such public and well-known resources like Way Back Machine (<a href="https://web.archive.org/">https://web.archive.org/</a>), though. They have publicly announced mission</p><blockquote>“is a digital archive of the World Wide Web.”</blockquote><p>In other words, they are going to re-publish all the content they found on the internet. They are doing it straightforwardly. And authors and platform owners definitely should care about it, because it is a de-facto mirror of your web site.</p><p>Recently, I made a couple of interesting investigations and reported both to GitHub and Twitter officials. So, I think it will be valuable to share some details, so reader might understand the problem better.</p><h3>Case with GitHub</h3><p>Usually, when some repository becomes a subject of <a href="https://en.wikipedia.org/wiki/Digital_Millennium_Copyright_Act">DCMA </a>claim, then you couldn’t access the code repository and see this kind of message:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Hoj1YaT_PHOuXggAU9mikA.jpeg" /><figcaption>DMCA’d code repository</figcaption></figure><p>However, you can go straight to the <a href="https://web.archive.org/">Wayback Machine</a> and find this page alive some time ago, look into it, read the whole content and, guess what? You can download the <a href="https://en.wikipedia.org/wiki/Digital_Millennium_Copyright_Act">DMCA</a>’d source code as a ZIP archive right from there.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Z0X8ACxo_ctWmMwl8tmYCQ.jpeg" /></figure><h3>Case with Twitter</h3><p>If you go to <a href="https://twitter.com/Notices_DMCA">https://twitter.com/Notices_DMCA</a> then you can find a whole bunch of DMCA’d twitter posts.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/596/1*RHOJ-Kcf3844EGct064FaQ.jpeg" /><figcaption>DMCA’d twitter post</figcaption></figure><p>Then, you should use your e-mail to officially request access to the original URLs via <a href="https://www.lumendatabase.org">https://www.lumendatabase.org</a> and get this information in a minute or so. Now, you can use it to find the images on the web archive.</p><p>I’m sharing this because it outlines things that you might not worry about until you reach the point of no return. And I already communicated with both GitHub and Twitter about these use cases. But guess how many more out there?</p><h3>Why do I care?</h3><p>Imagine how you feel if your business becomes threatened one day by somebody sharing your private assets? It’s a real issue many people are facing today.</p><p>Imagine if some source codes are not just stolen but also modified and re-published with some virus or trojan program inside?</p><p>At the end (maybe exaggerated example), imagine if you or your family member become slandered by the crowd with any photos or anything else that become publicly available. This might hurt a lot. And there are no cheap ways to do anything with it.</p><p>If all development teams followed some easy-to-use rules and frameworks and handle such cases appropriately and accurately, it would be beneficial.</p><h3>So, what do I do?</h3><p>Nowadays, if you develop a new “Clubhouse” startup that might reach millions of users daily, you should think about many things:<br>- <a href="https://gdpr-info.eu/">GDPR</a>(at least in the EU),<br>- copyright and <a href="https://en.wikipedia.org/wiki/Digital_Millennium_Copyright_Act">DMCA</a> (at least in the USA),<br>- data persistency and data retention policies,<br>- etc etc.<br>It’s not enough to put an image or an archive file on your web-site directly linked to the resource, allowing anyone to crawl all your resources and keep forever anywhere automatically. Again, this might play a GOOD or a BAD role, depending on the situation. But it would be best if you thought about potential cases in advance.</p><h3>Where to start</h3><p>If you feel that your database or file storage is full of content that might be a subject of <a href="https://en.wikipedia.org/wiki/Digital_Millennium_Copyright_Act">DMCA</a>, then I would start from this:</p><ol><li>Go over your database of the <a href="https://en.wikipedia.org/wiki/Digital_Millennium_Copyright_Act">DMCA</a>’d content (i.e., the content, links, etc., that was a subject of a claim under <a href="https://en.wikipedia.org/wiki/Digital_Millennium_Copyright_Act">DMCA</a>) and aggregated all necessary information about the content that has to be closed from public access. This list should be available to teams building <a href="https://en.wikipedia.org/wiki/Web_crawler">Web Crawlers</a> via subscription mechanisms of any kind (rss, email, xml/json/rest/wcf/… files or APIs, etc.).</li><li>Update terms &amp; conditions to publicly mention the rules of re-publishing content and specific steps, regulations, and requirements that you follow to protect data privacy &amp; security and <a href="https://en.wikipedia.org/wiki/Digital_Millennium_Copyright_Act">DMCA</a>. It would help if you said that anyone who’s copying their content should be automatically responsible for following the same rules. Publish the appropriate documentation and API for developers that will help them to learn about the <a href="https://en.wikipedia.org/wiki/Digital_Millennium_Copyright_Act">DMCA</a>’d content mentioned above and automatically remove it from their databases or storage of any kind.</li><li>Ask the community to work together and form a list/database of the most well-known web crawlers and web-sites available on the internet (I mean web-sites that are easily available and work similarly to <a href="https://web.archive.org">Wayback Machine</a> — mirror your data). Ask every team/company that you found to follow the rules and remove <a href="https://en.wikipedia.org/wiki/Digital_Millennium_Copyright_Act">DMCA</a>’d content, provide them documentation and API mentioned above.</li></ol><p>If you are starting a new feature that allows people to upload and share content with anyone,, then I would also recommend you to work on preventing such issues in the future as so:</p><ol><li>Any resource (image, file, external link, etc.) should not be available by simple direct link, i.e., it should require additional user interaction to see the content (i.e., to make it downloadable by browser or API). Technically, this might be done using additional scripting to form a link after the user interacts with the user interface. And not after <a href="https://learnreact.design/posts/what-is-react">DOM </a>(<a href="https://learnreact.design/posts/what-is-react">document object model</a>) has been loaded into the browser. By doing this, you will make 100% of “simple” web crawlers unable to download the content — only the look &amp; feel of the page. As been said in my disclaimer above: you still not protected from “smart” crawlers explicitly made for your web-site.</li><li>Know who crawls you, i.e., see #3 above. You can build a list of the most well-known crawlers and control their access to specific content by restricting URL access. For example, you would give access to HTML/CSS by crawling the link <em>https://twitter.com/server1/…</em> but limit downloading pictures from <em>https://mycoolwebsite.com/imgserver1/…</em> until the user interacts with the authenticated session.</li></ol><p>I listed some of the technical and organizational solutions that I would execute to handle the issues with <a href="https://en.wikipedia.org/wiki/Digital_Millennium_Copyright_Act">DMCA </a>in a more automotive way. I know that complicates things enough for somebody who has tiny budgets.<br>But as problems evolve, I believe large companies should figure out cheaper ways.</p><p>Please, help me in the comments to generate more ideas and ways to handle this.</p><h3>Links</h3><ul><li>DMCA (<a href="https://en.wikipedia.org/wiki/Digital_Millennium_Copyright_Act">https://en.wikipedia.org/wiki/Digital_Millennium_Copyright_Act</a>)</li><li>Way Back Machine (<a href="https://web.archive.org/">https://web.archive.org/</a>)</li><li>Web Crawlers (<a href="https://en.wikipedia.org/wiki/Web_crawler">https://en.wikipedia.org/wiki/Web_crawler</a>)</li><li>What is DOM? (<a href="https://learnreact.design/posts/what-is-react">https://learnreact.design/posts/what-is-react</a>)</li><li>The Lumen database <a href="https://www.lumendatabase.org">(https://www.lumendatabase.org</a>)</li><li>Twitter DMCA’d posts (<a href="https://twitter.com/Notices_DMCA">https://twitter.com/Notices_DMCA</a>)</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=43e5e032c" width="1" height="1" alt=""><hr><p><a href="https://medium.com/optiklab/web-crawlers-make-it-harder-to-follow-digital-millennium-copyright-act-so-developers-have-to-43e5e032c">Web crawlers make it harder to follow Digital Millennium Copyright Act, so developers have to…</a> was originally published in <a href="https://medium.com/optiklab">optiklab</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>