Use of SelectMany - c#

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

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.

convert string in an array of objects

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>()

LINQ Where query with object list

I have a list of objects ListA with property Id and I have to make a query in a table that has a column Id and find the rows that the ids are the same. How exactly can I achieve that with a single query and not a foreach loop of listA?
Thank you for your time
foreach(var object in listA)
{
context.Table.Where(x => x.id == object.Id)....
}
Looks like you want to return all rows from the table that have an ID contained in the list of objects with the same ID. The following will achieve this. I can modify my answer to suit your need. Just let me know if you are looking for something slightly different.
void Main()
{
var listA = new List<A> { new A { Id = 1 }, new A { Id = 4 } };
var results = context.Table
.Where(t => listA.Select(l => l.Id).Contains(t.Id))
}
public class A
{
public int Id { get; set; }
}

Foreach group items from a list of objects

I need to group a big list of elements according to a certain atribute.
Is it possible in C# to do a foreach with a 'where' clause in a list of objects or is there a better way?
For example, I have 5000 records and 3 groups that separate them.
Foreach list.item where item.group = group1{
do action one for every record from group1
}
and so on...
ps.: I already have the records at this point of code so I don't think Linq would help.
You can separate a larger list into smaller ones, based on a property, by using ToLookup. The ToLookup method will produce a dictionary of lists, where the key is the property value that you are separating them by and the list contains all of the elements that match.
For example, if your objects have a CategoryID you can separate them into a dictionary of lists like this:
var smallLists = bigList.ToLookup( item => item.CategoryID, item => item );
You can then iterate them like this:
foreach (var bucket in smallLists)
{
Console.WriteLine("Bucket:");
foreach (var item in bucket)
{
Console.WriteLine("Item {0} with category {1}", item.Name, item.CategoryID);
}
}
See a working example on DotNetFiddle.
I think that you want to do is to group items of list by a Group and then create another list with each group and his items.
If that is the case, you can do something like this:
var grouped = items/*.Where(c => c.group == //desired group if want's to filter//)*/
.GroupBy(c => c.group);
var results = grouped.Select(c => new {
Group = c.Key.group,
Items = c.Select(c => new { c.PropertyOfItem1, c.PropertyOfItem2, // etc // })
});
This basic template should do what you need. You can also use a dictionary to map the groups to.
using System.Linq;
class Program
{
class Item
{
public int Key { get; set; }
public string Name { get; set; }
}
static void Main(string[] args)
{
var actions = new Dictionary<int, Action<Item>> {
{ 1, Action1 },
{ 2, Action2 },
{ 3, Action3 }
};
var items = new List<Item>();
foreach (var group in items.GroupBy(x => x.Key))
{
var action = actions[group.Key];
foreach (var item in group)
{
action(item);
}
}
}
static void Action1(Item item)
{
}
static void Action2(Item item)
{
}
static void Action3(Item item)
{
}
}

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)));

Categories

Resources