Parse and pivot student assignment data in c# - c#

I've got a list of student/assignment pairs coming in to my application via flat file into a List object and I'd like to validate those assignment names against a list of assignments I have to see which student has done what assignment (if any).
So - I have a list of students, a list of student/assignment pairs, and a list of assignments.
I want to create output like the following:
1. (Heading) -- Student Name -- Assignent One -- Assignment TWo
2. (Detail) -- John Smith -- <complete> -- <complete>
How can I accomplish this?
What I have so far:
// HACCStudentBlogs stores the list of students in a dictionary I've been appending to the key's value with every found blog (crude, I know)
//
Dictionary<string, string> studentBlogs = new Dictionary<string, string>();
foreach (JObject o in root)
{
createDate = (string)o["createdDate"];
studentName = (string)d[(string)o["contributorName"]];
submittedStudents.Add(studentName);
title=(string)o["title"];
if (HACCstudentBlogs.ContainsKey(studentName))
{
HACCstudentBlogs[studentName] += "\t" + title.ToUpper();
}
else
{
}
}

Since you want one row per student, it's easiest to loop over the list of students and do something for each student.
In this case, "something" means that we need to assemble a line of output.
Each line of output is going to have the student's information, and then needs a piece for each assignment. So inside the loop over the students, there will be a loop over the assignments.
In the loop over the assignments, we need to look up and see if we have a pair for that student and assignment. If so, we spit out "complete." Otherwise, "incomplete."
So a plausible way to structure the program would be something like this:
// print some header information
foreach (var student in studentList)
{
Display(student.Name);
foreach (var assignment in assignmentList)
{
if (Exists(student, assignment, studentAssignmentPairs))
Display("<complete>");
else
Display("<incomplete>");
}
// newline
}
with some added flair for formatting it nicely.
Enough to get started?

we really need to know what the list object looks like
but using foreach over a Linq .Distint() woudld be a good startng point

Related

More efficient way of using LINQ to compare two items?

I am updating records on a SharePoint list based on data from a SQL database. Lets say my table looks something like this:
VendorNumber
ItemNumber
Descrpition
1001
1
abc
1001
2
def
1002
1
ghi
1002
3
jkl
There can be multiple keys in each table. I am trying to make a generic solution that will work for multiple different table structures. In the above example, VendorNumber and ItemNumber would be considered keys.
I am able to retrieve the SharePoint lists as c# List<Microsoft.SharePoint.Client.ListItem>
I need to search through the List to determine which individual ListItem corresponds to the current SQL datarow I am on. Since both ListItem and DataRow allow bracket notation to specify column names, this is pretty easy to do using LINQ if you only have one key column. What I need is a way to do this if I have anywhere from 1 key to N keys. I have found this solution but realize it is very inefficient. Is there a more efficient way of doing this?
List<string> keyFieldNames = new List<string>() { "VendorNumber", "ItemNumber" };
List<ListItem> itemList = MyFunction_GetSharePointItemList();
DataRow row = MyFunction_GetOneRow();
//this is the part I would like to make more efficient:
foreach (string key in keyFieldNames)
{
//this filters the list with each successive pass.
itemList = itemList.FindAll(item => item[key].ToString().Trim() == row[key].ToString().Trim());
}
Edited to Add: Here is a link to the ListItem class documentation:
Microsoft.SharePoint.Client.ListItem
While ListItem is not a DataTable object, its structure is very similar. I have intentionally designed it so that both the ListItem and my DataRow object will have the same number of columns and the same column names. This was done to make comparing them easier.
A quick optimization tip first:
Create a Dictionary<string, string> to use instead of row
List<string> keyFieldNames = new List<string>() { "VendorNumber", "ItemNumber" };
DataRow row = MyFunction_GetOneRow();
var rowData = keyFieldNames.ToDictionary(name=>row[name].ToString().Trim());
foreach (string key in keyFieldNames)
{
itemList = itemList.FindAll(item => item[key].ToString().Trim() == rowData[key]);
}
This will avoid doing the ToString & Trim on the same records over & over. That's probably taking 1/3rd to 1/2 the time of the loop. (The comparison is fast compared to the string manipulation)
Beyond that, all I can think of is to use reflection to build a specific function, on the fly to handle the comparison. BUT, that would be a big effort, and I don't see it saving that much time. Basically, whatever you do, will still have to do the same basics: Lookup the values by key, and compare them. That's what's taking the majority of the time.
After I stopped looking for an answer, I stumbled across one. I have now realized that using a .Where is implemented using deferred execution. This means that even though the foreach loop iterates several times, the LINQ query executes all at once. This was the part I was struggling to wrap my head around.
My new sudo code:
List<string> keyFieldNames = new List<string>() { "VendorNumber", "ItemNumber" };
List<ListItem> itemList = MyFunction_GetSharePointItemList();
DataRow row = MyFunction_GetOneRow();
//this is the part I would like to make more efficient:
foreach (string key in keyFieldNames)
{
//this filters the list with each successive pass.
itemList = itemList.Where(item => item[key].ToString().Trim() == row[key].ToString().Trim());
}
I know that the .ToString().Trim() is still inefficient, I will address this at some point. But for now at least my mind can rest knowing that the LINQ executes all at once.

