I've a List of custom objects called Product: List
class Product
{
string Key1 {get; set;}
string Key2 {get; set;}
int Count1 {get; set;}
int Count2 {get; set;}
}
I'm combining multiple Product lists and I need to create a new list which will have sum values for each of the Count properties.
For e.g.
List 1:
"Key1", "Key2", 1, 2
"Key2", "Key3", 3, 4
List 2:
"Key1", "Key2", 5, 6
"Key2", "Key3", 7, 8
So my new List should be:
New List:
"Key1", "Key2", 6, 8
"Key2", "Key3", 10, 12
Can someone help me on this please?
Thanks.
You can do this
var list1 = new List<Product>()
{
new Product(){Key1 = "Key1", Key2 ="Key2", Count1 = 1, Count2 = 2},
new Product(){Key1 = "Key2", Key2 ="Key3", Count1 = 1, Count2 = 2}
};
var list2 = new List<Product>()
{
new Product(){Key1 = "Key1", Key2 ="Key2", Count1 = 6, Count2 = 8},
new Product(){Key1 = "Key2", Key2 ="Key3", Count1 = 10, Count2 = 12}
};
var result = list1.Concat(list2)
.GroupBy(x => new {x.Key1,x.Key2})
.Select(x => new
{
x.Key.Key1,
x.Key.Key2,
SumCount1 = x.Sum(y => y.Count1),
SumCount2 = x.Sum(y => y.Count2)
}).ToList();
Output
Demo Here
Key1, Key2, 7, 10
Key2, Key3, 11, 14
Additional Resources
List.AddRange
Adds the elements of the specified collection to the end of the
List.
Enumerable.GroupBy Method (IEnumerable, Func, Func)
Groups the elements of a sequence according to a specified key
selector function and projects the elements for each group by using a
specified function.
Enumerable.Concat Method (IEnumerable, IEnumerable)
Concatenates two sequences.
List<Product> lst1 = new List<Product>();
List<Product> lst2 = new List<Product>();
lst1.Add(new Product() {Key1 = "K1",Key2 ="K2", Count1 =1, Count2=2 });
lst1.Add(new Product() { Key1 = "K2", Key2 = "K3", Count1 = 3, Count2 = 4 });
lst2.Add(new Product() { Key1 = "K1", Key2 = "K2", Count1 = 5, Count2 = 6});
lst2.Add(new Product() { Key1 = "K2", Key2 = "K3", Count1 = 7, Count2 = 8 });
// Way 1
var l = lst1.Join(lst2, l1 => l1.Key1, l2 => l2.Key1,
(lt1, lt2) => new Product { Key1 = lt1.Key1, Key2 = lt1.Key2, Count1 = lt1.Count1 + lt2.Count1, Count2 = lt1.Count2 + lt2.Count2 } ).ToList() ;
// Way 2
var result = lst1.Join(lst2, x => new { x.Key1, x.Key2 },
y => new { y.Key1, y.Key2 }, (x, y) =>
new Product { Key1 = x.Key1, Key2 = x.Key2, Count1 = x.Count1 + y.Count1, Count2 = x.Count2 + y.Count2 }).ToList();
Related
I have a collection of Products in a list (List<Product> ) where product holds id, name and price.
If I would order the list in a descending way based on price, is there a one liner or extensionmethod that allows me to insert a new product in the correct position of the list?
public class Product
{
public int Id {get; set;}
public string Name {get; set;}
public int Price {get; set;} // assume only whole integers for price
}
public class Main()
{
List<Product> products = new();
products.Add(new Product(id= 1, Name="Product1", Price=10 };
products.Add(new Product(id= 2, Name="Product2", Price=15 };
products.Add(new Product(id= 3, Name="Product3", Price=11 };
products.Add(new Product(id= 4, Name="Product4", Price=20 };
products = products.OrderByDescending(prd => prd.Price).ToList();
var newProduct = new({id = 5, Name="new product", Price = 17})
// Is there an short solution available that allows me to insert a new product with
// price = 17 and that will be inserted between products with price 15 and 20?
// Without repeatedly iterating over the list to find the one lower and the one higher
// than the new price and recalculate the index...
var lastIndex = products.FindLastIndex(x => x.Price >= newProduct.Price);
products.Insert(lastIndex + 1, p5);
}
Edit for Solution: I upvoted Tim Schmelter's answer as the most correct one. It is not a single line, as it requires a custom extension method, but I think a single line solution isn't available. Adding it and do a OrderByDescending() works, and is simple, but then depends on the OrderByDescending() statement for the rest of the code...
You can use a SortedList<TKey, TValue>:
SortedList<int, Product> productList = new();
var p = new Product{ Id = 1, Name = "Product1", Price = 10 };
productList.Add(p.Price, p);
p = new Product { Id = 2, Name = "Product2", Price = 15 };
productList.Add(p.Price, p);
p = new Product { Id = 3, Name = "Product3", Price = 11 };
productList.Add(p.Price, p);
p = new Product { Id = 4, Name = "Product4", Price = 20 };
productList.Add(p.Price, p);
p = new Product { Id = 5, Name = "Product5", Price = 17 };
productList.Add(p.Price, p);
foreach(var x in productList)
Console.WriteLine($"{x.Key} {x.Value.Name}");
outputs:
10 Product1
11 Product3
15 Product2
17 Product5
20 Product4
Edit: Note that it doesn't allow duplicate keys, so like a dictionary. You could solve it by using a SortedList<int, List<Product>>. For example with this extension method:
public static class CollectionExtensions
{
public static void AddItem<TKey, TValue>(this SortedList<TKey, List<TValue>> sortedList, TValue item, Func<TValue, TKey> getKey)
{
TKey key = getKey(item);
if (sortedList.TryGetValue(key, out var list))
{
list.Add(item);
}
else
{
sortedList.Add(key, new List<TValue> { item });
}
}
}
Usage:
SortedList<int, List<Product>> productLists = new();
productLists.AddItem(new Product { Id = 1, Name = "Product1", Price = 10 }, p => p.Price);
productLists.AddItem(new Product { Id = 2, Name = "Product2", Price = 10 }, p => p.Price);
productLists.AddItem(new Product { Id = 3, Name = "Product3", Price = 20 }, p => p.Price);
productLists.AddItem(new Product { Id = 4, Name = "Product4", Price = 20 }, p => p.Price);
productLists.AddItem(new Product { Id = 5, Name = "Product5", Price = 15 }, p => p.Price);
foreach (var x in productLists)
Console.WriteLine($"{x.Key} {string.Join("|", x.Value.Select(p => p.Name))}");
outputs:
10 Product1|Product2
15 Product5
20 Product3|Product4
You could calculate the position of the new element before adding it to the list, and then use List.Insert.
I have list of objects with three keys and I want to convert it to three levels of lookups (or dictionaries):
class MyClass
{
public int Key1;
public int Key2;
public int Key3;
public float Value;
}
...
IEnumerable<MyClass> table = new List<MyClass>()
{
new MyClass(){Key1 = 11, Key2 = 21, Key3 = 31, Value = 1},
new MyClass(){Key1 = 11, Key2 = 21, Key3 = 32, Value = 2},
new MyClass(){Key1 = 11, Key2 = 22, Key3 = 31, Value = 3},
new MyClass(){Key1 = 11, Key2 = 23, Key3 = 33, Value = 4},
new MyClass(){Key1 = 12, Key2 = 21, Key3 = 32, Value = 5},
new MyClass(){Key1 = 12, Key2 = 22, Key3 = 31, Value = 6}
};
I want the result to be of type:
ILookup<int, ILookup<int, Dictionary<int, float>>>
or
Dictionary<int, Dictionary<int, Dictionary<int, float>>>
I tried:
ILookup<int, MyClass> level1 = table.ToLookup(i => i.Key1, i => i);
ILookup<int, ILookup<int, MyClass>> level2 = level1.ToLookup(
i => i.Key, i => i.ToLookup(j => j.Key2, j => j));
ILookup<int, ILookup<int, Dictionary<int, float>>> level3 = ?
, but I'm stucked in third level. It's probably a dupe, but what I'm looking for is probably buried under tons of questions about lists of objects with parent-child relation. [1] [2] [3] [4]
It's a bit of an eyeful and not readable at all but this will sort you out:
ILookup<int, ILookup<int, Dictionary<int, float>>> result = table
.ToLookup(i => i.Key1)
.ToLookup(i => i.Key, i => i.ToLookup(j => j.Key2)
.ToLookup(x => x.Key, x => x.ToDictionary(y => y.Key3, y => y.Value)));
If you are certain that every combination of {Key1, Key2, Key3} is unique, you can create a Dictionary. A fetch value from the Dictionary will return one float.
If there might be duplicate combinations of {Key1, Key2, Key3}, then you need to create a LookupTable. A fetch returns the sequence of all original values that have this combination of keys.
For this you need the overload of Enumerable.ToLookup or the overload of Enumerable.ToDictionary with both a keySelector and an ElementSelector.
key: new {Key1, Key2, Key3}
element: Value
So:
IEnumerable<MyClass> table = ...
var lookupTable = table.ToLookup(
// parameter keySelector: for every object of MyClass take the keys:
myObject => new
{
Key1 = myObject.Key1,
Key2 = myObject.Key2,
Key3 = myObject.Key3,
},
// parameter elementSelector: for every object of MyClass take the Value
myObject => myObject.Value);
ToDictionary is similar:
var dictionary = table.ToLookup(myObject => new
{
Key1 = myObject.Key1,
Key2 = myObject.Key2,
Key3 = myObject.Key3,
},
myObject => myObject.Value);
Usage:
var keyToLookup = new
{
Key1 = 7,
Key2 = 14,
Key3 = 42,
};
float lookedUpValue = dictionary[keyToLookup];
IEnumerable<lookedUpValues = lookupTable[keyToLookup];
Simple comme bonjour!
I have a model class which looks something like this:
public class Employee
{
public int Id {get;set;}
public int ParentId {get;set;}
public string Name{get;set;}
public string Designation {get;set;}
}
using which I simulated a list:
new List<Employee> employees
{
new Employee{Id = 1, ParentId = 0, Name = "A", Designation = "CEO" },
new Employee{Id = 2, ParentId = 1, Name = "B", Designation = "Manager" },
new Employee{Id = 3, ParentId = 1, Name = "C", Designation = "Manager" },
new Employee{Id = 4, ParentId = 2, Name = "D", Designation = "Lead" },
new Employee{Id = 5, ParentId = 3, Name = "E", Designation = "Lead" },
new Employee{Id = 6, ParentId = 4, Name = "F", Designation = "Developer" },
new Employee{Id = 7, ParentId = 4, Name = "G", Designation = "Developer" },
new Employee{Id = 8, ParentId = 5, Name = "H", Designation = "Developer" }
};
Well I need to write a LINQ query to filter the above list so that even the parent objects(if present) are retained during the filtering. I could not quiet wrap my head around the retainment of the parent part where I always end up getting it wrong.
To make it more clear this is what is the expected filtered list in case the filter search criteria are the Ids 6 and 7:
{
new Employee{Id = 1, ParentId = 0, Name = "A", Designation = "CEO" },
new Employee{Id = 2, ParentId = 1, Name = "B", Designation = "Manager" },
new Employee{Id = 4, ParentId = 2, Name = "D", Designation = "Lead" }
new Employee{Id = 6, ParentId = 4, Name = "F", Designation = "Developer" },
new Employee{Id = 7, ParentId = 4, Name = "G", Designation = "Developer" }
}
and if the Id to filter is 8:
{
new Employee{Id = 1, ParentId = 0, Name = "A", Designation = "CEO" },
new Employee{Id = 3, ParentId = 1, Name = "C", Designation = "Manager" },
new Employee{Id = 5, ParentId = 3, Name = "E", Designation = "Lead" },
new Employee{Id = 8, ParentId = 5, Name = "H", Designation = "Developer" }
}
and if the Id to filter is 2:
{
new Employee{Id = 1, ParentId = 0, Name = "A", Designation = "CEO" },
new Employee{Id = 2, ParentId = 1, Name = "B", Designation = "Manager" }
}
You can implement a help method, EmployeeAndBosses which returns given employee and all the parents:
private static IEnumerable<Employee> EmployeeAndBosses(Employee value,
IEnumerable<Employee> collection) {
for (Employee item = value;
item != null;
item = collection.FirstOrDefault(x => x.ParentId == item.Id))
yield return item;
}
then you can filter topmost employee in the hierarchy, and add their bosses then:
HashSet<int> ids = new HashSet<int>() {
6, 7
};
var result = employees
.Where(item => ids.Contains(item.Id)) // topmost
.SelectMany(item => EmployeeAndBosses(item, employees)) // topmost and parents
.GroupBy(item => item.Id) // Duplicates (e.g. CEO) removing
.Select(group => group.First()); //
Edit: If you have a huge collection(s) and that's why FirstOrDefault and GroupBy are bad choice, you can implement Bread First Search:
private static IEnumerable<Employee> MyFilter(IEnumerable<Employee> list,
IEnumerable<int> idsToFind) {
Dictionary<int, Employee> stuff = list
.ToDictionary(item => item.Id, item => item);
HashSet<int> ids = new HashSet<int>(idsToFind);
HashSet<int> completed = new HashSet<int>();
Queue<Employee> agenda = new Queue<Employee>(list.Where(item => ids.Contains(item.Id)));
while (agenda.Count > 0) {
Employee current = agenda.Dequeue();
if (null != current && completed.Add(current.Id)) {
yield return current;
if (stuff.TryGetValue(current.ParentId, out current))
agenda.Enqueue(current);
}
}
}
As some comments seem to be quite... Subjective... Here is a simple (but somewhat inefficient) extension that handles your requirements like a charm:
(assuming you'll never hire an employee as a boss to another employee that in turn is their boss, but such madness would probably break the company quicker than it breaks the query)
public static IEnumerable<Employee> FindByIdsAndIncludeParents(this IEnumerable<Employee> employees, params int[] targetIds)
=> employees
.Where(r => targetIds.Contains(r.Id))
.SelectMany(r => employees.FindByIdsAndIncludeParents(r.ParentId).Append(r))
.Distinct();
As some are not quite as keen of exchanging this quite expensive operation for the mere beauty of it, we could trade in some beauty for speed using a dictionary as entry point for instant access to the appended boss-search:
public static IEnumerable<Employee> FindFriendsFaster(this IEnumerable<Employee> employees, params int[] targetIds)
=> employees
.ToDictionary(e => e.Id, e => e)
.FindWithBossFriend(targetIds)
.Distinct();
public static IEnumerable<Employee> FindWithBossFriend(this IDictionary<int, Employee> hierarchy, params int[] targetIds)
=> targetIds
.Where(eId => hierarchy.ContainsKey(eId))
.Select(eId => hierarchy[eId])
.SelectMany(e => hierarchy.FindWithBossFriend(e.ParentId).Append(e));
As you might be able to spot, I personally can't seem to be able to trade in any more of my dignity for the possible removal of that last .Distinct(), but there are rumors going around some would be.
This question already has answers here:
How to intersect list in c#?
(3 answers)
Closed 8 years ago.
I have following 2 list of Item objects in c#:
public class Item
{
public int Id { get; set; }
public List<string> Orders { get; set; }
}
List<Item> items1 = new List<Item>() {
new Item() { Id = 1, Code = 23, Orders = new List<string>() { "A", "B" }},
new Item() { Id = 2, Code = 24, Orders = new List<string>() { "C", "D" }}
};
List<Item> items2 = new List<Item>() {
new Item() { Id = 1, Code = 23, Orders = new List<string>() { "E", "F" }},
new Item() { Id = 2, Code = 24, Orders = new List<string>() { "G", "H" }}
};
I want to merge the Item objects from both lists whose Id and code is same, so the output of above 2 list should be single list with the following entries:
{
new Item() { Id = 1, Code = 23, Orders = new List<string>() { 'A', 'B', 'E', 'F' },
new Item() { Id = 2, Code = 24, Orders = new List<string>() { 'C', 'D', 'G', 'H' }
};
How can i do this in c# using linq ?
You can join both list and then Union their Orders like:
List<Item> combined = (from t in items1
join r in items2 on new { t.Id, t.Code } equals new { r.Id, r.Code }
select new Item
{
Id = t.Id,
Code = t.Code,
Orders = t.Orders.Union(r.Orders).ToList()
}).ToList();
You will get:
If you need your Orders to be concatenated then you can replace Union with Concat. So if your order contains "A", "B" and "A", "F", then with concat you will get "A","B,"A","F" and with Union you will get "A", "B", "F"
var result = from x in items1
join y in items2 on x.Id equals y.Id
select new Item
{
Id = x.Id,
Code = x.Code,
Orders = x.Orders.Concat(y.Orders).ToList()
};
I've got a dictionary laid out like so:
Dictionary<string, List<Series>> example = new Dictionary<string, List<Series>>();
example.Add("Meter1",new List<Series>(){ new Series{ name="Usage", data = new double[] {1,2,3}},
new Series{ name = "Demand", data= new double[]{4,5,6}}});
example.Add("Meter2", new List<Series>(){ new Series{ name="Usage", data = new double[] {1,2,3}},
new Series{ name = "Demand", data= new double[]{4,5,6}}});
What I need is:
Dictionary<string, List<Series>> exampleResult = new Dictionary<string, List<Series>>();
exampleResult.Add("Usage", new List<Series>(){ new Series{ name="Meter1", data = new double[] {1,2,3}},
new Series{ name = "Meter2", data= new double[]{1,2,3}}});
exampleResult.Add("Demand", new List<Series>(){ new Series{ name="Meter1", data = new double[] {4,5,6}},
new Series{ name = "Meter2", data= new double[]{4,5,6}}});
That is, the dictionary projected "sideways", with the name of each Series as the key in the new dictionary, with the key of the old dictionary used as the name of the series.
Here's the series class...
public class Series
{
public string name { get; set; }
public double[] data { get; set; }
}
Sorry if I am not expressing this problem clearly, please ask any questions you'd like, and thanks in advance for any help...
EDITED TO ADD EXAMPLE
Create a grouping and then select out the new keys and values to create a dictionary. Like this:
// source data
var d = new Dictionary<string, Series[]>
{
{
"key1", new[]
{
new Series
{
name = "Usage",
data = new double[] {1, 2, 3}
},
new Series
{
name = "Demand",
data = new double[] {4, 5, 6}
}
}
},
{
"key2", new[]
{
new Series
{
name = "Usage",
data = new double[] {1, 2, 3}
},
new Series
{
name = "Demand",
data = new double[] {4, 5, 6}
}
}
}
};
// transform
var y = (
from outer in d
from s in outer.Value
let n = new
{
Key = s.name,
Series = new Series
{
name = outer.Key,
data = s.data
}
}
group n by n.Key
into g
select g
).ToDictionary(g1 => g1.Key,
g2 => g2.Select(g3 => g3.Series).ToArray());
/* results:
var y = new Dictionary<string, Series[]>
{
{
"Usage",
new[]
{
new Series
{
name = "key1",
data = new double[] { 1, 2, 3 }
},
new Series
{
name = "key2",
data = new double[] { 1, 2, 3 }
}
}
},
{
"Demand",
new[]
{
new Series
{
name = "key1",
data = new double[] {4, 5, 6},
},
new Series
{
name = "key2",
data = new double[] {4, 5, 6}
}
}
}
};
*/
Try this:
example
.SelectMany(x => x.Value
.Select(y => y.name)
).Distinct()
.ToDictionary(
x => x,
x => example
.SelectMany(y => y.Value
.Where(z => z.name == x)
.Select(z => new Series{ name = y.Key, data = z.data })
).ToList()
)