Flattening a Dictionary of Dictionaries With SelectMany() - c#

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

Related

Flatten a Dictionary<int, List<object>>

I have a dictionary which has an integer Key that represents a year, and a Value which is a list of object Channel. I need to flatten the data and create a new object from it.
Currently, my code looks like this:
Dictionary<int, List<Channel>> myDictionary;
foreach(var x in myDictionary)
{
var result = (from a in x.Value
from b in anotherList
where a.ChannelId == b.ChannelId
select new NewObject
{
NewObjectYear = x.Key,
NewObjectName = a.First().ChannelName,
}).ToList();
list.AddRange(result);
}
Notice that I am using the Key to be the value of property NewObjectYear.
I want to get rid of foreach since the dictionary contains a lot of data and doing some joins inside the iteration makes it very slow. So I decided to refactor and came up with this:
var flatten = myDictionary.SelectMany(x => x.Value.Select(y =>
new KeyValuePair<int, Channel>(x.Key, y))).ToList();
But with this, I couldn't get the Key directly. Using something like flatten.Select(x => x.Key) is definitely not the correct way. So I tried finding other ways to flatten that would be favorable for my scenario but failed. I also thought about creating a class which will contain the year and the list from the flattened but I don't know how.
Please help me with this.
Also, is there also another way that doesn't have the need to create a new class?
It seems to me you are trying to do only filtering, you do not need join for that:
var anotherListIDs = new HashSet<int>(anotherList.Select(c => c.ChannelId));
foreach (var x in myDictionary)
{
list.AddRange(x.Value
.Where(c => anotherListIDs.Contains(c.ChannelId))
.Select(c => new NewObject
{
NewObjectYear = x.Key,
NewObjectName = c.First().ChannelName,
}));
}
You do realise, that if the second element of the list in a specific dictionary element has a matching channelId, that you return the first element of this list, don't you?
var otherList = new OtherItem[]
{
new OtherItem() {ChannelId = 1, ...}
}
var dictionary = new Dictionary<int, List<Channel>[]
{
{ 10, // Key
new List<Channel>() // Value
{
new Channel() {ChannelId = 100, Name = "100"},
new Channel() {ChannelId = 1, Name = "1"},
},
};
Although the 2nd element has a matching ChannelId, you return the Name of the first element.
Anyway, let's assume this is what you really want. You are right, your function isn't very efficient.
Your dictionary implements IEnumerable<KeyValuePair<int, List<Channel>>. Therefore every x in your foreach is a KeyValuePair<int, List<Channel>. Every x.Value is a List<Channel>.
So for every element in your dictionary (which is a KeyValuePair<int, List<Channel>), you take the complete list, and perform a full inner join of the complete list with otherList, and for the result you take the key of the KeyValuePair and the first element of the List in the KeyValuePair.
And even though you might not use the complete result, but only the first or the first few, because of FirstOrDefault(), or Take(3), you do this for every element of every list in your Dictionary.
Indeed your query could be much more efficient.
As you use the ChannelIds in your OtherList only to find out if it is present, one of the major improvements would be to convert the ChannelIds of OtherList to a HashSet<int> where you have superior fast lookup to check if the ChannelId of one of the values in your Dictionary is in the HashSet.
So for every element in your dictionary, you only have to check every ChannelId in the list to see if one of them is in the HashSet. As soon as you've found one, you can stop and return only the first element of the List and the Key.
My solution is an extension function of Dictionary>. See Extension Methods Demystified
public static IEnumerable<NewObject> ExtractNewObjects(this Dictionary<int, List<Channel>> dictionary,
IEnumerable<OtherItem> otherList)
{
// I'll only use the ChannelIds of the otherList, so extract them
IEnumerable<int> otherChannelIds = otherList
.Select(otherItem => otherItem.ChannelId);
return dictionary.ExtractNewObjects(otherChannelIds);
}
This calls the other ExtractNewobjects:
public static IEnumerable<NewObject> ExtractNewObjects(this Dictionary<int, List<Channel>> dictionary,
IEnumerable<int> otherChannelIds)
{
var channelIdsSet = new HashSet<int>(otherChannelIds));
// duplicate channelIds will be removed automatically
foreach (KeyValuePair<int, List<Channel>> keyValuePair in dictionary)
{
// is any ChannelId in the list also in otherChannelIdsSet?
// every keyValuePair.Value is a List<Channel>
// every Channel has a ChannelId
// channelId found if any of these ChannelIds in in the HashSet
bool channelIdFound = keyValuePair.Value
.Any(channel => otherChannelIdsSet.Contains(channel.ChannelId);
if (channelIdFound)
{
yield return new NewObject()
{
NewObjectYear = keyValuePair.Key,
NewObjectName = keyValuePair.Value
.Select(channel => channel.ChannelName)
.FirstOrDefault(),
};
}
}
}
usage:
IEnumerable<OtherItem> otherList = ...
Dictionary<int, List<Channel>> dictionary = ...
IEnumerable<Newobject> extractedNewObjects = dictionary.ExtractNewObjects(otherList);
var someNewObjects = extractedNewObjects
.Take(5) // here we see the benefit from the yield return
.ToList();
We can see four efficiency improvements:
the use of HashSet<int> enables a very fast lookup to see if the ChannelId is in OtherList
the use of Any() stops enumerating the List<Channel> as soon as we've found a matching Channelid in the HashSet
the use of yield return makes that you don't enumerate over more elements in your Dictionary than you'll actually use.
The use of Select and FirstOrDefault when creating NewObjectName prevents exceptions if List<Channel> is empty

How to store the result of a linq query in a KeyDictionary variable

So I have a collection of objects who have multiple properties, two of these are groupname and personname. Now I need to count in the collection how many of each object belong to a certain group and person. So in other words, I need to group by groupname, then personname and then count how many objects have this combination. First I created this
public MultiKeyDictionary<string, string, int> GetPersonsPerGroup(IEnumerable<Home> homes ,List<string> gr, List<string> na)
{
List<string> groups = gr;
groups.Add("");
List<string> names = na;
names.Add("");
List<Home> Filtered = homes.ToList();
Filtered.ForEach(h => h.RemoveNull());
var result = new MultiKeyDictionary<string, string, int>();
int counter1 = 0;
foreach (var g in groups)
{
int counter2 = 0;
foreach (var n in names)
{
int counter3 = 0;
foreach (Home h in Filtered)
{
if (h.GroupName == g && h.PersonName == n)
{
counter3++;
if (counter3 > 100)
break;
}
}
if (counter3 > 0)
{
result.Add(g,n,counter3);
}
counter2++;
}
counter1++;
}
Which may look good, but the problem is that the "home" parameter can contain more than 10000 objects, with more than 1500 unique names and around 200 unique groups. Which causes this to iterate like a billion times really slowing my program down. So I need an other way of handling this. Which made me decide to try using linq. Which led to this creation:
var newList = Filtered.GroupBy(x => new { x.GroupName, x.PersonName })
.Select(y => (MultiKeyDictionary<string, string, int>)result.Add(y.Key.GroupName, y.Key.PersonName, y.ToList().Count));
Which gives an error "Cannot convert type 'void' to 'MultiKeyDictionary<string,string,int>' and I have no idea how to solve it. How can I make it so that the result of this query gets stored all in one MultikeyDictionary without having to iterate over each possible combination and counting all of them.
Some information:
MultiKeyDictionary is a class I defined (something I found on here actually), it's just a normal dictionary but with two keys assosiated to one value.
The RemoveNull() method on the Home object makes sure that all the properties of the Home object are not null. If it is the case the value gets sets to something not null ("null", basic date, 0, ...).
The parameters are:
homes = a list of Home objects received from an other class
gr = a list of all the unique groups in the list of homes
na = a list of all the unique names in the list of homes
The same name can occur on different groups
Hopefully someone can help me get further!
Thanks in advance!
Select must return something. You are not returning but only adding to an existing list. Do this instead:
var newList = Filtered.GroupBy(x => new { x.GroupName, x.PersonName }):
var result = new MultiKeyDictionary<string, string, int>);
foreach(var y in newList)
{
result.Add(y.Key.GroupName, y.Key.PersonName, y.ToList().Count));
}
The reason you are getting error below:
"Cannot convert type 'void' to 'MultiKeyDictionary'
is because you are trying to cast the returned value from Add which is void to MultiKeyDictionary<string,string,int> which clearly cannot be done.
If MultiKeyDictionary requires the two keys to match in order to find a result, then you might want to just use a regular Dictionary with a Tuple as a composite type. C# 7 has features that make this pretty easy:
public Dictionary<(string, string), int> GetPersonsPerGroup(IEnumerable<Home> homes ,List<string> gr, List<string> na)
{
return Filtered.GroupBy(x => (x.GroupName, x.PersonName))
.ToDictionary(g => g.Key, g => g.Count);
}
You can even associate optional compile-time names with your tuple's values, by declaring it like this: Dictionary<(string groupName, string personName), int>.
Your grouping key anonymous object should work fine as a standard Dictionary key, so no reason to create a new type of Dictionary unless it offers special access via single keys, so just convert the grouping to a standard Dictionary:
var result = Filtered.GroupBy(f => new { f.GroupName, f.PersonName })
.ToDictionary(fg => fg.Key, fg => fg.Count());

