George Vasilakis
George Vasilakis

Reputation: 15

How to check how many times a number appears in a specific line inside a text file

So this is my code so far. Its a mess I know, any ideas about how I can make it work?

I want to output how many times a number appears inside a textfile. I want to get the numbers from a specific line inside the code that starts with the letter Time

Count the total number of members across all 3 time slots

The text file is like this:

*****************************
Participant: 1

Location: UK
Name: George 
Phone Number: 69347653633
Time Slot: 1
*****************************

*****************************
Participant: 2

Location: FR
Name: Alex 
Phone Number: 69635343623
Time Slot: 2
*****************************

*****************************
Participant: 3

Location: gr
Name: Maria 
Phone Number: 694785896
Time Slot: 3
*****************************

For example, I want an output like this:

Total Member Registered for Slot 1: 5

Total Member Registered for Slot 2: 1

Total Member Registered for Slot 3: 3

The numbers are in a range of 1 to 3

The Output that i get so far is:

1 was found 1 times
1 was found 1 times
1 was found 1 times
2 was found 1 times
3 was found 1 times
1 was found 1 times
1 was found 1 times
1 was found 1 times
1 was found 1 times
1 was found 1 times

Any ideas about how I can improve it and fix it?

public static void ReadNumbers()
{
    // Declare list
    string[] lines = File.ReadAllLines("text.TXT");

    IEnumerable<string> selectLines = lines.Where(line => line.StartsWith("Time"));

    foreach (var item in selectLines)
    {
        var getNumbers = (from num in item where char.IsNumber(num) select num).ToArray();

        //Console.WriteLine(new string(getNumbers));
        getNumbers.ToArray();

        foreach (var group in getNumbers.GroupBy(n => n))
        {
            Console.WriteLine("{0} was found {1} times", group.Key, group.Count());
        }
    }
}

Upvotes: 1

Views: 262

Answers (3)

Trevor
Trevor

Reputation: 8004

Here's an alternative solution using a different approach, not the best, but an alternative. A few notes:

  • Could use better error checking?
  • You have a lazy IEnumerable<Participant> with all their properties; you now can use them when ever.

You can create a new class that would represent your data (notes in code):

public class Participant
{

    public IEnumerable<Participant> Participants {get; set;}

    public int ParticipantNumber { get; set; }

    public string ParticipantName { get; set; }

    public string ParticipantLocation { get; set; }

    public long ParticipantPhoneNumber { get; set; }

    public int ParticipantTimeSlot { get; set; }

    public void GetParticipants()
    {
        IEnumerable<string> lines = null;
        using (OpenFileDialog ofd = new OpenFileDialog())            
            if (ofd.ShowDialog() == DialogResult.OK)
                // remove empty lines and lines that start with *
                lines = File.ReadLines(ofd.FileName).Where(line => !string.IsNullOrEmpty(line) && !line.StartsWith("***"));
        // if we don't have anything return
        if (lines == null)
            return;
        // get all our participants we can based on just 5 fields
        Participants = lines.Select((value, index) => new { value, index})
            .GroupBy(grp => grp.index / 5, myVal => myVal.value)
            .Select(val => new Participant() 
                            { 
                                ParticipantNumber = int.TryParse(val.Select(s => s).Where(s=> s.StartsWith("Participant:"))
                                .FirstOrDefault().Replace("Participant:", string.Empty).Trim(), out int parNumber) ? parNumber : 0,
                                ParticipantLocation = val.Select(s => s).Where(s => s.StartsWith("Location:"))
                                .FirstOrDefault().Replace("Location:", string.Empty).Trim(),
                                ParticipantName = val.Select(s => s).Where(s => s.StartsWith("Name:"))
                                .FirstOrDefault().Replace("Name:", string.Empty).Trim(),
                                ParticipantPhoneNumber = long.TryParse(val.Select(s => s).Where(s => s.StartsWith("Phone Number:"))
                                .FirstOrDefault().Replace("Phone Number:", string.Empty).Trim(), out long parPhone) ? parPhone : 0,
                                ParticipantTimeSlot = int.TryParse(val.Select(s => s).Where(s => s.StartsWith("Time Slot:"))
                                .FirstOrDefault().Replace("Time Slot:", string.Empty).Trim(), out int parTime) ? parTime : 0
            }) ;            
    }              
}

