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

[Easy] Reduce in C#

The following are the C# solutions to the reduce 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 Reduce

// EasyReduceUnitTest.cs using MapFilterReduceClassLibrary; using NUnit.Framework; namespace MapFilterReduceUnitTests; public static class EasyReduceTests { [Test] [TestCase(new int[] { }, ExpectedResult = 0)] [TestCase(new[] { 0 }, ExpectedResult = 0)] [TestCase(new[] { 1 }, ExpectedResult = 1)] [TestCase(new[] { 1, 2, 3 }, ExpectedResult = 6)] public int TestReduceNumbers(int[] input) { return EasyReduce.ReduceNumbers(input); } [Test] [TestCase(ExpectedResult = "")] [TestCase(new object[] { "" }, ExpectedResult = "")] [TestCase(new object[] { " " }, ExpectedResult = "")] [TestCase(new object[] { "aaa" }, ExpectedResult = "aaa")] [TestCase(new object[] { "aaa", "bbb", "ccc" }, ExpectedResult = "aaabbbccc")] public string TestReduceString(params string[] input) { return EasyReduce.ReduceString(input); } [Test] [TestCase(new bool[] { }, ExpectedResult = false)] [TestCase(new[] { false }, ExpectedResult = false)] [TestCase(new[] { false, false }, ExpectedResult = false)] [TestCase(new[] { true }, ExpectedResult = true)] [TestCase(new[] { true, true }, ExpectedResult = true)] [TestCase(new[] { true, false }, ExpectedResult = false)] [TestCase(new[] { false, true, false, true, false, true }, ExpectedResult = false)] public bool TestReduceBool(bool[] input) { return EasyReduce.ReduceBool(input); } }
Code language: C# (cs)

And the associated implementation:

// EasyReduce.cs namespace MapFilterReduceClassLibrary; public static class EasyReduce { public static int ReduceNumbers(int[] input) { return input.Aggregate(0, (acc, current) => acc + current); } public static string ReduceString(string[] input) { return input.Aggregate("", (acc, current) => string.Concat(acc, current).Trim()); } public static bool ReduceBool(bool[] input) { return input.Length != 0 && input.Aggregate(true, (acc, current) => acc && current); } }
Code language: C# (cs)

Lots to cover here so let’s dive right in.

C# uses Aggregate not reduce

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

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

Regardless of the function name, the methodology appears essentially identical.

We have an accumulator and a function which takes each element and updates that accumulator.

The ordering is different here to TypeScript. In TypeScript / JS we would provide the function first and then the accumulator. Here the accumulator comes first. Poe-tay-toe Pah-tah-toe.

Use of static

My take on this is that there is no requirement for a specific instance of an EasyReduce class to ever be instantiated. This class has a very “helper” or “utility” vibe about it.

Originally I wrote out the class definition as below, and got the green squiggle in Rider:

Rider could tell that my EasyReduce class was never being instantiated so was flagging this up. What it didn’t suggest was the use of the static keyword in the definition:

public static class EasyReduceTests
Code language: C# (cs)

The concept of static isn’t new to me. I learned about that back in my PHP days.

However a public static class is not something I have used before to the best of my knowledge. I may have come across it in Java… honestly not sure.

Short Form Functions Are Not Preferred?

Whilst working through these exercises a pretty useful way that I have been improving my overall code style is by relying on JetBrains Rider to suggest improvements to my code.

One example of this would be in that I learned it’s possible to convert function with a full body into a short hand expression syntax:

public static int ReduceNumbers(int[] input) { return input.Aggregate(0, (acc, current) => acc + current); } // could be shortened to: public static int ReduceNumbers(int[] input) => input.Aggregate(0, (acc, current) => acc + current);
Code language: C# (cs)

I actually prefer the second approach. It feels very much like the arrow function expressions in JavaScript that are, in my experience, very commonly used.

After doing some digging into the MS docs, it turns out that the second approach would be called an expression bodied member in C#.

However, Rider is automatically expanding my short hand functions back to the longer equivalents when using their Reformat Code functionality. As I’m using that as a kind of makeshift dot net replacement for Prettier, I guess either I need to fiddle around with the settings, or just go with what it suggests.

Concatenating Strings in C#

Now, for some reason I instinctively went for using the object oriented approach to concatenating strings in C#. This may be because in the IDE I was prompted with intellisense to explore methods available on instances of string and found that Concat was available.