Filtering list of objects based on properties and their values

Good evening,
I'm building a basic console application to learn how to filter object list property data. I'm trying to filter a list of objects based on user selected object property and it's value, but I'm struggling to conceptualize how to connect user input to objects property, since properties do not have an index.
For example, if we have a list of cars and user selects to filter by year and enters a specific year we would return those cars.
foreach(var car in listOfCars)
{
if(...)
{
Console.WriteLine(car.Name);
Console.WriteLine(car.Year);
}
}
I can filter the data using wildcards, but how do you connect a user selected input (number) to a property?
Well, it doesn't have to be a number:
Console.WriteLine("Filter by what? You can write YEAR, MAKE or MODEL");
var byWhat = Console.ReadLine();
Console.WriteLine("And what is the search term?");
var term = Console.ReadLine();
List<Car> filtered = new List<Car>();
if(byWhat == "YEAR"){
int whatYear = int.Parse(term);
foreach(var car in cars){
if(car.Year == whatYear)
filtered.Add(car);
}
} else if(byWhat == ...) {
... foreach(...)
}
Feel free to convert that to using numbers if you want, and add some validation of input, case insensitivity.. etc..
The main point here is that you can make a variable that represents the common thing you'll get out; a filtered list of cars. You can do an if this then that, else if other then another.. and each if branch means the list of filtered cars ends up different.. But it's still a list of cars at the end and so by whatever process you did the filtering the common form of output can be treated in the same way.
Note; if you like you can flip it over and put the if inside the foreach - it's not appreciably slower

Is there a way in linq wherin i can insert a row(from dictionary) in datatable using the list of column names c#?

