I just asked a question regarding linq for ranks in C#: Linq List Ranking in C# which was answered. The question was to rank a list of people I have based on their score. So now I would like to add a number of stars that are equal to the person's score. For example:
Rank Name Score Stars
1 John 5 *****
2 Ed 4 ****
3 Sara 3 ***
The stars should be an actual .png star image. My question is, what would be the best way to implement this? Here's an attempt I tried, but it's not working with me:
public class Person
{
public int Rank { get; set; }
public string Name { get; private set; }
public int Score { get; set; }
public Image Star { get; set; }
public Person(int rank, string name, int score, Image star)
{
Rank = rank;
Name = name;
Score = score;
Star = star;
}
}
public ObservableCollection<Person> Persons { get; set; }
public MainWindow()
{
InitializeComponent();
var lines = File.ReadAllLines(filepath);
Persons = new ObservableCollection<Person>();
var data = lines.Select(line => {
var column = line.Split(',');
var name = column[0];
int score = int.Parse(column[1]);
int rank =0;
Image star = new Image();
return new { name, score, avg, rank, star };
});
var groupedData = data.GroupBy(p => p.name).Select((g, i) => new { name = g.Key, rank = i + 1, score = g.Sum(p => p.score), star = new BitmapImage(new Uri(#"\Pics\MinusRate.png", UriKind.Relative))}).OrderByDescending(x => x.score);
var persons = groupedData.Select((p, i) => new Person(i+1, p.name, p.score,p.star));
foreach (var person in persons) {
Persons.Add(person);
}
datagrid.ItemsSource = Persons;
}
So what would be the best approach to include stars into this list?
In my opinion you should definitely put that as UI concern. Don't try to prerender the star images for the memory representation of your data. You have the information there (int Score), that's it. Let the rest do your UI.
Imagine any developer requesting the data for non-UI purposes: He would certainly not expect the code to render images for each query and he might wonder where the memory consumtion (both, in RAM and network traffic) comes from.
Once the data hits the UI, you can do your imaging. For example, if you go for GDI+ for example, you could render the image like this:
// the icon file with one single star
var starIcon = Image.FromFile(...);
// the image of all stars (the width has to be the width of one star multiplied by the count of stars)
var image = new Bitmap(starsCount * starIcon.Width, starIcon.Height);
using (var g = Graphics.FromImage(image))
{
for (int i = 0; i < starCount; i++)
{
// place the star icon to its position in the loop
int x = (i * starIcon.Width);
// https://msdn.microsoft.com/de-de/library/dbsak4dc(v=vs.110).aspx
g.DrawImage(starIcon, x, 0, starIcon.Width, starIcon.Height);
}
g.Flush();
}
Note this is more like pseudo-code out of the head - I did not throw it into a compiler, so it might not compile like that.
Update:
I made a working fiddle over here: https://dotnetfiddle.net/HPB7BR
Note that you won't need GetImageFromURL() it's just to get an image into that online fiddle. And, because I cannot show the image as output in that fiddle, I chose to show the image's size as output. Anyway, the image in variable image is ready to use. You can change the starCount to see how the width of the image changes accordingly.
Related
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;
}
}
}
I am making a quiz game for my computing coursework and I have completed the quiz part of my application and I am currently moving on to my leader board but to do that I need to sort my scores text file which contains the users name, score and time but I am now trying to retrieve these data and display them in a table.
The users' name score and time saves fine into the scores text file but to display these details I first need to order these details by the score.
My test file is organised like this:
username,score,time
userName1,33,12
userName2,-55,33
userName3,34,2
userName4,23,27
userName5,63,72
This is the code that I am currently using but this only works if I have the data in the text file sorted first.
string[] readFile = File.ReadAllLines(file).ToArray();
for (int i = 0; i < 5; i++)
{
string[] userDetails = File.ReadAllLines(file).ToArray();
string username = userDetails[0];
string score = userDetails[1];
// Apply the text of lblUsername1-5 to be what the names
// of the top 5 scorers are in the file.
lblUsername1.Text = userDetails[0].Split(',')[0];
lblUsername2.Text = userDetails[1].Split(',')[0];
lblUsername3.Text = userDetails[2].Split(',')[0];
lblUsername4.Text = userDetails[3].Split(',')[0];
lblUsername5.Text = userDetails[4].Split(',')[0];
// Apply the text of lblScore1-5 to be what the scores
// of the top 5 scorers are in the file.
lblScore1.Text = userDetails[0].Split(',')[1];
lblScore2.Text = userDetails[1].Split(',')[1];
lblScore3.Text = userDetails[2].Split(',')[1];
lblScore4.Text = userDetails[3].Split(',')[1];
lblScore5.Text = userDetails[4].Split(',')[1];
}
So if some one could help me to sort the data in my scores ext file that would be great. Thanks in advance.
You can use linq to sort data from your file
string[][] userDetails = File.ReadAllLines(file).Select(s => s.Split(',')).OrderBy(arr => int.TryParse(arr[1], out int result) ? result : 0)).Take(5).ToArray();
lblUsername1.Text = userDetails[0][0];
lblUsername2.Text = userDetails[1][0];
lblUsername3.Text = userDetails[2][0];
lblUsername4.Text = userDetails[3][0];
lblUsername5.Text = userDetails[4][0];
// Apply the text of lblScore1-5
// to be what the scores of the top 5 scorers are in the file.
lblScore1.Text = userDetails[0][1];
lblScore2.Text = userDetails[1][1];
lblScore3.Text = userDetails[2][1];
lblScore4.Text = userDetails[3][1];
lblScore5.Text = userDetails[4][1];``
You should use objects to manage this. Your class should be a user with properties, se below.
Now you have full control on sorting and managing of your objects
using System.Collections.Generic;
using System.Linq;
public class User
{
public string Name { get; set; }
public int Score { get; set; }
public int Time { get; set; }
}
class Program
{
public static void Main(string[] args)
{
//This you get from file, no need for this in your code
string[] fromFile = new string[5]
{ "userName1,33,12", "userName2,-55,33", "userName3,34,2", "userName4,23,27", "userName5,63,72" };
List<User> users = new List<User>();
foreach (string line in fromFile)
{
string[] splitLine = line.Split(',');
users.Add(new User() { Name = splitLine[0], Score = int.Parse(splitLine[1]), Time = int.Parse(splitLine[2]) });
}
foreach (User oneUser in users.OrderByDescending(x => x.Score))
{
//Here the store to file or what you want to do
}
}
}
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
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);
}
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.