Mapping values of grouped query? - c#

I'm new to C# and LINQ, so I'm unsure what the best practice for this would be:
I have an IEnumerable<Something>, where Something has properties A, B and C. I want to group my list of Somethings by the property A: However, I want the list of values for each group to be mapped to a different object, TargetSomething. I have a helper class, MyHelper, which has a static function converting any Something into a TargetSomething.
Therefore, I have source value / structure as follows:
IEnumerable<Something>
And target value / structure as follows:
IEnumerable<IGrouping<A, TargetSomething>>
Although, really, instead of an IGrouping I'd prefer to have a Dictionary or something, basically the equivalent of a Map in other languages.
So far, to group by keys, I used the following code:
from smth in smths group smth by smth.A into grp select grp;
Which seems pretty verbose for a simple groupBy operation, but what can I do.
Now my question is: How can I add the mapping of Something to TargetSomething using MyHelper.map(mySomething) in this LINQ query?
Also, as far as I'm aware, the IEnumerable resulting from this query is lazy so that not the entire content has to be in memory all the time - Is that a misconception? If not, will I lose this feature by adding a map somewhere?
Thank you

You can use ToLookup to return a Lookup which is a kind of multi-valued Dictionary.
A Lookup<TKey, TElement> implements IEnumerable<IGrouping<TKey,TElement>> so this should work fine for your use case.
var lookup = smths.ToLookup(smth => smth.A, HelperFunction);
The HelperFunction should have the return type TargetSomething and a single parameter of Something.
You could also use a lambda for that:
var lookup = smths.ToLookup(smth => smth.A, smth => new TargetSomething(...));

Related

How to obtain an item from HashSet depending on a condition in c#

In c#, for List data structure, we can specify a condition as a lambda expression for finding single or multiple elements by List.Find(Predicate) or List.FindAll(Predicate) methods. Is there any way to do a similar operation with HashSet?
If you want multiple results you can use Where method:
hash.Where(x => Predicate(x));
If you want a single result you can use the FirstOrDefault method
hash.FirstOrDefault(x => Predicate(x));
Since HashSet implements the IEnumerable interface, you can use extension methods like Where.
So:
var matches = yourHashSet.Where(item => predicate(item));
should work.

Efficient way to group all equal objects defined by IEqualityComparer

I have a big list of certain objects that are of type Order. Furthermore, I have defined an IEqualityComparer<Order> and my goal is to efficiently create a list of lists (or groupings) where every sublist contains the objects that are equal to each other. Mathematically, I have an equivalence relation (IEqualityComparer) and want to have a list of all equivalence classes. I thought about using HashSet<Order>, Dictionary<> or Lookup<> but they dont seem to be perfect for my case. So any ideas?
I think this something like this shluod to the job:
var uniqueItems = yourRawList.Distinct(yourComparer);
var result = uniqueItems.Select(i => new List<Order>(yourRawList.Where(o =>yourComparer.Equals(i, o))));

Converting for-loop to a linq query

I like to think of myself as pretty good with LINQ, but every now and then I try to accomplish something that I just can't get to work. I'd like to convert a SPListItemCollection to a dictionary so I can use the key to look up a value without the need for a LINQ query each time:
var formsConfigItems = new Dictionary<string, string>();
foreach (SPListItem item in list.GetItems(query))
formsConfigItems.Add(item.Title, (item["Value"] == null ? string.Empty : item["Value"].ToString()));
This works, but I was hoping to do it in a cleaner fashion, using LINQ. (not a big deal but I like to use LINQ over for-loops whenever possible, although it's the same thing behind the scenes.
I tried to do something like this:
var formsConfigItems = (from SPListItem i in list.GetItems(query)
select new { i.Title, i["Value"].ToString() }).ToDictionary<string, string>(k=>k.Key, k=>k.Value);
But that doesn't seem to work. If I try to use a lambda expression on list.GetItems(query), I'm not given the option to use .Where or any LINQ commands (which is weird because it is an SPListCollection)
Thanks in advance.
Try:
var formsConfigItems = list.GetItems(query)
.Cast<SPListItem>()
.ToDictionary(item => item.Title,
item => Convert.ToString(item["Value"]));
To answer your queries:
If I try to use a lambda expression on list.GetItems(query), I'm not
given the option to use .Where or any linq commands (which is weird
because it is an SPListCollection)
That's because SPListCollection is an "old-school" collection that implements IEnumerable but not IEnumerable<T>, so C# / LINQ (at compile-time anyway) can't tell what type of items it contains. The Cast<SPListItem>() call helps work around this issue - it turns an IEnumerable into an IEnumerable<T>, allowing the type-algebra to work out at compile-time . Your for loop doesn't have this issue since you explicitly specify the type of the loop variable - the compiler inserts a cast on your behalf for each item in the sequence.
I tried to do something like this (query expression). But that
doesn't seem to work.
That's because you are not constructing the anonymous type instance correctly (property names can't be inferred for arbitrary expressions) and your lambda expression isn't quite right either (the property names you use don't match the property names of the anonymous type). Try this instead:
var formsConfigItems = (from SPListItem i in list.GetItems(query)
select new
{
i.Title,
Value = Convert.ToString(i["Value"])
}).ToDictionary(a => a.Title, a => a.Value);
Ani's got the better solution IMO, but one other thing you're missing: Your LINQ statement is creating a collection of anonymous items, but you're not giving names to the properties in that anonymous class.
The k=>k.Key expression doesn't work, because it doesn't know what Key is - you've only defined Title (since you didn't give it a name, it borrowed the one from the object). The Value one can't be automatically figured out, so it would throw a compiler error.
To do it this way, you'd need to specifically declare the names:
new { Key = i.Title, Value = i["Value"].ToString() }

c# in memory query of objects without linq

We are still using .Net Framework 2.0 / VS 2005 so i do not have LINQ. If i don't want to go with the poor man's LINQ solution, what are some other alternatives for being able to query in memory custom objects in a dictionary?
I'm not sure if one of your poor man's LINQ solution is LINQBridge but I used it for a few weeks and it seemed to be working okay before we actually switched to .NET 3.5 and the real deal.
Dictionary<T> would seem like a good choice, although you haven't provided much information about what you mean by "query." Are you just looking to retrieve data based on some key value? Get a count of total items? Do a sum or average based on some condition? You really need to give more information to get a better answer.
To elaborate on what Chesso said, you'll have to iterate the loop just like LINQ does...
for example:
static T FindFirst<T>(IEnumerable<T> col, Predicate<T> predicate)
{
foreach(T t in col)
{
if(predicate(t))
{
return t;
}
}
return default(T);
}
I was not aware of the Predicate delegate, that seems to be pretty much what i was looking for. As far as the context for which i'm querying:
Say i have a object X with properties A (name, guaranteed to be unique) and B (age)
1) I have a series of objects in a dictionary whose keys are say Property A of a given object, and of course the value is the object iself.
Now i want to retrieve all objects in this dictionary which meet a certain criteria of B, say age > 20.
I can add all the values of the dictionary into a list then call the .FindAll on it, passing in a delegate. I can create an anonymous delegate to do this, but say i will reuse this many times. How can i dynamically specify an age criteria for the delegate method? Would the only choice be to encapsulate the Predicate method in a class, then create a new instance of that class with my criteria as an instance variable?

Common problem for me in C#, is my solution good, stupid, reasonable? (Advanced Beginner)

