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)));
Related
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.
I have two different classes: Employee and Customer. Each have two properties in common: Name and Address. Is there a way convert the string directly into an array of objects without using the List<>?
private static List<Employee> NewMethod1(string strArr)
{
List<Employee> lst = new List<Employee>();
if (strArr !=null)
{
strArr.Split(',').ToList().ForEach(x => lst.Add(new Employee() { Name = x }));
}
return lst.ToArray();
}
or make this line of code generic enough so I can use it inline code?
strArr.Split(',').ToList().ForEach(x => lst.Add(new Employee() { Name = x }));
as #canton7 said in comments you can use Linq:
strArr?.Split(',').Select(x => new Employee() {...}).ToList() ?? new List<Employee>()
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>();
}
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
Let's say we have 2 sets
A = [ PersonA,PersonB];
B = [ ManagerZ,ManagerT,ManagerY];
Result : ManagerT,ManagerY
There exists One to One mapping between the objects in A and the objects in B.
I'm interested in those objects in B for which exists such an entry in A.
For completeness let's say we are matching on property named Name
Try following
SetB.Where(b => SetA.Any(a => a.Name == b.Name))
You have to perform a join on both lists:
var query =
from person in persons
join manager in managers on person.Name equals manager.Name
select new { Person = person, Manager = manager };
This will select all data from your Person-dataset together with the corresponding data from Manager-dataset.
Alternativly you can flatten the results into a datatype providing the whole data for every match:
select new { Name = person.Name, Age = person.Age, Departement = Manager.Departement }
Or if you´re only interested on the items from B which match use simply select manager.
Try with this code:
List<BType> result = B.Where(x >= A.Exists(y => y.Name == x.Name)).ToList();
In this way you mantain only managers that exists in people list.
Also you can use Intersect.
Example:
public class Person
{
public string Name { get; set; }
}
public class PersonEqualityComparer : IEqualityComparer<Person>
{
public bool Equals(Person x, Person y)
{
return x.Name.Equals(x.Name);
}
public int GetHashCode(Person obj)
{
return obj.Name.GetHashCode();
}
}
And now you can use:
var persons = new List<Person>() { new Person { Name = "John" } };
var managers = new List<Person>() { new Person { Name = "John" } };
var results = persons.Intersect(managers, new PersonEqualityComparer());
If you want compare two different class just edit Comparer.