Visit the series exercise on Exercism to read the full instructions and download the exercise files.
Dig Deeper
For loop
for-loop
using System;
using System.Collections.Generic;
public static class Series
{
public static IEnumerable<string> Slices(string input, int length)
{
if (length < 1 || length > input.Length)
throw new ArgumentException("Invalid length");
for (var i = 0; i <= input.Length - length; i++)
yield return input.Substring(i, length);
}
}
The first step is to check the input for validity:
if (length < 1 || length > input.Length)
throw new ArgumentException("Invalid length");
Then we iterate over the input parameter character by character:
for (var i = 0; i <= input.Length - length; i++)
The tricky thing is to determine when to stop.
Let’s look at an example input and some different slice lengths to examine which slice indexes we want to be getting the slice from:
| Input | Slice length | Slice indexes | Slices |
|---|
| ”abc” | 1 | 0, 1, 2 | ”a”, “b”, “c" |
| "abc” | 2 | 0, 1 | ”ab”, “bc" |
| "abc” | 3 | 0 | ”abc” |
From this table, we can see the last slice index to use is input length - slice length.
Therefore, our loop’s condition is i <= input.Length - length.
The final step is to return the slice, for which we use a yield statement and the Substring() method:
yield return input.Substring(i, length);
The value we’re returning is the substring of the input that starts at index i and which has length equal to length.
The yield statement then returns the slice to the caller.
Even though we yield an indidivual string, what is returned from a caller’s viewpoint is a sequence of strings.
When a `yield` statement is written, the compiler transforms the method into a state machine that is suspended after each yield statement.
Methods that use a `yield` statement are also _lazy_, which means that calling `Sequence(number)` by itself does not do anything.
It is only when a method is called that forces evaluation (like `Count()` or `ToArray()`), that we're forcing iteration over the elements in the sequence.
Shortening
The Substring() call could be replaced with a range call:
input[i..(i + length)]
While slightly shorter, the fact that the parentheses are required makes this a minor gain.
In this case, the Substring() call probably better captures the domain logic, as it is more clear that we’re return a string of size length.
LINQ
LINQ
using System;
using System.Collections.Generic;
using System.Linq;
public static class Series
{
public static IEnumerable<string> Slices(string input, int length)
{
if (length < 1 || length > input.Length)
throw new ArgumentException("Invalid length");
return Enumerable.Range(0, input.Length - length + 1)
.Select(i => input.Substring(i, length));
}
}
The first step is to check the input for validity:
if (length < 1 || length > input.Length)
throw new ArgumentException("Invalid length");
Then we use Enumerable.Range() to create the sequence of integers that represent the index from which we want to retrieve an individual slice:
Enumerable.Range(0, input.Length - length + 1)
The tricky thing is to determine which indexes to return.
Let’s look at an example input and some different slice lengths to examine which slice indexes we want to be getting the slice from:
| Input | Slice length | Slice indexes | Slices |
|---|
| ”abc” | 1 | 0, 1, 2 | ”a”, “b”, “c" |
| "abc” | 2 | 0, 1 | ”ab”, “bc" |
| "abc” | 3 | 0 | ”abc” |
From this table, we can see the first slice index is always 0 and that input length - slice length + 1 indexes are used.
Therefore, the Enumerable.Range() call starts at 0 and has returns input.Length - length + 1 elements.
The final step is to map the indexes to the slices, for which we use Select() and the Substring() method:
Select(i => input.Substring(i, length))
The value we’re returning is the substring of the input that starts at index i and which has length equal to length.
Shortening
The Substring() call could be replaced with a range call:
input[i..(i + length)]
While slightly shorter, the fact that the parentheses are required makes this a minor gain.
In this case, the Substring() call probably better captures the domain logic, as it is more clear that we’re return a string of size length.
Source: Exercism csharp/series