Vector-kind Class structure in C#, multi-dimensional - c#

I would like to initialize a C++-kind-of-multi-dimensional vector in C# as a Class (Teachers), where one particular teacher (member) can teach one or several subjects, let' say:
Teacher A teaches Biology
but
Teacher B teaches Biology and Math (and so forth)
I would like the user to input the subjects, so that the user can increment the number of subjects as he wishes and that this "list" can be be alphabetically ordered to the teacher names.
Would that be a Class Structure and if yes, how?

You seem to be asking for multidimensional arrays, which you can declare in C#, but this isn't making use of the power of classes:
var teachers = new string[2][];
teachers[0] = new [] {"Biology"};
teachers[1] = new[] { "Biology" , "Maths"};
Better would be to maintain collections of teachers and subjects, and then use Linq to return the teacher-subject pairs in whatever order you require. I have kept the subjects as strings, but you might, in a real life situation, want to encapsulate them in a class as well.
void Main
{
School school = new School();
school.Add("Harry", "Quiddich");
school.Add("Ron", "Quiddich");
school.Add("Neville", "Herbology");
school.Add("Hermione", "Herbology");
school.Add("Hermione", "Divination");
var orderedTeachers =
school.Teachers.OrderBy(t => t.TeacherName)
.SelectMany(
x => x.Subjects.OrderBy(s => s).Select(s => new {Teacher = x.TeacherName, Subject = s}));
}
internal class School
{
internal List<string> TaughtSubjects { get; } = new List<string>();
internal List<Teacher> Teachers { get; } = new List<Teacher>();
internal void Add(string teacherName, string subjectName)
{
// check to see if we have alreay defined this subject
var subject = TaughtSubjects.SingleOrDefault(s => s == subjectName);
// else create the subject and add to the school curriculum
if (subject == null)
{
subject = subjectName;
TaughtSubjects.Add(subject);
}
// check to see if the teacher is already on the school payroll
var teacher = Teachers.SingleOrDefault(s => s.TeacherName == teacherName);
// if not then add him or her
if (teacher == null)
{
teacher = new Teacher(teacherName);
Teachers.Add(teacher);
}
// check that the teacher isn't already down for teaching that subject before making the link
if (!teacher.Subjects.Contains(subject))
teacher.Subjects.Add(subject);
}
}
internal class Teacher
{
internal Teacher(string teacherName)
{
TeacherName = teacherName;
}
internal string TeacherName { get; }
public List<string> Subjects { get; } = new List<string>();
}

Related

filter by list in an attribute that is also a list C#

