Foreach group items from a list of objects - c#

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)
{
}
}

Related

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

Advanced LINQ filtering

I have problem with advanced filtering data using LINQ.
I'd like to get list of Plan classes with Details list where Arguments in Items Lists contains specific characters. Also the Items list should contains only this filtered elements.
My classes look like below:
class Plan
{
public string Name { get; set; }
public List<Detail> Details { get; set; }
public Plan()
{
Details = new List<Detail>();
}
}
class Detail
{
public string Setting { get; set; }
public List<Item> Items { get; set; }
public Detail()
{
Items = new List<Item>();
}
}
class Item
{
public string Arguments { get; set; }
}
My current solution look like this, but I think it isn't the best option. I tried to write this code using Where and Any, but I've got Plans list where Items contains all items.
var filteredPlans = plans.Select(x =>
new Plan
{
Name = x.Name,
Details = x.Details.Select(y =>
new Detail
{
Setting = y.Setting,
Items = y.Items.Where(c => c.Arguments.Contains("...")).Select(z =>
new Item
{
Arguments = z.Arguments
}).ToList()
}).ToList()
});
How can I write this code using WHERE statement or What is the best solution to do that?
Also how can I get harvest difference using LINQ EXPECT based on Items List? e.g. plans: contains all plans with items, plans2: contains all plans with filtered items, and the plans3 should contains all plans with items which not belong to plans2.
Does this work for you?
First I limit to only the plans where any of their details contain any item that matches the filter.
Then I limit details for each plan to only those with any item that matches the filter
Then I limit items for each plan
private List<Plan> FilteredPlans(List<Plan> plans, string filter)
{
List<Plan> filteredPlans = plans.Where(plan => plan.Details.Any(detail => detail.Items.Any(item => item.Arguments.Contains(filter)))).ToList();
foreach (var plan in filteredPlans)
{
plan.Details = plan.Details.Where(detail => detail.Items.Any(item => item.Arguments.Contains(filter))).ToList();
foreach (var detail in plan.Details)
{
detail.Items = detail.Items.Where(item => item.Arguments.Contains(filter)).ToList();
}
}
return filteredPlans;
}
Also, here's another version as a single statement, but I think it's far less readable. I essentially limit the items first and then work my way backwards only keeping containers that aren't empty
private List<Plan> FilteredPlansWithSelect(List<Plan> plans, string filter)
{
List<Plan> filteredPlans = plans.Select(plan =>
new Plan()
{
Name = plan.Name,
Details = plan.Details.Select(detail =>
new Detail()
{
Setting = detail.Setting,
Items = detail.Items.Where(item => item.Arguments.Contains(filter)).ToList()
}).Where(detail => detail.Items.Count > 0).ToList()
}).Where(plan => plan.Details.Count > 0).ToList();
return filteredPlans;
}
Edited for grammer

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

Mapping elements without creating duplicates

I have two classes:
public class Element
{
public Item Item { get; set; }
}
public class Item
{
public Element Element { get; set; }
}
And I have DTO with same structure for this classes.
This method creates source data for mapping:
static Element[] CreateElements()
{
var element2 = new Element();
return new[]
{
new Element(new Item(element2)),
element2,
new Element()
};
}
Then I configuring mapping and map elements:
Mapper.CreateMap<Element, ElementDto>();
Mapper.CreateMap<Item, ItemDto>();
var elements = CreateElements();
var mappedElements = elements
.Select(_ => Mapper.Map(_, typeof(Element), typeof(ElementDto)))
.OfType<ElementDto>()
.ToArray();
After I check result of mapping:
foreach (var element in mappedElements)
{
Console.WriteLine(mappedElements.Any(e => e?.Item?.Element == element));
}
This code shows "False" three times. It follows that the "element2" from "CreateElements" was created two copies.
The same test for the source elements will return "False True False":
foreach (var element in elements)
{
Console.WriteLine(elements.Any(e => e?.Item?.Element == element));
}
As I need to configure the mapping so as not to duplicate elements? Is it possible?
I don't think it is AutoMapper issue.
You are creating three different Element items and map them to some kind of ElementDto. They are three different objects(both in terms of structure and reference), you cannot expect that if you map them to the same type, they will be equal.
If you consider your items:
var element2 = new Element();
return new[]
{
new Element(new Item(element2)),
element2,
new Element()
};
and compare them, you will see that none is equal. You haven't provided ElementDto class bu my guess is that you should implement IEquatable interface, what will ensure proper comparison(or overload operators).
This can be done manually. First, ignore the property Item to AutoMapper did not copy chain of elements:
Mapper.CreateMap<Item, ItemDto>()
.ForMember(_ => _.Element, _ => _.Ignore());
Secondly, copy the chain manually with a mark viewed items:
static IEnumerable<ElementDto> MapElements(Element[] elements)
{
var elementToDtoMap = new Dictionary<Element, ElementDto>();
foreach (var element in elements)
{
MapElement(element, null, elementToDtoMap);
}
return elementToDtoMap.Select(_ => _.Value);
}
static void MapElement(Element element, ItemDto parentItem, Dictionary<Element, ElementDto> elementToDtoMap)
{
ElementDto elementDto = null;
if (elementToDtoMap.TryGetValue(element, out elementDto))
return;
elementDto = Mapper.Map<ElementDto>(element);
elementToDtoMap.Add(element, elementDto);
if (parentItem != null)
{
parentItem.Element = elementDto;
}
if (element.Item != null)
{
MapElement(element.Item.Element, elementDto.Item, elementToDtoMap);
}
}

Can I add same object to multiple groups in LINQ?

I have a set of objects I want to group in Linq. However the key I want to use is a combination of multiple keys. for eg
Object1: Key=SomeKeyString1
Object2: Key=SomeKeyString2
Object3: Key=SomeKeyString1,SomeKeyString2
Now I'd like the results to be only two groups
Grouping1: Key=SomeKeyString1 : Objet1, Object3
Grouping2: Key=SomeKeyString2 : Object2, Object3
Basically I want the same object to be part of two groups. Is that possible in Linq?
Well, not directly with GroupBy or GroupJoin. Both of those extract a single grouping key from an object. However, you could do something like:
from groupingKey in groupingKeys
from item in items
where item.Keys.Contains(groupingKey)
group item by groupingKey;
Sample code:
using System;
using System.Collections.Generic;
using System.Linq;
class Item
{
// Don't make fields public normally!
public readonly List<string> Keys = new List<string>();
public string Name { get; set; }
}
class Test
{
static void Main()
{
var groupingKeys = new List<string> { "Key1", "Key2" };
var items = new List<Item>
{
new Item { Name="Object1", Keys = { "Key1" } },
new Item { Name="Object2", Keys = { "Key2" } },
new Item { Name="Object3", Keys = { "Key1", "Key2" } },
};
var query = from groupingKey in groupingKeys
from item in items
where item.Keys.Contains(groupingKey)
group item by groupingKey;
foreach (var group in query)
{
Console.WriteLine("Key: {0}", group.Key);
foreach (var item in group)
{
Console.WriteLine(" {0}", item.Name);
}
}
}
}

Categories

Resources