简体   繁体   中英

Read 10 lines at a time from text file in C#

I'm looking for a solution to read 10 lines from a text files and then more 10 lines until the end of the file. This is what I started, of course it shows me the first 10 lines, but how can I repeat this process for the next 10 lines and so on, until the end of the file?

private void openFile_Click(object sender, EventArgs e)
{
    int counter = 0;
    string line;

    using (var file =
       new System.IO.StreamReader(@"C:\\Users\\LJ_TEX\\Desktop\\Book1.txt"))
    {
        while ((line = file.ReadLine()) != null)
        {
            counter++;
            if (counter <= 10)
            {
                tboxreadData.AppendText(line + '\r' + '\n');
            }
            if (counter == 10)
            {
                tboxreadData.AppendText("NEXT");
            }
        }
    }
}

EDIT

So I managed to make a little change, show one line at a click of the button with this

 System.IO.StreamReader file = null;

 private void openFile_Click(object sender, EventArgs e)
    {
        string line;

        if (file == null)
            file = new System.IO.StreamReader(@"C:\\Users\\LJ_TEX\\Desktop\\Book1.txt");

        if (!file.EndOfStream)
        {
            line = file.ReadLine();
            tboxreadData.AppendText(line + '\r' + '\n');
        }
        else
        {
            tboxreadData.AppendText("End");
        }
       
    }

Sure, it only shows one line line at a time. If anyone have a idea how to show more lines, 5 or 10, feel free to share.

Thanks, Jonathan

You can achieve that by using the modulo operator (%). So you check if your counter%10=0 to print out the "NEXT"-Line.

Your example could look like this:

private void openFile_Click(object sender, EventArgs e)
    {
        int counter = 0;
        string line;

        System.IO.StreamReader file = new System.IO.StreamReader(@"C:\\Users\\LJ_TEX\\Desktop\\Book1.txt");

        while ((line = file.ReadLine()) != null)
        {
            counter++;               
            tboxreadData.AppendText(line + '\r' + '\n');
            if(counter%10 == 0){
                tboxreadData.AppendText("NEXT");
            }
        }
    }

EDIT:

For read everytime 10 lines (if present), you could use the following snippet. It isnt performant, because you will read everytime from the beginning:

int _lineCounter = 0;

private void openFile_Click(object sender, EventArgs e)
    {
        int counter = 0;
        string line;

        System.IO.StreamReader file = new System.IO.StreamReader(@"C:\\Users\\LJ_TEX\\Desktop\\Book1.txt");

        while ((line = file.ReadLine()) != null)
        {
            counter++;
            if(counter < _linecounter)
                continue;
            tboxreadData.AppendText(line + '\r' + '\n');
            if(counter%10 == 0){
                tboxreadData.AppendText("NEXT");
                break;
            }
        }
        _lineCounter = counter;
    }

You could create your own extension method which takes an IEnumerbale<T> and partitions it in the chunks. From there you can iterate it with a foreach or any standard linq style chain method

Given

// updated, this is completely lazy loaded
public static class Extensions
{
   public static IEnumerable<IEnumerable<TValue>> Partition<TValue>(this IEnumerable<TValue> source, int size)
   {
      using var enumerator = source.GetEnumerator();
      static IEnumerable<T> GetChunk<T>(IEnumerator<T> enumerator, int chunkSize)
      {
         for (var i = 0; i < chunkSize && enumerator.MoveNext(); i++)
            yield return enumerator.Current;
      }

      while (enumerator.MoveNext())
         yield return GetChunk(enumerator, size);
   }
}

Usage

var partitions = File
     .ReadLines(@"D:\ids.txt")
     .Partition(10);

foreach (var partition in partitions)
{
   Console.WriteLine("Start partition");
   foreach (var line in partition )
      Console.WriteLine(line);
}

Output

Start partition
1516556 E763794 1773595 33EBM5015703
1516556 E763794 1773657 33EBM5015703
1518933 E764710 1776347 33EBM5015705
1519147 H182989 1776617 33EBM5015702
1519264 H183025 1776745 33EBM5015686
1519310 X186946 1776805 33EBM5015695
1519622 X186992 1777159 33EBM5015704
1519623 E765015 1777160 33EBM5015716
1519818 H183205 1777375 33EBM5015681
1519932 E765141 1777546 33EBM5015700
Start partition
1520345 E765295 1778070 33EBM5015715
1520366 E765303 1778097 33EBM5015684
1520385 X187075 1778117 33EBM5015675
1520420 H183413 1778157 33EBM5015662
1520429 H183418 1778166 33EBM5015654
1520466 X187085 1778203 33EBM5015663
1520468 E765345 1778205 33EBM5015658
1520476 E765349 1778214 33EBM5015677
1520486 H183441 1778224 33EBM5015664
1520496 H183444 1778234 33EBM5015671
Start partition
1520506 E765361 1778244 33EBM5015666
1520510 E765364 1778248 33EBM5015670
1520528 H183462 1778270 33EBM5015680
1520550 H183474 1778292 33EBM5015653
1520553 X187092 1778295 33EBM5015706
1520558 E765382 1778300 33EBM5015650
1520574 E765389 1778316 33EBM5015656
1520585 E765396 1778327 33EBM5015669
1520618 X187102 1778360 33EBM5015682
1520621 E765408 1778363 33EBM5015667

