Ejemplo n.º 1
0
        /// <summary>
        /// The solution to question 2 uses the solution to question 1: The idea here is that we can
        /// pass more than one reader objects to this task and just one output object. The readers can then
        /// run in parallel, meaning that they can read streams of characters, process them, form words and
        /// create outputs, all in parallel. This happens by utiliasing the solution to question 1 and
        /// calling the necessary methods in an asynchronous way.
        ///
        /// Additionally a mechanism has been added to the solution of question 1 for delaying the creation
        /// of outputs for specified periods of times, i.e. creating outputs in specific intervals, as
        /// per the requirements for the solution to question 2.
        /// </summary>
        /// <param name="readers">An array of objects of a class that implements the ICharacterReader interface,
        /// which provides a method for reading the next character of a character stream.</param>
        /// <param name="output">An object of a class that implements the IOutputResult interface, which allows to
        /// format an output according to specific requirements.</param>
        /// <returns>A task that calls the method RunQuestionOne one or more times asynchronously.</returns>
        public Task RunQuestionTwo(ICharacterReader[] readers, IOutputResult output)
        {
            // Reset the following variable to false in order to de-activate the mechanism that produces
            // an output in specified intervals, e.g. 10 seconds.
            wasCallInitiatedInQuestionTwo = true;

            return(Task.Run(async() =>
            {
                // Create a timer that will wait for this task to complete.
                Task mainTimer = DelayTimerAsync(questionTwoTimeout);

                // Create an array of tasks that has the same length with the array of readers
                // that is passed as a parameter to this method.
                Task[] tasks = new Task[readers.Length];

                // Create a task per reader in order to be able to call the solution for
                // question one asynchronously per reader.
                for (int index = 0; index < readers.Length; index++)
                {
                    tasks[index] = RunQuestionOneAsync(readers[index], output);
                }

                // Create an output every 10 seconds (or any other given interval).
                CreateIntervalOutputs(wordDictionary, output);

                // Await the Question One tasks to complete.
                for (int index = 0; index < readers.Length; index++)
                {
                    await tasks[index];
                }

                // Await the main timer to complete.
                await mainTimer;
            }));
        }
Ejemplo n.º 2
0
        public async Task RunQuestionOne(ICharacterReader reader, IOutputResult output)
        {
            var dt = new DeveloperTest();
            await dt.ProcessReaderAsync(reader);

            await dt.Print(output);
        }
Ejemplo n.º 3
0
        public void RunQuestionOne(ICharacterReader reader, IOutputResult output)
        {
            // var text = "It was the best of times, it was the worst of times".ToLower();
            var text = reader.ToString();

            var match = Regex.Match(text, "\\w+");
            Dictionary <string, int> freq = new Dictionary <string, int>();

            while (match.Success)
            {
                string word = match.Value;
                if (freq.ContainsKey(word))
                {
                    freq[word]++;
                }
                else
                {
                    freq.Add(word, 1);
                }

                match = match.NextMatch();
            }

            Console.WriteLine("Rank  Word  Frequency Details");

            int rank = 1;

            foreach (var elem in freq.OrderByDescending(a => a.Value).Take(10))
            {
                Console.WriteLine("{0,2}    {1,-4}    {2,5}", rank++, elem.Key, elem.Value);
                output.AddResult(elem.ToString());
            }
        }
Ejemplo n.º 4
0
        public void RunQuestionTwo(ICharacterReader[] readers, IOutputResult output)
        {
            var wordCount = new Dictionary <string, int>();

            //   string line = "a list of word frequencies ordered by word count and then alphabetically";
            string line = readers.ToString();

            while ((line != null))
            {
                var words = line.Split(separators, StringSplitOptions.RemoveEmptyEntries);

                foreach (var word in words)
                {
                    if (wordCount.ContainsKey(word))
                    {
                        wordCount[word] = wordCount[word] + 1;
                    }
                    else
                    {
                        wordCount.Add(word, 1);
                        output.AddResult(wordCount.ToString());
                    }
                }
            }
        }
        public void RunQuestionOne(ICharacterReader reader, IOutputResult output)
        {
            var wordCount = new Dictionary <string, int>();

            CalculateWords(reader, wordCount);

            SendWordsOrdered(output, wordCount);
        }
