<repo_name>MartinShishkov/blog-demos<file_sep>/clean-controllers/CleanControllers.Web/Controllers/DirtyController.cs
﻿using System;
using System.Linq;
using System.Net;
using CleanControllers.Web.Models.Business;
using CleanControllers.Web.Models.Web;
using Microsoft.AspNetCore.Mvc;

namespace CleanControllers.Web.Controllers
{
    public class DirtyController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }

        [HttpPost]
        public IActionResult DoStuff(SimpleFormPostModel model)
        {
            if (model.Consent == null || model.Consent.Contains("yes") == false)
            {
                // This is business logic right here
                // we could have created a custom ValidationAttribute.
                // Although this is going to be slightly better - 
                // the issue still remains. Furthermore, we are going 
                // to have some trouble if we want to use localization for
                // our errors if we rely on [ValidationAttribute]s
                ModelState.AddModelError(
                    nameof(SimpleFormPostModel.Consent), 
                    "You have to give your consent"
                );
            }

            if (ModelState.IsValid == false)
                return new JsonResult(new
                {
                    // even getting the error messages is an abomination
                    errors = ModelState.Values.SelectMany(e => e.Errors.Select(err => err.ErrorMessage))
                }) {StatusCode = (int)HttpStatusCode.BadRequest};

            try
            {
                var person = new Person(model.FirstName, model.LastName, model.Age);

                if(model.ShouldCauseServerError)
                    throw new InvalidOperationException("Something happened with our Person because our validation logic is all over the place! It was not in valid state to begin with!");

                // ...execute operations with 'person'...

                return new JsonResult(new
                {
                    message = "Everything went smooth"
                }) {StatusCode = (int)HttpStatusCode.OK};
            }
            catch (Exception e)
            {
                return new JsonResult(new
                {
                    errors = new string[]{e.Message}
                }){ StatusCode = (int)HttpStatusCode.InternalServerError};
            }
        }
    }
}
<file_sep>/clean-controllers/CleanControllers.Web/Controllers/CleanController.cs
﻿using System;
using System.Net;
using CleanControllers.Web.Models.Business;
using Microsoft.AspNetCore.Mvc;

namespace CleanControllers.Web.Controllers
{
    public class CleanController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }

        [HttpPost]
        public IActionResult DoStuff(Person.Builder builder)
        {
            var buildResult = builder.Build();
            if(buildResult.IsValid == false)
                return new JsonResult(new
                {
                    errorCodes = buildResult.ErrorCodes
                }) {StatusCode = (int)HttpStatusCode.BadRequest};

            try
            {
                // we have a valid person at this point for sure
                var person = buildResult.Value;

                // ...execute operations with 'person'...
                if(builder.ShouldCauseServerError)
                    throw new InvalidOperationException("Something happened but this time we can be sure that our Person was valid which means something else failed in our services.");

                return new JsonResult(new
                {
                    message = "Everything went smooth"
                }) {StatusCode = (int)HttpStatusCode.OK};
            }
            catch (Exception e)
            {
                return new JsonResult(new
                {
                    errors = new string[]{e.Message}
                }){ StatusCode = (int)HttpStatusCode.InternalServerError};
            }
        }
    }
}<file_sep>/problems/HackerRank/_02.Sort-1-and-0s/README.md
﻿# Sort 1s and 0s

This problem was given to me in an interview with SBTech in HackerRank,
alongside with 79.Minimum-Window and MinimumSum

I don't have the exact problem description but it goes something like this:
Given an array of 1s and 0s, find the least number of swaps of adjacent elements required to
sort the array. The direction of sorting does not matter.

Example:

[1, 1, 1, 0, 0, 0] => 0

[0, 0, 0, 1, 1, 1] => 0

[1, 0, 1, 0, 0, 0] => 1

[1, 0, 0, 1, 0, 1, 1, 0, 0, 1] => 12<file_sep>/problems/HackerRank/_01.MininumSum/README.md
﻿*Minimum Sum
This problem was given to me in an interview with SBTech in HackerRank,
alongside with 79.Minimum-Window problem

