Maximum number of occurrences a character appears in an array of strings - c#

In C#, given the array :
string[] myStrings = new string[] {
"test#test",
"##test",
"######", // Winner (outputs 6)
};
How can I find the maximum number of occurrences that the character # appears in a single string ?
My current solution is :
int maxOccurrences = 0;
foreach (var myString in myStrings)
{
var occurrences = myString.Count(x => x == '#');
if (occurrences > maxOccurrences)
{
maxOccurrences = occurrences;
}
}
return maxOccurrences;
Is their a simplier way using linq that can act directly on the myStrings[] array ?
And can this be made into an extension method that can work on any IEnumerable<string> ?

First of all let's project your strings into a sequence with count of matches:
myStrings.Select(x => x.Count(x => x == '#')) // {1, 2, 6} in your example
Then pick maximum value:
int maximum = myStrings
.Select(s => s.Count(x => x == '#'))
.Max(); // 6 in your example
Let's make an extension method:
public static int CountMaximumOccurrencesOf(this IEnumerable<string> strings, char ch)
{
return strings
.Select(s => s.Count(c => c == ch))
.Max();
}
However there is a big HOWEVER. What in C# you call char is not what you call character in your language. This has been widely discussed in other posts, for example: Fastest way to split a huge text into smaller chunks and How can I perform a Unicode aware character by character comparison? then I won't repeat everything here. To be "Unicode aware" you need to make your code more complicate (please note code is wrote here then it's untested):
private static IEnumerable<string> EnumerateCharacters(string s)
{
var enumerator = StringInfo.GetTextElementEnumerator(s.Normalize());
while (enumerator.MoveNext())
yield return (string)enumerator.Value;
}
Then change our original code to:
public static int CountMaximumOccurrencesOf(this IEnumerable<string> strings, string character)
{
return strings
.Select(s => s.EnumerateCharacters().Count(c => String.Equals(c, character, StringComparison.CurrentCulture))
.Max();
}
Note that Max() alone requires collection to don't be empty (use DefaultIfEmpty() if collection may be empty and it's not an error). To do not arbitrary decide what to do in this situation (throw an exception if it should happen or just return 0) you can may make this method less specialized and leave this responsibility to caller:
public static int CountOccurrencesOf(this IEnumerable<string> strings,
string character,
StringComparison comparison = StringComparison.CurrentCulture)
{
Debug.Assert(character.EnumerateCharacters().Count() == 1);
return strings
.Select(s => s.EnumerateCharacters().Count(c => String.Equals(c, character, comparison ));
}
Used like this:
var maximum = myStrings.CountOccurrencesOf("#").Max();
If you need it case-insensitive:
var maximum = myStrings.CountOccurrencesOf("à", StringComparison.CurrentCultureIgnoreCase)
.Max();
As you can now imagine this comparison isn't limited to some esoteric languages but it also applies to invariant culture (en-US) then for strings that must always be compared with invariant culture you should specify StringComparison.InvariantCulture. Don't forget that you may need to call String.Normalize() also for input character.

You can write something like this. Note the usage of DefaultIfEmpty, to not throw an exception if myStrings is empty, but revert to 0.
var maximum = myStrings.Select(e => e.Count(ee => ee == '#')).DefaultIfEmpty().Max();

You can do that with Linq combined to Regex:
myStrings.Select(x => Regex.Matches(x, "#").Count).max();

Related

How to find index of a value in array of char?

Suppose I have this array of char:
private char[] _alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
.ToCharArray();
I want find the index of the character B, so the code must return 1.
I wrote this method:
public int? GetCharIndex(string code)
{
return _alpha.FirstOrDefault(c => c.ToString() == code.ToUpper());
}
where code is b. The result of GetCharIndex is: 66, why I get this result?
Thanks in advance for any help.
use Array.IndexOf
https://learn.microsoft.com/en-us/dotnet/api/system.array.indexof?view=netframework-4.7.2
The 66 you are getting is 'B' ascii value.
The FirstOrDefault function returns the actual item (B) not the index.
So:
int index = Array.IndexOf(_alpha, code.ToUpper());
As you've mentioned, you'd like to use Linq. The Select in LINQ has an override that let's you expose the index. Getting the first matching value will give us the index of the occurrence. You can also replace the .FirstOrDefault() to .ToList() if you want all indexes on the match.
ToList
char[] _alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToArray();
var code = "b";
var key = _alpha.Select((s, i) => new { i, s })
.Where(t => t.s.ToString().ToUpper() == code.ToUpper())
.Select(t => t.i)
.ToList();
Alternatively the above can also be written as
FirstOrDefault
return _alpha.Select((s, i) => new { i, s }).FirstOrDefault(t => t.s.ToString().ToUpper() == code.ToUpper()).i;
Why I get this result?
Nullable<Char> is implicitly convertible to Nullable<Int32> because Char is implicitly convertible to Int32. That happens in your return statement. You're returning the character from the array if found.
Where people are saying ASCII code, they mean character code; in particular, since .NET uses the UTF-16 character encoding of the Unicode character set the UTF-16 code unit value.
BTW—VB4/5/6/A/Script, Java, JavaScript, … also use UTF-16 for text datatypes.
To use standard LINQ operators, choose one that supplies an index like Select:
Char[] _alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray();
Func<Char, StringComparison, Int32?> GetIndexOf = (code, comparisonType) => {
var value = code.ToString();
return _alpha
.Select((c, i) => c.ToString()
.Equals(value, StringComparison.InvariantCultureIgnoreCase) ? i : (Int32?)null)
.FirstOrDefault(i => i.HasValue); } ;
Debug.Assert(1 == GetIndexOf('B', StringComparison.InvariantCultureIgnoreCase));
Debug.Assert(null == GetIndexOf('3', StringComparison.InvariantCultureIgnoreCase));
And, you do have to decide what you really intend by your ".ToUpper". As a question/code review issue, it's unclear. It would be better to write your question and code (or at least as a code comment) in such a way as it explains what the proper culture is for letter case comparison. You used one that depends on the program's current culture, which is initialized from the user's culture at the time the program started.

How to create series of string combination of Alphabet and Number?

I have a collection of data list for example:
List<String> Dummy = new List<String>()
{
"1001A",
"1003A",
"1002B",
"1002A",
"1003B",
"1001B",
"1003C",
"1002C",
"1001C",
};
I want to arrange this data list into series. The main series will focus on the Alphabet(the last char of the string) and the sub series will be base on the numbers left. The output will like this:
1001A
1002A
1003A
1001B
1002B
1003B
1001C
1002C
1003C
I already have function codes only for the series of numbers except about the example above. Thanks for the reading my post.
var result = Dummy
.OrderBy(p => p[p.Length - 1])
.ThenBy(p => p.Substring(0, p.Length - 1));
This will first order by the last character of the string and then by everything except the last character of the string.
If all strings have the same length, you can also leave the last part at .ThenBy(p => p), as the strings are already sorted by the last character. If string lengths differ, you need the substring as in my code though.
If it's possible for the strings to have different lengths then the following would be needed.
var result = data.OrderBy(d => d[d.Length - 1])
.ThenBy(d => int.Parse(d.Substring(0, d.Length - 1])));
You'd of course need to guard against possible parsing exceptions with bad data.
This assumes that you'd want "200A" to come before "1000A".
version a) (fastest)
Use built in Sort method (sorts in place), with a custom Comparision delegate/lambda
dummy.Sort((s1, s2) =>
{
// TODO: Handle null values, now supposing s1 and s2 are not null
// TODO: Handle variable length if needed. Supposing fixed 4+1 data
var result = s1[4].CompareTo(s2[4]);
if (result != 0)
{
return result;
}
return s1.Substring(0, 4).CompareTo(s2.Substring(0, 4));
});
To reuse the Comparision you can write it as a static method instead of an inline lambda, however this case I recommend to implement an IComparator instead. (Sort method has an overload which accepts IComparator)
version b):
Use LINQ:
// TODO: Handle variable length if needed, supposing fixed 4+1 data structure:
var orderedList = dummy.OrderBy(s => s[4]).ThenBy(s => s.SubString(0,4).ToList();
A solution based on grouping:
var res = Dummy.GroupBy(str => str.Last()).OrderBy(g => g.Key)
.SelectMany(g => g.OrderBy(str => str.Substring(0, str.Length - 1)))
.ToList();

Convert file with int value in each line to IEnumerable<int>

I have a file with int values in each line (although it's possible that some values are not ints like some comments). But the structure of the file is:
1
2
3
4
5
6
7
#some comment
9
10
etc...
What's the fastest way to convert it to IEnumerable. I could read line by line and use List and call Add method, but I guess it's not the best in terms of performance.
Thanks
You could create your IEnumerable on-the-fly while reading the file:
IEnumerable<Int32> GetInts(string filename)
{
int tmp = 0;
foreach(string line in File.ReadLines(filename))
if (Int32.TryParse(line, out tmp))
yield return tmp;
}
This way, you can do whatever you want to do with your integers while reading the file, using a foreach loop.
foreach(int i in GetInts(#"yourfile"))
{
... do something with i ...
}
If you just want to create a list, simply use the ToList extension:
List<Int32> myInts = GetInts(#"yourfile").ToList();
but there probably won't be any measurable performance difference if you "manually" create a list as you described in your question.
var lines = File.ReadLines(path).Where(l => !l.StartsWith("#"));
you can also append .Select(x => int.Parse(x))
public static IEnumerable<int> ReadInts(TextReader tr)
{
//put using here to have this manage cleanup, but in calling method
//is probably better
for(string line = tr.ReadLine(); line != null; line = tr.ReadLine())
if(line.Length != 0 && line[0] != '#')
yield return int.Parse(line);
}
I assume from your description that a line that doesn't match should throw an exception, but I guessed also that blank lines where you don't want them are very common, so I do cathc that case. Adapt to catch that as appropriate otherwise.
If you want to add lines only if they are convertible to ints, you could use int.TryParse. I suggest to use File.ReadLines instead of File.ReadAllLines(creates an array in memory):
int value;
IEnumerable<String>lines = File.ReadLines(path)
.Where(l => int.TryParse(l.Trim(), out value));
or (if you want to select those ints):
int value;
IEnumerable<int>ints= File.ReadLines(path)
.Where(l => int.TryParse(l.Trim(), out value))
.Select(l => value);

int array to string

In C#, I have an array of ints, containing digits only. I want to convert this array to string.
Array example:
int[] arr = {0,1,2,3,0,1};
How can I convert this to a string formatted as: "012301"?
at.net 3.5 use:
String.Join("", new List<int>(array).ConvertAll(i => i.ToString()).ToArray());
at.net 4.0 or above use: (see #Jan Remunda's answer)
string result = string.Join("", array);
You can simply use String.Join function, and as separator use string.Empty because it uses StringBuilder internally.
string result = string.Join(string.Empty, new []{0,1,2,3,0,1});
E.g.: If you use semicolon as separator, the result would be 0;1;2;3;0;1.
It actually works with null separator, and second parameter can be enumerable of any objects, like:
string result = string.Join(null, new object[]{0,1,2,3,0,"A",DateTime.Now});
I realize my opinion is probably not the popular one, but I guess I have a hard time jumping on the Linq-y band wagon. It's nifty. It's condensed. I get that and I'm not opposed to using it where it's appropriate. Maybe it's just me, but I feel like people have stopped thinking about creating utility functions to accomplish what they want and instead prefer to litter their code with (sometimes) excessively long lines of Linq code for the sake of creating a dense 1-liner.
I'm not saying that any of the Linq answers that people have provided here are bad, but I guess I feel like there is the potential that these single lines of code can start to grow longer and more obscure as you need to handle various situations. What if your array is null? What if you want a delimited string instead of just purely concatenated? What if some of the integers in your array are double-digit and you want to pad each value with leading zeros so that the string for each element is the same length as the rest?
Taking one of the provided answers as an example:
result = arr.Aggregate(string.Empty, (s, i) => s + i.ToString());
If I need to worry about the array being null, now it becomes this:
result = (arr == null) ? null : arr.Aggregate(string.Empty, (s, i) => s + i.ToString());
If I want a comma-delimited string, now it becomes this:
result = (arr == null) ? null : arr.Skip(1).Aggregate(arr[0].ToString(), (s, i) => s + "," + i.ToString());
This is still not too bad, but I think it's not obvious at a glance what this line of code is doing.
Of course, there's nothing stopping you from throwing this line of code into your own utility function so that you don't have that long mess mixed in with your application logic, especially if you're doing it in multiple places:
public static string ToStringLinqy<T>(this T[] array, string delimiter)
{
// edit: let's replace this with a "better" version using a StringBuilder
//return (array == null) ? null : (array.Length == 0) ? string.Empty : array.Skip(1).Aggregate(array[0].ToString(), (s, i) => s + "," + i.ToString());
return (array == null) ? null : (array.Length == 0) ? string.Empty : array.Skip(1).Aggregate(new StringBuilder(array[0].ToString()), (s, i) => s.Append(delimiter).Append(i), s => s.ToString());
}
But if you're going to put it into a utility function anyway, do you really need it to be condensed down into a 1-liner? In that case why not throw in a few extra lines for clarity and take advantage of a StringBuilder so that you're not doing repeated concatenation operations:
public static string ToStringNonLinqy<T>(this T[] array, string delimiter)
{
if (array != null)
{
// edit: replaced my previous implementation to use StringBuilder
if (array.Length > 0)
{
StringBuilder builder = new StringBuilder();
builder.Append(array[0]);
for (int i = 1; i < array.Length; i++)
{
builder.Append(delimiter);
builder.Append(array[i]);
}
return builder.ToString()
}
else
{
return string.Empty;
}
}
else
{
return null;
}
}
And if you're really so concerned about performance, you could even turn it into a hybrid function that decides whether to do string.Join or to use a StringBuilder depending on how many elements are in the array (this is a micro-optimization, not worth doing in my opinion and possibly more harmful than beneficial, but I'm using it as an example for this problem):
public static string ToString<T>(this T[] array, string delimiter)
{
if (array != null)
{
// determine if the length of the array is greater than the performance threshold for using a stringbuilder
// 10 is just an arbitrary threshold value I've chosen
if (array.Length < 10)
{
// assumption is that for arrays of less than 10 elements
// this code would be more efficient than a StringBuilder.
// Note: this is a crazy/pointless micro-optimization. Don't do this.
string[] values = new string[array.Length];
for (int i = 0; i < values.Length; i++)
values[i] = array[i].ToString();
return string.Join(delimiter, values);
}
else
{
// for arrays of length 10 or longer, use a StringBuilder
StringBuilder sb = new StringBuilder();
sb.Append(array[0]);
for (int i = 1; i < array.Length; i++)
{
sb.Append(delimiter);
sb.Append(array[i]);
}
return sb.ToString();
}
}
else
{
return null;
}
}
For this example, the performance impact is probably not worth caring about, but the point is that if you are in a situation where you actually do need to be concerned with the performance of your operations, whatever they are, then it will most likely be easier and more readable to handle that within a utility function than using a complex Linq expression.
That utility function still looks kind of clunky. Now let's ditch the hybrid stuff and do this:
// convert an enumeration of one type into an enumeration of another type
public static IEnumerable<TOut> Convert<TIn, TOut>(this IEnumerable<TIn> input, Func<TIn, TOut> conversion)
{
foreach (TIn value in input)
{
yield return conversion(value);
}
}
// concatenate the strings in an enumeration separated by the specified delimiter
public static string Delimit<T>(this IEnumerable<T> input, string delimiter)
{
IEnumerator<T> enumerator = input.GetEnumerator();
if (enumerator.MoveNext())
{
StringBuilder builder = new StringBuilder();
// start off with the first element
builder.Append(enumerator.Current);
// append the remaining elements separated by the delimiter
while (enumerator.MoveNext())
{
builder.Append(delimiter);
builder.Append(enumerator.Current);
}
return builder.ToString();
}
else
{
return string.Empty;
}
}
// concatenate all elements
public static string ToString<T>(this IEnumerable<T> input)
{
return ToString(input, string.Empty);
}
// concatenate all elements separated by a delimiter
public static string ToString<T>(this IEnumerable<T> input, string delimiter)
{
return input.Delimit(delimiter);
}
// concatenate all elements, each one left-padded to a minimum length
public static string ToString<T>(this IEnumerable<T> input, int minLength, char paddingChar)
{
return input.Convert(i => i.ToString().PadLeft(minLength, paddingChar)).Delimit(string.Empty);
}
Now we have separate and fairly compact utility functions, each of which are arguable useful on their own.
Ultimately, my point is not that you shouldn't use Linq, but rather just to say don't forget about the benefits of creating your own utility functions, even if they are small and perhaps only contain a single line that returns the result from a line of Linq code. If nothing else, you'll be able to keep your application code even more condensed than you could achieve with a line of Linq code, and if you are using it in multiple places, then using a utility function makes it easier to adjust your output in case you need to change it later.
For this problem, I'd rather just write something like this in my application code:
int[] arr = { 0, 1, 2, 3, 0, 1 };
// 012301
result = arr.ToString<int>();
// comma-separated values
// 0,1,2,3,0,1
result = arr.ToString(",");
// left-padded to 2 digits
// 000102030001
result = arr.ToString(2, '0');
To avoid the creation of an extra array you could do the following.
var builder = new StringBuilder();
Array.ForEach(arr, x => builder.Append(x));
var res = builder.ToString();
string result = arr.Aggregate("", (s, i) => s + i.ToString());
(Disclaimer: If you have a lot of digits (hundreds, at least) and you care about performance, I suggest eschewing this method and using a StringBuilder, as in JaredPar's answer.)
You can do:
int[] arr = {0,1,2,3,0,1};
string results = string.Join("",arr.Select(i => i.ToString()).ToArray());
That gives you your results.
I like using StringBuilder with Aggregate(). The "trick" is that Append() returns the StringBuilder instance itself:
var sb = arr.Aggregate( new StringBuilder(), ( s, i ) => s.Append( i ) );
var result = sb.ToString();
string.Join("", (from i in arr select i.ToString()).ToArray())
In the .NET 4.0 the string.Join can use an IEnumerable<string> directly:
string.Join("", from i in arr select i.ToString())
I've left this here for posterity but don't recommend its use as it's not terribly readable. This is especially true now that I've come back to see if after a period of some time and have wondered what I was thinking when I wrote it (I was probably thinking 'crap, must get this written before someone else posts an answer'.)
string s = string.Concat(arr.Cast<object>().ToArray());
The most efficient way is not to convert each int into a string, but rather create one string out of an array of chars. Then the garbage collector only has one new temp object to worry about.
int[] arr = {0,1,2,3,0,1};
string result = new string(Array.ConvertAll<int,char>(arr, x => Convert.ToChar(x + '0')));
This is a roundabout way to go about it its not much code and easy for beginners to understand
int[] arr = {0,1,2,3,0,1};
string joined = "";
foreach(int i in arr){
joined += i.ToString();
}
int number = int.Parse(joined);
If this is long array you could use
var sb = arr.Aggregate(new StringBuilder(), ( s, i ) => s.Append( i ), s.ToString());
// This is the original array
int[] nums = {1, 2, 3};
// This is an empty string we will end up with
string numbers = "";
// iterate on every char in the array
foreach (var item in nums)
{
// add the char to the empty string
numbers += Convert.ToString(item);
}
// Write the string in the console
Console.WriteLine(numbers);

How to split this string

I have some strings, entered by users, that may look like this:
++7
7++
1++7
1+7
1++7+10++15+20+30++
Those are to mean:
Anything up to and including 7
Anything from 7 and up
1 and 7 and anything inbetween
1 and 7 only
1 to 7, 10 to 15, 20 and 30 and above
I need to parse those strings into actual ranges. That is I need to create a list of objects of type Range which have a start and an end. For single items I just set the start and end to the same, and for those that are above or below, I set start or end to null. For example for the first one I would get one range which had start set to null and end set to 7.
I currently have a kind of messy method using a regular expression to do this splitting and parsing and I want to simplify it. My problem is that I need to split on + first, and then on ++. But if I split on + first, then the ++ instances are ruined and I end up with a mess.
Looking at those strings it should be really easy to parse them, I just can't come up with a smart way to do it. It just have to be an easier (cleaner, easier to read) way. Probably involving some easy concept I just haven't heard about before :P
The regular expression looks like this:
private readonly Regex Pattern = new Regex(#" ( [+]{2,} )?
([^+]+)
(?:
(?: [+]{2,} [^+]* )*
[+]{2,} ([^+]+)
)?
( [+]{2,} )? ", RegexOptions.IgnorePatternWhitespace);
That is then used like this:
public IEnumerable<Range<T>> Parse(string subject, TryParseDelegate<string, T> itemParser)
{
if (string.IsNullOrEmpty(subject))
yield break;
for (var item = RangeStringConstants.Items.Match(subject); item.Success; item = item.NextMatch())
{
var startIsOpen = item.Groups[1].Success;
var endIsOpen = item.Groups[4].Success;
var startItem = item.Groups[2].Value;
var endItem = item.Groups[3].Value;
if (endItem == string.Empty)
endItem = startItem;
T start, end;
if (!itemParser(startItem, out start) || !itemParser(endItem, out end))
continue;
yield return Range.Create(startIsOpen ? default(T) : start,
endIsOpen ? default(T) : end);
}
}
It works, but I don't think it is particularly readable or maintainable. For example changing the '+' and '++' into ',' and '-' would not be that trivial to do.
My problem is that I need to split on + first, and then on ++. But if I split on + first, then the ++ instances are ruined and I end up with a mess.
You could split on this regex first:
(?<!\+)\+(?!\+)
That way, only the 'single' +'s are being split on, leaving you to parse the ++'s. Note that I am assuming that there cannot be three successive +'s.
The regex above simple says: "split on the '+' only if there's no '+' ahead or behind it".
Edit:
After reading that there can be more than 2 successive +'s, I recommend writing a small grammar and letting a parser-generator create a lexer+parser for your little language. ANTLR can generate C# source code as well.
Edit 2:
But before implementing any solution (parser or regex) you'd first have to define what is and what isn't valid input. If you're going to let more than two successive +'s be valid, ie. 1+++++5, which is [1++, +, ++5], I'd write a little grammar. See this tutorial how that works: http://www.antlr.org/wiki/display/ANTLR3/Quick+Starter+on+Parser+Grammars+-+No+Past+Experience+Required
And if you're going to reject input of more than 2 successive +'s, you can use either Lasse's or my (first) regex-suggestion.
Here's some code that uses regular expressions.
Note that the issue raised by Bart in the comments to your question, ie. "How do you handle 1+++5", is not handled at all.
To fix that, unless your code is already out in the wild and not subject to change of behaviour, I would suggest you change your syntax to the following:
use .. to denote ranges
allow both + and - for numbers, for positive and negative numbers
use comma and/or semicolon to separate distinct numbers or ranges
allow whitespace
Look at the difference between the two following strings:
1++7+10++15+20+30++
1..7, 10..15, 20, 30..
The second string is much easier to parse, and much easier to read.
It would also remove all ambiguity:
1+++5 = 1++ + 5 = 1.., 5
1+++5 = 1 + ++5 = 1, ..5
There's no way to parse wrong the second syntax.
Anyway, here's my code. Basically it works by adding four regex patterns for the four types of patterns:
num
num++
++num
num++num
For "num", it will handle negative numbers with a leading minus sign, and one or more digits. It does not, for obvious reasons, handle the plus sign as part of the number.
I've interpreted "and up" to mean "up to Int32.MaxValue" and same for down to Int32.MinValue.
public class Range
{
public readonly Int32 From;
public readonly Int32 To;
public Range(Int32 from, Int32 to)
{
From = from;
To = to;
}
public override string ToString()
{
if (From == To)
return From.ToString();
else if (From == Int32.MinValue)
return String.Format("++{0}", To);
else if (To == Int32.MaxValue)
return String.Format("{0}++", From);
else
return String.Format("{0}++{1}", From, To);
}
}
public static class RangeSplitter
{
public static Range[] Split(String s)
{
if (s == null)
throw new ArgumentNullException("s");
String[] parts = new Regex(#"(?<!\+)\+(?!\+)").Split(s);
List<Range> result = new List<Range>();
var patterns = new Dictionary<Regex, Action<Int32[]>>();
patterns.Add(new Regex(#"^(-?\d+)$"),
values => result.Add(new Range(values[0], values[0])));
patterns.Add(new Regex(#"^(-?\d+)\+\+$"),
values => result.Add(new Range(values[0], Int32.MaxValue)));
patterns.Add(new Regex(#"^\+\+(-?\d+)$"),
values => result.Add(new Range(Int32.MinValue, values[0])));
patterns.Add(new Regex(#"^(-?\d+)\+\+(-?\d+)$"),
values => result.Add(new Range(values[0], values[1])));
foreach (String part in parts)
{
foreach (var kvp in patterns)
{
Match ma = kvp.Key.Match(part);
if (ma.Success)
{
Int32[] values = ma.Groups
.OfType<Group>()
.Skip(1) // group 0 is the entire match
.Select(g => Int32.Parse(g.Value))
.ToArray();
kvp.Value(values);
}
}
}
return result.ToArray();
}
}
Unit-tests:
[TestFixture]
public class RangeSplitterTests
{
[Test]
public void Split_NullString_ThrowsArgumentNullException()
{
Assert.Throws<ArgumentNullException>(() =>
{
var result = RangeSplitter.Split(null);
});
}
[Test]
public void Split_EmptyString_ReturnsEmptyArray()
{
Range[] result = RangeSplitter.Split(String.Empty);
Assert.That(result.Length, Is.EqualTo(0));
}
[TestCase(01, "++7", Int32.MinValue, 7)]
[TestCase(02, "7", 7, 7)]
[TestCase(03, "7++", 7, Int32.MaxValue)]
[TestCase(04, "1++7", 1, 7)]
public void Split_SinglePatterns_ProducesExpectedRangeBounds(
Int32 testIndex, String input, Int32 expectedLower,
Int32 expectedUpper)
{
Range[] result = RangeSplitter.Split(input);
Assert.That(result.Length, Is.EqualTo(1));
Assert.That(result[0].From, Is.EqualTo(expectedLower));
Assert.That(result[0].To, Is.EqualTo(expectedUpper));
}
[TestCase(01, "++7")]
[TestCase(02, "7++")]
[TestCase(03, "1++7")]
[TestCase(04, "1+7")]
[TestCase(05, "1++7+10++15+20+30++")]
public void Split_ExamplesFromQuestion_ProducesCorrectResults(
Int32 testIndex, String input)
{
Range[] ranges = RangeSplitter.Split(input);
String rangesAsString = String.Join("+",
ranges.Select(r => r.ToString()).ToArray());
Assert.That(rangesAsString, Is.EqualTo(input));
}
[TestCase(01, 10, 10, "10")]
[TestCase(02, 1, 10, "1++10")]
[TestCase(03, Int32.MinValue, 10, "++10")]
[TestCase(04, 10, Int32.MaxValue, "10++")]
public void RangeToString_Patterns_ProducesCorrectResults(
Int32 testIndex, Int32 lower, Int32 upper, String expected)
{
Range range = new Range(lower, upper);
Assert.That(range.ToString(), Is.EqualTo(expected));
}
}

Categories

Resources