Solving a programming puzzle

My fellow devlicio.us blogger Tim Barcz posted an interesting problem to solve. I think it’s fun, so I decided to give it a try. Now, I don’t know enough context to solve it for just about any case (and I don’t think that’s even possible), so I made a few assumptions.

  • The strings we’re gonna be handling are not too long. Otherwise I’d probably use a StringReader/Writer.
  • I’m looking at minimal solution. YAGNI – I could do some optimizations probably, but I want to keep it simple, because the sample problem (just a handful of characters) is simple.
  • Solution is not context specific. In other words, if you want to swap characters differently based on context, it won’t do it. Again, I’m keeping it simple.
  • I’m using a factory here, to create the cleaner and feed it with mapping but based on the needs and tools available, I’d probably use an IoC container to get the instance, and if needed get the mapping from an external file, where it’s more readable, and can be easily changed.

Now, here’s the code:

public string CleanInput( string input )
{
    StringCleaner cleaner = CleanerFactory.BuildCleaner( input );
    return cleaner.Clean( input );
}

The CleanerFactory is not important. All it does is construct an instance of a StringCleaner and feed it with the mapping. However, I’ll post it for completeness:

public static class CleanerFactory
{
    public static StringCleaner BuildCleaner( string input )
    {
 
        var cleaner = new StringCleaner();
        BuildMapping( cleaner );
        return cleaner;
    }
 
    private static void BuildMapping( StringCleaner cleaner )
    {
        /*
         *  Character         Correct ASCII Value     Bad Values
            Hyphen             45                         8208, 8211, 8212, 8722, 173, 8209, 8259
            Single Quote     39                         96, 8216, 8217, 8242, 769, 768
            Double Quote     34                         8220, 8221, 8243, 12291
            Space             32                         160, 8195, 8194
         */
        cleaner.AddMapping( (char) 45, (char) 8208, (char) 8211, (char) 8212, (char) 8722, (char) 173, (char) 8209, (char) 8259 );
        cleaner.AddMapping( (char) 39, (char) 96, (char) 8216, (char) 8217, (char) 8242, (char) 769, (char) 768 );
        cleaner.AddMapping( (char) 34, (char) 8220, (char) 8221, (char) 8243, (char) 12291 );
        cleaner.AddMapping( (char) 32, (char) 160, (char) 8195, (char) 8194 );
    }
}

The cleaner itself looks like this:

public class StringCleaner
{
        private IDictionary<char,char> mappings = new Dictionary<char, char>();
 
        public string Clean( string input )
        {
            if( string.IsNullOrEmpty( input ) )
            {
                return string.Empty;
            }
 
            var buffer = new StringBuilder( input );
            for( int i = 0; i < buffer.Length; i++ )
            {
                var character = buffer[i];
                char validCharacter;
                if( this.mappings.TryGetValue( character, out validCharacter ) )
                {
                    buffer[ i ] = validCharacter;
                }
            }
 
            return buffer.ToString();
        }
 
        public void AddMapping( char validValue, params char[] invalidValues)
        {
            if( invalidValues == null )
            {
                throw new ArgumentNullException( "invalidValues" );
            }
 
            foreach( var invalidValue in invalidValues )
            {
                this.mappings[ invalidValue ] = validValue;
            }
        }
}

Since strings are immutable, I’m using a StringBuilder to iterate over all the characters in the string, and replace these I want. I’m using Dictionary for the mapping, because it has a readable API, and O(1) search time.