-2

I am working on a program where I read data from a file, store that data in a list and write that data to a new file in a special format. I have created the method to read the original file and store it in a list.

The file I read from is a list of numbers. One number per line.

I'm having issue with my method that writes to the new file. I'm having an issue with taking 50 items and writing them to a line and then taking the next 50 items and writing on the next line. The method is taking the first 50 items and writing them and repeating those 50 items on each line. I know this is because of my second for loop. Just not sure how to fix. any help would be appreciated. Below is my code:

public static void WriteFormattedTextToNewFile(List<string> groupedStrings)
{
    string file = @"C:\Users\e011691\Desktop\New folder\formatted.txt";
    StreamWriter sw = new StreamWriter(file, true);

    for (int i = 0; i < ReadFile.GroupedStrings.Count; i++)
    {
        sw.Write($"{DateTime.Now:yyyy MM dd  hh:mm:ss}\t\t");

        for (int j = 0; j < 50; j++)
        {
            sw.Write($"{ReadFile.GroupedStrings[j]}\t");
        }
        sw.WriteLine();
    }
    sw.Close();
}
  • 3
    Why are you passing a list of strings to this method and then totally ignore this variable while writing to file? – Steve Oct 03 '18 at 20:59
  • 1
    Where is the 50 in your code? What do you expect looping through `ReadFile.GroupedStrings.Count` to do for you? Where did `ReadFile.GroupedStrings` come from and what is its type? – NetMage Oct 03 '18 at 21:00
  • ReadFile.GroupedStrings is the list of strings that I pass to this method. I didn't ignore the variable. When I run this method it's WriteFile.WriteFormattedTextToNewFile(ReadFile.GroupedStrings); – user3294360 Oct 03 '18 at 21:02
  • 1
    ReadFile.GroupedStrings is not the list of strings that you pass to this method. groupedStrings is. – Zair Henrique Oct 03 '18 at 21:04
  • NetMage, I just updated the code block with the 50. I copied the wrong block. – user3294360 Oct 03 '18 at 21:04
  • is groupStrings not just a generic name for the List? Can I not pass any List when I call this method? – user3294360 Oct 03 '18 at 21:06
  • Yes, then refer to it as groupedStrings. – Zair Henrique Oct 03 '18 at 21:06
  • Do you want the date + two tabs at the beginning of each line, or just at the beginning of the file? – Joel Coehoorn Oct 03 '18 at 21:51

1 Answers1

2

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.

Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794
  • Thanks a lot Joel! I went with the second option as it was what I was more familiar with. I'll look into using Chunk( ) in the future. – user3294360 Oct 09 '18 at 17:05