/// <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; })); }
public async Task RunQuestionOne(ICharacterReader reader, IOutputResult output) { var dt = new DeveloperTest(); await dt.ProcessReaderAsync(reader); await dt.Print(output); }
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()); } }
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); }
/// <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); } } }
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"); }
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)); }
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()); }
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()); }
/// <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; } } }
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); }
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; } } }
/// <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()}"); } }
/// <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); } }
/// <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); }
public static IEnumerable <IExceptionDescriptor> GetExceptions(this IOutputResult result) { return(result.GetOperationResults().Select(x => x.Exception).WhereNotNull()); }
/// <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("--------------------"); } })); }
/// <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); }
public Task RunQuestionTwo(ICharacterReader[] readers, IOutputResult output) { throw new System.NotImplementedException(); }
public Startup(IReadFileService readFileService, IBlockProcessingService blockProcessingService, IOutputResult outputResult) { _readFileService = readFileService; _blockProcessingService = blockProcessingService; _outputResult = outputResult; }
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)); }
public Startup(IStringParserService stringParserService, ICharProcessingService charProcessingService, IOutputResult outputResult) { _stringParserService = stringParserService; _charProcessingService = charProcessingService; _outputResult = outputResult; }
public Task RunQuestionOne(ICharacterReader reader, IOutputResult output) { throw new NotImplementedException(); }
public void RunQuestionTwo(ICharacterReader[] readers, IOutputResult output) { throw new NotImplementedException(); }
public Task RunQuestionTwo(ICharacterReader[] readers, IOutputResult output, CancellationToken cancellationToken) { throw new NotImplementedException(); }