C# iterate keys in reverse order - c#

I have a dictionary that has int keys. The keys are in random order and are not necesserily consequtive (e.g. 5, 3, 11, 12, 10, 4). I would like to visit each key-value pair in reverse order of key size. So for the example above I'd like to visit (12,11,10...).
The way I see how to do this is to get a count of the number of elements, find the max key by say binary search and then find the next largest value that is smaller than the current max etc. untill I've processed the number of elements contained in the dictionary.
However, there could be a method already existing. For a discussion on how to find the max key: Get the largest key in a dictionary

var pairs = dictionary.OrderByDescending(pair => pair.Key);
foreach(var pair in pairs)
{
var value = pair.Value;
...
}

foreach (var p in myDict.OrderByDescending(pair => pair.Key)) {
// process pair
}

Well, it's easy enough to retrieve all of the keys from a dictionary, you can then use the LINQ OrderByDescending() operator to get them in reverse order:
foreach( var key in yourDictionary.Keys.OrderByDescending(x => x) )
{
// your logic here
}
If you need the value associated with the key, you can also do:
foreach( var keyValuePair in yourDictionary.OrderByDescending(kvp => kvp.Key) )
{
// your logic here
}
You can, of course, use query comprehension syntax is LINQ as well:
var yourResult = from kvp in dictionary
order by kvp.Key descending
select YourProjectionFunction(kvp);

dic = dic.OrderByDescending(p=>p.Key).ToDictionary(p => p.Key, p => p.Value);

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 order keys in Dictionary?

I have a Dictionary<int?,List<string>>. I need to output pairs of keys and values sorted by keys in ascending order. First, I think to use OrderedDictionary but it saves both keys and values in type of object. Maybe somehow it could be done through the extension methods?
You have two options here:
Use a SortedList<TKey,TValue>/SortedDictionary<TKey,TValue>:
var sortedData = new SortedList<int?, List<string>>(currentDictionary);
Use Linq's OrderBy:
var sortedData = currentDictionary.OrderBy(x => x.Key);
You can use any of these options with the following printing:
foreach (var entry in sortedData)
{
Console.WriteLine("Key: {0}, Values: ", entry.Key);
foreach (var value in entry.Value)
{
Console.WriteLine(value);
}
}
Dictionary<int?, List<string>> yourDictionary = GetTheDictionary();
var sortedKeys = yourDictionary.Select(kvp => kvp.Key).OrderBy(k => k);
This will give you a list of all of your keys in ascending order
If you want your dictionary the same. Ie still as Key Value Pairs. Just ordered by the key then you need to do.
yourDictionary.OrderBy(kvp => kvp.Key);

How do I match/search for a specific dictionary key?