What you want is quite common: you want to read your input "per page".

In other words: you have a sequence of similar items, and you want to divide this into subgroups of equal size.

If you will be using this regularly, consider to create some classes for it. This way you can use it for several problems where you need to fetch items "per page".

I regularly use it to fetch items from a database per page. Because of the generic classes, I can put an IQueryable / IEnumerable in a PageCollection class and ask for the number of pages, and for Page[3].

If you do it smart, you won't have to fetch items that you don't use, and you don't re-fetch items that you already fetched.

We hide the internals of a page. Therefore we create an interface:

interface IPage<T> : IReadOnlyCollection<T>, IEnumerable<T>
{
    int PageNr {get; }             // the zero based number of the page
    int PageCount {get; }          // the total number of available pages
    int PageLength {get; }         // The number of items on this page

I chose to implement IReadOnlyCollection<T> instead of IReadOnlyList<T> , because the index usually gives the incorrect impression.

For example, if you have a collection of ProductPages , then every ProductPage has zero or more Products . But what do you expect if you are on ProductPage[10] , and ask for Product[3] ? Some might confuse this with the Product with primary key 3.

Maybe the following methods might also be handy:

    IPage<T> PreviousPage {get;}
    IPage<T> NextPage {get;}
    IPage<T> GetPage(int pageNr);
    IReadOnlyList<T> PageCollection {get;}
}

First let's create the PageCollection. The PageCollection will create the pages:

class PageCollection<T> : IReadOnlyList<T>
{
    private readonly IDictionary<int, IPage<T>> fetchedPages
        = new Dictionary<int, IPage<T>();

    private int pageCount = -1; // not known yet

    public PageCollection<IEnumerable<T> query, pageSize)
    {
        // TODO: check input parameters for incorrect values
        this.Query = query;
        this.PageSize = pageSize;
    }

    public IEnumerable<T> Query {get;} 
    // TODO: consider IQueryable if you use databases / entity framework

    public int PageSize {get;}
    ...
}

We need methods to fetch the number of Pages, and to fetch a page by index:

public int Count
{
    get
    {
        if (this.pageCount < 0)
           this.pageCount = this.Query.Count();
        return this.pageCount;
    }
}

public IPage this[int pageIndex] => this.GetPage(pageIndex);

Finally we come to the part where a page is created:

public IPage<T> GetPage(int pageIndex)
{
    if (0 < pageIndex || pageIndex >= this.Count)
    {
        // pageIndex out of range.
        // TODO: decide whether to return null or throw exception
    }

    if (!this.fetchedPages.TryGetValue(pageIndex, out Page<T> fetchedPage)
    {
        // page never fetched before, fetch it now
        fetchedPage = this.FetchPage(pageIndex);
        this.FetchedPages.Add(pageIndex, fetchedPage);
    }
    return fetchedPage;
}

I decided to save fetched pages in a Dictonary, rather than in a List. This way you can ask for Page[5] before you have fetched pages 0 to 4.

private Page<T> FetchPage(int pageIndex)
{
    return new Page(this, pageIndex);
}

Well, that didn't do much: apparently it is the page that does all the work. Time to create the Page.

You'll have to decide for yourself whether you read the complete page immediately, or only when you ask for it

class Page<T> : IPage<T>, IReadOnlyCollection<T>, IEnumerable<T>
{
    public Page(PageCollection<T> pageCollection, int pageNr)
    {
         this.PageCollection = pageCollection;
         this.PageNr = pageNr;

         // if you want to fetch the data immediately:
         this.PageContent = this.Query.ToList();
    }

    public PageCollection<T> PageCollection {get;}
    public int PageNr {get;}
    public int PageCount => this.PageCollection.Count;
    public IReadOnlyCollection<T> PageContent {get;}

    public IEnumerable<T> Query => this.PageCollection.Query
        .Skip(this.PageNr * this.PageSize)
        .Take(this.PageSize);
}

The implementations of IReadOnlyCollection<T> and IEnumerable<T> are fairly straight forward, the methods all call this.PageContent :

IEnumerator<T> GetEnumerator() {return this.PageContent.GetEnumerator();}
int Count => this.PageContent.Count;

etc.

"Nice to have" procedures like PreviousPage / NextPage / GetPage are one-liners, because they can be handled by asking the PageCollection:

IPage<T> PreviousPage => this.PageCollection.GetPage(this.PageNr-1);

Of course you have to decide what to do if Page gets out of range: exception or return null?

Finally usage:

const int pageSize = 25;
IEnumerable<Product> queryProducts = ...
PageCollection<Product> productPages =
   new PageCollection<Product>(queryProducts, pageSize);

Page<Product> productPage = productPages.FirstOrDefault();
// this page can be used as a collection of Products
DisplayProducts(productPage);

// get the next page:
Page<Product> nextPage = productPage.NextPage;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM