How to group a list of objects into a dictionary? - c#

I have list of:
public class Item
{
public int FirstId { get; set; }
public int SecondId { get; set; }
public int Sequence { get; set; }
}
A sample list might be:
var list = new List<Item>();
list.Add( new Item{ FirstId = 1, SecondId = 10, Sequence = 1 } );
list.Add( new Item{ FirstId = 2, SecondId = 10, Sequence = 1 } );
list.Add( new Item{ FirstId = 2, SecondId = 11, Sequence = 2 } );
list.Add( new Item{ FirstId = 3, SecondId = 10, Sequence = 1 } );
list.Add( new Item{ FirstId = 4, SecondId = 10, Sequence = 2 } );
list.Add( new Item{ FirstId = 4, SecondId = 11, Sequence = 1 } );
I need to group the items by FirstId and sort each group by Sequence. The result should be:
Dictionary<int, IEnumerable<int>>();
where the key is FirstId, and value is the list of SecondId ordered by Sequence. So the result of the above should be:
Key (int), Value (IEnumerable<int>)
1, (10)
2, (10, 11)
3, (10)
4, (11, 10)
Currently I have the following:
var items = from t in tags
group t by t.FirstId into g
orderby g.Key
select g;
How can I expand on the above to achieve what I need?

You can use method ToDictionary. Try this code:
tags
.GroupBy(t => t.FirstId)
.ToDictionary(k => k.Key, g => g.OrderBy(t => t.Sequence).Select(t => t.SecondId));
Demo

Related

How to Select a list by having all the lists in that list

