C#: read text file and process it - c#

I need a program in C# which is write out
how many Eric Clapton songs played in the radios.
is there any eric clapton song which all 3 radios played.
how much time they broadcasted eric clapton songs in SUM.
The first columns contain the radio identification(1-2-3)
The second column is about the song playtime minutes
the third column is the song playtime in seconds
the last two is the performer : song
So the file looks like this:
1 5 3 Deep Purple:Bad Attitude
2 3 36 Eric Clapton:Terraplane Blues
3 2 46 Eric Clapton:Crazy Country Hop
3 3 25 Omega:Ablakok
2 4 23 Eric Clapton:Catch Me If You Can
1 3 27 Eric Clapton:Willie And The Hand Jive
3 4 33 Omega:A szamuzott
.................
And more 670 lines.
so far i get this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
namespace radiplaytime
{
public struct Adat
{
public int rad;
public int min;
public int sec;
public Adat(string a, string b, string c)
{
rad = Convert.ToInt32(a);
min = Convert.ToInt32(b);
sec = Convert.ToInt32(c);
}
}
class Program
{
static void Main(string[] args)
{
String[] lines = File.ReadAllLines(#"...\zenek.txt");
List<Adat> adatlista = (from adat in lines
//var adatlista = from adat in lines
select new Adat(adat.Split(' ')[0],
adat.Split(' ')[1],
adat.Split(' ')[2])).ToList<Adat>();
var timesum = (from adat in adatlista
group adat by adat.rad into ertekek
select new
{
rad = ertekek.Key,
hour = (ertekek.Sum(adat => adat.min) +
ertekek.Sum(adat => adat.sec) / 60) / 60,
min = (ertekek.Sum(adat => adat.min) +
ertekek.Sum(adat => adat.sec) / 60) % 60,
sec = ertekek.Sum(adat => adat.sec) % 60,
}).ToArray();
for (int i = 0; i < timesum.Length; i++)
{
Console.WriteLine("{0}. radio: {1}:{2}:{3} playtime",
timesum[i].rad,
timesum[i].hour,
timesum[i].min,
timesum[i].sec);
}
Console.ReadKey();
}
}
}

You can define a custom class to store the values of each line. You will need to use Regex to split each line and populate your custom class. Then you can use linq to get the information you need.
public class Plays
{
public int RadioID { get; set; }
public int PlayTimeMinutes { get; set; }
public int PlayTimeSeconds { get; set; }
public string Performer { get; set; }
public string Song { get; set; }
}
So you then read your file and populate the custom Plays:
String[] lines = File.ReadAllLines(#"songs.txt");
List<Plays> plays = new List<Plays>();
foreach (string line in lines)
{
var matches = Regex.Match(line, #"^(\d+)\s(\d+)\s(\d+)\s(.+)\:(.+)$"); //this will split your line into groups
if (matches.Success)
{
Plays play = new Plays();
play.RadioID = int.Parse(matches.Groups[1].Value);
play.PlayTimeMinutes = int.Parse(matches.Groups[2].Value);
play.PlayTimeSeconds = int.Parse(matches.Groups[3].Value);
play.Performer = matches.Groups[4].Value;
play.Song = matches.Groups[5].Value;
plays.Add(play);
}
}
Now that you have your list of songs, you can use linq to get what you need:
//Get Total Eric Clapton songs played - assuming distinct songs
var ericClaptonSongsPlayed = plays.Where(x => x.Performer == "Eric Clapton").GroupBy(y => y.Song).Count();
//get eric clapton songs played on all radio stations
var radioStations = plays.Select(x => x.RadioID).Distinct();
var commonEricClaptonSong = plays.Where(x => x.Performer == "Eric Clapton").GroupBy(y => y.Song).Where(z => z.Count() == radioStations.Count());
etc.

String splitting works only if the text is really simple and doesn't have to deal with fixed length fields. It generates a lot of temporary strings as well, that can cause your program to consume many times the size of the original in RAM and harm performance due to the constant allocations and garbage collection.
Riv's answer shows how to use a Regex to parse this file. It can be improved in several ways though:
var pattern=#"^(\d+)\s(\d+)\s(\d+)\s(.+)\:(.+)$";
var regex=new Regex(pattern);
var plays = from line in File.ReadLines(filePath)
let matches=regex.Match(line)
select new Plays {
RadioID = int.Parse(matches.Groups[1].Value),
PlayTimeMinutes = int.Parse(matches.Groups[2].Value),
PlayTimeSeconds = int.Parse(matches.Groups[3].Value),
Performer = matches.Groups[4].Value,
Song = matches.Groups[5].Value
};
ReadLines returns an IEnumerable<string> instead of returning all of the lines in a buffer. This means that parsing can start immediatelly
By using a single regular expression, we don't have to rebuild the regex for each line.
No list is needed. The query returns an IEnumerable to which other LINQ operations can be applied directly.
For example :
var durations = plays.GroupBy(p=>p.RadioID)
.Select(grp=>new { RadioID=grp.Key,
Hours = grp.Sum(p=>p.PlayTimeMinutes + p.PlayTimeSecons/60)/60,)
Mins = grp.Sum(p=>p.PlayTimeMinutes + p.PlayTimeSecons/60)%60,)
Secss = grp.Sum(p=> p.PlayTimeSecons)%60)
});
A farther improvement could be to give names to the groups:
var pattern=#"^(?<station>\d+)\s(?<min>\d+)\s(?<sec>\d+)\s(?<performer>.+)\:(?<song>.+)$";
...
select new Plays {
RadioID = int.Parse(matches.Groups["station"].Value),
PlayTimeMinutes = int.Parse(matches.Groups["min"].Value),
...
};
You can also get rid of the Plays class and use a single, slightly more complex LINQ query :
var durations = from line in File.ReadLines(filePath)
let matches=regex.Match(line)
let play= new {
RadioID = int.Parse(matches.Groups["station"].Value),
Minutes = int.Parse(matches.Groups["min"].Value),
Seconds = int.Parse(matches.Groups["sec"].Value)
}
group play by play.RadioID into grp
select new { RadioID = grp.Key,
Hours = grp.Sum(p=>p.Minutes + p.Seconds/60)/60,)
Mins = grp.Sum(p=>p.Minutes + p.Seconds/60)%60,)
Secs = grp.Sum(p=> p.Seconds)%60)
};
In this case, no strings are generated for Performer and Song. That's another benefit of regular expressions. Matches and groups are just indexes into the original string. No string is generated until the .Value is read. This would reduce the RAM used in this case by about 75%.
Once you have the results, you can iterate over them :
foreach (var duration in durations)
{
Console.WriteLine("{0}. radio: {1}:{2}:{3} playtime",
duration.RadioID,
duration.Hours,
duration.Mins,
duration.Secs);
}