Ejemplo n.º 6
0
        /// <summary>
        /// This is the answer to question 1.
        ///
        /// ASSUMPTIONS:
        /// ============
        ///
        /// 1. The text read by the reader only contains words of the English language, so in this
        /// case there is no provision for words of other languages written in different alphabets,
        /// or for numerals, etc. Please also see the assumptions listed in class CharExtensions.cs
        /// for more details.
        ///
        /// 2. The second assumption, and in accordance to assumption 1 above, is that when it comes
        /// to deciding if a character is part of a word or not, we assume that we are always dealing
        /// with characters of the English alphabet, both lower case and capitals, and with any special
        /// symbols that can be accepted as part of an English word, i.e. hyphen. In this case, there
        /// is no provision for characters of different alphabets, numerals, etc.
        ///
        /// 3. Another assumption is that it is preferable to deal with the input character stream
        /// dynamically, on the fly, instead of reading the whole stream and storing it in a local
        /// variable prior to processing it. This, for example, could help in situations of extremely
        /// long streams (such as when reading a character stream from a large file) that would require
        /// the use of extensive amounts of in-memory storage prior to processing.
        ///
        /// LOGIC:
        /// ======
        ///
        /// An instance of this class (DeveloperTestImplementationAsync) is created elsewhere in the code
        /// (for example in a unit test such as StandardTestAsync.TestQuestionOneAsync()). This method
        /// can also be called asynchronously as part of the solution of question 2. When it is called
        /// two objects are passed into it as dependencies using the method dependency injection pattern,
        /// the first one is a reader object, for example an instance of the SimpleCharacterReader or the
        /// SlowCharacterReader class, they both implement the ICharacterReader interface, and the second
        /// one is an output object, which is an instance of the Question1TestOutput class that implements
        /// the IOutputResult interface.
        ///
        /// The purpose of this method is to use these two objects in order to read a character stream,
        /// dynamically process the character stream in order to separate it into English words, then order
        /// those words by frequency and then alphabetically, and finally create an appropriate output in
        /// the required output format that will be tested by the relevant unit test mentioned above for its
        /// correctness (i.e. the words that it contains, the frequency of appearence of each word in the character
        /// stream, and whether these words have been ordered by frequency and alphabetically as required).
        ///
        /// Reading the character stream: In order to successfully read the character stream this method uses
        /// a simple do-while loop that in each iteration reads a new character, then process the character
        /// in order to make a decision if it should be accepted as part of an English word or not, and
        /// consequently adds the character in the next word or rejects it.
        ///
        /// EndOfStreamException: The algorithm then deals with the EndOfStreamException, thrown by the reader
        /// when the end of the character stream has been reached, by making sure in the finally sub-block of the
        /// try-catch-finally block that the very last word read is captured correctly and stored in the dictionary in the
        /// same way as with all the previous words.
        ///
        /// Finally, this method makes sure that the dictionary of words and word frequences is sorted
        /// according to the requirements, i.e. first by word frequency and then alphabetically, and then creates
        /// the desired output.
        ///
        /// Parts of this algorithm, such as processing a character, forming a word, sorting the dictionary, and
        /// creating the required output, have been implemented as separate methods in order to modularise and
        /// declutter the main algorithm. The purpose here is to improve its readibity and at the same time to
        /// demonstrate how to create methods for reoccurring tasks and functions, for improved reusability,
        /// and for making it more clear where and how the state of certain objects is changed (instead of changing the
        /// state of these objects all over the place). I have chosen to pass parameters back and forth to these
        /// methods (even if in some cases they are dealing with the member variables that are already available
        /// to them) in order to show that we could easily move them out of this class altogether, maybe into some
        /// sort of helper class where they could be re-used by other classes of a hypothetical bigger program.
        /// For simplicity I have left these methods in this class.
        /// </summary>
        /// <param name="reader">An object of a class that implements the ICharacterReader interface, which provides
        /// a method for reading the next character of a character stream.</param>
        /// <param name="output">An object of a class that implements the IOutputResult interface, which allows to
        /// format an output according to specified requirements.</param>
        public Task RunQuestionOne(ICharacterReader reader, IOutputResult output)
        {
            // This whole task can run asynchronously. This is useful when we need to run several readers
            // in parallel and we do not want to wait for the synchronous completion of each reader before
            // starting the next one.
            return(Task.Run(async() =>
            {
                // A string variable that helps us form the next word from the input character stream.
                string nextWord = string.Empty;

                using (reader)
                {
                    try
                    {
                        // This is the main loop that reads a stream of characters, one by one,
                        // processes the characters according to the assumptions made above, forms
                        // English words and stores these words to a dictionary collection,
                        // keeping also track of how often each of these words appear in the input
                        // stream (word frequency).
                        do
                        {
                            ProcessNextChar(reader.GetNextChar(), ref nextWord, wordDictionary);
                        } while (true);
                    }
                    catch (EndOfStreamException e)
                    {
                        // Normally an error message, like the one below, would be logged in a log file or
                        // log database, by being passed to an appropriate method of a dedicated logger object.
                        // As this is out of the scope of this exercise, for now I am just imitating
                        // logging the error message by just displaying the error message to the console.
                        Console.WriteLine($"Error reading stream: {e.GetType().Name}.");
                    }
                    finally
                    {
                        // Here we make sure that we do not miss out the very last word of the input stream
                        // because of the EndOfStreamException thrown by the GetNextChar() method of the reader.
                        if (nextWord != string.Empty)
                        {
                            AddStringToDictionary(wordDictionary, nextWord);
                        }
                    }

                    // Sort the dictionary by word frequency and then alphabetically and then
                    // create the required output. The delay period is part of the mechanism that
                    // allows to create an output on specified intervals and it is used only for
                    // question two. The default value is zero, meaning an immediate creation of the output.
                    if (!wasCallInitiatedInQuestionTwo)
                    {
                        CreateOutputAsync(SortDictionary(wordDictionary), output, defaultDelayPeriod);
                    }

                    // Allow some time for the completion of this task before exiting.
                    await DelayTimerAsync(questionOneTimeout);
                }
            }));
        }
 private void SendWordsOrdered(IOutputResult output, Dictionary <string, int> wordCount)
 {
     lock (_padlock)
     {
         foreach (var item in wordCount.OrderBy(o => o.Key).OrderByDescending(o => o.Value))
         {
             output.AddResult(item.Key + " - " + item.Value);
         }
     }
 }
