Code Review Videos > Map, Filter, Reduce > [Easy] Map in C#

[Easy] Map in C#

The following are the C# solutions to the map problems defined under Map, Filter, Reduce.

As I am learning C# and much of this stuff is new to me, I will call out all the things I found interesting or different to what I am used to when working with TypeScript.

Easy Map

// EasyMapUnitTest.cs

using System;
using System.Linq;
using MapFilterReduceClassLibrary;
using NUnit.Framework;

namespace MapFilterReduceUnitTests;

public class EasyMapUnitTests
{
    private static object[] _mapStringTestCases =
    {
        Tuple.Create(
            Array.Empty<string>(),
            Array.Empty<string>()
        ),
        Tuple.Create(
            new[] { "    " },
            Array.Empty<string>()
        ),
        Tuple.Create(
            new[] { "a" },
            new[] { "A" }
        ),
        Tuple.Create(
            new[] { "XYZ AbC " },
            new[] { "Xyz abc" }
        ),
        Tuple.Create(
            new[]
            {
                "aaa",
                "bbb",
                "ccc",
                "a",
                "AAA",
                "Abba",
                "",
                "   l33t sp4aK "
            },
            new[]
            {
                "Aaa",
                "Bbb",
                "Ccc",
                "A",
                "Aaa",
                "Abba",
                "L33t sp4ak"
            }
        )
    };

    [Test]
    [TestCase(new int[] { }, ExpectedResult = new int[] { })]
    [TestCase(new[] { 1 }, ExpectedResult = new[] { 1 })]
    [TestCase(new[] { 3, 5, 10 }, ExpectedResult = new[] { 9, 25, 100 })]
    public int[] TestMapNumbers(int[] input)
    {
        return EasyMap.MapNumbers(input).ToArray();
    }

    [TestCaseSource(nameof(_mapStringTestCases))]
    public void TestFilterString(Tuple<string[], string[]> input)
    {
        var (given, expected) = input;
        Assert.AreEqual(
            expected.ToArray(),
            EasyMap.MapStrings(given).ToArray()
        );
    }

    [Test]
    [TestCase(new bool[] { }, ExpectedResult = new object[] { })]
    [TestCase(new[] { false }, ExpectedResult = new[] { "False" })]
    [TestCase(new[] { true }, ExpectedResult = new[] { "True" })]
    [TestCase(new[] { false, true, false, true, false, true }, ExpectedResult = new[]
    {
        "False",
        "True",
        "False",
        "True",
        "False",
        "True"
    })]
    public string[] TestMapBool(bool[] input)
    {
        return EasyMap.MapBool(input).ToArray();
    }
}Code language: C# (cs)

And the associated implementation:

// EasyMap.cs

namespace MapFilterReduceClassLibrary;

public static class EasyMap
{
    public static IEnumerable<int> MapNumbers(IEnumerable<int> input)
    {
        string[] i = { "", " ", "  ", "   ", "x" };
        var x = i.Select(s => s.Trim());
        Console.WriteLine(x);

        return input.Select(i => i * i);
    }

    public static IEnumerable<string> MapStrings(IEnumerable<string> input)
    {
        return input
            .Select(s => s.Trim())
            .Where(s => s.Length > 0)
            .Select(s => $"{s.First().ToString().ToUpper()}{s.Substring(1).ToString().ToLower()}");
    }

    public static IEnumerable<string> MapBool(IEnumerable<bool> input)
    {
        return input.Select(b => b.ToString());
    }
}Code language: C# (cs)

C# uses Select not map

I’m unsure whether idiomatic C# considers Select to be the correct approach to solving this kind of problem.

To the best of my knowledge, in C# the Select function is the closest equivalent to what I would use map for in other languages.

Making Use Of What I Have Learned So Far

As the third of these three exercises I have now completed in C#, I was able to put in place many of the lessons I have already learned.

Things to call out specifically are:

  • TestCase vs TestCaseSource and when I might choose one over the other
  • Tuple vs array of arrays – though even more learning was had on this one (see below)
  • Returning IEnumerable vs a specific implementation (still not sold on this)
  • Accepting IEnumerable vs a specific implementation (but I am sold on this)

The other less tangible thing that I took away from this exercise in particular was in that this was the first time I didn’t feel like an absolute newb.

Don’t get me wrong, I am still very much a beginner, but I’m over the fear of creating new classes / tests from scratch, and have some initial understandings of why things may not be working… rather than just staring at the screen, wishing it worked, then alt+tabbing back to Reddit to procrastinate for another 10 minutes.

Return IEnumerable Or Use .ToArray() First?

I initially (dis)covered this in the previous exercise.

My theory here goes that I can retain more flexibility in the code by returning before I call .ToArray() when using LINQ methods such as Select and Aggregate.