Related

Find Range of Most Profitable Products

I have over 1,000 records and I am using this to find the highest value of (profit * volume).
In this case its "DEF" but then I have to open excel and sort by volume and find the range that produces the highest profit.. say excel column 200 to column 800 and then I'm left with say from volume 13450 to volume 85120 is the best range for profits.. how can I code something like that in C# so that I can stop using excel.
public class Stock {
public string StockSymbol { get; set; }
public double Profit { get; set; }
public int Volume { get; set; }
public Stock(string Symbol, double p, int v) {
StockSymbol = Symbol;
Profit = p;
Volume = v;
}
}
private ConcurrentDictionary<string, Stock> StockData = new();
private void button1_Click(object sender, EventArgs e) {
StockData["ABC"] = new Stock("ABC", 50, 14000);
StockData["DEF"] = new Stock("DEF", 50, 105000);
StockData["GHI"] = new Stock("GHI", -70, 123080);
StockData["JKL"] = new Stock("JKL", -70, 56500);
StockData["MNO"] = new Stock("MNO", 50, 23500);
var DictionaryItem = StockData.OrderByDescending((u) => u.Value.Profit * u.Value.Volume).First();
MessageBox.Show( DictionaryItem.Value.StockSymbol + " " + DictionaryItem.Value.Profit);
}
I wrote up something that may or may not meet your requirements. It uses random to seed a set of test data (you can ignore all of that).
private void GetStockRange()
{
var stocks = new Stock[200];
var stockChars = Enumerable.Range(0, 26).Select(n => ((char)n + 64).ToString()).ToArray();
var random = new Random(DateTime.Now.Millisecond);
for (int i = 0; i < stocks.Length; i++)
{
stocks[i] = new Stock(stockChars[random.Next(0, 26)], random.NextDouble() * random.Next(-250, 250), random.Next(1, 2000));
}
var highestPerformaceSelectionCount = 3;
var highestPerformanceIndices = stocks
.OrderByDescending(stock => stock.Performance)
.Take(Math.Max(2, highestPerformaceSelectionCount))
.Select(stock => Array.IndexOf(stocks, stock))
.OrderBy(i => i);
var startOfRange = highestPerformanceIndices.First();
var endOfRange = highestPerformanceIndices.Last();
var rangeCount = endOfRange - startOfRange + 1;
var stocksRange = stocks
.Skip(startOfRange)
.Take(rangeCount);
var totalPerformance = stocks.Sum(stock => stock.Performance);
var rangedPerformance = stocksRange.Sum(stock => stock.Performance);
MessageBox.Show(
"Range Start: " + startOfRange + "\r\n" +
"Range End: " + endOfRange + "\r\n" +
"Range Cnt: " + rangeCount + "\r\n" +
"Total P: " + totalPerformance + "\r\n" +
"Range P: " + rangedPerformance
);
}
The basics of this algorithm to get some of the highest performance points (configured using highestPerformanceSelectionCount, min of 2), and using those indices, construct a range which contains those items. Then take a sum of that range to get the total for that range.
Not sure if I am way off from your question. This may also not be the best way to handle the range. I wanted to post what I had before heading home.
I also added a Performance property to the stock class, which is simply Profit * Volume
EDIT
There is a mistake in the use of the selected indices. The indices selected should be used against the ordered set in order to produce correct ranged results.
Rather than taking the stocksRange from the original unsorted array, instead create the range from the ordered set.
var stocksRange = stocks
.OrderByDescending(stock => stock.Performance)
.Skip(startOfRange)
.Take(rangeCount);
The indices should be gathered from the ordered set as well. Caching the ordered set is probably the easiest route to go.
As is generally the case, there are any number of ways you can go about this.
First, your sorting code above (the OrderByDescending call). It does what you appear to want, more or less, in that it produces an ordered sequence of KeyValuePair<string, Stock> that you can then choose from. Personally I'd just have sorted StockData.Values to avoid all that .Value indirection. Once sorted you can take the top performer as you're doing, or use the .Take() method to grab the top n items:
var byPerformance = StockData.Values.OrderByDescending(s => s.Profit * s.Volume);
var topPerformer = byPerformance.First();
var top10 = byPerformance.Take(10).ToArray();
If you want to filter by a particular performance value or range then it helps to pre-calculate the number and do your filtering on that. Either store (or calculate) the Performance value in the class, calculate it in the class with a computed property, or tag the Stock records with a calculated performance using an intermediate type:
Store in the class
public class Stock {
// marking these 'init' makes them read-only after creation
public string StockSymbol { get; init; }
public double Profit { get; init; }
public int Volume { get; init; }
public double Performance { get; init; }
public Stock(string symbol, double profit, int volume)
{
StockSymbol = symbol;
Profit = profit;
Volume = volume;
Performance = profit * volume;
}
}
Calculate in class
public class Stock
{
public string StockSymbol { get; set; }
public double Profit { get; set; }
public int Volume { get; set; }
public double Performance => Profit * Volume;
// you know what the constructor looks like
}
Intermediate Type (with range filtering)
// let's look for those million-dollar items
var minPerformance = 1000000d;
var highPerformance = StockData.Values
// create a stream of intermediate types with the performance
.Select(s => new { Performance = s.Profit * s.Volume, Stock = s })
// sort them
.OrderByDescending(i => i.Performance)
// filter by our criteria
.Where(i => i.Performance >= minPerformance)
// pull out the stocks themselves
.Select(i => i.Value)
// and fix into an array so we don't have to do this repeatedly
.ToArray();
Ultimately though you'll probably end up looking for ways to store the data between runs, update the values and so forth. I strongly suggest looking at starting with a database and learning how to do things there. It's basically the same, you just end up with a lot more flexibility in the way you handle the data. The code to do the actual queries looks basically the same.
Once you have the data in your program, there are very few limits on how you can manipulate it. Anything you can do in Excel with the data, you can do in C#. Usually easily, sometimes with a little work.
LINQ (Language-Integrated Native Query) makes a lot of those manipulations trivial, with extensions for all sorts of things. You can take the average performance (with .Average()) and then filter on those that perform 10% above it with some simple math. If the data follows some sort of Normal Distribution you can add your own extension (or use this one) to figure out the standard deviation... and now we're doing statistics!
The basic concepts of LINQ, and the database languages it was roughly based on, give you plenty of expressive power. And Stack Overflow is full of people who can help you figure out how to get there.
try following :
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
List<Stock> stocks = null;
public Form1()
{
InitializeComponent();
stocks = new List<Stock>() {
new Stock("ABC", 50, 14000),
new Stock("DEF", 50, 105000),
new Stock("GHI", -70, 123080),
new Stock("JKL", -70, 56500),
new Stock("MNO", 50, 23500)
};
}
private void button1_Click(object sender, EventArgs e)
{
DataTable dt = Stock.GetTable(stocks);
dataGridView1.DataSource = dt;
}
}
public class Stock {
public string StockSymbol { get; set; }
public double Profit { get; set; }
public int Volume { get; set; }
public Stock(string Symbol, double p, int v) {
StockSymbol = Symbol;
Profit = p;
Volume = v;
}
public static DataTable GetTable(List<Stock> stocks)
{
DataTable dt = new DataTable();
dt.Columns.Add("Symbol", typeof(string));
dt.Columns.Add("Profit", typeof(int));
dt.Columns.Add("Volume", typeof(int));
dt.Columns.Add("Volume x Profit", typeof(int));
foreach(Stock stock in stocks)
{
dt.Rows.Add(new object[] { stock.StockSymbol, stock.Profit, stock.Volume, stock.Profit * stock.Volume });
}
dt = dt.AsEnumerable().OrderByDescending(x => x.Field<int>("Volume x Profit")).CopyToDataTable();
return dt;
}
}
}

