changing item values after grouping with linq - c#

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

Related

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

How to group a list of objects into a dictionary?

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

List union that depends / selects on property values

I have a class "item":
public class Item
{
public int Id { get; set; }
public decimal Price { get; set; }
public override bool Equals(object obj)
{
if (obj is Item item)
return item.Id == Id;
return false;
}
// GetHashCode omitted...
}
And I have 2 lists that I need to union:
var items1 = new List<Item>
{
new Item { Id = 1, Price = 10 },
new Item { Id = 2, Price = 10 },
new Item { Id = 3, Price = 10 },
};
var items2 = new List<Item>
{
new Item { Id = 1, Price = 10 },
new Item { Id = 2, Price = 8 },
new Item { Id = 4, Price = 10 },
};
The union I get like this:
var union = items1.Union(items2).ToList();
But I need also the constraint that the items with the lowest price is in the union. So for example in the above lists Item.ID = 2 from "items2" must be in the union...so the result should be a list consisting of these 4 items:
Item { Id = 1, Price = 10 }
Item { Id = 2, Price = 8 } // Not the one with Price = 10
Item { Id = 3, Price = 10 }
Item { Id = 4, Price = 10 }
Is there an elegant way of doing this in C# (preferably using Linq)?
You can try using groupby, like below :
var result = items1.Union(items2).GroupBy(x => x.Id)
.Select(x => new Item
{
Id = x.Key,
Price = x.Min(i => i.Price)
});

How to get same results from select as foreach when using GroupBy()

I would like to be able to attain the same results that I can get by using foreach on a grouping when using the select method and an anonymous method.
public class ExportData
{
public int Id { get; set; }
public string Colour { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public int Money { get; set; }
}
public class ExportDataDictionary
{
public IDictionary<string, object> ColumnData { get; set; } = new Dictionary<string, object>();
}
Given the two classes above as an example.
I create some data..
var dataCollection = new List<ExportData>
{
new ExportData { Name = "Name1", Age = 1, Colour = "Blue", Id = 1, Money = 10 },
new ExportData { Name = "Name1", Age = 2, Colour = "Red", Id = 2, Money = 20 },
new ExportData { Name = "Name1", Age = 2, Colour = "Green", Id = 3, Money = 30 },
new ExportData { Name = "Name2", Age = 1, Colour = "Yellow", Id = 4, Money = 40 },
new ExportData { Name = "Name3", Age = 2, Colour = "Blue", Id = 5, Money = 50 },
new ExportData { Name = "Name4", Age = 3, Colour = "Blue", Id = 6, Money = 10 }
};
Next I group this data by, for example, two properties as follows..
var dataGrouping = dataCollection.GroupBy(g => new { g.Name, g.Age });
I then create a list of ExportDataDictionaries and foreach through each group in the grouping, creating a new ExportDataDictionary each time and adding both of the keys to the dictionary.
var data = new List<ExportDataDictionary>();
foreach (var grouping in dataGrouping)
{
var datadictionary = new ExportDataDictionary();
datadictionary.ColumnData.Add("NAME", grouping.Key.Name);
datadictionary.ColumnData.Add("AGE", grouping.Key.Age);
data.Add(datadictionary);
}
The result is a collection of 5 ExportDataDictionaries with 2 Columns in each one that contain the pair of keys that correspond to each of the groupings.
My attempt to achieve the same with the Select method is shown below.
var data2 = new List<ExportDataDictionary>();
var mydata = dataGrouping.Select(d =>
{
var datadictionary = new ExportDataDictionary();
datadictionary.ColumnData.Add("NAME", d.Key.Name);
datadictionary.ColumnData.Add("AGE", d.Key.Age);
data2.Add(datadictionary);
return data2;
});
The result is of the type:
mydata = {System.Linq.Enumerable.WhereSelectEnumerableIterator<System.Linq.IGrouping<<>f__AnonymousType0<string, int>, ConsoleApp2.Program.ExportData>, System.Collections.Generic.List<ConsoleApp2.Program.ExportDataDictionary>>}
and it contains 5 items and each item contains 10 dictionaries. The 5 dictionaries that I expect are there with the same values as when using foreach but then there are 2 copies of each. I believe that this must be because it is creating the dictionaries for both of the keys used in the grouping. So, I am wondering how to only do this for one of the keys or just each group in the collection?
The requirement is that mydata should contain the same result as obtained by foreach in data variable
Any help much appreciated :)
Just Add .ToList() at the end of your last statement remove the data2.Add(datadictionary); statement and only return the datadictionary return datadictionary; like this
var mydata = dataGrouping.Select(d =>
{
var datadictionary = new ExportDataDictionary();
datadictionary.ColumnData.Add("NAME", d.Key.Name);
datadictionary.ColumnData.Add("AGE", d.Key.Age);
return datadictionary;
}).ToList();
I have run your code and checked and saw that mydata contains 5 items, and each item contains 2 ColumnData members.
Actually, your Linq query is only executed when you call the .ToList() function

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