Linq - Groupby a Key and Then Put Each Grouped Item into Separate 'Buckets'

LINQ - GroupBy a key and then put each grouped item into separate 'buckets'

You can use GroupBy:

var groups = items.GroupBy(item => item.ListId);

foreach(var group in groups)
{
Console.WriteLine("List with ID == {0}", group.Key);
foreach(var item in group)
Console.WriteLine(" Item: {0}", item.ItemName);
}

LINQ Group By Wrong Results

The Enumerable.GroupBy method in LINQ to Objects preserves the order of the groupings, as per the docs:

The IGrouping<TKey,TElement> objects are yielded in an order based on the order of the elements in source that produced the first key of each IGrouping<TKey,TElement>. Elements in a grouping are yielded in the order they appear in source.

Which is probably what you expected to see 111....

However, you seem to be using GroupBy on a database table or something like that. This means that you are using Queryable.GroupBy. According to its docs,

The query behavior that occurs as a result of executing an expression tree that represents calling GroupBy<TSource,TKey>(IQueryable<TSource>, Expression<Func<TSource,TKey>>) depends on the implementation of the type of the source parameter. The expected behavior is that it groups the elements of source by a key value that is obtained by invoking keySelector on each element.

As you can see, the exact behaviour is implementation specific, and it doesn't have to preserve the order. Therefore, the first IGrouping is not necessarily the group with the key true.

So to get your desired results, either:

  • use Where to find the group with the key true, or
  • Load all the objects into memory first, then use LINQ to Object's GroupBy. (This could be slow if you have a lot of objects in the database)

Create Buckets ith Linq

Well, for arbitrary list you have to compute range: [min..max] and then

  step = (max - min) / 2;

Code:

  // Given

List<double> list = new List<double>() {
0, 0.1, 1.1, 2.2, 3.3, 4.1, 5.6, 6.3, 7.1, 8.9, 9.8, 9.9, 10
};

int n = 5;

// We compute step

double min = list.Min();
double max = list.Max();

double step = (max - min) / 5;

// And, finally, group by:

double[][] result = list
.GroupBy(item => (int)Math.Clamp((item - min) / step, 0, n - 1))
.OrderBy(group => group.Key)
.Select(group => group.ToArray())
.ToArray();

// Let's have a look:

string report = string.Join(Environment.NewLine, result
.Select((array, i) => $"[{min + i * step} .. {min + i * step + step,2}) : {{{string.Join("; ", array)}}}"));

Console.WriteLine(report);

Outcome:

[0 ..  2) : {0; 0.1; 1.1}
[2 .. 4) : {2.2; 3.3}
[4 .. 6) : {4.1; 5.6}
[6 .. 8) : {6.3; 7.1}
[8 .. 10) : {8.9; 9.8; 9.9; 10}

Please, note Math.Clamp method to ensure [0..n-1] range for groups keys. If you want a Dictionary<int, double[]> where Key is index of bucket:

  Dictionary<int, double[]> buckets = list
.GroupBy(item => (int)Math.Clamp((item - min) / step, 0, n - 1))
.ToDictionary(group => group.Key, group => group.ToArray());

Linq group by DateTime.Date and then group by another criterium

Try this, using an anonymous type for the grouping:

group myData by new { myData.TimeStamp.Date,
Hour = (int) myData.TimeStamp.TimeOfDay.TotalMinutes / 60 } into barData

Will C# Take(k) extension method execute a complete previous GroupBy in sequence?

The C# compiler translates your code into a state machine. That is, it creates a new class behind the scenes with state and behavior needed for iterating the student list. Each time you call the code you get an instance of this class.

Will .Take(2) force the complete .GroupByA(x=>x.Group) execution

Looking at the full students.GroupByA(x => x.Group).Take(2) expression, .Net is able to use the new class instance created by the GroupByA() with the Take() function, and you can think of it as execution only continues until the second time your code hits the yield line, but no further.

However, the nature of a GROUP BY operation is you must loop through the entire dataset to know the attributes of your group, meaning even though you only see the second yield expression, the source.Where() call still has to look at your entire data set and make for at least a O(n*m) operation... every time you identify a new group you go through the entire dataset again.

It should be possible to write a O(n) GROUP BY operation using a Dictionary rather than a List for finding new groups and accumulating aggregate info in the Dictionary values as you go. You might want to see if you can manage that. Of course, the catch is with small values for n (small source list sizes) the hash calculations and lookups can cost more than the sequence iterations.