I don't have the exact problem description but it goes something like this:
There's an input array with integers say [10, 20, 7], and a number **k** operations.
For each operation you must take the ceiling of a single number in the array / 2.
Math.Ceiling(n / 2), and replace the old number with the new one. You have to choose such numbers so 
that the end sum is the smallest possible. Example

input: [10, 20, 7]
k: 4

------- k = 1 -------
[10, 20, 7] => [10, 10, 7] // we take 20, divide by 2, and take the ceiling, so 10

------- k = 2 -------
[10, 10, 7] => [10, 5, 7] // Math.Ceiling(10 / 2) = 5

------- k = 3 -------
[10, 5, 7] => [5, 5, 7] // Math.Ceiling(10 / 2) = 5

------- k = 4 -------
[5, 5, 7] => [5, 5, 4] // Math.Ceiling(7 / 2) = 4

So we have to print out '14' since this is the sum of all numbers
<file_sep>/problems/LeetCode/76.Minimum-Window-Substring/README.md
﻿# Minimum Window Substring Problem
## The solution currently scores 267/268 tests

https://leetcode.com/problems/minimum-window-substring/

Difficulty: **HARD**

Given a string S and a string T, find the minimum window in S which will contain all the characters in T in complexity O(n).

Example:

> Input: S = "ADOBECODEBANC", T = "ABC"
>
> Output: "BANC"

Note:

If there is no such window in S that covers all characters in T, return the empty string "".
If there is such window, you are guaranteed that there will always be only one unique minimum window in S.

**This problem was given to me in an interview with SBTech**

It was the 1 of 3 problems at HackerRank, but I couldn't find it there
so I managed to get it in LeetCode<file_sep>/problems/LeetCode/205.Isomorphic-Strings/Program.cs
﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace _205.Isomorphic_Strings
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(AreIsomorphic("ab", "aa"));

            /*
               Runtime: 120 ms, faster than 21.31% of C# online submissions for Isomorphic Strings.
                Memory Usage: 23.4 MB, less than 66.39% of C# online submissions for Isomorphic Strings.
             */
        }

        public static bool AreIsomorphic(string str1, string str2)
        {
            if (string.IsNullOrWhiteSpace(str1) &&
                string.IsNullOrWhiteSpace(str2))
                return true;

            if (str1?.Length != str2.Length) return false;

            var map = new Dictionary<char, char>();
            var reverseMap = new Dictionary<char, char>();
            var length = str1.Length;
            for (int i = 0; i < length; i++)
            {
                var key = str1[i];
                var value = str2[i];

                if (map.ContainsKey(key) && value != map[key])
                    return false;

                if (reverseMap.ContainsKey(value) && key != reverseMap[value])
                    return false;

                map.TryAdd(key, value);
                reverseMap.TryAdd(value, key);
            }

            return true;
        }
    }
}
<file_sep>/problems/HackerRank/_02.Sort-1-and-0s/Program.cs
﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace _02.Sort_1_and_0s
{
    class Program
    {
        // This problem really looks like a bubblesort to me
        // or at least the brute force solution. The only issue
        // is that we have to determine in which direction to sort
        // so that we actually get the least amount of swaps
        static int BubbleSort(List<int> arr)
        {
            var count = 0;
            var n = arr.Count;

            var comparer = GetComparer(arr);

            for (var i = 0; i < n - 1; i++)
            {
                for (var j = 0; j < n - i - 1; j++)
                {
                    if (comparer(arr[j], arr[j + 1]))
                    { 
                        var temp = arr[j]; 
                        arr[j] = arr[j + 1]; 
                        arr[j + 1] = temp;
                        count++;
                    } 
                }
            }

            return count;
        }

        static Func<int, int, bool> GetComparer(List<int> arr)
        {
            var take = (int) Math.Ceiling((decimal) (arr.Count - 1) / 2);
            var leftPart = arr.GetRange(0, take);

            var startIndex = take;
            var rightPart = arr.GetRange(startIndex, arr.Count - take);

            if (leftPart.Sum() > rightPart.Sum())
                return (i, i1) => i < i1; // descending 111 -> 000

            return (i, i1) => i > i1; // ascending 000 -> 111
        }

        static void Main(string[] args)
        {
            var inputs = new List<List<int>>
            {
                /*new List<int>{ 0, 1 },
                new List<int> {1, 1, 1, 1, 0, 0, 0, 0},
                new List<int> {1, 1, 1, 1, 0, 0, 1, 0},
                new List<int> {0, 1, 1, 1, 1, 0, 0, 1, 0},*/
                new List<int> {0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1},
                /*new List<int> {0, 0, 0, 0, 0, 0, 1, 1},*/
            };

            foreach (var input in inputs)
            {
                var res = BubbleSort(input);
                Console.WriteLine(res);
            }
        }
    }
}
<file_sep>/problems/LeetCode/1138.Alphabet-Board-Path/Program.cs
﻿using System;

namespace _1138.Alphabet_Board_Path
{
    class Program
    {
        public static readonly string[] _board = new string[] {
            "abcde", 
            "fghij", 
            "klmno", 
            "pqrst", 
            "uvwxy", 
            "z"
        };

        static void Main(string[] args)
        {
            Console.WriteLine(3 - -3);
            Console.WriteLine(AlphabetBoardPath("leet"));
        }

        public static string AlphabetBoardPath(string target) {
            string path = string.Empty;
            int curCol = 0, curRow = 0;

            string GetHorizontalPath(int colsDiff)
            {
                if(colsDiff == 0) return string.Empty;
                var ch = colsDiff > 0 ? 'R' : 'L';

                return new string(ch, Math.Abs(colsDiff));
            }

            string GetVerticalPath(int rowsDiff)
            {
                if(rowsDiff == 0) return string.Empty;
                var ch = rowsDiff > 0 ? 'D' : 'U';

                return new string(ch, Math.Abs(rowsDiff));
            }

            string ProcessHorizontalDir(char direction, int rowsDiff, int colsDiff){
                if(direction == 'U')
                {
                    if(curCol - colsDiff < 0)
                    {
                        rowsDiff++;
                        colsDiff = 5 - colsDiff;
                    }

                    return GetVerticalPath(rowsDiff) + GetHorizontalPath(colsDiff);
                }
                else
                {

                }

                return string.Empty;
            };

            void ProcessVerticalDir(char direction, int rowsDiff, int colsDiff){
                
            };

            foreach(char targetCh in target)
            {
                var currentCh = _board[curRow][curCol];
                if(currentCh == targetCh)
                {
                    path += '!';
                    continue;
                }

                var diff = (int)targetCh - (int)currentCh;
                var shouldSearchForward = diff > 0;
                var rowsDiff = Math.Abs(diff / 5);
                var colsDiff = Math.Abs(diff % 5);

                if(!shouldSearchForward)
                {
                    if(curCol - colsDiff < 0)
                    {
                        rowsDiff++;
                        colsDiff = 5 - colsDiff;
                    }

                }
                else
                {
                    // target char is ahead relative to the current char
                }

                var directionV = diff > 0 ? 'D' : 'U';
                var directionH = diff > 0 ? 'R' : 'L';

                ProcessVerticalDir(directionV, rowsDiff, colsDiff);
                ProcessHorizontalDir(directionH, rowsDiff, colsDiff);
            }

            return path;
        }
    }
}
<file_sep>/problems/LeetCode/76.Minimum-Window-Substring/Program.cs
﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace _76.Minimum_Window_Substring
{
    class Program
    {
        static string MinimumWindowSubstr(string haystack, string letters)
        {
            var length = haystack.Length;
            var minWindowLength = letters.Length;

            if (minWindowLength > length) return string.Empty;
            if (minWindowLength == 1 && haystack.IndexOf(letters) < 0) return string.Empty;
            if (minWindowLength == 1 && haystack.IndexOf(letters) != -1) return letters;

            var map = ToMap(letters);

            var startPointer = 0;
            var endPointer = minWindowLength - 1;
            var currentSmallestWindow = string.Empty;

            do
            {
                var take = (endPointer + 1) - startPointer;
                var window = haystack.Substring(startPointer, take);

                if (window.Length >= minWindowLength
                && WindowMatchesCondition(window, map))
                {
                    startPointer++;

                    if (window.Length < currentSmallestWindow.Length
                        || currentSmallestWindow == string.Empty)
                    {
                        currentSmallestWindow = window;
                    }
                }
                else
                {
                    if (endPointer == length - 1)
                    {
                        if (length - Math.Max(1, startPointer) <= minWindowLength)
                        {
                            break;
                        }
                        else
                        {
                            startPointer++;
                        }
                    }

                    endPointer = Math.Min(endPointer + 1, length - 1);
                }
            } while (true);

            return currentSmallestWindow;
        }

        static bool WindowMatchesCondition(string window, Dictionary<char, int> map)
        {
            var copy = new Dictionary<char, int>(map);
            for (var i = 0; i < window.Length; i++)
            {
                var ch = window[i];
                if (copy.ContainsKey(ch))
                {
                    copy[ch]--;
                    if (copy[ch] == 0)
                        copy.Remove(ch);
                }
            }

            return !(copy.Values.Count > 0);
        }

        static Dictionary<char, int> ToMap(string str)
        {
            var map = new Dictionary<char, int>();
            foreach (var ch in str)
            {
                if (!map.ContainsKey(ch))
                    map.Add(ch, 0);

                map[ch]++;
            }
            return map;
        }

        static void Main(string[] args)
        {
            /* var tests = new Dictionary<string, string>{
                 {"ADOBECODEBANC", "ABC"},
                 //{"abc", "ac"},
                 {"abc", "a"},
                 //{"ab", "b"},
                 {"aa", "aa"},
                {"ab", "A"},
                {"cabwefgewcwaefgcf", "cae"}
            }; */

            /* foreach (var test in tests)
            {
                Console.WriteLine(MinimumWindowSubstr(test.Key, test.Value));
            } */
            var s = "<KEY>";
            var t = "<KEY>qiqrzwugtr";

            var sw = new Stopwatch();
            sw.Reset();

            sw.Start();
            var res = MinimumWindowSubstr(s, t);
            sw.Stop();

            Console.WriteLine($"Time: {sw.ElapsedMilliseconds}ms");
            Console.WriteLine(res);
        }
    }
}
<file_sep>/problems/LeetCode/217.Contains-Duplicate/Program.cs
﻿using System;
using System.Collections.Generic;

