Is there a better GroupBy to Dictionary (or solution) to bucketting? - c#

Is there a way to write the ToDictionary statement below using the SQL-ish Linq syntax?
public class KeyedType
{
public string Name { get; set; }
public string Value { get; set; }
}
Dictionary<string,List<KeyedType>> groups =
list.GroupBy((g) => g.Name)
.ToDictionary(g => g.Key, g => g.ToList());

Whenever you find yourself with a Dictionary<TKey, List<TSomething>>, you may find you can happily use a Lookup<TKey, TSomething>. If this proves to be the case, you can use ToLookup to make one.
However, neither for ToLookup nor for your code is there a query expression syntax available, unfortunately.

Somehow by doing the GroupBy you already bucked. Try to convert to a Dictionary in case is really necessary. E.g. using group by:
var groups = list.GroupBy(g => g.Name);
foreach (var group in groups)
{
var groupName = group.Key;
var valueList = group.Select(obj => obj.Value);
foreach (var value in valueList)
{
//...
}
}

Related

Dynamically constructing a GET query with optional parameters in C#

I have a class that contains all the properties of a query I'm constructing could possibly have (most of which are optional)
For example:
public class QueryClass
{
public string Offset {get; set;}
public string Limit {get; set;}
public string Sort {get; set;}
}
Then in a BuildQuery() method I am constructing the query by doing this:
private string BuildQuery(QueryClass query)
{
var queryDictionary = new Dictionary<string, string>();
if (!string.IsNullOrEmpty(query.Offset)
{
queryDictionary.Add("offset", query.Offset);
}
if (!string.IsNullOrEmpty(query.Limit)
{
queryDictionary.Add("limit", query.Limit);
}
if (!string.IsNullOrEmpty(query.Sort)
{
queryDictionary.Add("sort", query.Sort);
}
var content = new FormUrlEncodedContent(queryDictionary);
return content.ReadAsStringAsync().Result;
}
This works, but the issue is my actual QueryClass is significantly larger than this, there's got to be a better way to do this than to have a ton of IF statements for every optional property but I haven't been able to come up with a more elegant solution. I also don't care for adding the keys in the dictionary in this way, I probably need a new approach for how I structure the QueryClass.
If you don't mind taking the reflection hit, just project, filter nulls, then send to ToDictionary
Note : The assumptions here are, all the property names are your keys, and all the properties are convertible to string
var queryClass = new QueryClass()
{
Limit = "asdf",
Sort = "Bob"
};
var results = queryClass
.GetType()
.GetProperties()
.Select(x => (Value: x.GetValue(queryClass) as string, x.Name))
.Where(x => !string.IsNullOrWhiteSpace(x.Value))
.ToDictionary(
x => x.Name.ToLower(),
x => x.Value);
foreach (var (key, value) in results)
Console.WriteLine($"{key} : {value}");
Output
limit : asdf
sort : Bob
Add pepper and salt to taste
Or as an extension method
public static IDictionary<string, string> ConvertToDictionary<T>(this T source)
=> typeof(T).GetProperties()
.Select(x => (Value: x.GetValue(source) as string, x.Name))
.Where(x => !string.IsNullOrWhiteSpace(x.Value))
.ToDictionary(
x => x.Name.ToLower(),
x => x.Value!);

How to Iterate over grouped items and compare them?

I'm having a hard time with this approach since I am new to Entity Framework. I actually don't know if there is something special related to EF or if the limitation is on me.
I would like to group some records from my database and after that, I'd like to iterate over the groups, then iterate over the elements on each group comparing it with all the other elements in the same group.
I have created two simple classes to illustrate the scenario:
public class MyContext : DbContext
{
public DbSet<MyClass> MyClass { get; set; }
}
And:
public class MyClass
{
public int Id { get; set; }
public int Value { get; set; }
}
What I have so far with a context injected is:
this.MyContext.MyClass
.GroupBy(x => x.Value)
.ToList() // need to materialize here
.ForEach(grp =>
{
// compare each item with all the other
// items in the current group
});
But I don't know how to iterate over the items and then compare with the others in the same group.
With the following code, the quesiton becomes what type is grp?
this.MyContext.MyClass
.GroupBy(x => x.Value)
.ToList() // need to materialize here
.ForEach(grp =>
{
// compare each item with all the other
// items in the current group
});
Well the grp variable is of type IGrouping<TKey, TElement>. That type derives from IEnumerable<TElement> so each grp is a list of TElement so you can foreach or do whatever you want to all the items in the grp.
DotNetFiddle Example.
Your variable grp is an IGrouping<int, MyClass>. You can treat it as an IEnumerable<MyClass>. For instance, you can get the item with the biggest Id like this:
this.MyContext.MyClass
.GroupBy(x => x.Value)
.ToList() // need to materialize here
.ForEach(grp =>
{
MyClass itemWithMaxId = grp.FirstOrDefault();
foreach (MyClass item in grp)
{
if (item.Id > itemWithMaxId.Id)
{
itemWithMaxId = item;
}
}
});
Note, however, that the ForEach method does not return anything, it only performs the specified action on each element of the list. If you want to get something, for instance the item with the biggest Id of each group, I suggest you to use the Select method provided by Linq, like in this example:
var itemsWithMaxIdByGroup = this.MyContext.MyClass
.GroupBy(x => x.Value)
.ToList() // need to materialize here
.Select(grp =>
{
MyClass itemWithMaxId = grp.First();
foreach (MyClass item in grp.Skip(1))
{
if (item.Id > itemWithMaxId.Id)
{
itemWithMaxId = item;
}
}
return itemWithMaxId;
});

Linq sorting, grouping, and returning a dictionary

I have an array of custom objects (PONO) called FrameList, I can orderBy and GroupBy to sort and group. But now I want to return a dictionary, and I am getting the error:
(BTW, there is no error is I remove the .ToDictionary() method. )
Error 1 'System.Linq.IGrouping' does
not contain a definition for 'type' and no extension method 'type'
accepting a first argument of type
'System.Linq.IGrouping' could be found
(are you missing a using directive or an assembly
reference?) C:\Users\ysg4206\Documents\Visual Studio
2010\Projects\Watson\CatalogServiceClasses\BuildToby.cs 21 38 Watson
Here is the snippet that sorts and groups:
var dict = request.FrameList.OrderBy(f => f.trueDate)
.GroupBy(f => f.type)
.ToDictionary(f => f.type, f => f);
and here is the definition of the FrameList (which is a simple array of FrameData)
[DataContract]
public class FrameData
{
[DataMember]
public String idx { get; set; }
[DataMember]
public String type { get; set; }
[DataMember]
public String path { get; set; }
[DataMember]
public String date { get; set; }
[DataMember]
public DateTime trueDate { get; set; }
}
It's not clear what you're trying to do.
If you're just looking to create an idiomatic multi-map, try:
var lookup = request.FrameList.ToLookup(f => f.type);
If you're looking to create a multi-map with the Dictionary class, try:
var dict = request.FrameList
.GroupBy(f => f.type)
.ToDictionary(g => g.Key, g => g.ToList());
Do note that in both of these cases, OrderBy before grouping is useless since Lookup and Dictionary are inherently unordered collections. If you're trying to accomplish something with your ordering (perhaps you want items within a group to be ordered by date?), please clarify, and we might be able to provide something more appropriate.
EDIT: Based on your comment, looks like you need:
var dict = request.FrameList
.GroupBy(f => f.type)
.ToDictionary(g => g.Key,
g => g.OrderBy(f => f.trueDate)
.ToList());
Assuming your method signature is IDictionary<string,IList<FrameData>>
Try to use next code snippet:
var dict = request.FrameList
.OrderBy(f => f.trueDate)
.GroupBy(f => f.type)
.ToDictionary(f => f.Key, f => f.ToList());
GroupBy operator returns IGrouping objects. Those objects have Key property. But there is no type property on grouping object (its property of your entity):
var dict = request.FrameList
.OrderBy(f => f.trueDate)
.GroupBy(f => f.type) // this selects IEnumerable<IGrouping>
.ToDictionary(g => g.Key, g => g);
NOTE: If this is a Linq to Entities query, then GroupBy returns IQueryable<IGrouping<TKey, TElement>>

How to get values from IGrouping

I have a question about IGrouping and the Select() method.
Let's say I've got an IEnumerable<IGrouping<int, smth>> in this way:
var groups = list.GroupBy(x => x.ID);
where list is a List<smth>.
And now I need to pass values of each IGrouping to another list in some way:
foreach (var v in structure)
{
v.ListOfSmth = groups.Select(...); // <- ???
}
Can anybody suggest how to get the values (List<smth>) from an IGrouping<int, smth> in such a context?
Since IGrouping<TKey, TElement> implements IEnumerable<TElement>, you can use SelectMany to put all the IEnumerables back into one IEnumerable all together:
List<smth> list = new List<smth>();
IEnumerable<IGrouping<int, smth>> groups = list.GroupBy(x => x.id);
IEnumerable<smth> smths = groups.SelectMany(group => group);
List<smth> newList = smths.ToList();
Here's an example that builds/runs: https://dotnetfiddle.net/DyuaaP
Video commentary of this solution: https://youtu.be/6BsU1n1KTdo
foreach (var v in structure)
{
var group = groups.Single(g => g.Key == v. ??? );
v.ListOfSmth = group.ToList();
}
First you need to select the desired group. Then you can use the ToList method of on the group. The IGrouping is a IEnumerable of the values.
More clarified version of above answers:
IEnumerable<IGrouping<int, ClassA>> groups = list.GroupBy(x => x.PropertyIntOfClassA);
foreach (var groupingByClassA in groups)
{
int propertyIntOfClassA = groupingByClassA.Key;
//iterating through values
foreach (var classA in groupingByClassA)
{
int key = classA.PropertyIntOfClassA;
}
}
From definition of IGrouping :
IGrouping<out TKey, out TElement> : IEnumerable<TElement>, IEnumerable
you can just iterate through elements like this:
IEnumerable<IGrouping<int, smth>> groups = list.GroupBy(x => x.ID)
foreach(IEnumerable<smth> element in groups)
{
//do something
}
var groups = list.GroupBy(x => x.ID);
Can anybody suggest how to get the values (List) from an IGrouping<int, smth> in such a context?
"IGrouping<int, smth> group" is actually an IEnumerable with a key, so you either:
iterate on the group or
use group.ToList() to convert it to a List
foreach (IGrouping<int, smth> group in groups)
{
var thisIsYourGroupKey = group.Key;
List<smth> list = group.ToList(); // or use directly group.foreach
}
If you have an IGrouping<GroupItem, ListItem>, and you want to access the items of type ListItem of this group without utilizing a foreach loop, it's very simple. The object of type IGrouping<GroupItem, ListItem> is of type IEnumerable<ListItem> as well, as it is defined as:
public interface IGrouping<out TKey, out TElement> : IEnumerable<TElement>, IEnumerable
So you can simply say:
foreach (IGrouping<GroupItem, ListItem> group in list.GroupBy(x => x.ID))
{
IEnumerable<ListItem> itemsInThisGroup = group;
// ...
}
If for some reason, it has to be a List<T> instead of an IEnumerable<T>, you can of course still call itemsInThisGroup.ToList(). But usually it's better not to if you needn't.
Simply do this:
// this will "split" the list into groups
var groups = list.GroupBy(x => x.ID);
// groups is a "collection of lists"
foreach (var sublist in groups)
{
// now the sublist is only a part of the original list
// to get which is the value of ID, you can use sublist.Key
}
You don't need Select().GroupBy(expr) makes "a list of lists", kind of.
Assume that you have MyPayments class like
public class Mypayment
{
public int year { get; set; }
public string month { get; set; }
public string price { get; set; }
public bool ispaid { get; set; }
}
and you have a list of MyPayments
public List<Mypayment> mypayments { get; set; }
and you want group the list by year. You can use linq like this:
List<List<Mypayment>> mypayments = (from IGrouping<int, Mypayment> item in yearGroup
let mypayments1 = (from _payment in UserProjects.mypayments
where _payment.year == item.Key
select _payment).ToList()
select mypayments1).ToList();

Linq Query to IEnumerable<T> Extension Method

Consider this,
class Item
{
public string ID { get; set;}
public string Description { get; set; }
}
class SaleItem
{
public string ID { get; set;}
public string Discount { get; set; }
}
var itemsToRemoved = (List<Item>)ViewState["ItemsToRemove"];
// get only rows of ID
var query = from i in itemsToRemoved select i.ID;
var saleItems= (List<SaleItem>)ViewState["SaleItems"];
foreach (string s in query.ToArray())
{
saleItems.RemoveItem(s);
}
How can I write this LINQ phrase using IEnumerable/List Extension methods
// get only rows of ID
var query = from i in items select i.ID;
thanks in advance.
That one's easy:
var query = items.Select(i => i.ID);
A select clause always corresponds to a call to Select. Some of the other operators end up with a rather more complex expansion :) If you work hard, you can get the compiler to do some very odd stuff...
You can find all the details of this and other query expression translations in section 7.16 of the C# specification (v3 or v4).
<plug>
You could also buy C# in Depth, 2nd edition and read chapter 11 if you really wanted to :)</plug>
You can use this:
var query = items.Select(i => i.ID);
A couple of other points:
Here you don't need the call to ToArray:
foreach (string s in query.ToArray())
Also if your list is large and you are removing a lot of items you may want to use List.RemoveAll instead of iterating. Every time you remove an item from a list all the other items after it have to be moved to fill the gap. If you use RemoveAll this only has to be done once at the end, instead of once for every removed item.
List<Item> itemsToRemove = (List<Item>)ViewState["ItemsToRemove"];
HashSet<string> itemIds = new HashSet<string>(itemsToRemove.Select(s => s.ID));
saleItems.RemoveAll(c => itemIds.Contains(c.ID));
public static class ItemCollectionExtensions
{
public static IEnumerable<int> GetItemIds(this List<Item> list)
{
return list.Select(i => i.ID);
}
}

Categories