I have a table of data like this.
I want to perform numerous operations on it like
How many times was PLAY 'no' when it was sunny
How many times was WINDY 'true' when PLAY was 'yes'
What data structure should I use?
right now I have simple array which is coming out to be one heck of a task to control.
string[,] trainingData = new string[noOfRecords,5];
using (TextReader reader = File.OpenText("input.txt"))
{
int i =0;
string text = "";
while ((text = reader.ReadLine()) != null)
{
string[] bits = text.Split(' ');
for (int j = 0; j < noOfColumnsOfData; j++)
{
trainingData[i, j] = bits[j];
}
i++;
}
}
To widen #Doan cuong's anwer,
I would use an enumarable list of objects.
each object can be calle: Record and the collection can be called Table.
(Table is IEnumarable).
Here is a simple example:
static void Main(string[] args)
{
Table table = new Table();
int count1 = table.records.Where(r => r.Play == false && r.Outlook.ToLower() == "sunny").Count();
}
public class Record
{
public bool Play;
public string Outlook;
}
public class Table
{
//This should be private and Table should be IEnumarable
public List<Record> records = new List<Record>();
}
this is a highly uncomfortable question because it's about opinion more then a valid programing question. a lot of programmers would say this or that. for the table you are showing there is no problem using array as you did for simple queries as you mentioned. for more complex data and queries i would suggest you'll take the time and effort to study LINQ
Create a class and write the values to properties. I.e.:
public class Weather
{
public string Outlook {get;set;}
...
}
And then store them into a List<Weather> collection (during your loop). Like already said, you can run LINQ queries on it. The internet is full of example how to use LINQ.
Related
I'm not that great with coding and all since we've started learning the basics this year, and my groupmates aren't doing anything to help me, so I'm lost here and wanted to seek help from all the experts that are here.
I was wondering how I'd be able to sort the mixed data inside the Textbox properly
(I'm using the console.application to access windows.Forms because that's what our professor wants us to do) . As you can see in the image below, it gets arranged alphabetically instead of sorting the score in descending order with the names of the player beside their scores.
void show_Click(object sender, EventArgs e)
{
int counter = 0;
string line;
StreamReader TXT = new StreamReader("Leaderboard.txt");
List<string> list = new List<string>();
while ((line = TXT.ReadLine()) != null)
{
ListB1.Items.Add(line);
list.Add(line);
counter++;
}
string[] arr = list.ToArray();
Array.Sort(arr);
Array.Reverse(arr);
foreach (string item in arr)
{
ListB2.Items.Add(item);
}
this is barely a fraction of my whole code but this is the part I think is most essential to making my leaderboards function properly.
this is what my code results to, as of now...
Currently you're reading each line of the file as a single string. So there's no difference between "score" and "text", it's all just one text value.
It sounds like what you want is to parse that information into an object with two different properties. For example, suppose you have this class:
public class ScoreListing
{
public int Score { get; set; }
public string Text { get; set; }
}
Then your list would be something like:
List<ScoreListing> list = new List<ScoreListing>();
And to add to it, you'd need to parse information from line to create a new ScoreListing object. For example, it might look something like this:
while ((line = TXT.ReadLine()) != null)
{
var elements = line.Split(' | ');
var listing = new ScoreListing
{
Score = int.Parse(elements[0]),
Text = elements[1]
};
list.Add(listing);
counter++;
}
Once you have a populated List<ScoreListing> you can sort it with .OrderBy(), for example:
var sortedLeaderboard = list.OrderByDescending(l => l.Score);
There are multiple ways to approach the storage of the data, sorting the data, etc. But overall when you have a value like "123 | SomeText" then what you really have is a custom-serialized object with two distinct properties. Your first goal when reading this data into memory should be to de-serialize it into that meaningful object. Then you can easily use that object however you like.
By request, a bit more information on converting data back into a string...
The most object-oriented approach would be to override .ToString() on the class:
public class ScoreListing
{
public int Score { get; set; }
public string Text { get; set; }
public override string ToString()
{
return $"{Score} | {Text}";
}
}
Then you can add it to your list of strings:
foreach (var item in sortedLeaderboard)
{
ListB2.Items.Add(item.ToString());
}
Without overriding .ToString() you'd just manually perform the same operation:
foreach (var item in sortedLeaderboard)
{
ListB2.Items.Add($"{item.Score} | {item.Text}");
}
I want to iterate through my table, check to see if the Quantity received is higher than the quantity expected for each row, and if so execute some code.
The way I've done it below feels amateurish.
bool allRecieved = true;
foreach (DataRow row in SubVwr.Tables[0].Tbl.Rows)
{
if(row["QtyRcvd"] < row["QtyPer"]) allRecieved = false;
}
if(allRecieved) // execute code
You can use LINQ, for better readability (presuming those are int-columns):
bool allRecieved = SubVwr.Tables[0].Tbl.AsEnumerable()
.All(row => row.Field<int>("QtyRcvd") >= row.Field<int>("QtyPer"));
An advantage over your current loop is that this will stop as soon as one record doesn't match this condition. Your loop will continue until end without break.
This is a bit radical, but I'd start by not using DataTable, and instead use a simple model:
public class SomeType {
// I'd probably name these QuantityReceived etc, but... meh
public int QtyRcvd {get;set;}
public int QtyPer {get;set;}
// ... etc
}
Then I can very conveniently check properties etc. For example, to mirror Tim Schmelter's LINQ answer:
List<SomeType> someData = ...
var allReceived = someData.All(x => x.QtyRcvd >= x.QtyPer);
so now all we need is to load SomeType from the DB, which is where ORMs and micro-ORMs excel. For example, with "Dapper", this would be:
string region = "North"; // just to show parameter usage
var someData = connection.Query<SomeType>(
#"select * from SomeTable where Region=#region", new { region }).AsList();
I am getting data via a SqlDataReader and now looping through the results and putting the results in the list. I am trying to add 2 columns to each list, but I am unsuccessful.
Here is my code:
for (int i = 0; i < reader.FieldCount; i++)
{
List<string> costs = new List<string>();
if (reader.GetName(i).ToString().Contains("TotalCost"))
{
costs.Add(reader.GetValue(i).ToString());
}
if (reader.GetName(i).ToString().Contains("SqftCost"))
{
costs.Add(reader.GetValue(i).ToString());
}
jobList.Add(costs);
}
But this puts the two columns in separate lists, I really need the 2 columns in one list.
The reason I am doing it like this is because I have columns that are called TotalCost101, SqftCost101, TotalCost102, SqftCost102, TotalCost104, SqftCost104. So each column that contains TotalCost and SqftCost should be in its own list. I hope this makes sense, anyone got any ideas on how to put these 2 columns in their own list. So at the end I will have a bunch of lists with 2 values.
I updated my code so I now use a Class instead of a List
for (int i = 0; i < reader.FieldCount; i++)
{
CostMatrix costs = new CostMatrix();
if (reader.GetName(i).ToString().Contains("TotalCost"))
{
costs.TotalCost = reader.GetValue(i).ToString();
}
if (reader.GetName(i).ToString().Contains("SqftCost"))
{
costs.sqftCost = reader.GetValue(i).ToString();
}
jobList.Add(costs);
}
Here is the current output:
<d3p1:CostMatrix>
<d3p1:TotalCost>550</d3p1:TotalCost>
<d3p1:sqftCost i:nil="true"/>
</d3p1:CostMatrix>
<d3p1:CostMatrix>
<d3p1:TotalCost i:nil="true"/>
<d3p1:sqftCost>0.41</d3p1:sqftCost>
</d3p1:CostMatrix>
What I am looking for is:
<d3p1:CostMatrix>
<d3p1:TotalCost>550</d3p1:TotalCost>
<d3p1:sqftCost>0.41</d3p1:sqftCost>
</d3p1:CostMatrix>
Honestly, I would use an object.
public class Price
{
public decimal Sqft { get; set; }
public decimal Total { get; set; }
}
You have an object that actually represents something tangible. You're clearly indicating what type of price is applicable. This will avoid confusion with other people working on the project and for you, with an expressive usage. Nothing is being obfuscated into a Tuple or string.
Then when you use the data reader, you could do something along these lines:
public static T GetValueOrNull<T>(this IDataReader reader, string column)
{
int ordinal;
if(!string.IsNullOrEmpty(column) && !reader.IsDBNull(reader.GetOrdinal(column)))
if(int.TryParse(reader.GetOrdinal(column).ToString(), out ordinal))
return (T)reader.GetValue(ordinal);
return default(T);
}
You can basically tell this, "which column" then assign it to that property. This could also be handled by some form of object relational mapper.
// Example:
List<Product> products = db.GetProducts();
var example = products.Where(o => o.Price.Sqft >= 5.00);
var sample = products.Where(o => o.Price.Total <= 5.00);
You can store Price inside of another object, allowing a web-site to filter a product based on how multiple types of price values, for instance. It has other benefits as well, it will also document your code nicely, to know how pricing may be implemented.
Not search for a collection of strings, how would that persist throughout your application? A List<string> would be hard to find all implementations for price, unless seeking a data attribute. But these are a bunch of reasons.
Hope this clarifies a bit.
Based on your update, you could do:
public class CostMatrix
{
public ConcurrentList<decimal> Total { get; set; }
public ConcurrentList<decimal> Sqft {get; set; }
}
Your object would have two separate list, then as you read through the table column by column and row by row, you could simply add. So if you used the above static method it would be:
using(var connection = new SqlConnection(dbConnection))
using(var command = new SqlCommand(query, dbConnection))
using(var reader = new SqlDataReader())
while(reader.Read())
{
Total.Add(GetValueOrNull<decimal>(reader, "TotalCost");
Sqft.Add(GetValueOrNull<decimal>(reader, "Sqft1");
}
I placed ConcurrentList because your implementation may use async. Wasn't sure, if not you can use a normal List. But a List isn't thread safe by default, you'd need a ConcurrentList.
I currently have a list of objects that I am trying sort for a custom made grid view. I am hoping that I can achieve it without creating several customized algorithms. Currently I have a method called on page load that sorts the list by customer name, then status. I have a customized status order (new, in progress, has issues, completed, archived) and no matter which sort is used (customer, dates, so on) it should sort the status in the correct order. For example:
I have two customers with two orders each, the first customer is Betty White, the second is Mickey Mouse. Currently, Betty has a new order, and a completed order and Mickey has an order in progress and another on that has issues. So the display order should be:
Betty, New :: Betty, Completed
Mickey, In Progress :: Mickey, Has Issues
I am currently using Packages.OrderBy(o => o.Customer).ThenBy(o => o.Status). This works effectively to get the customers sorted, however this doesn't eliminate the custom sorting of the status property.
What would be the most efficient and standards acceptable method to achieve this result?
case PackageSortType.Customer:
Packages = Packages.OrderBy(o => o.Customer).ThenBy(o=>o.Status).ToList<Package>();
break;
I previously created a method that sorted by status only, however it is my belief that throwing the OrderBy into that algorithm would just jumble the status back up in the end.
private void SortByStatus() {
// Default sort order is: New, In Progress, Has Issues, Completed, Archived
List<Package> tempPackages = new List<Package>();
string[] statusNames = new string[5] { "new", "inProgress", "hasIssue", "completed", "archived" };
string currentStatus = string.Empty;
for (int x = 0; x < 5; x++) {
currentStatus = statusNames[x];
for (int y = 0; y < Packages.Count; y++) {
if (tempPackages.Contains(Packages[y])) continue;
else {
if (Packages[y].Status == currentStatus)
tempPackages.Add(Packages[y]);
}
}
}
Packages.Clear();
Packages = tempPackages;
}
Also, I'm not sure if it is relevant or not; however, the Packages list is stored in Session.
EDIT
Thanks to Alex Paven I have resolved the issue of custom sorting my status. I ended up creating a new class for the status and making it derive from IComparable, then created a CompareTo method that forced the proper sorting of the status.
For those who are curious about the solution I came up with (it still needs to be cleaned up), it's located below:
public class PackageStatus : IComparable<PackageStatus> {
public string Value { get; set; }
int id = 0;
static string[] statusNames = new string[5] { "new", "inProgress", "hasIssue", "completed", "archived" };
public int CompareTo(PackageStatus b) {
if (b != null) {
if (this == b) {
return 0;
}
for (int i = 0; i < 5; i++) {
if (this.Value == statusNames[i]) { id = i; }
if (b.Value == statusNames[i]) { b.id = i; }
}
}
return Comparer<int>.Default.Compare(id, b.id);
}
}
Use:
Packages.OrderBy(o => o.Customer).ThenBy(o => o.Status).ToList<Package>();
I'm not sure what exactly you're asking; why can't you use the Linq expressions in your first code sample? There's OrderByDescending in addition to OrderBy, so you can mix and match the sort order as you desire.
I've created an algorithm which weighs the relevance of a list of articles against two lists of keywords that correlate to attributes of the article.
It works great and is super efficient... but it's a mess. It's not terribly readable, so it's difficult to discern what's going.
The operation in pseudo code goes something like this:
Loop through every article in a list called articles(List<Article>)
For every article, loop through every role in a list of roles (List<string>)
Check to see if the current article has any roles (Article.Roles = List<string>)
If yes, then loop through each role in the article and try to match a role in the article to the role in the current loop
If a match is found, add weight to the article. If the index of the role on the article and the role in the roles list are both index 0 (in primary position) add extra weight for two matching primaries
Repeat for topics, but with no bonus for primary matches
What would be a better way to write the following code? I can't use foreach except in one or two places, because I need to match indexes to know what value to add on a match.
private static List<Article> WeighArticles(List<Article> articles, List<string> roles, List<string> topics, List<string> industries)
{
var returnList = new List<Article>();
for (int currentArticle = 0; currentArticle < articles.Count; currentArticle++)
{
for (int currentRole = 0; currentRole < roles.Count; currentRole++)
{
if (articles[currentArticle].Roles != null && articles[currentArticle].Roles.Count > 0)
{
for (int currentArticleRole = 0; currentArticleRole < articles[currentArticle].Roles.Count; currentArticleRole++)
{
if (articles[currentArticle].Roles[currentArticleRole].ToLower() == roles[currentRole].ToLower())
{
if (currentArticleRole == 0 && currentRole == 0)
articles[currentArticle].Weight += 3;
else
articles[currentArticle].Weight += 1;
}
}
}
}
for (int currentTopic = 0; currentTopic < topics.Count; currentTopic++)
{
if (articles[currentArticle].Topics != null && articles[currentArticle].Topics.Count > 0)
{
for (int currentArticleTopic = 0; currentArticleTopic < articles[currentArticle].Topics.Count; currentArticleTopic++)
{
if (articles[currentArticle].Topics[currentArticleTopic].ToLower() == topics[currentTopic].ToLower())
{
articles[currentArticle].Weight += 0.8;
}
}
}
}
returnList.Add(articles[currentArticle]);
}
return returnList;
}
//Article Class stub (unused properties left out)
public class Article
{
public List<string> Roles { get; set; }
public List<string> Topics { get; set; }
public double Weight { get; set; }
}
If you'll examine your code, you'll find that you are asking Article class to many times for data. Use Tell, Don't Ask principle and move weight adding logic to Article class, where it should belong. That will increase cohesion of Article, and make your original code much more readable. Here is how your original code will look like:
foreach(var article in articles)
{
article.AddWeights(roles);
article.AddWeights(topics);
}
And Article will look like:
public double Weight { get; private set; } // probably you don't need setter
public void AddWeights(IEnumerable<Role> roles)
{
const double RoleWeight = 1;
const double PrimaryRoleWeight = 3;
if (!roles.Any())
return;
if (Roles == null || !Roles.Any())
return;
var pirmaryRole = roles.First();
var comparison = StringComparison.CurrentCultureIgnoreCase;
if (String.Equals(Roles[0], primaryRole, comparison))
{
Weight += PrimaryRoleWeight;
return;
}
foreach(var role in roles)
if (Roles.Contains(role, StringComparer.CurrentCultureIgnoreCase))
Weight += RoleWeight;
}
Adding topics weights:
public void AddWeights(IEnumerable<Topic> topics)
{
const double TopicWeight = 0.8;
if (Topics == null || !Topics.Any() || !topics.Any())
return;
foreach(var topic in topics)
if (Topics.Contains(topic, StringComparer.CurrentCultureIgnoreCase))
Weight += TopicWeight;
}
Okay, you have several design flaws in your code:
1 - It's too procedural. You need to learn to think to write code to tell the machine "what you want" as opposed to "how to do it", similar to the analogy of going to a bar and instructing the bartender about the exact proportions of everything instead of just asking for a drink.
2 - Collections Should NEVER be null. Which means that checking for articles[x].Roles != null makes no sense at all.
3 - iterating on a List<string> and comparing each with someOtherString makes no sense either. Use List<T>.Contains() instead.
4 - You're grabbing each and every one of the items in the input list and outputting them in a new list. Also nonsense. Either return the input list directly or create a new list by using inputList.ToList()
All in all, here's a more idiomatic C# way of writing that code:
private static List<Article> WeighArticles(List<Article> articles, List<string> roles, List<string> topics, List<string> industries)
{
var firstRole = roles.FirstOrDefault();
var firstArticle = articles.FirstOrDefault();
var firstArticleRole = firstArticle.Roles.FirstOrDefault();
if (firstArticleRole != null && firstRole != null &&
firstRole.ToLower() == firstArticleRole.ToLower())
firstArticle.Weight += 3;
var remaining = from a in articles.Skip(1)
from r in roles.Skip(1)
from ar in a.Roles.Skip(1)
where ar.ToLower() == r.ToLower()
select a;
foreach (var article in remaining)
article.Weight += 1;
var hastopics = from a in articles
from t in topics
from at in a.Topics
where at.ToLower() == t.ToLower()
select a;
foreach (var article in hastopics)
article.Weight += .8;
return articles;
}
There are even better ways to write this, such as using .Take(1) instead of .FirstOrDefault()
Use the Extract Method refactoring on each for loop and give it a semantic name WeightArticlesForRole, WeightArticlesForTopic, etc. this will eliminate the nested loops(they are still there but via function call passing in a list).
It will also make your code self documenting and more readable, as now you have boiled a loop down to a named method that reflects what it accomplishes. those reading your code will be most interested in understanding what it accomplishes first before trying to understand how it accomplishes it. Semantic/conceptual function names will facilitate this. They can use GoTo Definition to determine the how after they udnerstand the what. Provide the summary tag comment for each method with elaborated explanation(similar to your pseudo code) and now others can wrap their head around what your code is doing without having to tediously read code they aren't concerned with the implementation details of.
The refactored methods will likely have some dirty looking parameters, but they will be private methods so I generally don't worry about this. However, sometimes it helps me see what dependencies are there that should probably be removed and restructure the code in the call such that it can be reused from multiple places. I suspect with some params for the weighting and delegate functions you might be able to combine WeightArticlesForRole and WeightArticlesForTopic into a single function to be reused in both places.