In previous exercises I have had my method signatures look like this:

public static int[] MapNumbers(int[] input)Code language: C# (cs)

Taking an array of integers as input, and returning an array of integers.

But Rider suggested the refactoring here in the first instance:

public static int[] MapNumbers(IEnumerable<int> input)Code language: C# (cs)

And that definitely gives flexibility in terms of what can be passed in to this function.

It was only later, during this exercise, that I figured, hey, maybe I could also return IEnumerable<int>:

public static IEnumerable<int> MapNumbers(IEnumerable<int> input)Code language: C# (cs)

But is that a step too far?

Is it necessary to offer that level of flexibility, or is it fine to return the .ToArray() output?

It’s a tough one for me to call at this stage. In returning early, prior to calling .ToArray(), I am giving the end user the option to do whatever best fits them. I saw online – on a Hacker News thread – that maybe calling .ToList() is more common than .ToArray().

I guess it depends on the context. My gut says in this very specific instance I have gone too far. Happy to hear your comments though.

A downside to returning IEnumerable from the code is the test code still needs to explicitly convert to something in order to do the equality check:

public int[] TestMapNumbers(int[] input)
{
    return EasyMap.MapNumbers(input).ToArray();
}Code language: C# (cs)

So that feels a bit odd.

Going Too Far(?) With Tuple

Unhappy with the array of arrays approach I took in the early tests I wrote for these exercises, whilst I was writing up my approach I remembered that C# has the concept of Tuples.

In my head, Tuples are synonymous with pairs. However, they need not just be two values. I believe the type signature for a Tuple in C# goes up to something like 8 or 9 values per entry.

Typescript has a Tuple type, though I can’t honestly remember a real world project and using a Tuple in the equivalent TS code is not the approach I would take either.

Comparing the original:

private static object[] FilterStringCases =
{
    new object[]
    {
        new string[] { },
        new string[] { }
    },
    new object[]
    {
        new[] { "    " },
        new string[] { }
    },
    new object[]
    {
        new[] { "b" },
        new[] { "b" }
    },
    new object[]
    {
        new[] { "aaa", "bbb", "ccc", "a", "AAA", "Abba", "" },
        new[] { "bbb", "ccc" }
    }
};Code language: C# (cs)

And the later revision:

private static object[] _filterStringCases =
{
    Tuple.Create(
        new string[] { },
        Array.Empty<string>()
    ),
    Tuple.Create(
        new[] { "    " },
        Array.Empty<string>()
    ),
    Tuple.Create(
        new[] { "b" },
        new[] { "b" }
    ),
    Tuple.Create(
        new[] { "aaa", "bbb", "ccc", "a", "AAA", "Abba", "" },
        new[] { "bbb", "ccc" }
    )
};Code language: C# (cs)

I definitely prefer the second approach.

Also notice that I learned that the variable naming I used in the original example was not the C# standard format. For a private static variable I should have been starting with an underscore and then a lowercase word.

In using a Tuple, what I hoped would be possible was an easy ability to destructure values from the Tuple.

Imagine in JavaScript / TypeScript we have the following:

const letters = [
    [
        ['a', 'b', 'c'],
        ['x', 'y', 'z']
    ],
    [
        ['d', 'e', 'f'],
        ['u', 'v', 'w']
    ]
];

const [row1, row2] = letters[0];
const [row3, row4] = letters[1];

console.log(row1); // outputs:  ['a', 'b', 'c']
console.log(row2); // outputs:  ['x', 'y', 'z']
console.log(row3); // outputs:  ['d', 'e', 'f']
console.log(row4); // outputs:  ['u', 'v', 'w']Code language: TypeScript (typescript)

This becomes more useful in some kind of iteration:

letters.map(subArray => console.log(subArray));

// outputs
[["a", "b", "c"], ["x", "y", "z"]]
[["d", "e", "f"], ["u", "v", "w"]]Code language: TypeScript (typescript)

Or more useful still:

letters.map(([row1, row2]) => console.log(row1, row2));

// outputs
[["a", "b", "c"], ["x", "y", "z"]]
[["d", "e", "f"], ["u", "v", "w"]]Code language: TypeScript (typescript)

Where the previous example is very ergonomic. We are able to reach inside the given argument (subArray in the first example) and say, hey, we know that subArray is really an array, right? So please can you take the first element of the array and put the contents into a new variable called row1, and do the same again for the second row into row2.

And now regardless of how many nested arrays we have under the letters variable, we can always refer to each sub array as if its elements were available to us as row1 and row2.

That’s a very long winded way of me saying I wanted the same in C#.

What I was very much hoping for was to be able to destructure from a Tuple directly in the method signature of my test:

// actual:
[TestCaseSource(nameof(_mapStringTestCases))]
public void TestFilterString(Tuple<string[], string[]> input)

// wanted:
[TestCaseSource(nameof(_mapStringTestCases))]
public void TestFilterString(given, expected)Code language: C# (cs)

I’m not sure if that is possible. If it is, I haven’t yet figured out how.

I think some advancements have been made to Tuple syntax in a newer version of C#, but I appear to be running an older version as best I understand it:

The best I’ve been able to do so far with my Tuple approach is to make use of the rather generic sounding .Item1, .Item2 approach:

[TestCaseSource(nameof(_filterStringCases))]
public void TestFilterString(Tuple<string[], string[]> input)
{
    Assert.AreEqual(
        EasyFilter.FilterString(input.Item1),
        input.Item2
    );
}Code language: C# (cs)

For these tests I went one step further to help make the code more descriptive:

[TestCaseSource(nameof(_mapStringTestCases))]
public void TestFilterString(Tuple<string[], string[]> input)
{
    var (given, expected) = input;
    Assert.AreEqual(
        expected.ToArray(),
        EasyMap.MapStrings(given).ToArray()
    );
}
Code language: C# (cs)

I know it is being nit picky but I don’t like having the extra line in there just to do the assignment.

I was hoping I might be able to name my Tuple properties – so rename .Item1 to .Given or something. But again, no dice with my version of C#.

List of Tuple ?

Am I going too deep down the rabbit hole?

I don’t know.

But I did discover that using a List<Tuple<string[], string[]>> will give a nicer developer experience thanks to the IDE being able to intelligently auto-complete / intellisense your input.

If we use object[] then the IDE has no way of determining what that object might be:

But if we give it more information about the type of data we are providing then good things happen:

To make this work we need a few extra bits:

using System.Collections.Generic;

public class EasyMapUnitTests
{
    private static List<Tuple<string[],string[]>> _mapStringTestCases = new()
    {
        Tuple.Create(...Code language: C# (cs)

It seems that NUnit has a List class of its own, which is deprecated. That gave me an initial source of confusion, not least of which because I had no idea what using statement I would need to use a List.

Honestly even after all of this, I’m still not sold on this being the normal way I would expect a professional working C# developer to solve this problem.

It feels convoluted.

Template Strings Are Just Different Enough To Be Fiddly

JavaScript’s ES6 update brought one of the nicest, most commonly used improvements in the form of Template Literals (or Template Strings, to me and thee).

By making use of the backtick (`) we can write strings that contain variables / logic without need to split everything with the plus operator (+).

Basically this:

const x = 'hello';
console.log(`why ${x} world`); // outputs: why hello worldCode language: JavaScript (javascript)

C# has similar functionality, but the location of the $ sign is different. Just enough to throw me.

var x = "hello";
Console.WriteLine($"why {x} world"); // outputs: why hello worldCode language: C# (cs)

I guess once I get good enough at C#, and combine that with my JS / TS knowledge, I will be able to print $$$’s yo.

C# Loves PascalCase Everything

It is still catching me out.

Fortunately the IDE is doing a solid duty of reminding me / flogging me whenever I get this wrong.

One thing that caught me out though was that even converting a boolean to a string will yield a PascalCased variable.

Console.WriteLine($"{true}"); // prints: True
Console.WriteLine($"{false}"); // prints: FalseCode language: C# (cs)

Printing Variables Is Painful

20+ years of working with scripting languages like PHP and JavaScript has instilled in me a strong reliance on print based debugging.

Yes, I am aware that draws derision from real programmers. But equally after 20 years I have gotten fast at doing it, and usually it is good enough.

However, sometimes problems call for the debugger.

Heck, part of why I want to migrate to C# is because of the debugger. There is simply no denying that being able to easily use the debugger is vastly better than the hacky world of print based debugging.

But for simple problems it would be super nice if things like this worked the way I expected:

var x = new List<string> { "hello", "world" };

// outputs: 2 - great!
Console.WriteLine(x.Count);

// outputs: System.Collections.Generic.List`1[System.String] - FUUUUUU!
Console.WriteLine(x);Code language: C# (cs)

I guess this is a blessing in disguise, as even this sort of thing pushes me down debugging the code with breakpoints which feels like the way I need to go:

Still So Much To Learn

These three exercises have already taught me an absolute ton of stuff.

I’ve explored avenues I have only read about up until now, and in turn got some great hands on experience with actual C# code.

What I’m keen to do next, aside from continuing to learn, learn, learn, is push beyond a Library style project and move into a Console or Command Line application.

If you’ve made it this far then thank you very much for taking the time to read my ramblings. I would genuinely love any critique, suggestions, and / or corrections.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.