# Creating range of numeric values based on array of strings

Basically I have an array of strings. In this array, a range separator word might exist. For this example, I'm going to use the word to as range separator. We can assume that the range separator is not the first or last entry in the array. If we find the range separator we need to get the entry before and the entry after the separator, compare the two entries that we can assume are safe to parse as integer and find the character that differs between them. We then need to create new entries by counting up the number that is different, and return an IEnumerable<string> containing the new entries. We can also assume that only one character will differ between the start of the range and the end of the range.

For example, if the array contains these values:

1102
to
1602


we need to return

1202
1302
1402
1502


And here's my code:

public static IEnumerable<string> GetRangeSegments(string[] segments, string rangeIndicator)
{
var rangeIndicatorIndex = Array.IndexOf(segments, rangeIndicator);
var rangeSegments = new string[] { };

if (rangeIndicatorIndex < 0)
{
return rangeSegments;
}

var rangeStart = segments[rangeIndicatorIndex - 1];
var rangeEnd = segments[rangeIndicatorIndex + 1];

var startChars = rangeStart.ToCharArray();
var endChars = rangeEnd.ToCharArray();

if (startChars.Length != endChars.Length)
{
return rangeSegments;
}

var diffIndex = -1;

for (var i = 0; i < startChars.Length; i++)
{
if (startChars[i] == endChars[i])
{
continue;
}

diffIndex = i;
break;
}

if (diffIndex == -1)
{
return rangeSegments;
}

var start = int.Parse(startChars[diffIndex].ToString()) + 1;
var end = int.Parse(endChars[diffIndex].ToString());

var numbers = Enumerable.Range(start, end - start);
return rangeSegments.Concat(numbers.Select(number => rangeStart.Remove(diffIndex, 1).Insert(diffIndex, number.ToString())));
}

• @Heslacher That shouldn't happen in my case, but if it does it should probably return an empty collection. – spersson Dec 15 '17 at 13:36

If a method returns an IEnumerable<T> we expect an deffered execution using the yield keyword.

To make this happen and have a valid parameter validation we need another method which calls the method which is yielding.

public static IEnumerable<string> GetRangeSegments(string[] segments, string rangeIndicator)
{
if (segments==null) { throw new ArgumentNullException(nameof(segments)); }
if (rangeIndicator == null) { throw new ArgumentNullException(nameof(rangeIndicator)); }
if (string.IsNullOrWhiteSpace(rangeIndicator)) { throw new ArgumentException("May not be empty.", nameof(rangeIndicator)); }

return GetSegments(segments, rangeIndicator);
}


All the if statements will be executed and if the code comes to return it will immediately return because it knows about the yieldings in the GetSegments() method.

Using yield makes the empty string[] rangeSegments superflous because we just yield break. If you call ToArray() on the result of these yield break you get an empty array.

Because you can access the chars of a string by using its index you don't need ToCharArray().

If there isn't by some mistake either rangeStart[diffIndex] or rangeEnd[diffIndex] a digit the code blows up at the call to int.Parse(). A better way would be to use int.TryParse() and if this doesn't succeed you could yield break.

But if we e.g do int start = rangeStart[diffIndex] + 1; then the ascii value of that char will be casted to an int which we then can check if 47 < start < 58 and if not we yield break here.

The return statmenet in your method is just unreadable, hence hard to maintain as well. To "replace" a single char inside a string we can just use a StringBuilder.

public static IEnumerable<string> GetRangeSegments(string[] segments, string rangeIndicator)
{
if (segments == null) { throw new ArgumentNullException(nameof(segments)); }
if (rangeIndicator == null) { throw new ArgumentNullException(nameof(rangeIndicator)); }
if (string.IsNullOrWhiteSpace(rangeIndicator)) { throw new ArgumentException("May not be empty.", nameof(rangeIndicator)); }
return GetSegments(segments, rangeIndicator);
}

private const int ascii0 = 48;
private const int ascii9 = 57;
private static IEnumerable<string> GetSegments(string[] segments, string rangeIndicator)
{
var rangeIndicatorIndex = Array.IndexOf(segments, rangeIndicator);

if (rangeIndicatorIndex < 0 || rangeIndicatorIndex == segments.Length - 1)
{
yield break;
}

var rangeStart = segments[rangeIndicatorIndex - 1];
var rangeEnd = segments[rangeIndicatorIndex + 1];

if (rangeStart.Length != rangeEnd.Length)
{
yield break;
}

var diffIndex = GetFirstDifferentCharPosition(rangeStart, rangeEnd);
if (diffIndex == -1)
{
yield break;
}

var start = rangeStart[diffIndex] + 1;
if (start < ascii0 || start > ascii9) { yield break; }

var end = rangeEnd[diffIndex];
if (end < ascii0 || end > ascii9) { yield break; }

if (end < start) { yield break; }

StringBuilder sb = new StringBuilder(rangeStart);
for (int i = start; i < end; i++)
{
sb[diffIndex] = (char)i;
yield return sb.ToString();
}

}
private static int GetFirstDifferentCharPosition(string first, string second)
{
var diffIndex = -1;

for (var i = 0; i < first.Length; i++)
{
if (first[i] != second[i])
{
diffIndex = i;
break;
}
}
return diffIndex;
}

• I'm not going to upvote your answer this time because I bet it's about winning another hat, probably for not getting any votes ;-P – t3chb0t Dec 15 '17 at 16:22
• Thanks, great answer. One simple question, I realize it doesn't have any impact on functionality, but sometimes you write the yield break on the same line as the condition, and sometimes you don't. Are there any general guidelines for this? Or is it just when it looks nice to do it? – spersson Dec 18 '17 at 9:35
• Just mixing styles, something one shouldn't do :-( – Heslacher Dec 18 '17 at 9:41
• You should choose one style to use and stick to it. Here in the edit windows the yield break; would have to far on the right side so I placed it on a new line which I should have done for the others as well. – Heslacher Dec 18 '17 at 9:44

Here is my implementation of this. I chose a simpler way to go about it:

    static void Main(string[] args)
{

IEnumerable<string> retorno = GetInterval(new string[] { "1201", "to", "1601" }, "to");
foreach(var item in retorno)
{
Console.WriteLine(item);
}
}

public static IEnumerable<string> GetInterval(string[] segments, string separator)
{
List<string> lReturn = new List<string>();
int separatorIndex = Array.IndexOf(segments, separator);
string first = segments[separatorIndex - 1];
string second = segments[separatorIndex + 1];
int indexDiffer = -1;
bool isDifferent = false;

while (!(isDifferent = first[indexDiffer] != second[indexDiffer])) { indexDiffer++; };

if (!isDifferent)
return null;

var counter = Convert.ToInt32(first.Remove(indexDiffer + 1)) + 1;
var endRange = Convert.ToInt32(second.Remove(indexDiffer + 1));

for (; counter < endRange; counter++)