I have a List<Dictionary<string,string>> something like this:
[0] key1 val,key2 val,key3 val
[1] key1 val,key2 val,key3 val
[2] key1 val,key2 val,key3 val
And i have a list of column names in the same order as columns in the datatable.
I want to filter only those keys which are there inside the list from the dictionary and also insert it in the proper order.
I'm able to filter the required keys to be inserted but then how do i insert it in the proper order in linq.
var colList = new List<string>() { "key3", "key1"};
dict.ForEach(p => jsonDataTable.Rows.Add(p.Where(q=>colList.Contains(q.key)).Select(r => r.Value).ToArray()));
I cannot do like this because number of columns will vary and also the method must work when we pass any list of column names:
foreach(var item in dict)
jsonDatatable.Rows.Add(item[colList[0]], item[colList[1]]);
Please suggest some ways.
LINQ will never ever change the input sources. You can only extract data from it.
Divide problems in subproblems
The only way to change the input sources is by using the extracted data to update your sources. Make sure that before you update the source you have materialized your query (= ToList() etc)
You can divide your problem into subproblems:
Convert the table into a sequence of columns in the correct order
convert the sequence of columns into a sequence of column names (still in the correct order)
use the column names and the dictionary to fetch the requested data.
By separating your problem into these steps, you prepare your solution for reusability. If in future you change your table to a DataGridView, or a table in an entity framework database, or a CSV file, or maybe even JSON, you can reuse the latter steps. If in future you need to use the column names for something else, you can still use the earlier steps.
To be able to use the code in a LINQ-like way, my advice would be to create extension method. If you are unfamiliar with extension methods, read Extension Methods Demystified
You will be more familiar with the layout of your table (System.Data.DataTable? Windows.Forms.DataGridView? DataGrid in Windows.Controls?) and your columns, so you'll have to create the first ones yourself. In the example I use MyTable and MyColumn; replace them with your own Table and Column classes.
public static IEnumerable<MyColumn> ToColumns(this MyTable)
{
// TODO: return the columns of the table
}
public static IEnumerable<string> ToColumnNames(this IEnumerable<MyColumn> columns)
{
return columns.Select(column => ...);
}
If the column name is just a property of the column, I wouldn't bother creating the second procedure. However, the nice thing is that it hides where you get the name from. So to be future-changes-proof, maybe create the method anyway.
You said these columns were sorted. If you want to be able to use ThenBy(...) consider returning an IOrderedEnumerable<MyColumn>. If you won't sort the sorted result, I wouldn't bother.
Usage:
MyTable table = ...
IEnumerable<string> columnNames = table.ToColumns().ToColumnNames();
or:
IEnumerable<string> columnNames = table.ToColumns()
.Select(column => column.Name);
The third subproblem is the interesting one.
Join and GroupJoin
In LINQ whenever you have two tables and you want to use a property of the elements in one table to match them with the properties of another table, consider to use (Group-)Join.
If you only want items of the first table that match exactly one item of the other table, use Join: "Get Customer with his Address", "Get Product with its Supplier". "Book with its Author"
On the other hand, if you expect that one item of the first table matches zero or more items from the other table, use GroupJoin: "Schools, each with their Students", "Customers, each with their Orders", "Authors, each with their Books"
Some people still think in database terms. They tend to use some kind of Left Outer Join to fetch "Schools with their Students". The disadvantage of this is that if a School has 2000 Students, then the same data of the School is transferred 2000 times, once for every Student. GroupJoin will transfer the data of the School only once, and the data of every Student only once.
Back to your question
In your problem: every column name is the key of exactly one item in the Dictionary.
What do you want to do with column names without keys? If you want to discard them, use Join. If you still want to use the column names that have nothing in the Dictionary, use GroupJoin.
IEnumerable<string> columNames = ...
var result = columnNames.Join(myDictionary,
columName => columName, // from every columName take the columnName,
dictionaryItem => dictionaryItem.Key, // from every dictionary keyValuePair take the key
// parameter resultSelector: from every columnName and its matching dictionary keyValuePair
// make one new object:
(columnName, keyValuePair) => new
{
// Select the properties that you want:
Name = columnName,
// take the whole dictionary value:
Value = keyValuePair.Value,
// or select only the properties that you plan to use:
Address = new
{
Street = keyValuePair.Street,
City = keyValuePair.City,
PostCode = keyValuePair.Value.PostCode
...
},
});
If you use this more often: consider to create an extension method for this.
Note: the order of the result of a Join is not specified, so you'll have to Sort after the Order
Usage:
Table myTable = ...
var result = myTable.ToColumns()
.Select(column => column.Name)
.Join(...)
.Sort(joinResult => joinResult.Name)
.ToList();
Instead of filtering on the List<Dictionary<string, string>>, filter on the colList so that you will get in the same order and only if the colList is available in the List<Dictionary<string, string>>
This is as per my understanding, please comment if you need the result in any other way.
var dictAllValues = dict.SelectMany(x => x.Select(y => y.Value)).ToList();
// Now you can filter the colList using the above values
var filteredList = colList.Where(x => dictAllValues.Contains(x));
// or you can directly add to final list as below
jsonDataTable.Rows.AddRange(colList.Where(x => dictAllValues.Contains(x)).ToList());

string grouping algorithm c#

I need something like a grouping algorithm for strings in C#.
I've tried for days and before I go mad, I should maybe ask someone :)
(no adjazenctmatrix^^)
what do I have is data in an Dictonary
something like this:
key|value
"bla","AAA;BBB;CCC" // ';' is split sign
"whatever","BBB;DDD;EEE;FF"
"hmm", "ZZZ,YYY,XXX"
"foo", "CCC,JJJ,VVV"
....
value1 and value2 contains "BBB" so group it to new string : (in a new dictionary,key whatever...counter?)
"AAA;BBB;CCC;EEE;FF" (or without distinct to "AAA;BBB;CCC;BBB;DDD;EEE;FF")
value3 is his own group
value4 contains "CCC" so group it to the others
"AAA;BBB;CCC;EEE;FF;JJJ;VVV" (or without distinct to "AAA;BBB;CCC;BBB;DDD;EEE;FF;CCC;JJJ;VVV")
I need that string for SQL update
update item set group = bar
where group in ('','',... )
I do it with split and join, this part works :-P
thanks
So first organize the data. Have a map keys["bla"] = some_set("AAA", "BBB", "CCC"); and so on. Then build a reverse map that should look like reverse["BBB"] = ["bla", "whatever"] both maps should be about the same size as the original data.
Next you can do a DFS over the implicit graph (pseudocode):
merge = some_set()
DFS(string key) {
if (key in merge) return; // Been here already.
merge.insert(key);
for (string edge : keys[key])
for (string other_key : reverse[edge])
DFS(other_key)
}
So you can now call DSF("bla"). When it returns it should contain "bla", "whatever", ..." and any other keys that might be in the group and you can concatenate their strings from keys to get the result you wanted.
You can call DFS for every key to get all the group each key belongs (complexity O(N^2*set_op)). Or, better, keep track of what keys you already processed to avoid working on them again (complexity O(N*set_op)).
If you use hash based sets/maps your set_op is O(average string length). If you use tree based structures then set_op is O(logN). This shouldn't matter unless you have very long strings or lots of keys.