public static class LinqExtentions
{
 // Extension method by: Chris St Clair
    public static IEnumerable<IEnumerable<T>> GroupWhile<T>(this IEnumerable<T> seq, Func<T, T, bool> condition)
    {
        T prev = seq.First();
        List<T> list = new List<T>() { prev };

        foreach (T item in seq.Skip(1))
        {
            if (condition(prev, item) == false)
            {
                yield return list;
                list = new List<T>();
            }
            list.Add(item);
            prev = item;
        }

        yield return list;
    }               

}

Next where ever you want to load these participants at you can throw this in:

 Participant participant = new Participant(); // create new instance
 participant.GetParticipants(); // actual grab the file and parse it

 // here we actually group our participants based on your condition
 var query = participant.Participants.GroupBy(p => p.ParticipantTimeSlot).Select(pNew => new { SlotNumber = pNew.ToList()[0].ParticipantTimeSlot, Count = pNew.Count() });

 // finally write all the data out  
 Console.WriteLine(string.Join(Environment.NewLine, query.Select(a => $"Total Member Registered for Slot {a.SlotNumber}: {a.Count}")));

Here's my output:

enter image description here

Based on this file structure:

enter image description here

Update

Here's a query to print their id's and names:

 var getNames = participant.Participants.Select(pNew => new { PartName = pNew.ParticipantName, PartNumber = pNew.ParticipantNumber });
 Console.WriteLine(string.Join(Environment.NewLine, getNames.Select(a => $"Participant {a.PartNumber} name: {a.PartName}")));

The output of this:

enter image description here

If there's something you don't understand please let my self know, again, comments are through-out code.

Side Note:

You may need to make sure to import a few namespaces as well:

  • using System.Linq;
  • using System.Collections.Generic;

References:

Grouping sequential blocks of data using Linq - Chris St Clair

Upvotes: 0

Darren Ruane
Darren Ruane

Reputation: 2485

Updated Answer

I see that you have made a few edits to your original question and left comments on another answer which leaves this answer in need of a few adjustments. Specifically, you appear to want the results to be displayed in ascending order (from slot 1 to 3). You also said that:

I want also if a slot has not any appear in the file to display Number 2 appeared 0 times.

So here is my proposed solution:

public static void ReadNumbers()
{
    string[] lines = File.ReadAllLines("text.TXT");

    var groups = lines
        .Where(line => line.StartsWith("Time"))
        .Select(line => Int32.Parse(new String(line.Where(Char.IsDigit).ToArray())))
        .GroupBy(number => number);

    for(int i = 1; i <= 3; i ++)
    {
        var count = groups.FirstOrDefault(group => group.Key == i)?.Count() ?? 0;
        Console.WriteLine($"Total Members Registered for Slot {i}: {count}");
    }
}

Note: This code is untested but should work.

I would also like to add that it is generally not considered good etiquette to make changes to your question after accepting an answer, such that the changes require a change to said answer. Typically you would ask a new question in such a case.

Original Answer

Here's how I would do it:

public static void ReadNumbers()
{
    string[] lines = File.ReadAllLines("text.TXT");

    var groups = lines
        .Where(line => line.StartsWith("Time"))
        .Select(line => Int32.Parse(new string(line.Where(Char.IsDigit).ToArray())))
        .GroupBy(number => number);

    foreach(var group in groups)
        Console.WriteLine($"{group.Key} appeared: {group.Count()} times");
}

Note that this approach assumes that your file follows the same format that you showed in your question.

It will also throw an error should your file have any occurrences of "Time" without also containing a number in the same line. For example, if your file contains a line like: "Time Slot: " or "Time Slot: SomeValueThatIsNotANumber" then it will throw.

Upvotes: 3

PajLe
PajLe

Reputation: 924

If you want to print every time slot even if they are not used, a general solution would be:

public static void ReadNumbers()
{
    string[] lines = File.ReadAllLines("text.TXT");

    // timeSlots[i] - how many members are registered in i-th time slot
    // 4 is number of time slots minus 1 (we skip the 0th element for convenience)
    int[] timeSlots = new int[4]; 

    var groups = lines
        .Where(line => line.StartsWith("Time"))
        .Select(line => Int32.Parse(new string(line.Where(Char.IsDigit).ToArray())))
        .GroupBy(number => number);

    foreach (var group in groups)
    {
        // group.Key - occupied time slot number
        // group.Count() - how many members in the occupied time slot

        if (group.Key < timeSlots.Count())
        {
            timeSlots[group.Key] = group.Count();
        }
    }

    for (int i = 1; i < timeSlots.Count(); i++)
    {
        Console.WriteLine($"Time slot {i} appeared: {timeSlots[i]} times");
    }

}

Upvotes: 1

Related Questions