namespace _217.Contains_Duplicate
{
    class Program
    {
        static void Main(string[] args)
        {
            var input = new int[] {1,1,1,3,3,4,3,2,4,2};
            Console.WriteLine(ContainsDuplicate(input));
        }

        static bool ContainsDuplicate(int[] nums)
        {
            var set = new HashSet<int>(nums);

            return set.Count != nums.Length;
        }
    }
}
<file_sep>/problems/HackerRank/_01.MininumSum/Program.cs
﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace _01.MininumSum
{
    class Program
    {
        static int MinSum(List<int> input, int k)
        {
            input.Sort();

            // we know that the last element is the largest
            // since we've sorted the input
            var largestNumIndex = input.Count - 1;
            int largestNum;

            void CompareToPrev()
            {
                var prevIndex = largestNumIndex - 1;
                var prev = input[prevIndex];
                if (largestNum < prev)
                {
                    largestNum = prev;
                    largestNumIndex = prevIndex;
                }
            }

            void CompareToNext()
            {
                var nextIndex = largestNumIndex + 1;
                var next = input[nextIndex];

                if (largestNum < next)
                {
                    largestNum = next;
                    largestNumIndex = nextIndex;
                }
            }

            for (int i = 0; i < k; i++)
            {
                largestNum = input[largestNumIndex];
                if (largestNumIndex == input.Count - 1)
                {
                    // largest num is the last element
                    // so compare it with the previous element
                    CompareToPrev();
                }
                else if (largestNumIndex == 0)
                {
                    // largest num is the first element
                    // so compare it to the next
                    CompareToNext();
                }
                else
                {
                    // largest num is somewhere in the middle
                    // compare it with the elements from both sides
                    CompareToNext();
                    CompareToPrev();
                }

                var valueToInsert = (int) Math.Ceiling((decimal)largestNum / 2);
                input[largestNumIndex] = valueToInsert;
            }

            return input.Sum();
        }

        static void Main(string[] args)
        {
            var input = new List<int> { 10, 20, 7 };
            var k = 4;

            Console.WriteLine(MinSum(input, k));
        }
    }
}
<file_sep>/clean-controllers/CleanControllers.Web/Models/Business/BuildResult.cs
﻿using System;
using Microsoft.EntityFrameworkCore.Internal;

namespace CleanControllers.Web.Models.Business
{
    public class BuildResult<T>
    {
        public BuildResult(string[] errorCodes, T value)
        {
            ErrorCodes = errorCodes ?? throw new ArgumentNullException(nameof(errorCodes));
            Value = value;
        }

        public string[] ErrorCodes { get; }

        public T Value { get; }

        public bool IsValid => ErrorCodes.Any() == false;
    }
}<file_sep>/clean-controllers/CleanControllers.Web/Models/Web/SimpleFormPostModel.cs
﻿using System.ComponentModel.DataAnnotations;
using System.Linq;

namespace CleanControllers.Web.Models.Web
{
    public class SimpleFormPostModel
    {
        // We can't use our Person error codes here because of a compile time error.
        // We can try to create some workaround but it's going to get messy.
        [Required]
        [MinLength(3, ErrorMessage = "This has to be at least 3 chars long dammit")]
        public string FirstName { get; set; }

        [Required]
        [MinLength(3, ErrorMessage = "This has to be at least 3 chars long dammit")]
        public string LastName { get; set; }

        [Required]
        [Range(minimum: 18, maximum: 1000, ErrorMessage = "You have to be between 18 and 1000 years old")]
        public int Age { get; set; }

        [Required]
        public string[] Consent { get; set; }

        public bool ShouldCauseServerError => SimulateServerError != null && SimulateServerError.Contains("yes");

        public string[] SimulateServerError { get; set; }
    }
}<file_sep>/problems/LeetCode/206.Reverse-Linked-List/ListNode.cs
﻿using System;

namespace _206.Reverse_Linked_List
{
    public class ListNode<T>
    {
        public ListNode(T val, ListNode<T> next = null)
        {
            this.Value = val;
            this.Next = next;
        }

        public T Value { get; }

        public ListNode<T> Next { get; set; }

        public void Insert(T value)
        {
            if (this.Next == null)
            {
                this.Next = new ListNode<T>(value);
            }
            else
            {
                this.Next.Insert(value);
            }
        }

        public void Print()
        {
            var p = this;
            while (p != null)
            {
                Console.WriteLine(p.Value);
                p = p.Next;
            }
        }
    }
}
<file_sep>/clean-controllers/CleanControllers.Web/Models/Business/Person.cs
﻿using System.Collections.Generic;
using System.Linq;

namespace CleanControllers.Web.Models.Business
{
    public class Person
    {
        // this constructor is obsolete; the Person class needs only properties with private setters
        public Person(string firstName, string lastName, int age)
        {
            FirstName = firstName;
            LastName = lastName;
            Age = age;
        }

        public string FirstName { get; }

        public string LastName { get; }

        public int Age { get; }

