I'll give you three options (and a bonus).
First option. Use a custom Chunk(int)
linq operator using iterator blocks. The trick is the inner method uses the same enumerator as the outer. Seems like a lot of code, but once you have the Chunk()
method, you can use it anywhere. Also note this option doesn't even need a List<string>
. It will work with any IEnumerable<T>
, since we never reference any elements by index.
public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> values, int chunkSize)
{
var e = values.GetEnumerator();
while (e.MoveNext())
{
yield return innerChunk(e, chunkSize);
}
}
private static IEnumerable<T> innerChunk<T>(IEnumerator<T> e, int chunkSize)
{
//If we're here, MoveNext() was already called once to advance between batches
// Need to yield the value from that call.
yield return e.Current;
//start at 1, not 0, for the first yield above
int i = 1;
while(i++ < chunkSize && e.MoveNext()) //order of these conditions matters
{
yield return e.Current;
}
}
public static void WriteFormattedTextToNewFile(IEnumerable<string> groupedStrings)
{
string file = @"C:\Users\e011691\Desktop\New folder\formatted.txt";
using (var sw = new StreamWriter(file, true))
{
foreach(var strings in groupedStrings.Chunk(50))
{
sw.Write($"{DateTime.Now:yyyy MM dd hh:mm:ss}\t\t");
foreach(var item in strings)
{
sw.Write($"{item}\t");
}
sw.WriteLine();
}
}
}
Here's a basic proof of concept Chunk() actually works.
As a bonus option, here is another way to use the Chunk()
method from the first option. Note how small and straight-forward the actual method becomes, but the construction of the long full-line strings likely makes this less efficient.
public static void WriteFormattedTextToNewFile(IEnumerable<string> groupedStrings)
{
string file = @"C:\Users\e011691\Desktop\New folder\formatted.txt";
using (var sw = new StreamWriter(file, true))
{
foreach(var strings in groupedStrings.Chunk(50))
{
sw.Write($"{DateTime.Now:yyyy MM dd hh:mm:ss}\t\t");
sw.WriteLine(string.Join("\t", strings));
}
}
}
Second option. Keep track using a separate integer/loop. Note the extra condition on the inner loop, still using the i
value rather than j
to reference the current position, and incrementing i
in the inner loop. This is called a Control/Break loop. Note how we are able to write the end line and initial date value on each line such that they also appear in the correct order in the code: first the header, then the items, then the footer, and we do it without complicated conditional checks.
public static void WriteFormattedTextToNewFile(List<string> groupedStrings)
{
string file = @"C:\Users\e011691\Desktop\New folder\formatted.txt";
using (var sw = new StreamWriter(file, true))
{
int i = 0;
while(i < groupedStrings.Count)
{
sw.Write($"{DateTime.Now:yyyy MM dd hh:mm:ss}\t\t");
for(int j = 0; j < 50 && i < groupedStrings.Count; j++)
{
sw.Write($"{groupedStrings[i]}\t");
i++;
}
sw.WriteLine();
}
}
}
Third option. Keep track using the modulus operator (%
). This option (or a similar option using a second j
value in the same loop) is where many would turn first, but beware; this option is deceptively difficult to get right, especially as the problem gets more complicated.
public static void WriteFormattedTextToNewFile(List<string> groupedStrings)
{
string file = @"C:\Users\e011691\Desktop\New folder\formatted.txt";
using (var sw = new StreamWriter(file, true))
{
for(int i = 0; i < groupedStrings.Count;i++)
{
if (i % 50 == 0)
{
if (i > 0) sw.WriteLine();
sw.Write($"{DateTime.Now:yyyy MM dd hh:mm:ss}\t\t");
}
sw.Write($"{groupedStrings[i]}\t");
}
sw.WriteLine();
}
}
Update:
A variant of Chunk()
is now included out of the box for .Net 6.