Ejemplo n.º 8
0
        public static string GetBriefSummary(this IOutputResult result)
        {
            var operationsCount = result.GetOperationResults().Count(x => !(x is FillingOperationResult));
            var exceptions      = result.GetExceptions().ToList();

            return(exceptions.Count == 0
          ? $"{operationsCount} {(operationsCount > 1 ? "operations" : "operation")} run"
          : exceptions.Count == 1
              ? exceptions.Single().Name
              : $"exceptions.Count {(exceptions.Count > 1 ? "exceptions" : "exception")} thrown");
        }
Ejemplo n.º 9
0
        public Task Print(IOutputResult output)
        {
            foreach (var keyValuePair in _dictionary.Dictionary
                     .OrderByDescending(x => x.Value)
                     .ThenBy(x => x.Key))
            {
                {
                    output.AddResult($"{keyValuePair.Key} - {keyValuePair.Value}");
                }
            }

            return(Task.FromResult(0));
        }
Ejemplo n.º 10
0
        public static string GetDetailedSummary(this IOutputResult result, ISymbolProvider symbolProvider = null, bool includeExceptions = true)
        {
            symbolProvider = symbolProvider ?? new DefaultSymbolProvider();
            var builder = new StringBuilder();

            AppendOperations(result.GetOperationResults(), builder, symbolProvider);
            AppendOutput(result.OutputEntries, builder, symbolProvider);
            if (includeExceptions)
            {
                AppendExceptions(result.GetExceptions(), builder);
            }

            return(builder.ToString());
        }
Ejemplo n.º 11
0
        private void Finished(IOutputResult result)
        {
            var task = _taskDictionary[result.Identity];

            // Don't process if task represents an assembly suite.
            if (!task.IsMeaningfulTask)
            {
                return;
            }

            _server.TaskOutput(task, result.GetDetailedSummary(includeExceptions: false), TaskOutputType.STDOUT);
            _server.TaskException(task, result.GetExceptions().ToList().Select(x => x.ToTaskException()).ToArray());
            _server.TaskFinished(task, result.GetBriefSummary(), result.GetTaskResult());
        }