Linq query update all items in groups that meet my condition

Demo on dotnet fiddle

  1. You need to get all qualified groups by grouping then where the condition
  2. Loop all StudentsList list to update IsQualified accordingly.

    var qualifiedGroups = StudentsList
    .GroupBy(x => x.GroupID)
    .Where(x => x.Any(y => y.University == "OPQ"))
    .Select(g => g.Key).ToArray();

    StudentsList.ForEach(x => x.IsQualified = Array.IndexOf(qualifiedGroups, x.GroupID) != -1);

    foreach(var item in StudentsList)
    Console.WriteLine("Group:" + item.GroupID + " Student:" + item.Student + " IsQualified:" + item.IsQualified);

Output

Group:1 Student:John IsQualified:False
Group:1 Student:Jack IsQualified:False
Group:1 Student:Peter IsQualified:False
Group:2 Student:Donald IsQualified:True
Group:2 Student:Olivia IsQualified:True
Group:2 Student:Emity IsQualified:True
Group:2 Student:Emma IsQualified:True
Group:2 Student:Alan IsQualified:True
Group:3 Student:Adam IsQualified:True
Group:3 Student:Jacob IsQualified:True
Group:3 Student:Matthew IsQualified:True
Group:3 Student:Saint IsQualified:True
Group:3 Student:Joshua IsQualified:True
Group:3 Student:Aubrey IsQualified:True

LINQ Group By and merge sublist of Group back into unique list

If you're sure that all the other properties are the same, you can use First like in your initial attempt and modify @StriplingWarrior's answer like this:

dataInput
.GroupBy(x => x.Id)
.Select(g =>
{
var customer = g.First();
customer.Emails = g.SelectMany(c => c.Emails).ToArray();
return customer;
})
.ToList();

How to group by on result of LINQ group by

In a nutshell,

var result = q.Groupby(x => new { x.a, x.c });

var result = q.Groupby(x => x.a).Select(g => g.Groupby(x => x.c));

Grouping lists into groups of X items per group

Here's one way to do this using LINQ...

public static IEnumerable<IGrouping<int, TSource>> GroupBy<TSource>
(this IEnumerable<TSource> source, int itemsPerGroup)
{
return source.Zip(Enumerable.Range(0, source.Count()),
(s, r) => new { Group = r / itemsPerGroup, Item = s })
.GroupBy(i => i.Group, g => g.Item)
.ToList();
}

Live Demo

how to group data into separate lists with distinct values c#?

Assuming that you want/have a class which maps these properties like:

public class Data
{
public int ID { get; set; }
public int RecordID { get; set; }
public int RecordFieldData { get; set; }
public int RowID { get; set; }
public int FieldID { get; set; }
public string FieldName { get; set; }
}

and you want a List<List<Data>> which contains a List<Data> for every unique RowID+FieldName combination, you can GroupBy an anonymous type:

List<List<Data>> allData = data.AsEnumerable()
.GroupBy(r => new { RowID = r.Field<int>("RowID"), FieldName = r.Field<string>("FieldName") })
.Select(g => g
.Select(r => new Data
{
RowID = g.Key.RowID,
FieldName = g.Key.FieldName,
FieldID = r.Field<int>("RowID"),
ID = r.Field<int>("ID"),
RecordFieldData = r.Field<int>("RecordFieldData"),
RecordID = r.Field<int>("RecordID")
})
.ToList())
.ToList();

If you want to use a Dictionary instead with the RowId + FieldName as key:

Dictionary<Tuple<int, string>, List<Data>> allData = data.AsEnumerable()
.GroupBy(r => new { RowID = r.Field<int>("RowID"), FieldName = r.Field<string>("FieldName") })
.ToDictionary(
g => Tuple.Create(g.Key.RowID, g.Key.FieldName),
g => g.Select(r => new Data
{
RowID = g.Key.RowID,
FieldName = g.Key.FieldName,
FieldID = r.Field<int>("RowID"),
ID = r.Field<int>("ID"),
RecordFieldData = r.Field<int>("RecordFieldData"),
RecordID = r.Field<int>("RecordID")
})
.ToList());

// lookup with RowId + FieldName, f.e.:
List<Data> datas;
if(allData.TryGetValue(Tuple.Create(1, "name"), out datas))
{
// dictionary contains this data
}


Related Topics



Leave a reply



Submit