/// <summary> /// Try to parse header delimiter from numbers string. /// </summary> /// <param name="numbers">Numbers string.</param> /// <param name="constraints">Constraint object.</param> /// <returns></returns> private static char[] TryGetDelimHeader(string numbers, DelimiterHeaderConstraints constraints) { // https://regex101.com/r/tXfHca/1 string delimHeaderMatchPattern = @$ "(?<={constraints.StartsWith}).*(?={constraints.EndsWith})"; var match = Regex.Match(numbers, delimHeaderMatchPattern); if (match.Success) { return(match.Value.ToCharArray()); } return(null); }
/// <summary> /// A simple string calculator which adds together specified numbers separated by commas. A custom /// delimiter can be specified in the input before the number in the following way: //[delimiter]\n[numbers…] /// </summary> /// <param name="numbers">String input containing numbers and optional delimiter header.</param> /// <returns>Sum of numbers.</returns> public static int Add(string numbers) { // Default delimiter. char[] delims = new[] { ',' }; // Default cases. if (numbers == string.Empty) { return(0); } if (numbers == null) { throw new ArgumentNullException(nameof(numbers)); } // Process header. if (numbers.SafeSubstring(0, 2) == "//") { // We have the start of the delimiter header of our input, worth trying to parse at this stage. // The assumption is made that a newline cannot be used as a delimiter. var constraints = new DelimiterHeaderConstraints() { StartsWith = "//", EndsWith = "\n" }; var delimsFromHeader = TryGetDelimHeader(numbers, constraints); if (delimsFromHeader != null) { if (delimsFromHeader.Contains('/')) { throw new ArgumentOutOfRangeException(nameof(numbers), "Invalid delimiter header specified: '/'"); } // We have found delims, so lets cut off the header up to the first newline encountered located AFTER the user specified delims. // This allows the input have any number of newlines after the delim header. var indexOfLastDelim = numbers.IndexOf(delimsFromHeader.Last()); numbers = numbers.Substring(indexOfLastDelim + 1); numbers = numbers.Substring(numbers.IndexOf('\n') + 1); // Brief point 4a states the requirements to "change" the delimiter, so we overwrite the default delim instead of adding to it. delims = delimsFromHeader; } } // Process numbers. var total = 0; var negativeAudit = ""; foreach (var strNum in numbers.Split(delims)) { bool hasNegative = false; int?parseResult = int.TryParse(strNum, out var num) ? num : default(int?); if (parseResult == null) { throw new ArgumentNullException(nameof(numbers), "Invalid characters specified."); } if (num >= 1000) { continue; } if (num < 0) { hasNegative = true; negativeAudit += num + ","; } if (!hasNegative) { total += num; } } if (!string.IsNullOrEmpty(negativeAudit)) { throw new ArgumentOutOfRangeException(nameof(numbers), $"Negatives not allowed: {negativeAudit.TrimEnd(',')}"); } return(total); }