Ejemplo n.º 12
0
        /// <summary>
        /// Mechanism for creating an output in given intervals (e.g. 10 seconds) as per requirements.
        /// </summary>
        /// <param name="dictionary">The collection that contains the data for the output.</param>
        /// <param name="output">The output where the data will be added.</param>
        /// <param name="delayInterval">The interval that specifies how often to create an output.</param>
        private void CreateIntervalOutputs(IDictionary <string, int> dictionary, IOutputResult output, int delayInterval = defaultDelayInterval)
        {
            if (wasCallInitiatedInQuestionTwo)
            {
                int delayPeriod = delayInterval;

                for (int i = 0; i < questionTwoTimeout / delayInterval; i++)
                {
                    CreateOutputAsync(SortDictionary(wordDictionary), output, delayPeriod);

                    delayPeriod += delayInterval;
                }
            }
        }
Ejemplo n.º 13
0
        public async Task RunQuestionTwo(ICharacterReader[] readers, IOutputResult output)
        {
            var dt               = new DeveloperTest();
            var tasks            = readers.Select(dt.ProcessReaderAsync).ToList();
            var printCounterTask = new Task(() => dt.DelayedPrint(output));

            printCounterTask.Start();

            Task.WhenAll(tasks).ContinueWith(x => dt.Print(output).ContinueWith(_ =>
            {
                dt.Finished = true;
            })).Wait();

            printCounterTask.Wait();
        }
        public void RunQuestionTwo(ICharacterReader[] readers, IOutputResult output)
        {
            var wordCount = new Dictionary <string, int>();

            var timer = new Timer(
                e => SendWordsOrdered(output, wordCount),
                null,
                TimeSpan.Zero,
                TimeSpan.FromSeconds(10));

            Parallel.ForEach(readers, reader =>
            {
                CalculateWords(reader, wordCount);
            });

            timer.Dispose();

            SendWordsOrdered(output, wordCount);
        }
Ejemplo n.º 15
0
        public async Task DelayedPrint(IOutputResult output)
        {
            while (true)
            {
                if (!Finished)
                {
                    //every 10 seconds should print.
                    //at least one print even if the execution is less than 10 seconds
                    //as this starts in paralel with the processing
                    Task.Delay(10 * 1000).Wait();
                    await Print(output);
                }
                else
                {
#if DEBUG
                    Console.WriteLine("Finished with printing");
#endif
                    break;
                }
            }
        }
Ejemplo n.º 16
0
 /// <summary>
 /// Creates an output in the desirable format.
 /// </summary>
 /// <param name="pairs">An ordered and enumerable collection of key/value pairs, in this case string/int pairs.</param>
 /// <param name="output">An object of a class that implements the IOutputResult interface.</param>
 private void CreateOutput(IOrderedEnumerable <KeyValuePair <string, int> > pairs, IOutputResult output)
 {
     foreach (var pair in pairs)
     {
         output.AddResult($"{pair.Key} - {pair.Value.ToString()}");
     }
 }
Ejemplo n.º 17
0
        /// <summary>
        /// This is the answer to question 1.
        ///
        /// ASSUMPTIONS:
        /// ============
        ///
        /// 1. The text read by the reader only contains words of the English language, so in this
        /// case there is no provision for words of other languages written in different alphabets,
        /// or for numericals, etc. Please also see the assumptions listed in class CharExtensions.cs
        /// for more details.
        ///
        /// 2. The second assumption, and in accordance to assumption 1 above, is that when it comes
        /// to deciding if a character is part of a word or not, we assume that we are always dealing
        /// with characters of the English alphabet, both lower case and capitals. So, there is no
        /// provision for characters of different alphabets, numericals, etc.
        ///
        /// 3. Another assumption is that it is preferable to deal with the input character stream
        /// dynamically, on the fly, instead of reading the whole stream and storing it in a local
        /// variable first before processing it. This, for example, could help in situations of extremely
        /// long streams (for example reading a character stream from a file) that would require the use
        /// of extensive amounts of in-memory storage prior to processing.
        ///
        /// LOGIC:
        /// ======
        ///
        /// An instance of this class (DeveloperTestImplementation) is created elsewhere in the code
        /// (specifically in the unit test: StandardTest.TestQuestionOne()). At that moment two objects
        /// are passed to this method as dependencies using the method dependency injection pattern,
        /// the first one is a reader object, which is an instance of the SimpleCharacterReader class that
        /// implements the ICharacterReader interface, and the second one is an output object, which is an
        /// instance of the Question1TestOutput class that implements the IOutputResult interface.
        ///
        /// The purpose of this method is to use these two objects in order to read a character stream,
        /// dynamically process the character stream in order to separate it into English words, then order
        /// those words by frequency and then alphabetically, and finally create an appropriate output in
        /// the required output format that will be tested by the relevant unit test mentioned above for its
        /// correctness (i.e. the words that contains, the frequency that each word appears in the character
        /// stream, and whether these words have been ordered by frequency and alphabetically as required).
        ///
        /// Reading the character stream: In order to successfully read the character stream this method uses
        /// a simple do-while loop that in each iteration reads a new character, then decides if the character
        /// is a letter of the English alphabet or not (using the extension method IsLetter()) and accordingly
        /// either adds the character to the next word if it is indeed a letter or adds the word to a dictionary
        /// of words if the last read character is a white space, new line character, a comma, etc, i.e. anything
        /// other than a letter. When a word is added to the dictionary the algorithm makes sure that the
        /// string that holds the next word is initialised to an empty string in order to be able to hold the
        /// next word successfully. The algorithm then deals with the EndOfStreamException raised by the reader
        /// by making sure in the finally part of the try-catch-finally block that the very last word read is not
        /// lost, but stored in the dictionary in the same way with all the previous words.
        ///
        /// The last line of this method makes sure that the dictionary of words and word frequences is sorted
        /// according to the requirements, i.e. first by word frequency and then alphabetically, and then creates
        /// the desired output. These two tasks, sorting the dictionary and creating the required output, have
        /// been implemented in separate private method in order to declutter the main algorithm, improve its
        /// readibity, and demonstrate how to create methods for reoccurring tasks and functions, improving reusability,
        /// and making it more clear where and how we change the state of certain objects (instead of changing the
        /// state of objects all over the place). For example we can see that the method AddStringToDictionary(...)
        /// is called twice, once from inside the main do-while loop and then again from the finally sub-block of the
        /// try-catch-finally block.
        /// </summary>
        /// <param name="reader">An object of a class that implements the ICharacterReader interface, which provides
        /// a method for reading the next character of a character stream.</param>
        /// <param name="output">An object of a class that implements the IOutputResult interface, which allows to
        /// format an output according to specific requirements.</param>
        public void RunQuestionOne(ICharacterReader reader, IOutputResult output)
        {
            // A dictionary collection that holds words as strings and the frequency of their appearence as integers.
            IDictionary <string, int> wordDictionary = new Dictionary <string, int>();

            // A string variable that helps us form the next word from the input character stream.
            string nextWord = string.Empty;

            using (reader)
            {
                try
                {
                    // This is the main loop that reads a stream of characters, one by one,
                    // splits the stream into English words, according to the assumptions
                    // made above, and then stores the words into a dictionary collection
                    // keeping also track of how often each word appears in the input stream
                    // (word frequency).
                    do
                    {
                        // Read the next character from the stream of characters.
                        char nextChar = reader.GetNextChar();

                        // As long as the next character is a letter keep adding it to the next word,
                        // as soon as you have encountered a word's end (indicated by a whitespace
                        // character, a symbol such as a comma or a full stop, a new line character or
                        // something similar) add the word to the dictionary and reset the variable
                        // in order to be used to form the next word from scratch.
                        if (nextChar.IsLetter())
                        {
                            nextWord += nextChar.ToString().ToLower();
                        }
                        else
                        {
                            if (nextWord != string.Empty)
                            {
                                AddStringToDictionary(wordDictionary, nextWord);
                                nextWord = string.Empty;
                            }
                        }
                    } while (true);
                }
                catch (EndOfStreamException e)
                {
                    // Normally an error message, like the one below, would be logged in a log file or
                    // log database, by being passed to an appropriate method of a dedicated logger object.
                    // As this is out of the scope of this exercise, for now I am just imitating the
                    // error message logging by just displaying the error message to the console.
                    Console.WriteLine($"Error reading stream: {e.GetType().Name}.");
                }
                finally
                {
                    // Here we make sure that we do not miss the very last word of the input stream
                    // because of the EndOfStreamException thrown by the GetNextChar() method of the reader.
                    if (nextWord != string.Empty)
                    {
                        AddStringToDictionary(wordDictionary, nextWord);
                    }
                }

                // Sort the dictionary by word frequency and then alphabetically and then
                // create the required output.
                CreateOutput(SortDictionary(wordDictionary), output);
            }
        }