Arrays/Array Lists

I am fairly new to C#
I am trying to retrieve some information from an external data source and store it in array, once it is in an array I wish to sort it by time.
I know how to do this for just one column in a row, however the information I require has multiple columns.
For example:
foreach (Appointment Appoint in fapts)
{
// Store Appoint.Subject, Appoint.Start, Appoint.Organiser.Name.ToString(), Appoint.Location in an array
}
// Sort my array by Appoint.Start
foreach ( item in myNewArray )
{
//print out Appoint.Subject - Appoint.Start, Appoint.Organiser.Name.ToString() and Appoint.location
}
Many thanks for your help.
EDIT:
I have multiple data sources which pull in this:
foreach (Appointment Appoint in fapts)
{
// Store Appoint.Subject, Appoint.Start, Appoint.Organiser.Name.ToString(), Appoint.Location in an array
}
Hence the need to sort the items in a new array, I know this isn't very efficent but there is no way of getting the information I need in any other way.
You can sort a list using the LINQ sorting operators OrderBy and ThenBy, as shown below.
using System.Linq;
and then...
var appointments = new List<Appointment>();
var sortedAppointments = list.OrderBy(l => l.Subject).ThenBy(l => l.Name).ToList();
This will create a new list of appointments, sorted by subject and then by name.
It's unclear what your final aim is but:
Use a generic List instead of an array:
See this SO question for more information as to why using a List is prefered.
List<Appointment> appointments = new List<Appointment>();
foreach (Appointment Appoint in fapts)
{
appointments.Add(Appoint);
}
foreach (var item in appointments)
{
Console.WriteLine(item.Subject);
Console.WriteLine(item.Foo);
// Here you could override ToString() on Appointment to print eveything in one Console.WriteLine
}
If the aim of your code is to order by time, try the following:
var sortedAppointments = fapts.OrderBy(a => a.Start); // assuming Start is a DateTime property of `Appointment`.
Consider a Dictionary Object instead of an array if the data is conceptually one row multiple columns.
foreach(KeyValuePair<string, string> entry in MyDic)
{
// do something with entry.Value or entry.Key
}
You already have a list of objects in fpts, sort that list itself:
fpts.OrderBy(x => x.Subject).ThenBy(x => x.Location).ToList();
LINQ is your friend here.
fapts appears to already be a collection so you could just operate on it.
var myNewArray = fapts.OrderBy(Appoint => Appoint.Start).ToArray()
I've used the ToArray() call to force immediate evaluation and means that myNewArray is already sorted so that if you use it more than once you don't have to re-evaluate the sort.
Alternatively if you are only using this once you can just as easily miss the ToArray() portion out and then execution of the sort will be deferred until you try and enumerate through myNewArray.
This solution puts the source objects into the array, but if you are just wanting to store the specific fields you mention then you will need to use a select. You have two choices for the array item type, you can either use an anonymous class which provides difficulties if you are returning this array from a function or define a class.
For anonymous:
var myNewArray = fapts.OrderBy(Appoint => Appoint.Start)
.Select(Appoint => new {
Start = Appoint.Start,
Organiser = Appoint.Organiser.Name.ToString(),
Location = Appoint.Location
}).ToArray();
For named class assuming class is MyClass:
var myNewArray = fapts.OrderBy(Appoint => Appoint.Start)
.Select(Appoint => new MyClass {
Start = Appoint.Start,
Organiser = Appoint.Organiser.Name.ToString(),
Location = Appoint.Location
}).ToArray();
You have a wide range of options. The 2 most common are:
1) Create a class, then define an array or list of that class, and populate that
2) Create a structure that matches the data format and create an array or list of that
Of course, you could put the data into an XML format or dataset, but that's probably more work than you need.
public List<foo> appointments = new List<foo>();
public struct foo
{
public string subject ;
public DateTime start ;
public string name ;
public string location ;
}
public void foo1()
{
// parse the file
while (!File.eof())
{
// Read the next line...
var myRecord = new foo() ;
myRecord.subject = data.subject ;
myRecord.start = data.Start ;
myRecord.name = data.Name ;
//...
appointments.Add(myRecord);
}
}
Enjoy
(Since I can't comment and reply to the comment - it wasn't clear if he had a class, etc. or was just showing us what he wanted to do. I assumed it was just for demonstration purposes since there wasn't any info as to how the data was being read. If he could already put it into a class, than the first answer applied anyway. I just tossed the last 2 in there because they were options for getting the data first.)

Categories