C# append text to a certain line

I try to build some table inside a text file which
look like this:
Name Grade
--------------------
John 100
Mike 94
...
...
I have this bunch of code:
List<string> NamesList = new List<string>();
List<int> Grades = new List<int>();
Grades.Add(98);
Grades.Add(100);
NamesList.Add("John");
NamesList.Add("Alon");
if (NamesList.Count() == Grades.Count())
{
var length = NamesList.Count();
var min = Grades.Min();
var max = Grades.Max();
using (System.IO.StreamWriter myF =
new System.IO.StreamWriter(#"C:\Users\axcel\textfolder\myFile.txt", true))
{
for (int i = 0; i < length; i++)
{
if (i == 0)
{
myF.WriteLine("Name Age Grade");
myF.WriteLine("==================================");
}
myF.WriteLine(NamesList.ElementAt(i));
myF.WriteLine(" ");
myF.WriteLine(Grades.ElementAt(i));
}
}
}
but my problem is that writing the grades after the names it is writing in a new line. I thought of writing it together to a string and to streaming it but I want to avoid an extra computing...
How can I solve it?
WriteLine() always add a new line after your text. So in your case it should be
myF.Write(NamesList.ElementAt(i));
myF.Write(" ");
myF.WriteLine(Grades.ElementAt(i));
var students = new List<(string name, int age, int grade)>()
{
("John", 21, 98),
("Alon", 45, 100)
};
students.Add(("Alice", 35, 99));
using (var writer = new StreamWriter("myFile.txt"))
{
writer.WriteLine(string.Join("\t", "Name", "Age", "Grade"));
foreach(var student in students)
{
writer.WriteLine(string.Join("\t", student.name, student.age, student.grade));
}
}
As some comments have suggested you could use a Student class to group name, age and grade. In this example I've used a Value Tuple instead.
You can see how it improves the readability of the code and you can focus on the problem you are actually trying to solve. You can reduce your write operation to a simple, readable expression - meaning you are less likely to make mistakes like mixing up Write and WriteLine.
You can always align the text by using string interpolation alignment.
To follow some of the comments, I also urge you to build a class holding the values.
public class Student
{
public string Name { get; set; }
public int Age { get; set; }
public int Grade { get; set; }
}
And here is the code using string interpolation alignment
var students = new List<Student>
{
new Student {Name = "John", Age = 10, Grade = 98},
new Student {Name = "Alon", Age = 10, Grade = 100}
};
var minGrade = students.Min(s => s.Grade);
var maxGrade = students.Max(s => s.Grade);
using (var myF = new System.IO.StreamWriter(#"C:\Users\axcel\textfolder\myFile.txt", true))
{
myF.WriteLine($"{"Name",-15}{"Age",-10}{"Grade",5}");
myF.WriteLine("==============================");
foreach (var student in students)
{
myF.WriteLine($"{student.Name,-15}{student.Age,-10}{student.Grade,5}");
}
}
This will produce the following result:
Name Age Grade
==============================
John 10 98
Alon 10 100
Positive numbers are right-aligned and negative numbers are left-aligned
You can read more about it on the string interpolation page at Microsoft Docs
To solve the issue you are having, you could just use:
myF.WriteLine(NamesList.ElementAt(i) + " " + Grades.ElementAt(i));
However the code you provided would benefit from being modified as described in the comments (create a class, use FileHelpers, etc.)
Solution 1:
Why you are not trying the concatenating the two string like:
string line = NamesList.ElementAt(i) + " " + Grades.ElementAt(i);
myF.WriteLine(line);
OR
Solution 2:
What you are using is WriteLine("Text") function which always writes the text to next line. Instead you can use Write("Text") function which will write the string on same line. you can try like:
myF.Write(NamesList.ElementAt(i));
myF.Write(" ");
myF.Write(Grades.ElementAt(i));
myF.WriteLine(); // Here it will enter to new line

c# Match between two lists, then add up time in between the two lists

I have two lists and a class
public class CommonLog
{
public string Break { get; set; }
public string Cart { get; set; }
public string Length { get; set; }
}
This is list one
commonlog.Add(new CommonLog { Break = breakTimeVar, Cart = cartVar,
Length = lengthHours });
and one like this list 2
commonlog2.Add(new CommonLog { Break = breakTimeVar2, Cart = cartVar2,
Length = lengthHours2 });
The two pieces of information I need to match are as follows
List 1 contains this
0016 009130 00:01:30
List 2 Contains this
0016 0066486 00:00:30
0016 0050093 00:00:30
0016 0063791 00:00:30
I need to match up the first number 0016 between the two lists, and then add up the last numbers 00:00:30 (3 x 30 seconds) from list 2 and compare that total time against list 1 total time, and then make a decision based on if the total of the last numbers (time) from list 2 equal list 1
How would I achieve that?
Here it is a LINQ solution which aggregates your List 2 entries in a similar (but more compact) way of Romoku answer:
var groupedLogs = commonlog2
.GroupBy(c => c.Break, c => TimeSpan.Parse(c.Length))
// group logs by Break, and get the TimeSpan representation of Length
// for each entry of the group
.ToDictionary(g => g.Key, g => g.Aggregate(TimeSpan.Zero, (s, c) => s + c));
// create a dictionary and aggregate each log group into sums of TimeSpans
Then you may iterate through each item of commonlog and compare the results:
foreach(var log in commonlog)
{
TimeSpan sum;
groupedLogs.TryGetValue(log.Break, out sum);
if(sum == TimeSpan.Parse(log.Length))
{
// do something
}
}
Or a one liner way to get only matching entries from commonlog (using C# 7 features):
var matching = commonlog.Where(
l => groupedLogs.TryGetValue(l.Break, out TimeSpan v)
&& TimeSpan.Parse(l.Length) == v);
You can group the individual breaks using GroupBy then loop through the aggregate breaks to find matches.
To sum the individual breaks there is Aggregate.
I recommend using TimeSpan instead of string for the Length.
Data
var totalBreaks = new List<CommonLog>
{
new CommonLog
{
Break = "0016",
Cart = "009130",
Length = "00:01:30"
}
};
var individualBreaks = new List<CommonLog>
{
new CommonLog
{
Break = "0016",
Cart = "0066486",
Length = "00:00:30"
},
new CommonLog
{
Break = "0016",
Cart = "0050093",
Length = "00:00:30"
},
new CommonLog
{
Break = "0016",
Cart = "0063791",
Length = "00:00:30"
}
};
Logic
//Group the individual breaks by their Break
var breakGroups = individualBreaks.GroupBy(x => x.Break);
// Loop through the aggregates
foreach (var totalBreak in totalBreaks)
{
// Match the aggregate to the individual
// The Key is the Break for all individual breaks in the group
var breaks = breakGroups.FirstOrDefault(x => x.Key == totalBreak.Break);
// Do we have a match?
if (breaks == null)
{
continue;
}
var breakLength = TimeSpan.Parse(totalBreak.Length);
// Add up the individual breaks with Aggregate
var breakTotal =
breaks
.Aggregate(
TimeSpan.Zero, // Initial break is 00:00:00
(time, b) => // Add each break to the initial
time.Add(TimeSpan.Parse(b.Length)));
// Does the break length match the total number of breaks?
if (breakLength == breakTotal)
{
}
}

finding max value in a c# array

I am making a program in c# that will take in a list of names and scores from a text document, get the score on its own and then find the highest of the scores. I can separate the name from the score when it is just one but as soon as I try make it an array I do not have any idea what I am doing.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
class Program
{
static void Main(string[] args)
{
System.IO.File.Exists(#"U:\StudentExamMarks.txt");
string[] text = System.IO.File.ReadAllLines(#"U:\StudentExamMarks.txt");
int a = 0;
string[] results = new string[a];
for(int i=0;i<text.Length ; i++ )
{
int x = text[i].LastIndexOf("\t");
int y = text[i].Length;
int z = (y - (x + 1));
results[a] = text[i].Substring((x+1), (z));
a++;
Console.WriteLine("{0}", results);
}
}
}
This is what I have so far
the list is as follows
John Cross 100
Christina Chandler 105
Greg Hamilton 107
Pearl Becker 111
Angel Ford 115
Wendell Sparks 118
like I said when I attempted it without an array I can get it to display the 100 from the first result. I also do not know how when I find the largest result how to link it back to the students name.
I suggest to use a class to hold all properties, that improves readability a lot:
public class StudentExam
{
public string StudentName { get; set; }
public int Mark { get; set; }
}
and following to read all lines and to fill a List<StudentExam>:
var lines = File.ReadLines(#"U:\StudentExamMarks.txt")
.Where(l => !String.IsNullOrWhiteSpace(l));
List<StudentExam> studentsMarks = new List<StudentExam>();
foreach (string line in lines)
{
string[] tokens = line.Split('\t');
string markToken = tokens.Last().Trim();
int mark;
if (tokens.Length > 1 && int.TryParse(markToken, out mark))
{
StudentExam exam = new StudentExam{
Mark = mark,
StudentName = String.Join(" ", tokens.Take(tokens.Length - 1)).Trim()
};
studentsMarks.Add(exam);
}
}
Now it's easy to get the max-mark:
int maxMark = studentsMarks.Max(sm => sm.Mark); // 118
To find the highest score, you can use Linq with Regex like this
var lines = new[] {
"John Cross 100",
"Christina Chandler 105",
"Greg Hamilton 107",
"Pearl Becker 111"
};
var maxScore = lines.Max(l => int.Parse(Regex.Match(l, #"\b\d+\b").Value));
Here, I'm assuming you have read the file correctly into lines and all of them has a valid int value of the score.
If the end of each entry is always a space followed by the student's score, you can use a simple substring:
int max = text.Max(x => Convert.ToInt32(x.Substring(x.LastIndexOf(' '))));
For each entry, create a substring that starts at the last index of ' ' and then convert that to an integer. Then return the max of those values.

How to count occurences of number stored in file containing multiple delimeters?

This is my input store in file:
50|Carbon|Mercury|P:4;P:00;P:1
90|Oxygen|Mars|P:10;P:4;P:00
90|Serium|Jupiter|P:4;P:16;P:10
85|Hydrogen|Saturn|P:00;P:10;P:4
Now i will take my first row P:4 and then next P:00 and then next like wise and want to count occurence in every other row so expected output will be:
P:4 3(found in 2nd row,3rd row,4th row(last cell))
P:00 2 (found on 2nd row,4th row)
P:1 0 (no occurences are there so)
P:10 1
P:16 0
etc.....
Like wise i would like to print occurence of each and every proportion.
So far i am successfull in splitting row by row and storing in my class file object like this:
public class Planets
{
//My rest fields
public string ProportionConcat { get; set; }
public List<proportion> proportion { get; set; }
}
public class proportion
{
public int Number { get; set; }
}
I have already filled my planet object like below and Finally my List of planet object data is like this:
List<Planets> Planets = new List<Planets>();
Planets[0]:
{
Number:50
name: Carbon
object:Mercury
ProportionConcat:P:4;P:00;P:1
proportion[0]:
{
Number:4
},
proportion[1]:
{
Number:00
},
proportion[2]:
{
Number:1
}
}
Etc...
I know i can loop through and perform search and count but then 2 to 3 loops will be required and code will be little messy so i want some better code to perform this.
Now how do i search each and count every other proportion in my planet List object??
Well, if you have parsed proportions, you can create new struct for output data:
// Class to storage result
public class Values
{
public int Count; // count of proportion entry.
public readonly HashSet<int> Rows = new HashSet<int>(); //list with rows numbers.
/// <summary> Add new proportion</summary>
/// <param name="rowNumber">Number of row, where proportion entries</param>
public void Increment(int rowNumber)
{
++Count; // increase count of proportions entries
Rows.Add(rowNumber); // add number of row, where proportion entry
}
}
And use this code to fill it. I'm not sure it's "messy" and don't see necessity to complicate the code with LINQ. What do you think about it?
var result = new Dictionary<int, Values>(); // create dictionary, where we will storage our results. keys is proportion. values - information about how often this proportion entries and rows, where this proportion entry
for (var i = 0; i < Planets.Count; i++) // we use for instead of foreach for finding row number. i == row number
{
var planet = Planets[i];
foreach (var proportion in planet.proportion)
{
if (!result.ContainsKey(proportion.Number)) // if our result dictionary doesn't contain proportion
result.Add(proportion.Number, new Values()); // we add it to dictionary and initialize our result class for this proportion
result[proportion.Number].Increment(i); // increment count of entries and add row number
}
}
You can use var count = Regex.Matches(lineString, input).Count;. Try this example
var list = new List<string>
{
"50|Carbon|Mercury|P:4;P:00;P:1",
"90|Oxygen|Mars|P:10;P:4;P:00",
"90|Serium|Jupiter|P:4;P:16;P:10",
"85|Hydrogen|Saturn|P:00;P:10;P:4"
};
int totalCount;
var result = CountWords(list, "P:4", out totalCount);
Console.WriteLine("Total Found: {0}", totalCount);
foreach (var foundWords in result)
{
Console.WriteLine(foundWords);
}
public class FoundWords
{
public string LineNumber { get; set; }
public int Found { get; set; }
}
private List<FoundWords> CountWords(List<string> words, string input, out int total)
{
total = 0;
int[] index = {0};
var result = new List<FoundWords>();
foreach (var f in words.Select(word => new FoundWords {Found = Regex.Matches(word, input).Count, LineNumber = "Line Number: " + index[0] + 1}))
{
result.Add(f);
total += f.Found;
index[0]++;
}
return result;
}
I made a DotNetFiddle for you here: https://dotnetfiddle.net/z9QwmD
string raw =
#"50|Carbon|Mercury|P:4;P:00;P:1
90|Oxygen|Mars|P:10;P:4;P:00
90|Serium|Jupiter|P:4;P:16;P:10
85|Hydrogen|Saturn|P:00;P:10;P:4";
string[] splits = raw.Split(
new string[] { "|", ";", "\n" },
StringSplitOptions.None
);
foreach (string p in splits.Where(s => s.ToUpper().StartsWith(("P:"))).Distinct())
{
Console.WriteLine(
string.Format("{0} - {1}",
p,
splits.Count(s => s.ToUpper() == p.ToUpper())
)
);
}
Basically, you can use .Split to split on multiple delimiters at once, it's pretty straightforward. After that, everything is gravy :).
Obviously my code simply outputs the results to the console, but that part is fairly easy to change. Let me know if there's anything you didn't understand.

Categories