        public static readonly string ERROR_FIRST_NAME_LENGTH = "person.firstname.length";
        public static readonly string ERROR_LAST_NAME_LENGTH = "person.lastname.length";
        public static readonly string ERROR_AGE_INVALID = "person.age.invalid";
        public static readonly string ERROR_CONSENT_REQUIRED = "person.consent.required";

        public class Builder
        {
            public string FirstName { get; set; }
            public string LastName { get; set; }
            public int Age { get; set; }
            public string[] Consent { get; set; }

            public bool ShouldCauseServerError => SimulateServerError != null && SimulateServerError.Contains("yes");
            public string[] SimulateServerError { get; set; }

            public BuildResult<Person> Build()
            {
                var errorCodes = new List<string>();

                if (string.IsNullOrEmpty(FirstName))
                    errorCodes.Add(ERROR_FIRST_NAME_LENGTH);
                
                if(string.IsNullOrEmpty(LastName))
                    errorCodes.Add(ERROR_LAST_NAME_LENGTH);

                if(Age < 18)
                    errorCodes.Add(ERROR_AGE_INVALID);

                if(Consent == null || Consent.Contains("yes") == false)
                    errorCodes.Add(ERROR_CONSENT_REQUIRED);

                // kinda dumb
                var person = errorCodes.Any() ? null : new Person(FirstName, LastName, Age);

                return new BuildResult<Person>(errorCodes.ToArray(), person);
            }
        }
    }
}<file_sep>/problems/LeetCode/206.Reverse-Linked-List/Program.cs
﻿namespace _206.Reverse_Linked_List
{
    class Program
    {
        static void Main(string[] args)
        {
            var input = new[] {1, 2, 3, 4, 5};

            var list = ListFromArray(input);
            //list.Print();

            var r = Reverse(list);
            r.Print();
        }

        static ListNode<int> ListFromArray(int[] array)
        {
            var head = new ListNode<int>(array[0]);
            for (int i = 1; i < array.Length; i++)
            {
                head.Insert(array[i]);
            }

            return head;
        }

        static ListNode<int> ReverseIterative(ListNode<int> head)
        {
            ListNode<int> prev = null;
            ListNode<int> current = head;
            ListNode<int> next = null;

            while (current != null)
            {
                next = current.Next;
                current.Next = prev;
                prev = current;
                current = next;
            }

            return prev;
        }

        // recursive
        static ListNode<int> Reverse(ListNode<int> head)
        {
            // we should be asking the code to reverse a smaller
            // and smaller list until there's only 1 element left
            // and the reversed version of that is itself

            if (head == null || head.Next == null)
                return head;

            var reversed = Reverse(head.Next);
            head.Next.Next = head;
            head.Next = null;

            return reversed;
        }
    }
}
<file_sep>/clean-controllers/README.md
This project contains demo code for https://shishkov.me/article/1000012/clean-up-your-controllers-builder-pattern-for-refactoring-your-asp-net-core-app
<file_sep>/problems/LeetCode/219.Contains-Duplicate-II/Program.cs
﻿using System;
using System.Collections.Generic;

namespace _219.Contains_Duplicate_II
{
    class Program
    {
        static void Main(string[] args)
        {
            /*
             * Runtime: 156 ms, faster than 18.31% of C# online submissions for Contains Duplicate II.
                Memory Usage: 29.3 MB, less than 21.21% of C# online submissions for Contains Duplicate II.
             */

            var input = new int[] {1, 0, 1, 1};
            var k = 1;

            Console.WriteLine(ContainsNearbyDuplicate(input, k));
        }

        static bool ContainsNearbyDuplicate(int[] nums, int k)
        {
            if (k == 0)
                return false;

            var map = new Dictionary<int, int>();

            for (var i = 0; i < nums.Length; i++)
            {
                if (!map.ContainsKey(nums[i]))
                {
                    map[nums[i]] = i;
                    continue;
                }

                var index = map[nums[i]];

                if (Math.Abs(i - index) <= k)
                    return true;

                map[nums[i]] = i;
            }

            return false;
        }
    }
}
