Help me with new theme "predicate"
I have a question to change this code for using predicate:
var d = dict.OrderBy(delegate(KeyValuePair<string,int> pair) { return pair.Value; });
But have no idea how it's working...
And where is the problem?
delegate(KeyValuePair<string, int> pair) { return pair.Value; } is the same as Func<KeyValuePair<string, int>, int>.
Thus you can easily replace your code by this:
var d = dict.OrderBy((KeyValuePair<string, int> pair) => pair.Value);
Or easier:
var d = dict.OrderBy(pair => pair.Value);
As the compiler can automatically infer the type of the instance within the delegate.
As an aside: a predicate is basically a method that expects an instance of your type (in your case KeyValuePair<string, int> and returns a bool, thus a Func<T, bool>.
Related
I have a list of type MyType and I want to group them by their key and only keep the last updated element. Here's what I have tried:
var myObjectsList = new List<MyType> {.....};
var myDictionary = new Dictionary<Guid, MyType>();
foreach (var myObject in myObjectsList)
{
if (myObject is null || (myObject.key ?? Guid.Empty) == Guid.Empty)
continue;
var key = myObject.key ?? Guid.Empty;
if (!myDictionary.ContainsKey(key))
{
myDictionary[key] = myObject;
continue;
}
var storedVehicle = myDictionary[key];
if (storedVehicle.LasatUpdate < myObject.LasatUpdate)
myDictionary[key] = myObject;
}
I am wondering if there is a cleaner way to this operation using Enumerable.ToDictionary like it is explained here. What I found so far is the following:
var groupedDictionary = myObjectsList
.GroupBy(x => x.key)
.ToDictionary(gdc => gdc.key, gdc => gdc.ToList());
which group them by key and store all in a list rather than only keeping the last-updated item.
You can use this overload of GroupBy to create a IEnumerable<KeyValuePair<Guid, MyType>>, then use that to create a dictionary.
new Dictionary<Guid, MyType>(
myObjectsList
.Where(x => x.Key is not null && x.Key != Guid.Empty)
.GroupBy(
x => x.Key, // group by the guid
// for each group, find the object with the latest update
(key, objects) => new KeyValuePair<Guid, MyType>(key.Value, objects.MaxBy(y => y.LastUpdate))
)
);
Note that MaxBy requires .NET 6. A simple but slow alternative on lower versions is to OrderByDescending and then First().
As you saw, there's an existing ToDictionary() method. It looks something like this:
public static Dictionary<TKey, TValue> ToDictionary(this IEnumerable<TValue> items, Func<TValue,TKey> keySelector)
{
Dictionary<TKey, TValue> result = new();
foreach(var item in items)
{
var key = keySelector(item);
if (result.ContainsKey(key)) throw new ArgumentException($"Key {key} already exists.");
result[key] = item;
}
return result;
}
But as you also noticed, it won't work for you because it throws an exception if a key already exists, instead of keeping the prior element or updating with the new.
But now that we've peeked behind the scenes to see how it works, you can also write your own version that won't do that. It won't take much... all we have to do is remove the line to throw the exception, like this:
public static Dictionary<TKey, TValue> ToDictionaryEx(this IEnumerable<TValue> items, Func<TValue,TKey> keySelector)
{
Dictionary<TKey, TValue> result = new();
foreach(var item in items)
{
result[keySelector(item)] = item;
}
return result;
}
One nice feature is you don't even need to use GroupBy() to call it. You can just let it run over the full enumerable, and the natural result will be for later items with a given key to replace the earlier items, such that the desired last item will be there at the end:
var groupedDictionary = myObjectsList.ToDictionaryEx(gdc => gdc.key);
I have declared multiple of these variables, but how can I then put them into a generic list?
Expression<Func<poco, string>> fieldToUpdate1 = x => x.Name;
Expression<Func<poco, bool>> fieldToUpdate2 = x => x.Id;
Currently I can only specify one type for the generic list.
So I can either get a List<string> or List<bool>. But not both. I want to be able to have a generic list that accepts both so I can pass that list as a parameter.
Use case:
The use case I am trying to do is create a generic wrapper for the Mongo method updateOne. With the below signature. I want to create a generic wrapper that will accept two parameters. I can use these parameters to call the actual mongo implementation. Something like this:
GenericWrapper(Expression<Func<TDocument, bool>> filter, List<(Expression<Func<TDocument, TField>> expression, TField actual value)>)
The problem is that TField can only be one type. So I can only do this:
Expression<Func<Student, string>> fieldToUpdate1 = x => x.name;
Expression<Func<Student, int>> fieldToUpdate2 = x => x.testScore;
var expressions = new List<(Expression<Func<Student, int>> expression, int value)>();
var item1 = (expression: fieldToUpdate2, value: 4);
var item2 = (expression: fieldToUpdate1, value: "test");
expressions.Add(item1);
//I can't add item2 since its of a different type. I can only pass a list of the same type. And my generic wrapper function will only accept a list of one type
http://api.mongodb.com/csharp/current/html/M_MongoDB_Driver_IMongoCollectionExtensions_UpdateOne__1.htm
public static UpdateResult UpdateOne<TDocument>(
this IMongoCollection<TDocument> collection,
Expression<Func<TDocument, bool>> filter,
UpdateDefinition<TDocument> update,
UpdateOptions options = null,
CancellationToken cancellationToken = null
)
Any ideas on how to make this generic wrapper?
Since Expression<T> inherits from Expression you can put it into a List<Expression>.
List<Expression> expressions = new List<Expression>();
expressions.Add(fieldToUpdate1);
expressions.Add(fieldToUpdate2);
You can use object as the return value:
Expression<Func<poco, object>> fieldToUpdate1 = x => x.Name;
Expression<Func<poco, object>> fieldToUpdate2 = x => x.Id;
List<Expression<Func<poco, object>>> testList = new List<Expression<Func<poco, object>>>();
testList.Add(fieldToUpdate1);
testList.Add(fieldToUpdate2);
Anyway, the general design seems a bit strange, since at the end, you have to cast at least the results.
I am trying to create a function whereby I can pass in a functor/predicate that can slot into a dictionary's 'Where' method.
(cardPool is the dictionary of type 'cardStats')
Pseudo of what I'd like to do:
void CardStats findCard(Predicate<CardStats> pred)
{
return cardPool.Where(pred);
}
This code obviously wont work but is simply a rough example of the functionality I am looking for.
I have had no problems setting this up for lists, but for a Dictionary, its really got me stumped.
Any help would be great, thanks!
Edit:
Ahh sorry I should have mentioned more: Cardstats is the value, the key is of type int. I'd like to sift through the values (cardStats) and test their properties such as ID(int) or name(string).
Dictionary<TKey, TValue> implements IEnumerable<KeyValuePair<TKey, TValue>>, so its Where extension method takes a predicate of type Func<KeyValuePair<TKey, TValue>, bool>.
You could implement your method like this:
void CardStats findCard(Func<int, CardStats, bool> pred)
{
return cardPool.Where(kv => pred(kv.Key, kv.Value))
.Select(kv => kv.Value)
.FirstOrDefault();
}
And use it like this:
CardStats stats = myCards.findCard((id, stats) => id == 7);
or
CardStats stats = myCards.findCard((id, stats) => stats.Name == "Ace of Clubs");
Note that using Where on a dictionary doesn't take advantage of the dictionary's quick lookup features and basically treats it as a linear collection of key-value pairs.
One more comment: I would suggest providing a method that returns an IEnumerable of found cards if there are several. Or you could provide one that does that, and one that just returns the first match:
void IEnumerable<CardStats> findCards(Func<int, CardStats, bool> pred)
{
return cardPool.Where(kv => pred(kv.Key, kv.Value))
.Select(kv => kv.Value);
}
void CardStats findCard(Func<int, CardStats, bool> pred)
{
return findCards(pred).FirstOrDefault();
}
I would use FirstOrDefault as the first statement because it will stop as soon it finds a matching element. another thing is that I will consider using something else than a dictionary - because when using it this way is abuse if its indexed purpose.
anyway, this is the code I will use:
public CardStats Find(Func<CardStats, bool> predicate)
{
KeyValuePair<int, Roster> kvCard = cardPool.FirstOrDefault(kvp => predicate(kvp.Value));
if (kvCard.Equals(default(KeyValuePair<int, Roster>)))
return null;
return kvCard.Value;
}
I have code where I get an IDictionary and need to return an IImmutableDictionary. No problem, I just run the extension method ToImmutableDictionary().
Elsewhere, I get an IImmutableDictionary and need to return an IDictionary. Is there a preferred way of doing this?
Use imutable.ToDictionary(r=> r.Key, r=> r.Value) like:
Dictionary<int,int> dictionary = new Dictionary<int, int>();
IImmutableDictionary<int, int> imutable = dictionary.ToImmutableDictionary();
IDictionary<int, int> dictionary2 = imutable.ToDictionary(r=> r.Key, r=> r.Value);
I have these dictionaries:
public class CiscoVSAN
{
private string _VSANName;
private string _VSANNum;
public string VSANName{ get{return _VSANName;} set{_VSANName=value;} }
public string VSANNum{ get{return _VSANNum;} set{_VSANNum=value;} }
public Dictionary<int, CiscoSwitch> MemberSwitches = new Dictionary<int, CiscoSwitch>();
}
public Dictionary<int, CiscoVSAN> VSANList = new Dictionary<int, CiscoVSAN>();
I am trying to replace this foreach
foreach (KeyValuePair<int, CiscoVSAN> vsanpair in this.VSANList)
{
var currSwitchAsEnumerable = vsanpair.Value.MemberSwitches.Where(cs => cs.Value.switchName == RemoteSwitchName);
if (currSwitchAsEnumerable != null)
{
//currVSAN.MemberSwitches.Add(DomainID, currSwitchAsEnumerable.FirstOrDefault().Value);
currSwitch = currSwitchAsEnumerable.FirstOrDefault().Value;
break;
}
}
with a SelectMany on the outer Dictionary. I want the first match in MemberSwitches that matches the condition. Not all VSANs have the same member switches in their respective dictionary. I have tried this:
var selectmany = this.VSANList.SelectMany(cs => cs.Value).Where( => cs).Where(cs.Value.SwitchName == RemoteSwitchName).First();
and this:
var selectmany = this.VSANList.Values.SelectMany(cs => cs.Value).Where( => cs).Where(cs.Value.SwitchName == RemoteSwitchName).First();
But each time the compiler tells me it cannot infer the type arguments from usage. I also tried feeding it a type argument with after the SelectMany statement but that didn't work either. Most of the examples I looked at either flattened lists of lists or simple dictionaries. They also didn't specify any type on the SelectMany.
Edited to add I tried this:
Dictionary<int, List<string>> mydict = new Dictionary<int, List<string>>();
var selectlist = mydict.Values.SelectMany(n => n).ToList();
and did not get the compiler error about inferring type. So then I tried this, just to flatten the dictionary and turn it to a list:
var selectmany = this.VSANList.Values.SelectMany(vs => vs).ToList();
and I get the compiler warning again. I am not sure what type to specify or how to specify it.
Assuming there is no typo, you have a syntax error: the part .Where ( => cs ) is illegal. What did you try to express here?
EDIT:
Actually, to think about it, your entire expression is illegal or mistyped.
The delegate that you pass to SelectMany should return IEnumerable<T>, but your expression returns CiscoVSAN
I figured it out:
var selectmany = this.VSANList.Values.SelectMany(vs => vs.MemberSwitches).Where(cs => cs.Value.switchName == RemoteSwitchName).First();