Ejemplo n.º 18
0
 /// <summary>
 /// Task that allows us to await the completion of the solution to question 1 without
 /// blocking the execution of the rest of the program.
 /// </summary>
 /// <param name="reader">An object of a class that implements the ICharacterReader interface, which provides
 /// a method for reading the next character of a character stream.</param>
 /// <param name="output">An object of a class that implements the IOutputResult interface, which allows to
 /// format an output according to specific requirements.</param>
 /// <returns>When the solution to Question 1 is complete the task returns.</returns>
 private async Task RunQuestionOneAsync(ICharacterReader reader, IOutputResult output)
 {
     await RunQuestionOne(reader, output);
 }
Ejemplo n.º 19
0
 public static IEnumerable <IExceptionDescriptor> GetExceptions(this IOutputResult result)
 {
     return(result.GetOperationResults().Select(x => x.Exception).WhereNotNull());
 }
Ejemplo n.º 20
0
        /// <summary>
        /// A task that allows the creation of an output in the required format.
        /// </summary>
        /// <param name="pairs">An ordered and enumerable collection of key/value pairs, in this case string/int pairs,
        /// containing the data that will be used for the creation of the output.</param>
        /// <param name="output">An object of a class that implements the IOutputResult interface.</param>
        /// <returns>The task that creates the output.</returns>
        private Task CreateOutput(IOrderedEnumerable <KeyValuePair <string, int> > pairs, IOutputResult output)
        {
            return(Task.Run(() =>
            {
                // The following tow lines are only used in verbose mode.
                if (verboseMode)
                {
                    Console.WriteLine("Output:");
                }
                if (verboseMode)
                {
                    Console.WriteLine("--------------------");
                }

                foreach (var pair in pairs)
                {
                    output.AddResult($"{pair.Key} - {pair.Value.ToString()}");
                }

                // The following line is only used in verbose mode.
                if (verboseMode)
                {
                    Console.WriteLine("--------------------");
                }
            }));
        }
Ejemplo n.º 21
0
 /// <summary>
 /// A method that allows to await the creation of an output.
 /// </summary>
 /// <param name="pairs">An ordered and enumerable collection of key/value pairs, in this case string/int pairs,
 /// containing the data that will be used for the creation of the output.</param>
 /// <param name="output">An object of a class that implements the IOutputResult interface.</param>
 /// <param name="delayPeriod">The dealy period needed before the creation of the output.</param>
 private async void CreateOutputAsync(IOrderedEnumerable <KeyValuePair <string, int> > pairs, IOutputResult output, int delayPeriod)
 {
     await DelayTimer(delayPeriod);
     await CreateOutput(pairs, output);
 }
Ejemplo n.º 22
0
 public Task RunQuestionTwo(ICharacterReader[] readers, IOutputResult output)
 {
     throw new System.NotImplementedException();
 }
Ejemplo n.º 23
0
 public Startup(IReadFileService readFileService, IBlockProcessingService blockProcessingService, IOutputResult outputResult)
 {
     _readFileService        = readFileService;
     _blockProcessingService = blockProcessingService;
     _outputResult           = outputResult;
 }
Ejemplo n.º 24
0
 public static IEnumerable <IOperationResult> GetOperationResults(this IOutputResult result)
 {
     return(result is ITestResult
   ? ((ITestResult)result).OperationResults
   : ((ISuiteResult)result).SetupResults.Concat(new FillingOperationResult()).Concat(((ISuiteResult)result).CleanupResults));
 }
Ejemplo n.º 25
0
 public Startup(IStringParserService stringParserService, ICharProcessingService charProcessingService, IOutputResult outputResult)
 {
     _stringParserService   = stringParserService;
     _charProcessingService = charProcessingService;
     _outputResult          = outputResult;
 }
 public Task RunQuestionOne(ICharacterReader reader, IOutputResult output)
 {
     throw new NotImplementedException();
 }
Ejemplo n.º 27
0
 public void RunQuestionTwo(ICharacterReader[] readers, IOutputResult output)
 {
     throw new NotImplementedException();
 }
 public Task RunQuestionTwo(ICharacterReader[] readers, IOutputResult output, CancellationToken cancellationToken)
 {
     throw new NotImplementedException();
 }