Ok, understand that I come from Cold Fusion so I tend to think of things in a CF sort of way, and C# and CF are as different as can be in general approach.
So the problem is: I want to pull a "table" (thats how I think of it) of data from a SQL database via LINQ and then I want to do some computations on it in memory. This "table" contains 6 or 7 values of a couple different types.
Right now, my solution is that I do the LINQ query using a Generic List of a custom Type. So my example is the RelevanceTable. I pull some data out that I want to do some evaluation of the data, which first start with .Contains. It appears that .Contains wants to act on the whole list or nothing. So I can use it if I have List<string>, but if I have List<ReferenceTableEntry> where ReferenceTableEntry is my custom type, I would need to override the IEquatable and tell the compiler what exactly "Equals" means.
While this doesn't seem unreasonable, it does seem like a long way to go for a simple problem so I have this sneaking suspicion that my approach is flawed from the get go.
If I want to use LINQ and .Contains, is overriding the Interface the only way? It seems like if there way just a way to say which field to operate on. Is there another collection type besides LIST that maybe has this ability. I have started using List a lot for this and while I have looked and looked, a see some other but not necessarily superior approaches.
I'm not looking for some fine point of performance or compactness or readability, just wondering if I am using a Phillips head screwdriver in a Hex screw. If my approach is a "decent" one, but not the best of course I'd like to know a better, but just knowing that its in the ballpark would give me little "Yeah! I'm not stupid!" and I would finish at least what I am doing completely before switch to another method.
Hope I explained that well enough. Thanks for you help.
What exactly is it you want to do with the table? It isn't clear. However, the standard LINQ (-to-Objects) methods will be available on any typed collection (including List<T>), allowing any range of Where, First, Any, All, etc.
So: what is you are trying to do? If you had the table, what value(s) do you want?
As a guess (based on the Contains stuff) - do you just want:
bool x= table.Any(x=>x.Foo == foo); // or someObj.Foo
?
There are overloads for some of the methods in the List class that takes a delegate (optionally in the form of a lambda expression), that you can use to specify what field to look for.
For example, to look for the item where the Id property is 42:
ReferenceTableEntry found = theList.Find(r => r.Id == 42);
The found variable will have a reference to the first item that matches, or null if no item matched.
There are also some LINQ extensions that takes a delegate or an expression. This will do the same as the Find method:
ReferenceTableEntry found = theList.FirstOrDefault(r => r.Id == 42);
Ok, so if I'm reading this correctly you want to use the contains method. When using this with collections of objects (such as ReferenceTableEntry) you need to be careful because what you're saying is you're checking to see if the collection contains an object that IS the same as the object you're comparing against.
If you use the .Find() or .FindAll() method you can specify the criteria that you want to match on using an anonymous method.
So for example if you want to find all ReferenceTableEntry records in your list that have an Id greater than 1 you could do something like this
List<ReferenceTableEntry> listToSearch = //populate list here
var matches = listToSearch.FindAll(x => x.Id > 1);
matches will be a list of ReferenceTableEntry records that have an ID greater than 1.
having said all that, it's not completely clear that this is what you're trying to do.
Here is the LINQ query involved that creates the object I am talking about, and the problem line is:
.Where (searchWord => queryTerms.Contains(searchWord.Word))
List<queryTerm> queryTerms = MakeQueryTermList();
public static List<RelevanceTableEntry> CreateRelevanceTable(List<queryTerm> queryTerms)
{
SearchDataContext myContext = new SearchDataContext();
var productRelevance = (from pwords in myContext.SearchWordOccuranceProducts
where (myContext.SearchUniqueWords
.Where (searchWord => queryTerms.Contains(searchWord.Word))
.Select (searchWord => searchWord.Id)).Contains(pwords.WordId)
orderby pwords.WordId
select new {pwords.WordId, pwords.Weight, pwords.Position, pwords.ProductId});
}
This query returns a list of WordId's that match the submitted search string (when it was List and it was just the word, that works fine, because as an answerer mentioned before, they were the same type of objects). My custom type here is queryTerms, a List that contains WordId, ProductId, Position, and Weight. From there I go about calculating the relevance by doing various operations on the created object. Sum "Weight" by product, use position matches to bump up Weights, etc. My point for keeping this separate was that the rules for doing those operations will change, but the basic factors involved will not. I would have even rather it be MORE separate (I'm still learning, I don't want to get fancy) but the rules for local and interpreted LINQ queries seems to trip me up when I do.
Since CF has supported queries of queries forever, that's how I tend to lean. Pull the data you need from the db, then do your operations (which includes queries with Aggregate functions) on the in-memory table.
I hope that makes it more clear.

Categories

Resources