I'm sure you all know "FizzBuzz" by now. It’s a simple test that can be used by interviewers to determine a prospective developer’s ability to code a simple program to “solve” the FizzBuzz problem. It’s based upon a children’s game, and in the context of its use as a test of a programmer’s ability, it’s been called the software developer’s “Stairway to Heaven”.
For a brief refresher, here’s the “FizzBuzz” test as it relates to software development:
Write a program that iterates through the numbers from 1 to 100. For all numbers that are multiples of 3, print the word "Fizz”. For all numbers that are multiples of 5, print the word “Buzz”, and for all numbers that are multiple of both 3 and 5, print the word “FizzBuzz”. For all other numbers, print the number itself.
“Traditional” solutions to the FizzBuzz test will be something similar to the following C# code:
for (int i = 1; i <= 100; i++)
if (i % 3 == 0 && i % 5 == 0)
else if (i % 3 == 0)
else if (i % 5 == 0)
There are, of course, various “clever” optimisations that can be applied (such as determining that a number is divisible by both 3 and 5 by checking if it’s divisible by 15 etc.) however, the basic approach, as outlined above, is sufficient to “solve” the FizzBuzz problem.
The funny thing about FizzBuzz is that, upon reading a blog post such as this one, many people absolutely have to rush off and code up their own solution. There’s nothing wrong with that, but most “solutions” are either wrong, or incredibly trite. However, every now and then, a truly novel and inspiring solution to the problem comes flying in, right out of leftfield, and really makes you stop, sit up and take notice!
Here is one such solution. I give you “Extensible FizzBuzz”:
// Extensible FizzBuzz!
Dictionary<Func<int, bool>, Func<int, string>> rules = new Dictionary<Func<int, bool>, Func<int, string>>();
rules.Add(x => x % 3 == 0, x => "fizz");
rules.Add(x => x % 5 == 0, x => "buzz");
rules.Add(x => x % 5 != 0 && x % 3 != 0, x => x.ToString());
rules.Add(x => true, x => "\n");
var output = from n in Enumerable.Range(1, 100)
from f in rules
output.ToList().ForEach(x => Console.Write(x));
This “extensible FizzBuzz” solution uses a Dictionary<T,T> type to contain two Func<T,T> delegates for each dictionary entry. The TKey of each dictionary entry is a unique lambda expression that represents a “rule” or predicate that can be applied to each int within the “loop” that is eventually created by iterating through all of the numbers created by the Enumerable.Range(1,100) expression. The lambda expression will return a boolean result depending upon whether the input int matches the rule or not. The TValue of each dictionary entry is effectively the output that we want to display whenever the int that has been input to our “rule” successfully passes that rule. Here, the lambda expressions are really as simple as outputting “fizz”, “buzz”, or the int.ToString() itself. The keen eyed amongst you will have noticed there is even a “rule” (the one defined last) that simply always returns true for whatever input we give it and outputs a “\n” newline control character.
Our output var represents a sequence of elements that “joins” a range of ints from 1 to 100 with the rule-set that we’ve defined. We then use the result of the “rule” predicate as a where filter and finally select the output from the TValue expression as our actual element value. Finally, after turning our sequence into a List<> (and thereby forcing the evaluation of all of the sequence’s elements), we iterate over that sequence of elements (using the super-handy .ForEach extension method) and simply output our element’s value to the Console.
Although this novel “rule-based” method of evaluating a given entity is used in the context of the FizzBuzz problem, and in that respect could well be argued to be “over-engineered”, the same method can have practical use in real-world problems. For example, I was recently developing a system to “grade” or “rank” datasets based upon a range of user-defined “rules”. This involved extensive and complex SQL queries to perform arbitrary groupings of data. These groups were then “ranked” based upon a range of user-defined rules, such that groups are deemed high-risk or low-risk based upon such example rules as: Less than 1 year span between the date stamps for each record within the group or, the same values for a given field occurring more than a certain number of times (where this threshold was itself defined by the user).
Through the use of this kind of “extensible” method for building up queries and clauses, and taking advantage of the wonderful functionality of Expression Trees built right into the .NET framework’s System.Linq.Expressions namespace, made implementing extendable queries and rule-sets a breeze.