DEV Community

Cover image for Practicing algorithms using Polyglot Notebooks - part 3 - helpers
Krzysztof Koziarski
Krzysztof Koziarski

Posted on

Practicing algorithms using Polyglot Notebooks - part 3 - helpers

In the previous post Practicing algorithms using Polyglot Notebooks - part 2 - introduction the execution and assert part was a bit messy with a lot of code repeating for each algorithm. We can reduce this part by using short extension methods instead.

Instead of writing:

foreach (var (targetSum, numbers, output) in testCases) {
    var result = HowSum(targetSum, numbers);
    var numbersStr = Newtonsoft.Json.JsonConvert.SerializeObject(numbers);
    var resultStr = Newtonsoft.Json.JsonConvert.SerializeObject(result);
    var outputStr = Newtonsoft.Json.JsonConvert.SerializeObject(output);
    Console.WriteLine($"f({targetSum}, {numbersStr}) => {outputStr}, actual: {resultStr}{ (resultStr == outputStr ? " ✔" : " | WRONG!!! ❌") }");
}
Enter fullscreen mode Exit fullscreen mode

we will be able to make it more concise:

foreach (var (targetSum, numbers, output) in testCases) {
    var result = HowSum(targetSum, numbers, new());
    Log($"f({targetSum}, {numbers.Str()})", result?.Str(), output?.Str());
}
Asrt.Flush();
Enter fullscreen mode Exit fullscreen mode

Helpers

helpers cell

We need a set of methods that will help us to prepare, execute, assert and write results.

In the last example above, you can notice three new methods:

  • Log(...)
  • .Str()
  • Asrt.Flush()

Those are helper methods that were added as first code cell in the notebook and should be executed at beginning (first action after the notebook is open).

Reminder - reusing previous cells

You can access methods and variables from previous cells in next cells, but only if you execute the previous cells first.
polyglot notebooks - previous values

Methods and variables from previous cells will not be available in next cells until you execute those previous cells first. Similarly, if you change values or method implementation in previous cells without executing those cells then only old values will be available in next cells.

polyglot notebooks - previous values error

Helper methods

Every algorithm is (and should be) independent and does not depend on any other cell except helpers cell.

Asrt class

This is a simple static helper class to assert condition, collect errors and write summary line at the end of cell.

public static class Asrt {
    private static bool hasErrors;
    public static string T(bool condition) {
        if (!condition) {
            hasErrors = true;
        }
        return condition ? "" : " | WRONG!!! ❌";
    }