I have a class named SchoolClass, which has a list of students as attribute.
Then I have a search filter, in which I can search by a list of students and I have to return all the SchoolClasses where those students are part of their "list of students".
public class SchoolClass
{
public List<string> students { get; set; }
}
List<string> searchedStudents = new List<string>
{ "Brian","Adrian","Matt","Chloe"};
So I have a list of SchoolClasses:
List<SchoolClass> schoolClasses = new List<SchoolClass>();
SchoolClass 1 ==>
//(it should return it because it matches Brian, one of the searchedStudents)
schoolClasses[0].students = { "Brian","Zara"};
SchoolClass 2 ==>
//(it shouldn't return it because there are no matches)
schoolClasses[1].students = { "Sophie","Zara"};
i assume this is some sort of school work / project.
please make sure that you actually understand the answer and learn some sort of lesson out of it!
since this isnt a very data intense operation (with billions of entries) we can simply use go over the lists and search for the students.
if you got huge amounts of data to go through, you should consider different data structures (eg. hashtables).
//polulate the list with data
SchoolClass schoolClass1 = new SchoolClass();
SchoolClass schoolClass2 = new SchoolClass();
SchoolClass schoolClass3 = new SchoolClass();
schoolClass1.students = new List<string> { "Brian", "Adrian", "Matt" };
schoolClass2.students = new List<string> { "Adrian", "Matt" };
schoolClass3.students = new List<string> { "Brian", "Matt", "Chloe" };
List<SchoolClass> schoolClasses = new List<SchoolClass>();
schoolClasses.Add(schoolClass1);
schoolClasses.Add(schoolClass2);
schoolClasses.Add(schoolClass3);
//set our filter
List<string> searchedStudents = new List<string> { "Brian", "Chloe" };
//filter the data by going over the lists
List<SchoolClass> classesWithSearchedStudents = new List<SchoolClass>();
for (int classIterator = 0; classIterator < schoolClasses.Count(); classIterator++)
{
for (int filterIterator = 0; filterIterator < searchedStudents.Count(); filterIterator++)
{
//comparing with our filter and add class to the results list
if (schoolClasses[classIterator].students.Contains(searchedStudents[filterIterator]))
{
classesWithSearchedStudents.Add(schoolClasses[classIterator]);
break;
}
}
}
printResult(classesWithSearchedStudents);
just for demonstration, here is a more compact version using linq
var searchResult = new List<SchoolClass>();
foreach (var student in searchedStudents)
{
searchResult.AddRange(schoolClasses.Where(x => x.students.Contains(student)));
}
searchResult = searchResult.Distinct().ToList();
printResult(searchResult);
for printing the answer, i used this function:
private static void printResult(List<SchoolClass> schoolClasses)
{
for (int classIterator = 0; classIterator < schoolClasses.Count(); classIterator++)
{
Console.Write("found class with students: ");
for (int studentIterator = 0; studentIterator < schoolClasses[classIterator].students.Count; studentIterator++)
{
Console.Write(schoolClasses[classIterator].students[studentIterator] + ", ");
}
Console.WriteLine();
}
}
output:
found class with students: Brian, Adrian, Matt,
found class withstudents: Brian, Matt, Chloe,
If I understand it correctly:
You want to filter all schoolclasses that have "any" student from the filterlist in their student list.
If that assumption is correct than here you go.
Here's a simpeler and shorter function you can use.
It has one less for loop and cuts off whenever it finds 1 of the students.
It looks through all classes and sees if "any" of the filtered students are in there".
public IEnumerable<SchoolClass> FindMatches(List<SchoolClass> schoolClasses, List<string> namesFilter)
{
return schoolClasses.FindAll(schoolClass => schoolClass.students.Any(student => namesFilter.Contains(student)));
}
Obviously you can get the single line out of the method, but I thought I post it like this so you have an idea of the inputs and what the variable names actually are.
An easy way to check whether two lists contain any identical values is to use .Intersect() and .Any() (both methods are found in the System.Linq namespace).
.Intersect() returns an IEnumerable of all distinct objects that are found in both lists.
By using listA.Intersect(listB).Any(), you basically get the answer to the question
Are there any items that exists in both listA and listB?
In your example, the intersection of each school class's students with the searched students would result in the following (pseudo code):
School class 1
    schoolClasses[0].Students .Intersect( searchedStudents )
= { "Brian", "Zara" } .Intersect( { "Brian", "Adrian", "Matt", "Chloe" } )
= { "Brian" }
"Brian" is the only student that is present in both lists.
School class 2
    schoolClasses[1].Students .Intersect( searchedStudents )
= { "Sophie", "Zara" } .Intersect( { "Brian", "Adrian", "Matt", "Chloe" } )
= { }
No student is present in both lists.
Now, let's imagine school class 3 (schoolClasses[2]) existed and had the following students:
{ "Stuart", "Chloe", "Adrian", "Maya", "Chloe" }
An intersection of school class 3's students and the searched students would result in:
School class 3
    schoolClasses[2].Students .Intersect( searchedStudents )
