Jekyll2022-08-14T09:59:37+00:00https://matthewonsoftware.com/feed.xmlMatthew on softwareMatthew is a software developer from Poland, who shares things that he learnt and already knew and want to share it in blog post form. Hope you will find something useful for yourself.Mateusz DeskaGraphs2022-08-14T00:00:00+00:002022-08-14T00:00:00+00:00https://matthewonsoftware.com/blog/graphs<p>Graph is a collection of nodes that may or may not be connected to each other.</p>
<p>The nodes are actually called vertices, the connections between vertices are called edges.<br />
So its made of vertices and edges, where vertices are just nodes that might have values, for instance integer values, and the edges are connections, things that connect the nodes to one another.</p>
<p>Many things in life can be represented by graphs for example, an airport, a social network or a city map where locations are a verticies and roads between locations are edges.</p>
<p>Important concepts in graphs.</p>
<ul>
<li>
<p>Graph Cycle</p>
<p>Simply put occurs in a graph when three or more vertices in the graph are connected to form a closed loop<br /></p>
<p>Note that the definition of a graph cycle is sometimes broadened to include cycles of length two or one.<br />
For example wikipedia page where one link, link to different pages, but finally you may end up on the page that you started.<br /><br /></p>
</li>
<li>
<p>Acyclic graph</p>
<p>A graph that has no cycles.<br /><br /></p>
</li>
<li>
<p>Cyclic graph</p>
<p>A graph that has at least one cycle.<br /><br /></p>
</li>
<li>
<p>Directed Graph</p>
<p>Meaning that edges are directed and can be traversed only in one direction which is specified.<br />
For example from some airports you can reach a different airport but just in one way.<br /><br /></p>
</li>
<li>
<p>Undirected Graph</p>
<p>A graph without directed edges, meaning that they can be traversed in both directions.<br />
For example a graph of friends would be probably undirected as one friend can reach one another.<br /><br /></p>
</li>
<li>
<p>Connected Graph</p>
<p>Whether graph is connected, it is connected if any node is reachable from another node, or rather if there is some path between any two nodes in the graph.</p>
<p>In case of a directed graph, the graph is:</p>
<ul>
<li>strongly connected: if there are bidirectional connections between the vertices of every pair of vertices (i.e., for every vertex pair (x, y) you can each y from x and x from y)</li>
<li>weakly connected (if there are connections of every pair of vertices (not necessarily bidirectional)</li>
</ul>
<p>A graph that is not connected may be called disconnected.</p>
</li>
</ul>Mateusz DeskaGraph is a collection of nodes that may or may not be connected to each other.Strings2022-08-09T00:00:00+00:002022-08-09T00:00:00+00:00https://matthewonsoftware.com/blog/strings<h4 id="string">String</h4>
<p>One of the fundamental data type in Computer Since, which is basically an array of integers under the hood, where each character in a given string is mapped to an integer via some character-encoding standard like ASCII.</p>
<p>We can divide Strings to mutable and immutable, in most languages they are immutable, meaning that they can’t be edited after creation. So as you can imagine, simple operations like appending a character to a string would be more expensive than it might appear.</p>
<p>The canonical example of an operation that’s deceptively expensive due to string immutability is the following:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">String</span> <span class="n">someString</span> <span class="o">=</span> <span class="s">"some string"</span><span class="o">;</span>
<span class="nc">String</span> <span class="n">newString</span> <span class="o">=</span> <span class="s">""</span><span class="o">;</span>
<span class="k">for</span> <span class="o">(</span><span class="kt">char</span> <span class="n">character</span> <span class="o">:</span> <span class="n">someString</span><span class="o">.</span><span class="na">toCharArray</span><span class="o">())</span> <span class="o">{</span>
<span class="n">newString</span> <span class="o">+=</span> <span class="n">character</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<p>The operation above has a time complexity O(n<sup>2</sup>) where n is the length of ‘someString’, because each addition of a character to newString creates an entirely new string and is itself an O(n) operation, but keep in mind that n O(n) operations are performed (O(n) for each character in given string), which is leading to an O(n<sup>2</sup>) time-complexity operation at the end.</p>Mateusz DeskaStringHash Tables2022-08-05T00:00:00+00:002022-08-05T00:00:00+00:00https://matthewonsoftware.com/blog/hash-tables<p>A data structure that store data in key-value pairs and provides fast insertion, deletion and searching.</p>
<p>Under the hood it use a dynamic array of linked lists to efficiently store key/value pairs.</p>
<p>When searching for some value or inserting a key/value pair, a hash function firstly map our key to an integer value which gonna be our index in the underlying dynamic array.</p>
<p>Then the value associated with the key is added to the linked list stored at the index in the dynamic array, and the reference to the key is stored with the value as well, so in case if the collision occur and more then one element will be stored in the same linked list, it would know which value is searched.</p>
<p>How ever hash tables rely on very powerful and highly optimized hash functions to minimize the number of collisions that occur when two or more keys hashed to to the same index.</p>
<p>Visualization of how HashTable may look under the hood:</p>
<p>[<br />
0: (key1, value1) ⇒ null<br />
1: (key2, value2) ⇒ (key3, value3) ⇒ (key4, value4)<br />
2: (key3, value3) ⇒ null<br />
3: (key4, value4) ⇒ (key5, value5)<br />
4: null <br />
5: (key6, value6) ⇒ null<br />
]</p>
<p>In the hash table above, the keys key2, key3, key4 collided by all being map to linked list under index 1 and the key4 and key5 collided by both being hashed to index 5.</p>
<p>Time complexity, as mentioned on the beginning they are fast and provide basics operations in following complexity:</p>
<ul>
<li>Inserting a pair: O(1) average, O(n) worst case</li>
<li>Removing a pair: O(1) average, O(n) worst case</li>
<li>Searching a pair: O(1) average, O(n) worst case</li>
</ul>
<p>The worst case, linear-time complexity occurs when a lot of collision happen leading to long linked list, but it happen extremely rarely as hash function nowadays are really and constant-time complexity is all but guaranteed.</p>Mateusz DeskaA data structure that store data in key-value pairs and provides fast insertion, deletion and searching.Stacks and Queues2022-08-05T00:00:00+00:002022-08-05T00:00:00+00:00https://matthewonsoftware.com/blog/stacks-and-queues<h4 id="stacks">Stacks</h4>
<p>Is an array-like data structure whose elements follow <br /><strong>L</strong>ast <strong>I</strong>n <strong>F</strong>irst <strong>O</strong>ut principle (<strong>LIFO</strong>).
It’s really easy to visualize because we faced which such structures on a daily basis, you can compare it to a plates in a sink, the last that was put in the sink is first that would be washed.</p>
<p>The following are a stack’s standard operations and their corresponding complexities:</p>
<ul>
<li>Pushing an element onto the stack: O(1)</li>
<li>Popping an element onto the stack: O(1)</li>
<li>Peeking at the element on the top of the stack: O(1)</li>
<li>Searching for an element in the stack: O(n)</li>
</ul>
<p>A stack is typically based on <a href="https://matthewonsoftware.com/blog/arrays/"><strong>dynamic array</strong></a> or with a <a href="https://matthewonsoftware.com/blog/linked-lists/#single-linked-list"><strong>single linked list</strong></a>.</p>
<h4 id="queues">Queues</h4>
<p>Is an array-like data structure whose elements follow <br /><strong>F</strong>irst <strong>I</strong>n <strong>F</strong>irst <strong>O</strong>ut principle (<strong>FIFO</strong>).</p>
<p>A queues in the other hand may be compared to a group of people standing in line to purchase a ticket - the first person to get in the line is the first one to purchase ticket and to get out of the queue.</p>
<p>The following are a queue’s standard operations and their corresponding complexities:</p>
<ul>
<li>Enqueuing an element into the queue: O(1)</li>
<li>Dequeueing an element out of the queue: O(1)</li>
<li>Peeking at the element at the front of the queue: O(1)</li>
<li>Searching for an element in the queue: O(n)</li>
</ul>
<p>A queue is typically implemented with a <a href="https://matthewonsoftware.com/blog/linked-lists/#doubly-linked-list"><strong>doubly linked list</strong></a>.</p>Mateusz DeskaStacksLinked Lists2022-08-04T00:00:00+00:002022-08-04T00:00:00+00:00https://matthewonsoftware.com/blog/linked-lists<p>Is very similar to array, at least conceptually, to an array.</p>
<p>Where linked lists differs from an array is how its implemented or rather how it’s stored in memory.</p>
<p>When we store an array for example [3,0,6,7] we would need 16 back to back memory slots to store that array, assuming that it contains 32-bit integers, and it has to be free space back to back.</p>
<p>Linked lists would store elements in the list anywhere in memory, and they’re going to connect the elements using pointers. Two memory slots back to back would be needed (for one node) where one store the value and the second one point to the next element (node), which could be literally anywhere in the memory space.</p>
<p>Summarizing, array elements are stored back to back in the memory, linked lis elements are not stored back to back, they can be anywhere in memory.</p>
<p><br /></p>
<h2 id="single-linked-list">Single Linked List</h2>
<p>The following are a singly linked list’s standard operations and their corresponding time complexities:</p>
<ul>
<li>Accessing the head: O(1)</li>
<li>Accessing the tail: O(n)</li>
<li>Accessing the middle: O(n)</li>
<li>Inserting / Removing the head: O(1)</li>
<li>Inserting / Removing the tail: O(n) to access + O(1)</li>
<li>Inserting / Removing a middle node: O(n) to access + O(1)</li>
<li>Searching for a value: O(n)</li>
</ul>
<p><br /></p>
<h2 id="doubly-linked-list">Doubly Linked List</h2>
<ul>
<li>Accessing the head: O(1)</li>
<li>Accessing the tail: O(1)</li>
<li>Accessing the middle: O(n)</li>
<li>Inserting / Removing the head: O(1)</li>
<li>Inserting / Removing the tail: O(1)</li>
<li>Inserting / Removing a middle node: O(n) to access + O(1)</li>
<li>Searching for a value: O(n)</li>
</ul>Mateusz DeskaIs very similar to array, at least conceptually, to an array.Arrays2022-08-03T00:00:00+00:002022-08-03T00:00:00+00:00https://matthewonsoftware.com/blog/arrays<p>There are two types of arrays. Static and dynamic ones.</p>
<p>Static means that as we declare our array we need to specify the size of it, to let your operating sytem know how much memory slots/ bytes it should allocate. If it would be 32 bit integer then 4 bytes (slots), if 64 bit integer, then 8 memory slots.</p>
<h2 id="time-complexities-of-standard-arrays-operations">Time complexities of standard arrays operations:</h2>
<p>T - Time complexity;
S - Space complexity;
None - ST complexity</p>
<ul>
<li>Accessing a value at a given index: O(1)</li>
<li>Updating a value at a given index: O(1)</li>
<li>Iterating over: O(n) T, O(1) S</li>
<li>Inserting a value at the beginning: O(n)</li>
<li>Inserting a value in the middle: O(n)</li>
<li>Inserting a value at the end:
<ul>
<li>amortized O(1) when dealing with a dynamic array</li>
<li>O(n) when dealing with a static array</li>
</ul>
</li>
<li>Removing a value at the beginning: O(n)</li>
<li>Removing a value in the middle: O(n)</li>
<li>Removing a value at the end: O(1)</li>
<li>Copying the array: O(n)</li>
</ul>
<p>Why updating and getting is constant?</p>
<p>As we know the memory address of first element then we may multiply the number of bytes that is needed for single element, so if its 32-bit integer then it would be 4 bytes. So imagine that array is placed in memory slot under address 8 so as we know that and also how many bytes took single element as its fixed. We can just multiply 4 bytes and for example 3 as we want to get element under index three. So 8 + 4 * 2 = 16. Third element would be under 16 memory slot.</p>
<p>In Java dynamic arrays are respectively vectors and ArrayLists, and in js and python and few other modern languages, standard arrays under the hood are dynamic arrays.</p>
<p>Dynamics arrays means that when we create array with three 64-bit integers, not 24 memory slots will be taken but 48, so it would be generally doubled (may differ on language/ operating system) and that second half will be empty and ‘reserved’ for us which will allows us faster insertions. So when it has space its O(1) but when the last memory slot will be taken, then the next insertions will perform copy of an array and also doubled it in size, and insert new element, then it will be O(n), but as more elements are added the rarely it happened, so we assume that insertions for dynamic arrays AT THE END of array have O(1) complexity, if we want to insert at the beginning or in the middle then it would be O(n), because we need to make a copy of the array shift elements and so on.</p>Mateusz DeskaThere are two types of arrays. Static and dynamic ones.Logarithm2022-08-02T00:00:00+00:002022-08-02T00:00:00+00:00https://matthewonsoftware.com/blog/logarithm<p>It’s mathematical concept which is very often used in Computer Science in context of algorithms complexity, it’s even sounds similar 😀</p>
<p>The equation:</p>
<p>log<sub>x</sub>(n) = y iff <sub>x</sub><sup>y</sup> = n</p>
<p>log<sub>2</sub>(n) = y iff <sub>2</sub><sup>y</sup> = n</p>
<p><br />
By default, in cs we assume that based of algorithm is 2, so we don’t even write it.</p>
<p>log<sub>2</sub>(n) = y → log(n) = y iff <sub>2</sub><sup>y</sup> = n</p>
<p><br />
So in other words what does it imply?
The Log of N is the power to which you need to put 2 to get N.</p>
<p>For example:</p>
<p>log(1) = 0 because <sub>2</sub><sup>0</sup></p>
<p>log(4) = 2 because <sub>2</sub><sup>2</sup></p>
<p>log(256) = 8 because <sub>2</sub><sup>8</sup></p>
<p><br />
But noticed one thing, as y is doubled the Log of N increase just by one.</p>
<p>log(16) = 4 (<sub>2</sub><sup>3</sup> * 2 = <sub>2</sub><sup>4</sup>)</p>
<p>log(32) = 5 (<sub>2</sub><sup>4</sup> * 2 = <sub>2</sub><sup>5</sup>)</p>
<p><br /></p>
<p>What does that mean for us? If we get algorithm with complexity log(n) its incredible good!
When N doubles, log(n) increases only by one, if we tie this back to complexity analysis, when we have an algorithm with a time complexity of log(n), that is incredibly good! That means as the input doubles, as the input increases, the number of elementary operations that we’re performing in the algorithm increases only by one.</p>
<p>Imagine a linear time complexity algorithm with an input of size 1000, it might take roughly 1000 operations to complete, whereas a logarithmic time complexity algorithm with the same input data would take roughly 10 operations to complete, since <sub>2</sub><sup>10</sup> = 1000.</p>
<p>Example of algorithm with complexity O(log(n)) may be a binary search.</p>Mateusz DeskaIt’s mathematical concept which is very often used in Computer Science in context of algorithms complexity, it’s even sounds similar 😀Big O Notation2022-08-01T00:00:00+00:002022-08-01T00:00:00+00:00https://matthewonsoftware.com/blog/big-o-notation<p>The Big O Notation is a mathematical, asymptotic notation that describes time complexity and space complexity of algorithms/ function when the argument tends towards a particular value of infinity.<br />
For example, O(n) might be the same complexity of an algorithm that traverses thought an array of length n, <br />similarly, O(n+m) might be the time complexity of an algorithm that traverses through an array of length n and through a string of length m.<br />
The following are examples of common complexities and their Big O notations, ordered from fastest to slowest</p>
<ul>
<li>Constant: O(1)</li>
<li>Logarithmic: O(log(n))</li>
<li>Linear: O(n)</li>
<li>Log-linear: O(n log(n))</li>
<li>Quadratic: O(n<sup>2</sup>)</li>
<li>Factorial: O(n!)</li>
</ul>
<p><img src="https://matthewonsoftware.com/assets/blog_images/2022-08-01-big-o-notation/big-o-notation-complexity-visualization.png" alt="img" /></p>
<p>Sometimes there may be best/ average/ worst case scenario depends on the input data, how they’re structured and so on, so in other words depend on algorithm (quick sort for example) but Big O usually refers to worst case scenario.</p>
<p>Examples of algorithms and their time complexity:</p>
<ul>
<li>fun(arr[]) ⇒ 1 + arr[0] we just return static number and first element of an array (assume that’s not empty), so the size of input data will not affect the complexity anyhow. the time complexity will be O(1), in other words constant.</li>
<li>fun(arr []) ⇒ sum(arr) here we are summing the given array, so we have to traverse through whole array, in other words the more elements are in given array, the more work our algorithm need to perform, so its linear complexity, in other words O(n).</li>
<li>fun(arr []) ⇒ pair(arr) here we are pairing all elements, assume that it’s done by nested for loops. Then guess what, it would require going through every element twice. In previous example it was O(n) to iterate over given array. In this case it will be O(n<sup>2</sup>).</li>
</ul>
<p>The constants in Big O Notation does not matter and how to simplify it.</p>
<p>Let’s assume that we have function that do bunch of elementary operations, it sums up few numbers, declare some variables, an array with few elements, it’s going to be algorithm that may run in O(25) but that would be written down to O(1). So you never want to say O(25) as it does not really make sense, and you should write it as O(1).
The second example might be an algorithm in which we iterate over a given array from left to right and from right to left. That would be an O(2n) complexity, but you would drop a two because that’s a constant, so it would be just O(n).
Also, important thing to remember, let’s assume that in our function we have a pairing algorithm and the one described just before. That is O(n<sup>2</sup> + 2n) but we would remove the two as its constant and n also as it’s become meaningless next to the N squared, so it would be O($n^2)$.</p>
<p>Other example: O(n! + n<sup>3</sup> + log(n) + 3) ⇒ O(n!) as factorial would growth the fastest.
We were able to drop this in above way as we were iterating on the same input data N.
Now assume that we do the same but for input we’re going to get two different arrays of size M and N.</p>
<p>O(m<sup>2</sup> + 2n) can we transform it to O(m<sup>2</sup>)?
No! We can not! As those are different inputs, so we want to know the behaviour as it relates to these two different inputs, and you can imagine that there is actually a scenario where m<sup>2</sup> is tiny compared to N. <br />
Imagine M is equal to two and N is equal to a thousand, then m<sup>2</sup> would be smaller than N. That’s why when you have two variables, you do always want to keep both of them. So it has to remain O(m<sup>2</sup>+ n)(we can still drop constants)</p>Mateusz DeskaThe Big O Notation is a mathematical, asymptotic notation that describes time complexity and space complexity of algorithms/ function when the argument tends towards a particular value of infinity. For example, O(n) might be the same complexity of an algorithm that traverses thought an array of length n, similarly, O(n+m) might be the time complexity of an algorithm that traverses through an array of length n and through a string of length m. The following are examples of common complexities and their Big O notations, ordered from fastest to slowestMemory2022-07-31T00:00:00+00:002022-07-31T00:00:00+00:00https://matthewonsoftware.com/blog/memory<h3 id="key-terms">Key Terms</h3>
<ul>
<li><strong>Bit</strong></li>
</ul>
<p>Short for binary digit, a bit is a fundamental unit of information in Computer Science that represents a state with one of two values, typically 0 and 1.</p>
<p>Any data stored in a computer is, at the most basic level, represented in bits.</p>
<p><br /></p>
<ul>
<li><strong>Byte</strong></li>
</ul>
<p>A group of eight bits. For example, 01101000 is a byte. <br />
A single byte can represent up to 256 data values (2<sup>8</sup>). <br />
Since a binary number is a number expressed with only two symbols, like 0 and 1, a byte can effectively represent all the numbers between 0 and 255, inclusive, in binary format.</p>
<p>The following bytes represents the numbers 1, 2, 3 and 4 in binary format.</p>
<p>1: 00000001 <br />
2: 00000010 <br />
3: 00000011 <br />
4: 00000100</p>
<p>Every place from right to left (google for endianness if you are interesting why it’s so) represent 2<sup>n</sup> starting from 0.</p>
<p><br /></p>
<ul>
<li><strong>Fixed-Width Integer</strong></li>
</ul>
<p>An integer represented by a fixed amount of bits. For example, a 32-bit integer is an integer represented by 32 bits (4 bytes), and a 64-bit integer is an integer represented by 64 bits (8 bytes). <br />
The following is the 32-bit representation of the number 1, with clearly separated bytes. <br />
00000000 00000000 00000000 00000001</p>
<p><br />
The following is the 64-bit representation of the number 10, with clearly separated bytes. <br />
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00001010</p>
<p>Regardless of how large an integer is, its fixed-width-integer representation is, by definition, made up of a constant number of bits. <br />
It follows that, regardless of how large an integer is, an operation performed on its fixed-width-integer representation consists of a constant number of bits manipulations, <br />
since the integer is made up of a fixed number of bits. So integer equal 1 will take same amount of memory as 2147483647.</p>
<p><br /></p>
<h4 id="memory">Memory</h4>
<p>Broadly speaking, memory is the foundational layer of the computing, where all data is stored.</p>
<p>It’s important to note the following points:</p>
<ul>
<li>Data stored in memory is stored in bytes and, by extension, bits.</li>
<li>Bytes in memory can “point” to other bytes in memory, to store references to other data.</li>
<li>The amount of memory that machine has is bounded, making it valuable to limit how much memory an algorithm takes up.</li>
<li>Accessing a byte or a fixed number of bytes (like 4 bytes or 8 bytes in the case of 32-bit and 64-bit integers) is an elementary operation, which can be loosely treated as a single unit of operational work.</li>
<li>Memory slot, can fit 8-bits which is 1 byte. For example 32-bit integer would take 4 memory slots.</li>
<li>Memory slot can store address to another memory slot, that’s called a pointer.</li>
<li>When you are storing integer, its typically fix-width integer, means It’s either 8/16/32/64 bit. Point is we know how many bytes it will take up.</li>
<li>If it takes more than one memory slot it’s going to have to be stored however many memory slots are needed to store it. Back to back and free.</li>
<li>Storing list work similar, if you’re going to want to store list of five 32-bit integers, for instance, that’s list it’s going to have 20 memory slots.</li>
</ul>Mateusz DeskaKey TermsComplexity Analysis2022-07-30T00:00:00+00:002022-07-30T00:00:00+00:00https://matthewonsoftware.com/blog/complexity-analysis<p>It’s a process in which we determine how efficient an algorithm is. There are multiple ways to solve the same issue but
the complexity analysis will likely differ, some solutions are much better than others. It’s effectively used to
determine how “good” an algorithm is and whether it’s “better” than another one.</p>
<p>There are usually two things that we will measure, time complexity and space complexity of an algorithm</p>
<ul>
<li><strong>Time Complexity</strong></li>
</ul>
<p>A measure of how fast an algorithm runs, time complexity is a central concept in the field of algorithms. It’s expressed
using Big O notation.</p>
<ul>
<li><strong>Space Complexity</strong></li>
</ul>
<p>A measure of how much auxiliary memory an algorithm takes up, space complexity is a central concept in the field of
algorithms. It’s expressed using Big O notation.</p>
<p><br /></p>
<p>What’s worh to add, is that different data structures are going to have different time complexity and space complexity
for functions and operations that they support.
The key thing is not only to figure what data structure best allows you
to solve the problem but also what data structure allows you to do so with the best time/space complexity.</p>Mateusz DeskaIt’s a process in which we determine how efficient an algorithm is. There are multiple ways to solve the same issue but the complexity analysis will likely differ, some solutions are much better than others. It’s effectively used to determine how “good” an algorithm is and whether it’s “better” than another one.