I haw this model:
public class Obj1
{
public long ID { get; set; }
public long Name { get; set; }
public List<int> NumberList { get; set; }
}
The values in Obj1 List:
List<Obj1> lst1 = new List<Obj1>();
lst1.Add(new Obj1()
{
ID = 1,
Name = "t1",
NumberList = new List<int>{1,3,4}
});
lst1.Add(new Obj1()
{
ID = 2,
Name = "t2",
NumberList = new List<int>{1,4,5}
});
lst1.Add(new Obj1()
{
ID = 3,
Name = "t3",
NumberList = new List<int>{4,5,6}
});
lst1.Add(new Obj1()
{
ID = 4,
Name = "t4",
NumberList = new List<int>{5,7,8}
});
I want to select list1 provided all list2 is in it. Also list 2 is equal to:
List<int> lst2 = new List<int>(){4,5};
I use this code, But it doesn't work properly:
var FinalList = lst1.Where(item => item.NumberList.Any(item2 => lst2.Contains(item2)).ToList();
The correct output should be this:
{
ID = 2,
Name = "t2",
NumberList = new List<int>{1,4,5}
},
{
ID = 3,
Name = "t3",
NumberList = new List<int>{4,5,6}
}
If I understand correctly, you want all Obj items in lst1 if the NumberList property is a superset of lst2. You can query this like so:
var finalList = lst1.Where(l => !lst2.Except(l.NumberList).Any()).ToList();
Full example on Ideone.

Linq get id of the employee with most entries in table

i am trying to write a linq that will return the id of the employee who has the most entries in the table.
This is how my class looks like
public class TrainingEmployee
{
public int EmployeeId { get; set; }
public int TrainingId { get; set; }
public List<TrainingEmployee> GenerateData()
{
return new List<TrainingEmployee>()
{
new TrainingEmployee() { EmployeeId = 1, TrainingId = 2},
new TrainingEmployee() { EmployeeId = 1, TrainingId = 2},
new TrainingEmployee() { EmployeeId = 1, TrainingId = 2},
new TrainingEmployee() { EmployeeId = 1, TrainingId = 2},
new TrainingEmployee() { EmployeeId = 2, TrainingId = 3},
new TrainingEmployee() { EmployeeId = 2, TrainingId = 3},
new TrainingEmployee() { EmployeeId = 2, TrainingId = 3},
new TrainingEmployee() { EmployeeId = 2, TrainingId = 3},
new TrainingEmployee() { EmployeeId = 2, TrainingId = 5},
new TrainingEmployee() { EmployeeId = 2, TrainingId = 5},
new TrainingEmployee() { EmployeeId = 2, TrainingId = 1},
};
}
}
And this is how my code looks so far
var lista = new TrainingEmployee();
var data = lista.GenerateData().GroupBy(x => x.EmployeeId);
var maxValue = 0;
var employeeId = 0;
foreach (var group in data)
{
var currentlyGroupCount = group.Count();
if(currentlyGroupCount > maxValue)
{
maxValue = currentlyGroupCount;
employeeId = group.Key;
}
}
Console.WriteLine("Value: {0} employeeid: {1}", maxValue, employeeId);
How can i do the above code in just a linq without using that much of a code?
You could order it descending and select the first one:
var employee = GenerateData()
// group on EmployeeId
.GroupBy(e => e.EmployeeId)
// reverse order it on count
.OrderByDescending(g => g.Count())
// select the first
.FirstOrDefault();
// check if the query returned anything other than default.
if(employee != default)
Console.WriteLine("Value: {0} employeeid: {1}", employee.Count(), employee.EmployeeId);
Another approach similar to jeroen-van-langen's answer but using MoreLINQ's MaxBy():
GenerateData()
.GroupBy(e => e.EmployeeId)
.MaxBy(e => e.Count());
This would also return multiple IDs if multiple employee's had the same "max count"; a possibility in your scenario.
This evaluates Count() once for each employees group so is a little more performant, it allows also to get both of employyId and the max count
var mostFrequentEmployeeId = GenerateData()
.GroupBy(x => x.EmployeeId, (employeeId, employeesGroup) => new { employeeId, count = employeesGroup.Count() })
.OrderByDescending(x => x.count)
.FirstOrDefault()?
.employeeId;

changing item values after grouping with linq

its hard to explain, but I try.
I did a linq grouping.
var grouping = aList.GroupBy(x => new { x.Width, x.Height, x.Quantity});
Lets say I have 3 groups now in grouping. Group 1 has 1 item, group 2 has 5 items and group 3 has 9 items.
All items are having a "stack" field. What I want to do now is, set a number into the stack field of each item based on the group.
All items in group 1 should have the same "stack" number lets say "1", all items in group 2 should have the same "stack" number, maybe "2" and all items in group 3 should have the same "stack" number. So, one "stack" number per group.
How to do that?
Linq is a query and is not meant to modify an existing object. So if you want to modify existing without creating a new then use code below
class Program
{
static void Main(string[] args)
{
List<MyList> aList = new List<MyList>() {
new MyList() { Width = 1, Height = 2, Quantity = 3},
new MyList() { Width = 1, Height = 2, Quantity = 3},
new MyList() { Width = 1, Height = 2, Quantity = 3},
new MyList() { Width = 2, Height = 2, Quantity = 3},
new MyList() { Width = 2, Height = 2, Quantity = 3},
new MyList() { Width = 3, Height = 2, Quantity = 3},
new MyList() { Width = 3, Height = 2, Quantity = 3},
new MyList() { Width = 3, Height = 2, Quantity = 3},
new MyList() { Width = 3, Height = 2, Quantity = 3}
};
var grouping = aList.GroupBy(x => new { x.Width, x.Height, x.Quantity });
int number = 0;
foreach (var group in grouping)
{
foreach (MyList myList in group)
{
myList.StackNumber = number;
}
number++;
}
}
}
public class MyList
{
public int Width { get; set; }
public int Height { get; set; }
public int Quantity { get; set; }
public int StackNumber { get; set; }
}
Select has another overload that passes the index number to the delegate, you could use that.
var grouping = aList
.GroupBy(x => new { x.Width, x.Height, x.Quantity});
.Select( (x,y) => new { Item = x, Stack = y } );

Merge multiple ObjectList into one

I have a object list like this:
using System;
using System.Collections.Generic;
using System.Linq;
public class Item
{
public int Id;
public int Price;
}
public class Program
{
public static void Main()
{
List<Item> food = new List<Item> {
new Item { Id = 1, Price = 1},
new Item { Id = 2, Price = 3},
new Item { Id = 4, Price = 9}
};
List<Item> drinks = new List<Item> {
new Item { Id = 1, Price = 1},
new Item { Id = 2, Price = 2},
new Item { Id = 3, Price = 0},
new Item { Id = 4, Price = 1},
new Item { Id = 6, Price = 1}
};
List<Item> magazines = new List<Item> {
new Item { Id = 3, Price = 1},
new Item { Id = 5, Price = 2},
};
var combined = food.Union(drinks).Union(magazines).Distinct().ToList();
}
}
What I want to do is, add all the prices into one list. Without any duplicates (Id). My goal is to have the total sum of the prices. So basically add all prices for the same ID together.
So the combined list should look like this:
List<Item> combined = new List<Item> {
new Item { Id = 1, Price = 2},
new Item { Id = 2, Price = 5},
new Item { Id = 3, Price = 1},
new Item { Id = 4, Price = 10},
new Item { Id = 5, Price = 2},
new Item { Id = 6, Price = 1}
};
Preferably using LINQ.
If you need to get a sum of prices for concatenated List<Item>, you should use GroupBy method to group the items by Id and then Sum of prices for every group
var combined = food.Concat(drinks).Concat(magazines)
.GroupBy(i => i.Id, i => i.Price, (i, prices) => new Item { Id = i, Price = prices.Sum() })
.OrderBy(i => i.Id).ToList();
You can also add OrderBy to sort the results by Id property, if it's important
var x =
// First, combine all lists
food.Concat(drinks).Concat(magazines)
// Group combined Items by Id
.GroupBy(item => item.Id)
// From all groups create final Items with Id and summed Price
.Select(g => new Item { Id = g.Key, Price = g.Sum(item => item.Price) });

C# LINQ Take limited results per grouped

I have list that includes class named 'ID', 'Name' and 'Category'. There are 6 item in list.
List<MyData> list =
{
{0, "John", "Police"},
{1,"Michael", "Police"},
{2,"Alice", "Police"},
{3, "Ferdinand", "Thief"},
{4, "Jocas", "Thief"},
{5, "Connor", "Thief"}
};
I wanna list them with limited quantity per group by 'Category' with LINQ.
Example : I want list 2 item for each 'Cateogory'. Listed should be below :
John Police
Michael Police
Ferdinand Thief
Jocas Thief
Use combination of Take and SelectMany:
var results = list.GroupBy(x => x.Category).SelectMany(g => g.Take(2)).ToList();
I've tested it on following Item class:
public class Item
{
public int ID { get; set; }
public string Name { get; set; }
public string Category { get; set; }
}
And query:
List<Item> list = new List<Item>
{
new Item { ID = 0, Name = "John", Category = "Police"},
new Item { ID = 1, Name = "Michael", Category = "Police"},
new Item { ID = 2, Name = "Alice", Category = "Police"},
new Item { ID = 3, Name = "Ferdinand", Category = "Thief"},
new Item { ID = 4, Name = "Jocas", Category = "Thief"},
new Item { ID = 5, Name = "Connor", Category = "Thief"}
};
var results = list.GroupBy(x => x.Category).SelectMany(g => g.Take(2)).ToList();
Returns 4 elements, right as you want.

Categories