Coding challenge: Creating an LCD display

A friend of mine and sharpfellow John Rayner posted a coding Kata to a c# group I subscribe to on Friday.   We have a lot of these posted in the group and its a great way to see how different people attack the problem.  Fridays Kata was described as follows.

Today’s problem is to create an LCD for displaying numbers.  You are given two integers s and n, where n is the number to be displayed and s is the size,  You need to use | and _ characters to construct a display.  Each digit occupies exactly s+2 columns and 2s+3 rows. There has to be exactly one column of blanks between digits.

Examples:
  s=2, n=12345 & s=3, n=67890

After thinking about this problem for a little while, I came to conclusion that each digit in the display has some fairly simple structure and that structure is common across all digits.  I set out some core rules that would help me break down this problem.  I have listed these out.

  • Each Digit has two box sections.  And upper and lower box.
  • Each Digit has a top, middle & bottom which must be formed of dashes.
  • Excluding the top, middle and bottom dash sections, you must assume that the gaps must be bars ( “|”)
  • In the “bars” sections a digit can be formed of 3 types of bar. Left, Right and Both.

With these rules in place I set about tacking the problem.  I went through a few iterations to reach the version below and I am sure I could go through a few more to make it cleaner and more efficient.  This version does pretty well though by generating the number 1234567890 with 5 chars per segment in 4 ms.

        private static StringBuilder Draw(int b, int n)
        {
            var format = string.Join("|", n.ToString().ToCharArray());
            var buffer = new StringBuilder();
            var line = new StringBuilder();
            var lineCount = 2 + (b * 2);
            var dash = string.Format(" {0} ", string.Concat(Enumerable.Repeat("-", b)));
            var blank = string.Format(" {0} ", string.Concat(Enumerable.Repeat(" ", b)));
            var barLeft = string.Format("|{0} ", string.Concat(Enumerable.Repeat(" ", b)));
            var barRight = string.Format(" {0}|", string.Concat(Enumerable.Repeat(" ", b)));
            var barAll = string.Format("|{0}|", string.Concat(Enumerable.Repeat(" ", b)));
            var dashLines = new Dictionary<int, string>
            {
                {0, "23567890"},
                {lineCount / 2, "2345689"},
                {lineCount, "2356890"},
            };

            for (int i = 0; i <= lineCount; i++)
            {
                line.Clear();
                foreach (var character in format)
                {
                    if (character.Equals("|"[0]))
                    {
                        line.Append(" ");
                        continue;
                    }

                    if (dashLines.ContainsKey(i))
                    {
                        line.Append(dashLines[i].Contains(character) ? dash : blank);
                        continue;
                    }

                    if(i < lineCount / 2)
                    {
                        if ("4890".Contains(character))
                        {
                            line.Append(barAll);
                            continue;
                        }
                        if ("1237".Contains(character))
                        {
                            line.Append(barRight);
                            continue;
                        }
                        line.Append(barLeft);
                    }
                    else
                    {
                        if ("086".Contains(character))
                        {
                            line.Append(barAll);
                            continue;
                        }
                        if ("1345679".Contains(character))
                        {
                            line.Append(barRight);
                            continue;
                        }
                        line.Append(barLeft);
                    }
                }
                buffer.AppendLine(line.ToString());
            }

            return buffer;
        }
Advertisement

Using And, Then & When in Reactive Extensions

When you look over the list of observable method’s on MSDN I can see that I use maybe 60% of them quite frequently.  I find that I’ll use Do’s or SelectMany etc almost every day and more involved operators like Publish and Merge at least once a week.

Today I wanted to cover 3 operators that I find aren’t used frequently but can be used together to provide powerful synchronization techniques.  The operators are “And”, “Then” and “When”.

The “And” operator

If we look on MSDN for the “And” operator we can see that documentation tells us this “Matches when both observable sequences have an available value.”. Well ok, that sounds pretty simple.  I reckon I can put some code together to show how this works.   I just need two streams which both produce a value. Let’s have a try.

Hmmm, well firstly that reads well.  I can see quite easily from reading this code what its intent is, but what I expected as a result of the “And” operator was another IObservable which I could then subscribe to.  So it seems on its own the “And” operator doesn’t work as I expected.  When I look at the return type for “And” I can see it returns something called a “Pattern” and the only operator I can apply to this seems to be a “Then”.

The “Then” Operator

So it seems I have been led to the “Then” operator by some clever design decisions from the Rx designers.  They obviously want me the use the then operator as its really the only thing available to me.  Therefore I better go back to msdn and see what it tells me “Observable.Then<TSource, TResult> Method – Matches when the observable sequence has an available value and projects the value.”.  Well this operator seems very familiar, its almost exactly the same as the “Select” operator.  The select operator takes the value that is pushed to it and transforms the pipeline by returning a different return type.  I can do that!!! lets implement “Then” and we are done right?

Uh oh!! Dead end.  Well this isn’t good.  The one thing that gets me through with LINQ and RX is that operators are discoverable and now I’ve got nowhere to go.  Digging a little deeper I can see that actually what I thought I would get back from “Then” an IObservable<T> what I’m  actually getting is a Plan<T>.  What the heck is a Plan? Why does it have no methods on it so I can do something with it? I’m going to have to do some research.

The “When” Operator

Back to msdn looking for a Plan<T> lets see what it tells us. “Represents an execution plan for join patterns.”  Well that makes sense as that’s essentially what I have been trying to do.  But unfortunately its not giving me the answers for how I actually get values from my stream.  Therefore it’s time to stretch my google-fu and see what I can find.  Turn’s out all the information I needed was actually at the same msdn page I had been looking at for other observable operators.   When I search the page for “Plan” I found that only 1 operator seems to be able to use this construct and thats the “When” operator.

This time I’m told that the “When” operator takes in a Plan<T> and returns a IObservable<T> “Joins together the results from several patterns.”. Yes! This looks like the final piece of the puzzle.  Lets finish the code.

As you can see from the code above, I now use the When operator on my plan so that we can actually gain access to the projected values. Then I write the values to the console window.  The slower stream is in essence the regulating stream in this scenario and therefore the stream only pumps once every 10 seconds.

Summary

As you can see the three operators “And”, “Then” and “When” working together provide some powerful synchronization primitives that we can use in our code to solve a number of different scenarios.  Discovery of these operators is a little trickier than the basic operators and I feel that a few extensions could have been added to make life easier (Why no “When” extension on a Plan<T> for example?).