= { "Stuart", "Chloe", "Adrian", "Maya", "Chloe" } .Intersect( { "Brian", "Adrian", "Matt", "Chloe" } )
= { "Chloe", "Adrian" }
Both "Chloe" and "Adrian" are present in both lists.
Note how "Chloe" is present twice in the school class's student list, but only once in the result from the intersection. Seeing as your need simply is to know whether a student name is present in both lists, (and not how many times a student name is present in either list,) .Intersect() nonetheless suits your use case.
So, to the implementation.
If your SchoolClass class looks like this:
public class SchoolClass
{
public List<string> Students { get; set; }
}
and you define searchedStudents and schoolClasses as follows:
List<string> searchedStudents = new() { "Brian", "Adrian", "Matt", "Chloe" };
List<SchoolClass> schoolClasses = new()
{
new() { Students = new() { "Brian", "Zara" } },
new() { Students = new() { "Sophie", "Zara" } },
};
, the intersection may be implemented as follows, using a foreach loop:
var matchingSchoolClasses = new List<SchoolClass>();
foreach (var schoolClass in schoolClasses)
{
if (schoolClass.Students.Intersect(searchedStudents).Any())
{
matchingSchoolClasses.Add(schoolClass);
}
}
or as follows, using .Where(), also from the System.Linq namespace:
var matchingSchoolClasses = schoolClasses
.Where(schoolClass => schoolClass.Students.Intersect(searchedStudents).Any())
.ToList();
Either implementation will result in matchingSchoolClasses containing one object. That one object is school class 1 (schoolClasses[0]), which contains the following students:
{ "Brian", "Zara" }
Example fiddle here.

Use LINQ to retrieve multiple scalar values

I am wondering if there is some clever way to retrieve data from an enumerable using LINQ when individual values from multiple records are needed.
For example, let's say you have a person with three different phone fields:
public class Person
{
public Phone HomePhone { get; set; }
public Phone WorkPhone { get; set; }
public Phone CellPhone { get; set; }
}
...but the phone list is stored in a normalized format:
public enum PhoneType
{
Home, Work, Cell
}
public class Phone
{
public PhoneType Type { get; set; }
public string Number { get; set; }
}
static public IEnumerable<Phone> GetPhoneList()
{
yield return new Phone { Type = PhoneType.Home, Number = "8005551212" };
yield return new Phone { Type = PhoneType.Work, Number = "8005551313" };
yield return new Phone { Type = PhoneType.Cell, Number = "8005551414" };
}
If you needed to populate Person, you could write a loop, and get everything you need in one pass:
public static Person GetPerson1()
{
var result = new Person();
foreach (var ph in GetPhoneList())
{
switch (ph.Type)
{
case PhoneType.Home: result.HomePhone = ph; break;
case PhoneType.Work: result.WorkPhone = ph; break;
case PhoneType.Cell: result.CellPhone = ph; break;
}
}
return result;
}
But if you wanted to use LINQ, it seems like three passes may be needed:
public static Person GetPerson2()
{
return new Person
{
HomePhone = GetPhoneList().Single( ph => ph.Type == PhoneType.Home ),
WorkPhone = GetPhoneList().Single( ph => ph.Type == PhoneType.Work ),
CellPhone = GetPhoneList().Single( ph => ph.Type == PhoneType.Cell )
};
}
Is there a clever way to use LINQ to get it all with only one pass over the enumeration?
Here is a link to a Fiddle if you'd like to explore my code.
(I am aware I could use a dictionary or other data structure to solve this particular problem; this is just an example.)
Normally, you can't do this in LINQ.
If you really want to, you can create a Foreach extension method and do the same as your GetPerson1 method.
public static class Ext
{
public static void Foreach<T>(this IEnumerable<T> e, Action<T> action)
{
foreach (T item in e)
{
action(item);
}
}
}
and then
public static Person GetPerson2()
{
var p = new Person();
var pl = GetPhoneList();
pl.Foreach(ph =>
{
switch (ph.Type)
{
case PhoneType.Home: p.HomePhone = ph; break;
case PhoneType.Work: p.WorkPhone = ph; break;
case PhoneType.Cell: p.CellPhone = ph; break;
}
});
return p;
}
But you really shouldn't. LINQ is meant to operate on IEnumerables (item by item), and LINQ functions should be without side effects, while your foreach loop and Foreach extension methods are only creating side effects, changing the state of the Person object.
And, besides, the fact that you need a 'clever way' should be an indication that this is not the way it's meant to be used :)
There's a great article by Eric Lippert with more details here: https://blogs.msdn.microsoft.com/ericlippert/2009/05/18/foreach-vs-foreach/
If there is no guarantee that numbers from the same person come in a sequence then you have to enumerate the list until you find all the numbers. It does not seem to me this is a good candidate for LINQ, whose purpose is to make the code more readable. Your foreach is just fine, and I would just break the loop when all numbers are found.
If you want to enumerate all the persons, and not just one then Dictionary approach is probably most effective. GroupBy internally uses a dictionary and you can use GroupBy to collect all the numbers belonging to a person, and then Aggregate to make a Person out of them. Let's assume there is some property Phone.PersonID, and also Person.PersonID, then you would have something like this:
GetPhoneList()
.GroupBy(x => x.PersonID)
.Select(x => x.Aggregate(new Person() { PersonID = x.Key },
(person, phone) =>
{
switch (phone.Type)
{
case PhoneType.Home: person.HomePhone = phone; break;
case PhoneType.Work: person.WorkPhone = phone; break;
case PhoneType.Cell: person.CellPhone = phone; break;
}
return person;
}));
I assume here that GetPhoneList() returns all the phones of all persons.