Making a list distinct in C#

In C#, I have an object type 'A' that contains a list of key value pairs.
The key value pairs is a category string and a value string.
To instantiate object type A, I would have to do the following:
List<KeyValuePair> keyValuePairs = new List<KeyValuePair>();
keyValuePairs.Add(new KeyValuePair<"Country", "U.S.A">());
keyValuePairs.Add(new KeyValuePair<"Name", "Mo">());
keyValuePairs.Add(new KeyValuePair<"Age", "33">());
A a = new A(keyValuePairs);
Eventually, I will have a List of A object types and I want to manipulate the list so that i only get unique values and I base it only on the country name. Therefore, I want the list to be reduced to only have ONE "Country", "U.S.A", even if it appears more than once.
I was looking into the linq Distinct, but it does not do what I want because it I can't define any parameters and because it doesn't seem to be able to catch two equivalent objects of type A. I know that I can override the "Equals" method, but it still doesn't solve the my problem, which is to render the list distinct based on ONE of the key value pairs.
To expand upon Karl Anderson's suggestion of using morelinq, if you're unable to (or don't want to) link to another dll for your project, I implemented this myself awhile ago:
public static IEnumerable<T> DistinctBy<T, U>(this IEnumerable<T> source, Func<T, U>selector)
{
var contained = new Dictionary<U, bool>();
foreach (var elem in source)
{
U selected = selector(elem);
bool has;
if (!contained.TryGetValue(selected, out has))
{
contained[selected] = true;
yield return elem;
}
}
}
Used as follows:
collection.DistinctBy(elem => elem.Property);
In versions of .NET that support it, you can use a HashSet<T> instead of a Dictionary<T, Bool>, since we don't really care what the value is so much as that it has already been hashed.
Check out the DistinctBy syntax in the morelinq project.
A a = new A(keyValuePairs);
a = a.DistinctBy(k => new { k.Key, k.Value }).ToList();
You need to select the distinct property first:
Because it's a list inside a list, you can use the SelectMany. The SelectMany will concat the results of subselections.
List<A> listOfA = new List<A>();
listOfA.SelectMany(a => a.KeyValuePairs
.Where(keyValue => keyValue.Key == "Country")
.Select(keyValue => keyValue.Value))
.Distinct();
This should be it. It will select all values where the key is "Country" and concat the lists. Final it will distinct the country's. Given that the property KeyValuePairs of the class A is at least a IEnumerable< KeyValuePair< string, string>>
var result = keyValuePairs.GroupBy(x => x.Key)
.SelectMany(g => g.Key == "Country" ? g.Distinct() : g);
You can use the groupby statement. From here you can do all kind off cool stuf
listOfA.GroupBy(i=>i.Value)
You can groupby the value and then sum all the keys or something other usefull

How to do "like" on dictionary key?

How can I do a "like" to find a dictionary key? I'm currently doing:
mydict.ContainsKey(keyName);
But some keyNames have an additional word appended (separated by a space), I'd like to do a "like" or .StartsWith(). The comparisons will look like this:
"key1" == "key1" //match
"key1" == "key1 someword" //partial match
I need to match in both cases.
You can use LINQ to do this.
Here are two examples:
bool anyStartsWith = mydict.Keys.Any(k => k.StartsWith("key1"))
bool anyContains = mydict.Keys.Any(k => k.Contains("key1"))
It is worth pointing out that this method will have worse performance than the .ContainsKey method, but depending on your needs, the performance hit will not be noticable.
mydict.Keys.Any(k => k.StartsWith("key1"));
While enumerating over the Keys you will lose the performance benefits of a dictionary:
mydict.ContainsKey(someKey); // O(1)
mydict.Keys.Any(k => k.StartsWith("key1")); // O(n)
If you run the .Contains() method of the string and not the dictionary you'll get what you want.
var matchingKeys = mydict.Keys.Where(x => x.Contains("key1"));
var keys = (from key mydict.keys where key.contains(keyName) select key).ToList();
The Keys member of the dictionary can be manipulated to check for presence with more complicated semantics than equality. It need not be a LINQ extension method check, but that is likely the simplest.
If you want the keys themselves and not a true/false you can do:
string match = "key1";
var matches = from k in mydict
where k.Key.Contains(match)
select new
{
k.Key
};
For matching and retrieving keys "like" you could use these extensions.
public static class Extensions
{
public static bool HasKeyLike<T>(this Dictionary<string, T> collection, string value)
{
var keysLikeCount = collection.Keys.Count(x => x.ToLower().Contains(value.ToLower()));
return keysLikeCount > 0;
}
public static List<string> GetKeysLike<T>(this Dictionary<string, T> collection, string value)
{
return collection.Keys.Select(x => x.ToLower().Contains(value.ToLower())).ToList();
}
}

Dictionary of Linq Expression [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Declaring func<in T,Out Result> dynamically
I'm trying to build a query using linq-to-sql and, in order to provide sorting capabilities, I wrote something like this:
private Dictionary<string, Expression<Func<DataAccess.Auditing, object>>> OrderDict { get; set; }
OrderDict = new Dictionary<string,Expression<Func<Augeos.GereonWeb.DataAccess.Auditing, object>>>();
OrderDict.Add("Date", p => (DateTime)p.RequestDateTime);
OrderDict.Add("Username", p => (string)p.CdUsername);
OrderDict.Add("Portfolio", p => (string)p.CdPortfolio);
OrderDict.Add("Url", p => (string)p.RequestedUrl);
OrderDict.Add("Result", p => (bool)p.RequestResult);
OrderDict.Add("Duration", p => (float)p.RequestDuration);
private IQueryable<DataAccess.Auditing> SetOrder(string orderBy, bool orderDirection, IQueryable<DataAccess.Auditing> query)
{
if (orderDirection)
return query.OrderByDescending(OrderDict[orderBy]);
else
return query.OrderBy(OrderDict[orderBy]);
}
My goal is to use the SortOrder function to sort the query. The main problem is that Func returns an object and linq cannot sort elements of this type. I truly need to use object as a return type because I have to sort by a wide range of types. Is it actually possible to slightly modify this code and make it to work?
Thank you,
Gio
Hm you'd have to go very low-level in the expression tree of your query to do such a thing. A much easier way would be to use the DynamicLinq library, where you can do .OrderBy() with a column as string value. Documentation can be found here on Scott Guthrie's blog.
If you imagine ordering your query like so:
query = orderDict["Date"](query, true);
Then your dictionary would have to be defined like this:
var orderDict = new Dictionary<string, Func<IQueryable<Auditing>, bool, IQueryable<Auditing>>>();
and you could add items to it like so:
orderDict.Add("Date", MakeOrderedQueryDelegate(a => a.RequestDateTime));
orderDict.Add("UserName", MakeOrderedQueryDelegate(a => a.CdUsername));
which would need a MakeOrderedQueryDelegate to be like this:
private Func<IQueryable<Auditing>, bool, IQueryable<Auditing>> MakeOrderedQueryDelegate<T>(Expression<Func<Auditing, T>> keyselector)
{
return (q, descending) => { return MakeOrderedQuery(q, descending, keyselector); };
}
..and you can implement MakeOrderedQuery like this:
private IQueryable<Auditing> MakeOrderedQuery<T>(IQueryable<Auditing> query, bool descending,
Expression<Func<Auditing, T>> keyselector)
{
if (descending)
{
return query.OrderByDescending(keyselector);
}
else
{
return query.OrderBy(keyselector);
}
}

Categories

Resources