    public static void Flush() {
        if (hasErrors) {
            hasErrors = false;
            Console.WriteLine("\n❌ ❌ ❌ ❌ ❌ ❌ ❌ ❌ ❌");
            // throw new System.Data.DataException("Invalid result");
        } else {
            Console.WriteLine("\n✅ ✅ ✅ ✅ ✅ ✅ ✅ ✅ ✅");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

The T(bool condition) method is used inside Log(...) method to print assert results.
The Flush() method must be called at the end of each cell to write summary line and reset hasErrors flag. It is very important to always call it at the end if you use Log() method in a cell, otherwise you will get an incorrect result in a next cell.

summary - errors

summary - success

Log(...) method

This is a helper method that prints the result for each test case. It accepts function description (funcDefStr) as first argument, result as current return value from algorithm function and expected as expected value. The last parameter is optional and allows you to override default comparison logic.

public static void Log<T>(string funcDefStr, T result, T expected, bool? condition = null) {
    condition = condition ?? EqualityComparer<T>.Default.Equals(result, expected);
    Console.Write($"{funcDefStr} => ");
    Console.Write($"{(result == null ? "<null>" : result)}");
    if (condition == false) {
        Console.Write($" expected: {expected}");
    } else {
        Console.Write(" ✔");
    }
    Console.WriteLine(Asrt.T(condition == true));
}
Enter fullscreen mode Exit fullscreen mode

log method output

.Str() method

This is an extension method which converts IEnumerable to JSON string array new int[] { 1, 2, 3 } => [1,2,3] or new List<string> { "aa", "bb", "cc" } => [aa, bb, cc].

public static string Str(this IEnumerable<int> arr, int? size = null) => { ... }

public static string Str(this IEnumerable<object> arr, int? size = null) { ... }
Enter fullscreen mode Exit fullscreen mode

Example algorithms

Here are some algorithms which are returning different result types - primitives (int), array (int[]) and array of arrays (string[][]) to demonstrate different usages of helper methods.

Fib

"✅Fibonacci memoization".Display();
public int Fib(int n, Dictionary<int, int> memo = null) {
    if (n <= 2) return 1;
    if (memo.ContainsKey(n)) return memo[n];

    int result = Fib(n-1, memo) + Fib(n-2, memo);
    memo[n] = result;
    return result;
}

(int intput, int output)[] testCases = {
    (6, 8),
    (7, 13),
    (8, 21),
    (45, 1134903170),
};

foreach (var (input, output) in testCases) {
    var result = Fib(input, memo: new());
    Log($"f({input})", result, output);
}
Asrt.Flush();
Enter fullscreen mode Exit fullscreen mode

HowSum

For IEnumerable<T> results use .Str() method to convert result into JSON string. This is an extension method for IEnumerable<T>.
If you want to sort result first, use .SortNStr() to sort elements firts and then convert sequence to JSON string.

"✅HowSum memoization".Display();

public int[] HowSum(int targetSum, int[] numbers, Dictionary<int, int[]> memo) {
    if (targetSum < 0) return null;
    if (targetSum == 0) return new int[0];
    if (memo.ContainsKey(targetSum)) return memo[targetSum];

    foreach (int n in numbers) {
        int remainder = targetSum - n;

        var result = HowSum(remainder, numbers, memo);
        if (result != null) {
            result = result.Append(n).ToArray();
            memo[targetSum] = result;
            return result;
        }
    }

    memo[targetSum] = null;
    return null;
}

(int targetSum, int[] numbers, int[] output)[] testCases = {
    (7, new int[] { 2,3 }, new int[] { 3,2,2 }),
    (7, new int[] { 5,3,4,7 }, new int[] { 4,3 }),
    (7, new int[] { 2,4 }, null),
    (8, new int[] { 2,3,5 }, new int[] { 2,2,2,2 }),
    (300, new int[] { 7,14 }, null),
};

foreach (var (targetSum, numbers, output) in testCases) {
    var result = HowSum(targetSum, numbers, new());
    Log($"f({targetSum}, {numbers.Str()})", result?.SortNStr(), output?.SortNStr());
}
Asrt.Flush();
Enter fullscreen mode Exit fullscreen mode

AllConstruct

For two dimensional results use .Str2() instead. This is an extension method for IEnumerable<IEnumerable<object>> or int[][].

"✅AllConstruct memoization".Display();

public List<List<string>> AllConstruct(string target, string[] wordBank, Dictionary<string, List<List<string>>> memo) {
    if (target == string.Empty) return new() { new List<string>() };
    if (memo.ContainsKey(target)) return memo[target];

    List<List<string>> result = new();

    foreach (string word in wordBank) {
        if (target.StartsWith(word)) {
            string suffix = target.Substring(word.Length);

            var suffixWays = AllConstruct(suffix, wordBank, memo);

            foreach (var way in suffixWays) {
                List<string> targetWay = new();
                targetWay.Add(word);
                targetWay.AddRange(way);
                result.Add(targetWay);
            }
        }
    }

    memo[target] = result;
    return result;
}

(string target, string[] wordBank, string[][] output)[] testCases = {
    ("purple", FromJson<string[]>("[ 'purp', 'p', 'ur', 'le', 'purpl' ]"), FromJson<string[][]>(@"
     [
        [ 'purp', 'le' ],
        [ 'p', 'ur', 'p', 'le' ],
     ]")),
     ("abcdef", FromJson<string[]>("[ 'ab', 'abc', 'cd', 'def', 'abcd', 'ef', 'c' ]"), FromJson<string[][]>(@"
     [
        [ 'ab', 'cd', 'ef' ],
        [ 'ab', 'c', 'def' ],
        [ 'abc', 'def' ],
        [ 'abcd', 'ef' ],
     ]")),
    ("skateboard", FromJson<string[]>(" ['bo', 'rd', 'ate', 't', 'ska', 'sk', 'boar' ]"), FromJson<string[][]>("[]")),
    ("eeeeeeeeeeeeeeeeeeeeeeeeeeeef", FromJson<string[]>(" [ 'e', 'ee', 'eee', 'eeee', 'eeeee', 'eeeeee' ]"), FromJson<string[][]>("[]")),
};

foreach (var (target, wordBank, output) in testCases) {
    var result = AllConstruct(target, wordBank, new());
    Log($"f('{target}', '{wordBank.Str()}')", result?.Str2(), output?.Str2());
}
Asrt.Flush();
Enter fullscreen mode Exit fullscreen mode

Navigation

For easier navigation between cells, you can use breadcrumbs

polyglot notebooks - breadcrumbs

or press CTRL+SHIFT+O to find cell by title

polyglot notebooks - jump to by title

All C# algorithms notebook from the dynamic programming course can be found here

Top comments (0)