very new to c#.
I'm working with an API that returns a dictionary with 2 keys and 1 value.
This is my current code:
var dic = API.getVehicleValidMods((VehicleHash)sender.vehicle.model);
foreach (KeyValuePair<int, Dictionary<int, string>> kvp in dic)
{
Dictionary<int, string> kvp2 = kvp.Value;
foreach (KeyValuePair<int, string> kvp3 in kvp2)
{
API.consoleOutput("Key = {0}, Key = {1}, Value = {2}", kvp.Key, kvp3.Key, kvp3.Value);
Here is a sample from the result it returns:
As you can see the first keys are sometimes the same number and they sometimes skip numbers.
I'm trying to return all matching "key number 2" that matches a certain "key 1".
The string values are not of importance in my case, I'm only interested in the ints.
So, my pseudo logic tells me something like
"foreach keynumber2 in keynumber1 (the integer, eg. 23) do this:"
should work, however I'm unsure how to code it properly.
tl;dr How do i find all "key2" that matches a certain "key1" ?
So, you want to find all inner keys of specific outer key. You can do this with this line of code:
var key1 = 42;
var allKeys = dic.ContainsKey(key1)
? dic[key1].Keys.ToArray()
: new int[0];
If outer dictionary contains 42 as a key this will return all inner keys. Otherwise it will return an empty ints array.
var matchesFrom1 = from val in dic where val.Key == 1 select val.Value;
var valueList = from val in matchesFrom1 where val.Key == 2 select val.Value;
valueList is the string of values returned
You could use LINQ methods:
Use .Where method to filter your initial collection of KeyValuePairs<> by key.
Use SelectMany to flatten resulting IEnumerable<KeyValuePair<int, Dictionary<>>> to a simple IEnumerable<int>
Use Distinct to make sure there are no duplicate keys (or omit this part if it is not necessary).
var someKey = 16;
var result = dic
.Where(x => x.Key == someKey)
.SelectMany(kv => kv.Value.Keys)
.Distinct()
.ToArray();

c# ToDictionary with ContainsKey check

I have a list that I want to put in a dictionary, for simplicity the values being inserted will all be the same.
I can use a foreach loop.
List<string> list = new List<string>();
list.Add("Earth");
list.Add("Wind");
list.Add("Fire");
list.Add("Water");
list.Add("Water"); // Will NOT BE INSERTED using the foreach loop
var myDictionary= new Dictionary<string, int>();
foreach (string value in list)
{
if (!myDictionary.ContainsKey(value))
{
myDictionary.Add(value, 1);
}
}
The above works.
But I want to use ToDictionary do the same in the following way -
Dictionary<string, int> myDictionary2 = list.ToDictionary(i => i, i => 1);
Of course this fails because I'm adding "Water" twice.
What is the correct way of checking for duplicate entries when using ToDictionary?
You could use Distinct() to filter out duplicates:
Dictionary<string, int> myDictionary2 = list.Distinct().ToDictionary(i => i, i => 1);
The same approach would make your traditional loop much clearer too, since you don't have to check "manually" for duplicates:
foreach (string value in list.Distinct())
{
myDictionary.Add(value, 1);
}
Distinct is one option that avoids the duplicate key issue. If you need a count of duplicates, you might try something more like this GroupBy as follows:
var dict = list.GroupBy(i => i).ToDictionary(g => g.Key, g => g.Count());
If your application is not just a simple string-list/duplicate-count structure, you might get some mileage from choosing a different structure like a Lookup that you can get from calling the ToLookup extension -or possibly going with a Grouping like the GroupBy I used above.

C#: Is a SortedDictionary sorted when you enumerate over it?

A SorteDictionary is according to MSDN sorted on the key. Does that mean that you can be sure that it will be sorted when you enumerate it in a foreach? Or does it just mean that the SortedDictionary works that way internally to have better performance in various cases?
From MSDN:
The dictionary is maintained in a
sorted order using an internal tree.
Every new element is positioned at the
correct sort position, and the tree is
adjusted to maintain the sort order
whenever an element is removed. While
enumerating, the sort order is
maintained.
When you enumerate the collection it is sorted by keys (even if you enumerate say the Values collection). Internally the collection is implemented as a binary search tree (according to the documentation). Both insertion and lookup of values are O(log n) (meaning they are pretty efficient).
If you enumerate the items in a SortedDictionary, the items will be returned in the sort order of the item keys. And if you enumerate by the keys in the SortedDictionary, the keys will also be returned in sorted order. And perhaps somewhat surprisingly, if you enumerate the SortedDictionary by its values, the values are returned in the sort order of the keys, not the sort order of the values as you might expect.
Demonstration:
Note that in this demo the items added to the SortedDictionary are not added in sorted order.
Also, if you plan to enumerate your dictionary by its values and there is a possibility of duplicate values, consider having your reverse lookup function return an IEnumerable<T>. (Of course, for large dictionaries, looking up a key by its value may result in poor performance.)
using System;
using System.Collections.Generic;
using System.Linq;
class SortedDictionaryEnumerationDemo
{
static void Main()
{
var dict = new SortedDictionary<int, string>();
dict.Add(4, "Four");
dict.Add(5, "Five");
dict.Add(1, "One");
dict.Add(3, "Three");
dict.Add(2, "Two");
Console.WriteLine("== Enumerating Items ==");
foreach (var item in dict)
{
Console.WriteLine("{0} => {1}", item.Key, item.Value);
}
Console.WriteLine("\n== Enumerating Keys ==");
foreach (int key in dict.Keys)
{
Console.WriteLine("{0} => {1}", key, dict[key]);
}
Console.WriteLine("\n== Enumerating Values ==");
foreach (string value in dict.Values)
{
Console.WriteLine("{0} => {1}", value, GetKeyFromValue(dict, value));
}
}
static int GetKeyFromValue(SortedDictionary<int, string> dict, string value)
{
// Use LINQ to do a reverse dictionary lookup.
try
{
return
(from item in dict
where item.Value.Equals(value)
select item.Key).First();
}
catch (InvalidOperationException e)
{
return -1;
}
}
}
Expected Output:
== Enumerating Items ==
1 => One
2 => Two
3 => Three
4 => Four
5 => Five
== Enumerating Keys ==
1 => One
2 => Two
3 => Three
4 => Four
5 => Five
== Enumerating Values ==
One => 1
Two => 2
Three => 3
Four => 4
Five => 5
Yes, that's exactly what it means.
Edit: the part that says "Does that mean that you can be sure that it will be sorted when you enumerate it in a foreach?"

Categories

Resources