Use of SelectMany

Here in below code we can show difference between Select and SelectMany operator.
Is there any way to avoid the common skills? For example if two employees have the C# skill then I want to print them only once.
namespace LinqOperators
{
class Employee
{
public string Name { get; set; }
public List<string> Skills { get; set; }
}
class Program
{
static void Main(string[] args)
{
List<Employee> employees = new List<Employee>();
Employee emp1 = new Employee { Name = "Deepak", Skills = new List<string> { "C", "C++", "Java" } };//Adding Skills List to Employee List i.e List of List
Employee emp2 = new Employee { Name = "Karan", Skills = new List<string> { "SQL Server", "C#", "ASP.NET" } };
Employee emp3 = new Employee { Name = "Lalit", Skills = new List<string> { "C#", "ASP.NET MVC", "Windows Azure", "SQL Server" } };
employees.Add(emp1);
employees.Add(emp2);
employees.Add(emp3);
// Query using Select()
IEnumerable<List<String>> resultSelect = employees.Select(e => e.Skills);
Console.WriteLine("**************** Select ******************");
// Two foreach loops are required to iterate through the results
// because the query returns a collection of arrays.
foreach (List<String> skillList in resultSelect)
{
foreach (string skill in skillList)
{
Console.WriteLine(skill);
}
Console.WriteLine();//To differntiate Two Skill Lists
}
// Query using SelectMany()
IEnumerable<string> resultSelectMany = employees.SelectMany(emp => emp.Skills);
Console.WriteLine("**************** SelectMany ******************");
// Only one foreach loop is required to iterate through the results
// since query returns a one-dimensional collection.
foreach (string skill in resultSelectMany)
{
Console.WriteLine(skill);
}
Console.ReadKey();
}
}
}
SelectMany will flatten your IEnumerable such that it won't produce IEnumerable of IEnumerables but IEnumerable:
IEnumerable<IEnumerable<string>> skills; //not this [[C#, Java], [C, C++, Java, C#]]
IEnumerable<string> skills; //but this [C#, Java, C, C++, Java, C#]
You could use Distinct in your resultSelectMany to get common skill only once.
resultSelectMany = resultSelectMany.Distinct(); //[C#, Java, C, C++]
Or to put it in the same line:
// Query using SelectMany()
IEnumerable<string> resultSelectMany = employees.SelectMany(emp => emp.Skills).Distinct();
You can use .Distinct() to remove duplicates

Linq Count Expression

Suppose the following Employee class (yes I know I shouldn't publicly expose Lists but this is just an example):
class Employee
{
public string Name {get; set;}
public List<string> Skills {get; set;}
}
Skills is just a list of skills the employee has, for example "programming", "customer service", etc.
Now suppose I have a List<Employee> CurrentEmployees, and I also have another employee, Employee NewHire that is not in the CurrentEmployees list.
My goal is to use a lambda expression to count how many employees in CurrentEmployees have at least one skill that NewHire also has. So for example, if CurrentEmployees contains one employee with Skills{'Programming', 'Troubleshooting'}, and another employee with Skills{'Accounting','Finance'}, and NewHire has Skills{'Programming','Networking'}, I would want an expression that returns 1, because the first employee in the list also has 'Programming' as a skill... is this possible?
Thanks for any help.
currentEmployees.Count(ce =>
ce.Skills.Intersect(newHire.Skills).Any())
var currentEmployees = new List<Employee>
{
new Employee { Skills = new List<string> { "Programming", "Troubleshooting" } },
new Employee { Skills = new List<string> { "Accounting", "Finance" } },
};
var newHire = new Employee { Skills = new List<string> { "Programming", "Networking" } };
var count = currentEmployees.Count(e => e.Skills.Any(newHire.Skills.Contains));
// count == 1
If performance was important, I would use a HashSet<string>:
var newHireSkills = new HashSet<string>(newHire.Skills);
var count = currentEmployees.Count(e => e.Skills.Any(s => newHireSkills.Contains(s)));

How to compare two distinctly different objects with similar properties

This is all in C#, using .NET 2.0.
I have two lists of objects. They are not related objects, but they do have certain things in common that can be compared, such as a GUID-based unique identifier. These two lists need to be filtered by another list which just contains GUIDs which may or may not match up with the IDs contained in the first two lists.
I have thought about the idea of casting each object list to just object and sorting by that, but I'm not sure that I'll be able to access the ID property once it's cast, and I'm thinking that the method to sort the two lists should be somewhat dumb in knowing what the list to be sorted is.
What would be the best way to bring in each object list so that it can be sorted against the list with only the IDs?
You should make each of your different objects implement a common interface. Then create an IComparer<T> for that interface and use it in your sort.
Okay, if you have access to modify your original classes only to add the interface there, Matthew had it spot on. I went a little crazy here and defined out a full solution using 2.0 anonymous delegates. (I think I'm way addicted to 3.0 Lambda; otherwise, I probably would've written this out in foreach loops if I was using 2005 still).
Basically, create an interface with the common properties. Make yoru two classes implement the interface. Create a common list casted as the interface, cast and rip the values into the new list; remove any unmatched items.
//Program Output:
List1:
206aa77c-8259-428b-a4a0-0e005d8b016c
64f71cc9-596d-4cb8-9eb3-35da3b96f583
List2:
10382452-a7fe-4307-ae4c-41580dc69146
97f3f3f6-6e64-4109-9737-cb72280bc112
64f71cc9-596d-4cb8-9eb3-35da3b96f583
Matches:
64f71cc9-596d-4cb8-9eb3-35da3b96f583
Press any key to continue . . .
using System;
using System.Collections.Generic;
using System.Text;
namespace ConsoleApplication8
{
class Program
{
static void Main(string[] args)
{
//test initialization
List<ClassTypeA> list1 = new List<ClassTypeA>();
List<ClassTypeB> list2 = new List<ClassTypeB>();
ClassTypeA citem = new ClassTypeA();
ClassTypeB citem2 = new ClassTypeB();
citem2.ID = citem.ID;
list1.Add(new ClassTypeA());
list1.Add(citem);
list2.Add(new ClassTypeB());
list2.Add(new ClassTypeB());
list2.Add(citem2);
//new common list.
List<ICommonTypeMakeUpYourOwnName> common_list =
new List<ICommonTypeMakeUpYourOwnName>();
//in english, give me everything in list 1
//and cast it to the interface
common_list.AddRange(
list1.ConvertAll<ICommonTypeMakeUpYourOwnName>(delegate(
ClassTypeA x) { return (ICommonTypeMakeUpYourOwnName)x; }));
//in english, give me all the items in the
//common list that don't exist in list2 and remove them.
common_list.RemoveAll(delegate(ICommonTypeMakeUpYourOwnName x)
{ return list2.Find(delegate(ClassTypeB y)
{return y.ID == x.ID;}) == null; });
//show list1
Console.WriteLine("List1:");
foreach (ClassTypeA item in list1)
{
Console.WriteLine(item.ID);
}
//show list2
Console.WriteLine("\nList2:");
foreach (ClassTypeB item in list2)
{
Console.WriteLine(item.ID);
}
//show the common items
Console.WriteLine("\nMatches:");
foreach (ICommonTypeMakeUpYourOwnName item in common_list)
{
Console.WriteLine(item.ID);
}
}
}
interface ICommonTypeMakeUpYourOwnName
{
Guid ID { get; set; }
}
class ClassTypeA : ICommonTypeMakeUpYourOwnName
{
Guid _ID;
public Guid ID {get { return _ID; } set { _ID = value;}}
int _Stuff1;
public int Stuff1 {get { return _Stuff1; } set { _Stuff1 = value;}}
string _Stuff2;
public string Stuff2 {get { return _Stuff2; } set { _Stuff2 = value;}}
public ClassTypeA()
{
this.ID = Guid.NewGuid();
}
}
class ClassTypeB : ICommonTypeMakeUpYourOwnName
{
Guid _ID;
public Guid ID {get { return _ID; } set { _ID = value;}}
int _Stuff3;
public int Stuff3 {get { return _Stuff3; } set { _Stuff3 = value;}}
string _Stuff4;
public string Stuff4 {get { return _Stuff4; } set { _Stuff4 = value;}}
public ClassTypeB()
{
this.ID = Guid.NewGuid();
}
}
}
Using only .NET 2.0 methods:
class Foo
{
public Guid Guid { get; }
}
List<Foo> GetFooSubset(List<Foo> foos, List<Guid> guids)
{
return foos.FindAll(foo => guids.Contains(foo.Guid));
}
If your classes don't implement a common interface, you'll have to implement GetFooSubset for each type individually.
I'm not sure that I fully understand what you want, but you can use linq to select out the matching items from the lists as well as sorting them. Here is a simple example where the values from one list are filtered on another and sorted.
List<int> itemList = new List<int>() { 9,6,3,4,5,2,7,8,1 };
List<int> filterList = new List<int>() { 2, 6, 9 };
IEnumerable<int> filtered = itemList.SelectMany(item => filterList.Where(filter => filter == item)).OrderBy(p => p);
I haven't had a chance to use AutoMapper yet, but from what you describe you wish to check it out. From Jimmy Bogard's post:
AutoMapper conventions
Since AutoMapper flattens, it will
look for:
Matching property names
Nested property names (Product.Name
maps to ProductName, by assuming a
PascalCase naming convention)
Methods starting with the word “Get”,
so GetTotal() maps to Total
Any existing type map already
configured
Basically, if you removed all the
“dots” and “Gets”, AutoMapper will
match property names. Right now,
AutoMapper does not fail on mismatched
types, but for some other reasons.
I am not totally sure what you want as your end results, however....
If you are comparing the properties on two different types you could project the property names and corresponding values into two dictionaries. And with that information do some sort of sorting/difference of the property values.
Guid newGuid = Guid.NewGuid();
var classA = new ClassA{Id = newGuid};
var classB = new ClassB{Id = newGuid};
PropertyInfo[] classAProperties = classA.GetType().GetProperties();
Dictionary<string, object> classAPropertyValue = classAProperties.ToDictionary(pName => pName.Name,
pValue =>
pValue.GetValue(classA, null));
PropertyInfo[] classBProperties = classB.GetType().GetProperties();
Dictionary<string, object> classBPropetyValue = classBProperties.ToDictionary(pName => pName.Name,
pValue =>
pValue.GetValue(classB, null));
internal class ClassB
{
public Guid Id { get; set; }
}
internal class ClassA
{
public Guid Id { get; set; }
}
classAPropertyValue
Count = 1
[0]: {[Id, d0093d33-a59b-4537-bde9-67db324cf7f6]}
classBPropetyValue
Count = 1
[0]: {[Id, d0093d33-a59b-4537-bde9-67db324cf7f6]}
Thist should essentially get you what you want - but you may be better of using linq
class T1
{
public T1(Guid g, string n) { Guid = g; MyName = n; }
public Guid Guid { get; set; }
public string MyName { get; set; }
}
class T2
{
public T2(Guid g, string n) { ID = g; Name = n; }
public Guid ID { get; set; }
public string Name { get; set; }
}
class Test
{
public void Run()
{
Guid G1 = Guid.NewGuid();
Guid G2 = Guid.NewGuid();
Guid G3 = Guid.NewGuid();
List<T1> t1s = new List<T1>() {
new T1(G1, "one"),
new T1(G2, "two"),
new T1(G3, "three")
};
List<Guid> filter = new List<Guid>() { G2, G3};
List<T1> filteredValues1 = t1s.FindAll(delegate(T1 item)
{
return filter.Contains(item.Guid);
});
List<T1> filteredValues2 = t1s.FindAll(o1 => filter.Contains(o1.Guid));
}
}

Categories

Resources