I have a Hashtable that maps strings to ints. Strings are unique, but several may be mapped to the same integer.
My naive approach was to simply invert the Hashtable to a SortedList that is indexed by the Hashtable's values, but the problem is that you get a clash as soon as two of the Hashtable's strings map to the same value.
What is the most efficient way to list my entire Hashtable (keys and values) ordered by the values? (Where two values are the same, I don't care about their ordering.)
Using Linq:
hashtable.Cast<DictionaryEntry>().OrderBy(entry => entry.Value).ToList()
You said you wanted the most efficient method. The following code is the best I could find.
Hashtable hashtable = GetYourHashtable();
var result = new List<DictionaryEntry>(hashtable.Count);
foreach (DictionaryEntry entry in hashtable)
{
result.Add(entry);
}
result.Sort(
(x, y) =>
{
IComparable comparable = x.Value as IComparable;
if (comparable != null)
{
return comparable.CompareTo(y.Value);
}
return 0;
});
foreach (DictionaryEntry entry in result)
{
Console.WriteLine(entry.Key.ToString() + ":" + entry.Value.ToString());
}
I experimented with various different approaches using Linq, but the above method was about 25-50% faster.
Maybe this could work:
myhashtable.Keys.Select(k => new List<string, int>() {k, myhashtable[k]})
.OrderBy(item => item[1]);
This should give you a list of lists, with the nested lists containing exactly two elements, the key and the value. Sorted by the value (second element).
I'm not quite sure if the Hashtable has a KeyValuePair<K, V> type... something like this could also work:
myhashtable.Items.OrderBy(kvp => kvp.Value);
The immediate way that springs to mind is along the lines of what you have except that you have a SortedList (or similar) that uses the original values (ie the integers) as keys and as values has a list of the original keys (ie the strings if I understand correctly). There is a bit more faff involved in adding values (since you need to check if they exist and add them to the list if so or create a new list otherwise). There may be better methods but this is the one that immediately springs to mind...
Related
I'm new to programming, and I created a tuple list with
var tupleList = new List<Tuple<string, string>> { };
Later on in the code, I'd like to add an element like
tupleList.Add(string1,string2);
but .Add doesn't support this somehow?
Basically, I'm going through a loop and adding to the tuple and later I want to search through the tuple for a sample string, so my second question is how would I search through tupleList.Item1 and get all the pairs that equal, for example string10? I saw an answer for dictionary values, but can I do the same for tuples?
var matches = tupleList.Where(pair => pair.Item1.Equals(string10))
.Select(pair => Item2.Key);
I don't know if that makes sense though, this was the original code:
var matches = dict.Where(pair => pair.Value == "abc")
.Select(pair => pair.Key);
List<T> does not have any specific methods for working with tuples. It works with any type T. If you want to add new item to list, you should create item of list's type T and pass it to list. Adding new tuple:
tupleList.Add(Tuple.Create(string1,string2));
For searching just filter tuples list. You should not project tuples with Select operator if you want to get them as result:
var matches = tupleList.Where(pair => pair.Item1 == string10);
NOTE: I don't like tuples for their meaningless names Item1, Item2 etc, which is hard to understand. Consider creating custom class which will have properties with descriptive names.
I'd say why do the same for tuples?
If you are using a tuple to represent a key value pair, just stick to a key value pair, which is what a dictionary contains a collection of. If you model a row with more than 2 values, I'd probably favour a strongly typed model over this, where you can be more explicit in your LINQ queries.
I have tried to sort a Dictionary object by value which is generic.
Here is my code
Dictionary<string, ReportModel> sortedDic = new Dictionary<string, ReportModel>();
Dictionary<string, ReportModel> rDic = new Dictionary<string, ReportModel>();
var ordered = sortedDic.OrderByDescending(x => x.Value.totalPurchase);
foreach (var item in ordered)
{
rDic.Add(item.Key, item.Value);
}
The variable, ordered, just has the same order like sortedDic.
What is wrong with this?
Any idea?
This happens because Dictionary is generally an unordered container*. When you put the data into rDic, it becomes unordered again.
To retain the desired order, you need to put the results into a container that explicitly keeps the ordering that you supply. For example, you could use a list of KeyValuePair<string,ReportModel>, like this:
IList<KeyValuePair<string,ReportModel>> ordered = sortedDic
.OrderByDescending(x => x.Value.totalPurchase)
.ToList();
* Due to the way the Dictionary<K,V> is implemented by Microsoft, it happens to retain the insertion order, but that is incidental and undocumented, so it may change in the future versions, and should not be relied upon.
When adding the items back to the dictionary, it would not keep their order.
You can either:
Use the following implementation.
Use a list in the below form.
IEnumrable> lst=
sortedDic.OrderByDescending(x => x.Value.totalPurchase).ToArray();
[EDIT] If you don't mind the key changing then you can use SortedDictionary<,>.
If I were having a Generic list I would have done some thing like this
myListOfObject.FindAll(x=>(x.IsRequired==false));
What if I need to do similar stuff in Hashtable? Copying to temporary hashtable and looping and comparing would be that last thing I would try :-(
Firstly, use System.Collections.Generic.Dictionary<TKey, TValue> for better strong-type support as opposed to Hashtable.
If you need to just find one key or one value, use the methods ContainsKey(object key) or ContainsValue(object value), both of which are found on the Hashtable type.
Or you can go further and use linq extensions on the Hashtable parts:
Hashtable t = new Hashtable();
t.Add("Key", "Adam");
// Get the key/value entries.
var itemEntry = t.OfType<DictionaryEntry>().Where(de => (de.Value as string) == "Adam");
// Get just the values.
var items = t.Values.OfType<string>().Where(s => s == "Adam");
// Get just the keys.
var itemKey = t.Keys.OfType<string>().Where(k => k == "Key");
The whole story; I have some KeyValuePairs that I need to store in a session and my primary goal is to keep it small. Therefore I don't have the option of using many different collection. While the key is a different enum value of of a different enum type the value is always just a enum value of the same enum type. I have chosen a HashTable for this approach which content look like this (just many more):
// The Key-Value-Pairs
{ EnumTypA.ValueA1, MyEnum.ValueA },
{ EnumTypB.ValueB1, MyEnum.ValueB },
{ EnumTypC.ValueC1, MyEnum.ValueA },
{ EnumTypA.ValueA2, MyEnum.ValueC },
{ EnumTypB.ValueB1, MyEnum.ValueC }
At most I am running contains on that HashTable but for sure I also need to fetch the value at some point and I need to loop through all elements. That all works fine but now I have a new requirement to keep the order I have added them to the HashTable -> BANG
A HashTable is a map and that is not possible!
Now I thought about using a SortedList<object, MyEnum> or to go with more Data but slightly faster lookups and use a SortedSet<object> in addition to the HashTable.
Content below has been edited
The SortedList is implemented as
SortedList<Enum, MyEnum> mySortedList = new SortedList<Enum, MyEnum>();
the SortedSet is implemented as
SortedSet<Enum> mySortedSet = new SortedSet<Enum>();
The described Key - Value - Pairs are added to the sorted list with
void AddPair(Enum key, MyEnum value)
{
mySortedList.Add(key, value);
}
And for the SortedSett like this
void AddPair(Enum key)
{
mySortedSet.Add(key);
}
Both are failing with the exception:
Object must be the same type as the
enum
My question is: What goes wrong and how can I archive my goal?
Used Solution
I've decided to life with the downside
of redundant data against slower
lookups and decided to implement a
List<Enum> which will retain the
insert order parallel to my already
existing HashTable.
In my case I just have about 50-150
Elements so I decided to benchmark the
Hashtable against the
List<KeyValuePair<object,object>>
Therefore I have create me the
following helper to implement
ContainsKey() to the
List<KeyValuePair<object,object>>
static bool ContainsKey(this List<KeyValuePair<object, object>> list, object key)
{
foreach (KeyValuePair<object, object> p in list)
{
if (p.Key.Equals(key))
return true;
}
return false;
}
I inserted the same 100 Entries and
checked randomly for one of ten
different entries in a 300000 loop.
And... the difference was tiny so I
decided to go with the
List<KeyValuePair<object,object>>
I think you should store your data in an instance of List<KeyValuePair<Enum, MyEnum>> or Dictionary<Enum, MyEnum>.
SortedSet and SortedList are generic, but your keys are EnumTypeA/EnumTypeB, you need to specify the generic T with their base class(System.Enum) like:
SortedList<Enum, MyEnum> sorted = new SortedList<Enum, MyEnum>();
EDIT
Why you got this exception
SortedList and SortedSet use a comparer inside to check if two keys are equal. Comparer<Enum>.Default will be used as the comparer if you didn't specify the comparer in the constructor. Unfortunately Comparer<Enum>.Default isn't implemented as you expected. It throws the exception if the two enums are not the same type.
How to resolve the problem
If you don't want to use a List<KeyValuePair<Enum, MyEnum>> and insist using SortedLIst, you need to specify a comparer to the constructor like this:
class EnumComparer : IComparer<Enum>
{
public int Compare(Enum x, Enum y)
{
return x.GetHashCode() - y.GetHashCode();
}
}
var sorted = new SortedList<Enum, MyEnum>(new EnumComparer());
Btw, I think you need to obtain the "inserting order"? If so, List<KeyValuePair<K,V>> is a better choice, because SortedSet will prevent duplicated items.
I have a Dictionary<int, int> and would like to update certain elements all at once based on their current values, e.g. changing all elements with value 10 to having value 14 or something.
I imagined this would be easy with some LINQ/lambda stuff but it doesn't appear to be as simple as I thought. My current approach is this:
List<KeyValuePair<int, int>> kvps = dictionary.Where(d => d.Value == oldValue).ToList();
foreach (KeyValuePair<int, int> kvp in kvps)
{
dictionary[KeyValuePair.Key] = newValue;
}
The problem is that dictionary is pretty big (hundreds of thousands of elements) and I'm running this code in a loop thousands of times, so it's incredibly slow. There must be a better way...
This might be the wrong data structure. You are attempting to look up dictionary entries based on their values which is the reverse of the usual pattern. Maybe you could store Sets of keys that currently map to certain values. Then you could quickly move these sets around instead of updating each entry separately.
I would consider writing your own collection type to achieve this whereby keys with the same value actually share the same value instance such that changing it in one place changes it for all keys.
Something like the following (obviously, lots of code omitted here - just for illustrative purposes):
public class SharedValueDictionary : IDictionary<int, int>
{
private List<MyValueObject> values;
private Dictionary<int, MyValueObject> keys;
// Now, when you add a new key/value pair, you actually
// look in the values collection to see if that value already
// exists. If it does, you add an entry to keys that points to that existing object
// otherwise you create a new MyValueObject to wrap the value and add entries to
// both collections.
}
This scenario would require multiple versions of Add and Remove to allow for changing all keys with the same value, changing only one key of a set to be a new value, removing all keys with the same value and removing just one key from a value set. It shouldn't be difficult to code for these scenarios as and when needed.
You need to generate a new dictionary:
d = d.ToDictionary(w => w.Key, w => w.Value == 10 ? 14 : w.Value)
I think the thing that everybody must be missing is that it is exceeeeedingly trivial:
List<int> keys = dictionary.Keys.Where(d => d == oldValue);
You are NOT looking up keys by value (as has been offered by others).
Instead, keys.SingleOrDefault() will now by definition return the single key that equals oldValue if it exists in the dictionary. So the whole code should simplify to
if (dictionary.ContainsKey(oldValue))
dictionary[key] = newValue;
That is quick. Now I'm a little concerned that this might indeed not be what the OP intended, but it is what he had written. So if the existing code does what he needs, he will now have a highly performant version of the same :)
After the edit, this seems an immediate improvement:
foreach (var kvp in dictionary.Where(d => d.Value == oldValue))
{
kvp.Value = newValue;
}
I'm pretty sure you can update the kvp directly, as long as the key isn't changed