I figured that Rider would suggest an alternative approach if something better was available.

But it didn’t, so I assumed this is what C# people must do.

One of the benefits of taking the approach I have here – coding, then writing up and sharing – is that I inevitably end up investigating my implementations, questioning my choices and subsequently learning a ton and improving along the way. There’s a good reason this site has “code review” in the title (even if I don’t make videos lately).

The upshot of all of this is I learned C# has a bunch of ways to concatenate strings, and the one I chose was perhaps not the best.

// Use the + and += operators for one-time concatenations. string str = "Hello " + userName + ". Today is " + dateString + "."; System.Console.WriteLine(str); str += " How are you today?"; System.Console.WriteLine(str);
Code language: C# (cs)

It turns out then that the preferred approach seems to be the same as in JavaScript.

Personally I think string addition like this is far less intuitive to the untrained eye than an explicit call to e.g. .Concat. But I will also concede that it takes a really short time to grasp what is happening with the + format, and it is far more concise than my approach.

It does make me wonder what is most common out there in real world / paid projects, so if you know please leave a comment.

With all that in mind, a possible refactor might be:

public static string ReduceString(string[] input) { return input.Aggregate("", (acc, current) => acc + current.Trim()); }
Code language: C# (cs)

I guess it’s not pretty however you write it.

Discovering A Better reduceBool

Perhaps the most interesting aspect of this exercise, from my point of view, is in the implementation of ReduceBool in this code versus the equivalent in TypeScript.

To begin with, a recap of the TypeScript implementation I used:

export const reduceBool = (input: boolean[]): boolean => { return input.length === 0 ? false : input.reduce((acc, bool) => acc && bool, true); };
Code language: TypeScript (typescript)

I covered how even though that is an immediate return, sometimes going with the short form leads to less readable code. Personal opinion, of course.

That same code can be implemented in C# with a few small changes:

public static bool ReduceBool(bool[] input) { return input.Length == 0 ? false : input.Aggregate(true, (acc, current) => acc && current); }
Code language: C# (cs)

Looks fine, and runs fine. The tests pass.

But Rider gives a suggested refactoring:

Which then resolves to:
public static bool ReduceBool(bool[] input) { return input.Length != 0 && input.Aggregate(true, (acc, current) => acc && current); }
Code language: C# (cs)

And I like that. I prefer it to the ternary.

Note that C# uses the != vs JavaScript / TypeScript which would use !== for safely checking the value and type. In C#, as far as I know, there is no way for 0 to be anything other than an int.

This same change can then be applied to the TypeScript code:

export const reduceBool = (input: boolean[]): boolean => input.length !== 0 && input.reduce((acc, bool) => acc && bool, true);
Code language: TypeScript (typescript)

Which begs the question then, given that Rider and WebStorm both use the “same stuff” internally, why is that not a suggested refactoring in WebStorm?

Extension Method

This page is called [Easy] Reduce in C#, and I was therefore in two minds whether to cover this or not.

Why I’m adding this in is because:

  1. It’s something I am aware of;
  2. The problems are easy, not necessarily the code used to solve them 🙂

OK, so extension methods are, I think, fairly niche things. I may be wrong on this, because as above, I do not have experience in real world C# projects to know how frequently they are used.

Extension methods enable you to write code that appears to be a method on an existing type.

And in this case I could make it appear as though an array of integers has a method called ReduceNumbers which I can call directly. As I’m already using static methods, this change is actually really simple to implement:

public static int ReduceNumbers(this int[] input) { return input.Aggregate(0, (acc, current) => acc + current); }
Code language: C# (cs)

The change here is to add the this keyword before the int[] input argument.

I can then change how I call this code from:

public int TestReduceNumbers(int[] input) { return EasyReduce.ReduceNumbers(input); }
Code language: C# (cs)

to:

public int TestReduceNumbers(int[] input) { return input.ReduceNumbers(); }
Code language: C# (cs)

Kinda neat.

But potentially kinda confusing.

Again, I don’t know. I only know you can do this, but how prevalent it is, I have no idea.

Such a Sweet Test Suite

The resulting unit test suite for the C# code, I think, is considerably nicer than the Jest test code I wrote in the TypeScript implementation.

There was so much to cover regarding the NUnit test suite that I split it out into a separate page.

Have a read here about how I’m using NUnit for unit testing in C# if you are curious.

Leave a Reply

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

%d bloggers like this: