Another one of my can't see the wood for the trees questions.
Background
I've got a requirement to query a predefined structure for cooking equivalents across (at the moment) a small range of cultures. The structure won't change but the possibility of other cultures and/or equivalents being introduced is a very strong possibility.
Problem
I would like to put in place a 2nd 'structure' that would allow me to retain the core of what I've been given, but at the same time, allow me to intelligently input the equivalent measurements only once. In the example below, I've only created an equivalent for a UK measurement to return it's partner Metric and US counterparts. My aim would be that from this single input, the structure would be able to spit back a UK equivalent if given a US counterpart etc, etc.
Question
Is it asking too much of such an implicit structure to be able to operate in this fashion. Is it bad practice to ask for such a thing?? What would be your approach to solving such a dilema. The end game would be that any equivalent should be produced as a single liner along the lines of:
// this should produce a value of **CupLitre**
var eqv = conversion["CupUS"][LocaleM.Metric];
Anyway, without further ado:
The Example
Whip up a console app and paste the code below into it (.net v3.5).
using System;
using System.Collections.Generic;
using UnitOfMeasurements;
namespace UnitOfMeasurements
{
[Flags]
public enum LocaleM
{
None = 0, //(0000)
Metric = 1, //(0001)
UK = 2, //(0010)
US = 4, //(0100)
}
}
class Program
{
public static void Main(string[] args)
{
// single structure representing UK equivalents
// this should be extensible to any equivalent
var conversionUK
= new Dictionary<string, Dictionary<LocaleM, string>>
{
{"PintUK",
new Dictionary<LocaleM, string>
{
{LocaleM.US, "PintUS"},
{LocaleM.Metric, "Litre"}
}
},
{"FlOzUK",
new Dictionary<LocaleM, string>
{
{LocaleM.US, "FlOzUS"},
{LocaleM.Metric, "MilliLitre"}
}
},
{"CupUK",
new Dictionary<LocaleM, string>
{
{LocaleM.US, "CupUS"},
{LocaleM.Metric, "CupLitre"}
}
}
};
// basic printout of results
Console.WriteLine(string.Format("{0}\t{1}\t{2}", "Key","US","Metric"));
foreach (var item in conversionUK)
{
Console.WriteLine(string.Format("{0}\t{1}\t{2}",
item.Key,
item.Value[LocaleM.US],
item.Value[LocaleM.Metric]));
}
Console.WriteLine(string
.Format("Here's an example of our direct 'query' - {0}",
conversionUK["CupUK"][LocaleM.Metric]));
Console.Read();
}
}
Good luck - looking fwd to some simple but elegant answers.
[edit] - i must add that the requirement for the inputs ("PintUK", "CupMetric") etc, to be strings is a must as these values will be driving (IVolumeUnit)Activator.CreateInstance(eqv) methods further downstream to create concrete conversion classes of the given string names.
Following approach assumes that all your mappings are bidirectional and if a->b and b->c then a->c
I would introduce a concept of something like default name (cross-culture) for each your item to convert. For example in your code these default names could be 'Pint', 'FlOz' and 'Cup':
UPDATE: replaced custom MapKey class with .Net tuple to make code simplier.
class MapClass
{
private readonly Dictionary<string, string> _localToDefaultNameMap = new Dictionary<string, string>();
private readonly Dictionary<Tuple<string, LocaleM>, string> _defaultNameToLocalMap = new Dictionary<Tuple<string, LocaleM>, string>();
public void AddMapping(string defaultName, LocaleM locale, string localName)
{
_localToDefaultNameMap.Add(localName, defaultName);
_defaultNameToLocalMap.Add(Tuple.Create(defaultName, locale), localName);
}
// maps from source name to target
public string Map(string sourceLocalName, LocaleM targetLocale)
{
string defaultName = _localToDefaultNameMap[sourceLocalName];
var mapKey = Tuple.Create(defaultName, targetLocale);
var localName = _defaultNameToLocalMap[mapKey];
return localName;
}
}
Usage:
// Creating map:
var map = new MapClass();
map.AddMapping("Pint", LocaleM.UK, "PintUK");
map.AddMapping("Pint", LocaleM.US, "PintUS");
map.AddMapping("Pint", LocaleM.Metric, "Litre");
string ukPintMappedToUS = map.Map("PintUK", LocaleM.US);
UPDATE 2 MapKey equality members generated by resharper:
class MapKey
{
...
public bool Equals(MapKey other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Equals(other.Locale, Locale) && Equals(other.Key, Key);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != typeof (MapKey)) return false;
return Equals((MapKey) obj);
}
public override int GetHashCode()
{
int result = 0;
result = (result * 397) ^ Locale.GetHashCode();
result = (result * 397) ^ (Key != null ? Key.GetHashCode() : 0);
return result;
}
}
Related
I have a HashSet<string> that is instantiated using StringComparer.CurrentCultureIgnoreCase and am making extensive use of .Contains(string input) to check user input. If the user inputs a value in the wrong case, .Contains = true, which is correct, but I need to also correct the case; if e.g. the user asks for myvalue and MyValue is in the hashset, what is the most efficient way to also return MyValue so the user's input is case-corrected?
Here's a rough code sample of what I mean:
static HashSet<string> testHS = new HashSet<string>(StringComparer.CurrentCulture);
static bool InputExists(string input, out string correctedCaseString)
{
correctedCaseString = input;
if (testHS.Contains(input))
{
// correctedCaseString = some query result of the cased testHS value?
return true;
}
return false;
}
You could use a Dictionary instead of a HashSet. Map from a string to itself and use a case-insensitive equality comparer (http://msdn.microsoft.com/en-us/library/ms132072.aspx). Your code then becomes something like:
static Dictionary<string, string> testD = new Dictionary<string, string>(StringComparer.CurrentCulture);
static bool InputExists(string input, out string correctedCaseString)
{
correctedCaseString = input;
if (testD.ContainsKey(input))
{
correctedCaseString = testD[input];
return true;
}
return false;
}
I have a dialog, when spawned it gets populated with the data in an object model. At this point the data is copied and stored in a "backup" object model. When the user has finished making their changes, and click "ok" to dismiss the dialog, I need a quick way of comparing the backup object model with the live one - if anything is changed I can create the user a new undo state.
I don't want to have to go and write comparison function for every single class in the object model if possible.
If I serialised both object models and they were identical but stored in different memory locations would they be equal? Does some simple way exist to compare two serialised object models?
I didn't bother with a hash string but just a straight Binary serialisation works wonders. When the dialog opens serialise the object model.
BinaryFormatter formatter = new BinaryFormatter();
m_backupStream = new MemoryStream();
formatter.Serialize(m_backupStream,m_objectModel);
Then if the user adds to the object model using available controls (or not). When the dialog closes you can compare to the original serialisation with a new one - this for me is how i decide whether or not an Undo state is required.
BinaryFormatter formatter = new BinaryFormatter();
MemoryStream liveStream = new MemoryStream();
formatter.Serialize(liveStream,m_objectModel);
byte[] streamOneBytes = liveStream.ToArray();
byte[] streamTwoBytes = m_backupStream.ToArray();
if(!CompareArrays(streamOneBytes, streamTwoBytes))
AddUndoState();
And the compare arrays function incase anybody needs it - prob not the best way of comparing two arrays im sure.
private bool CompareArrays(byte[] a, byte[] b)
{
if (a.Length != b.Length)
return false;
for (int i = 0; i < a.Length;i++)
{
if (a[i] != b[i])
return false;
}
return true;
}
I'd say the best way is to implement the equality operators on all classes in your model (which is usually a good idea anyway if you're going to do comparisons).
class Book
{
public string Title { get; set; }
public string Author { get; set; }
public ICollection<Chapter> Chapters { get; set; }
public bool Equals(Book other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Equals(other.Title, Title) && Equals(other.Author, Author) && Equals(other.Chapters, Chapters);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != typeof (Book)) return false;
return Equals((Book) obj);
}
public override int GetHashCode()
{
unchecked
{
int result = (Title != null ? Title.GetHashCode() : 0);
result = (result*397) ^ (Author != null ? Author.GetHashCode() : 0);
result = (result*397) ^ (Chapters != null ? Chapters.GetHashCode() : 0);
return result;
}
}
}
This snippet is auto-generated by ReSharper, but you can use this as a basis. Basically you will have to extend the non overriden Equals method with your custom comparison logic.
For instance, you might want to use SequenceEquals from the Linq extensions to check if the chapters collection is equal in sequence.
Comparing two books will now be as simple as saying:
Book book1 = new Book();
Book book2 = new Book();
book1.Title = "A book!";
book2.Title = "A book!";
bool equality = book1.Equals(book2); // returns true
book2.Title = "A different Title";
equality = book1.Equals(book2); // returns false
Keep in mind that there's another way of implementing equality: the System.IEquatable, which is used by various classes in the System.Collections namespace for determining equality.
I'd say check that out as well and you're well on your way!
I understand your question to be how one can compare two objects for value equality (as opposed to reference equality) without prior knowledge of the types, such as if they implement IEquatable or override Equals.
To do this I recommend two options:
A. Use an all-purpose serialization class to serialize both objects and compare their value. For example I have a class called XmlSerializer that takes any object and serializes its public properties as an XML document. Two objects that have the same values and possibly the same reference will have the same values in this sense.
B. Using reflection, compare the values of all of the properties of both objects, like:
bool Equal(object a, object b)
{
// They're both null.
if (a == null && b == null) return true;
// One is null, so they can't be the same.
if (a == null || b == null) return false;
// How can they be the same if they're different types?
if (a.GetType() != b.GetType()) return false;
var Props = a.GetType().GetProperties();
foreach(var Prop in Props)
{
// See notes *
var aPropValue = Prop.GetValue(a) ?? string.Empty;
var bPropValue = Prop.GetValue(b) ?? string.Empty;
if(aPropValue.ToString() != bPropValue.ToString())
return false;
}
return true;
}
Here we're assuming that we can easily compare the properties, like if they all implement IConvertible, or correctly override ToString. If that's not the case I would check if they implement IConvertible and if not, recursively call Equal() on the properties.
This only works if you're content with comparing public properties. Of course you COULD check private and protected fields and properties too, but if you know so little about the objects you're probably asking for trouble but doing so.
Trying to wrap my head around perl's Autovivification and based on what it sounds like, It seems to work similar to dynamics in C# as a dynamic object is not assigned a type until runtime or, am I totally off here. If so then is there a comparable idea that I can bridge off of in C# that makes sense?
Edit
Okay so I'm apparently way off. So as second part of the 2 part question, is there anything conceptually comparable in C#? To be clear I'm looking for a concept in C# that is comparable to Autovivification. Doesn't have to be exactly the same but close enough conceptually to make sense. And as I stated eariler I am by no means a perl hacker or python hacker by any stretch of the imagination but, I am familar with c based languages C, C++, C#, java, javascript. I was thinking of C#'s dynamics but, as of right now I'm thinking lazy loading based on the info here if that helps....
I can't speak to C#, but in layman's terms, Perl's autovivification is the process of creating a container object out of an undefined value as soon as it is needed.
Despite most of Perl being quite dynamic, Perl's dereferencing syntax unambiguously specifies the type of the reference at compile time. This allows the interpreter to know what it needs out of a variable before the variable is ever defined.
my $var; # undefined
# to autovivify to an array:
#$var = 1..5; # # here implies ARRAY
$$var[4] = 5; # square brackets imply ARRAY
$#$var; # $# implies ARRAY (returns the last index number)
# to autovivify to a hash:
%$var = (a => 1); # % implies HASH
$$var{asdf} = 5; # curly braces imply HASH
This list could be longer, but should give you an idea.
So basically, when you have a line like this:
my $var;
$var->[1]{x}[3]{asdf}
Perl looks on the right side of the -> and sees square braces. This means that the invocant $var must be an array reference. Since the invocant is undefined, Perl creates a new array and installs its reference into $var. This same process is then repeated for every subsequent dereferencing.
So the line above really means:
(((($var //= [])->[1] //= {})->{x} //= [])->[3] //= {})->{asdf};
which is fairly hideous, and hence autovivification. (//= is the defined-or assignment operator in perl 5.10+)
Update:
As per cjm's comment, to put this into general non-perl terms, to achieve autovivification in another language, you need a lazy object that supports indexing via [...] and {...}. When either of these indexing operations are performed, the object replaces itself with either an array or hash. Every time the object is then accessed, if the cell is empty, it should return another lazy object.
obj = new lazy_obj()
level1 = obj[4] # sets obj to be an array, returns a new lazy_obj for level1
level2 = level1{asdf} # sets level1 (and obj[4]) to a hash,
# returns a new lazy_obj for level2
So basically you need two things, the ability to create an object that supports indexing with both array and hash subscripts (or the equivalent), and a mechanism such that an object can replace itself in memory with another object (or that can lock itself to one interpretation, and then store the new object internally.
Something like the following pseudo-code could be a start:
class autoviv {
private var content;
method array_subscript (idx) {
if (!content) {
content = new Array();
}
if (typeof content == Array) {
if (exists content[idx]) return content[idx];
return content[idx] = new autoviv();
} else {
throw error
}
}
method hash_subscript (idx) {
if (!content) {
content = new Hash();
}
if (typeof content == Hash) {
if (exists content{idx}) return content{idx};
return content{idx} = new autoviv();
} else {
throw error
}
}
// overload all other access to return undefined, so that the value
// still looks empty for code like:
//
// var auto = new autoviv();
// if (typeof auto[4] == autoviv) {should run}
// if (auto[4]) {should not run}
}
Uri Guttman's autovivification tutorial might be of some use.
Basically, it is the ability of hitherto untouched aggregates and members of aggregates to spring to life upon first use.
For example, I can do this:
#!/usr/bin/perl
use strict; use warnings;
use Data::Dumper;
my #dummy;
push #{ $dummy[0] }, split ' ', 'this that and the other';
push #{ $dummy[1] }, { qw(a b c d) };
print Dumper \#dummy;
Neither $dummy[0] nor $dummy[1] exist before they are dereferenced.
Now, if you are willing to forgo strict (which, you shouldn't be), you can also do things like:
use Data::Dumper;
#$x = qw(a b c d);
print Dumper $x;
whereby the undefined variable $x becomes an array reference because it is being dereferenced as such.
You can implement autovification-like behavior with creating say, an IDictionary<X,Y> that returns (and stores) a new IDictionary<X,Y> (e.g. recursively the same type) when a [] to an unset key occurs. This approach is used in Ruby to great success (an example) -- however, it's really not so useful in a statically typed language because there is no way to "get to" the leaf values cleanly -- at least in context of most existing contracts such as an IDictionary.
With the advent of dynamic, this may be possible in C# to do sanely, but I do not know.
How about something like this for a simple implementation of auto-vivification like behaviour of a Dictionary in C#? Obviously this doesn't handle it in the generic way that Perl does, but I believe that it has the same effect.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
// The purpose of this class is to provide a dictionary with auto-vivification behaviour similar to Perl's
// Using dict[index] will succeed regardless of whether index exists in the dictionary or not.
// A default value can be set to be used as an initial value when the key doesn't exist in the dictionary
namespace XMLTest
{
class AutoDictionary<TKey,TValue> : Dictionary<TKey,TValue> {
Object DefaultValue ;
public AutoDictionary(Object DefaultValue) {
this.DefaultValue = DefaultValue;
}
public AutoDictionary() {
this.DefaultValue = null;
}
public new TValue this[TKey index] {
get {
try {
return base[index];
}
catch (KeyNotFoundException) {
base.Add(index, (TValue)DefaultValue);
return (TValue)DefaultValue ;
}
}
set {
try {
base[index] = value ;
}
catch (KeyNotFoundException) {
base.Add(index, value);
}
}
}
}
}
I would recommend using extension methods instead of inheritance.
e.g.:
namespace DictionaryEx
{
public static class Ex
{
public static TV Vivify<TK, TV>(this IDictionary<TK, TV> dict, TK key)
{
var value = default(TV);
if (dict.TryGetValue(key, out value))
{
return value;
}
value = default(TV);
dict[key] = value;
return value;
}
public static TV Vivify<TK, TV>(this IDictionary<TK, TV> dict, TK key, TV defaultValue)
{
TV value;
if (dict.TryGetValue(key, out value))
{
return value;
}
dict[key] = defaultValue;
return defaultValue;
}
public static TV Vivify<TK, TV>(this IDictionary<TK, TV> dict, TK key, Func<TV> valueFactory)
{
TV value;
if (dict.TryGetValue(key, out value))
{
return value;
}
value = valueFactory();
dict[key] = value;
return value;
}
}
}
Using indexers and C# 4.0 dynamics,
class Tree
{
private IDictionary<string, object> dict = new Dictionary<string, object>();
public dynamic this[string key]
{
get { return dict.ContainsKey(key) ? dict[key] : dict[key] = new Tree(); }
set { dict[key] = value; }
}
}
// Test:
var t = new Tree();
t["first"]["second"]["third"] = "text";
Console.WriteLine(t["first"]["second"]["third"]);
DynamicObject can be used for implementing different syntaxes also,
using System;
using System.Collections.Generic;
using System.Dynamic;
class Tree : DynamicObject
{
private IDictionary<object, object> dict = new Dictionary<object, object>();
// for t.first.second.third syntax
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
var key = binder.Name;
if (dict.ContainsKey(key))
result = dict[key];
else
dict[key] = result = new Tree();
return true;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
dict[binder.Name] = value;
return true;
}
// for t["first"]["second"]["third"] syntax
public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
{
var key = indexes[0];
if (dict.ContainsKey(key))
result = dict[key];
else
dict[key] = result = new Tree();
return true;
}
public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value)
{
dict[indexes[0]] = value;
return true;
}
}
// Test:
dynamic t = new Tree();
t.first.second.third = "text";
Console.WriteLine(t.first.second.third);
// or,
dynamic t = new Tree();
t["first"]["second"]["third"] = "text";
Console.WriteLine(t["first"]["second"]["third"]);
I am expecting a HashSet that has been created with a specified EqualityComparer to use that comparer on a Remove operation. Especially since the Contains operations returns true!
Here is the code I am using:
public virtual IEnumerable<Allocation> Allocations { get { return _allocations; } }
private ICollection<Allocation> _allocations;
public Activity(IActivitySubject subject) { // constructor
....
_allocations = new HashSet<Allocation>(new DurationExcludedEqualityComparer());
}
public virtual void ClockIn(Allocation a)
{
...
if (_allocations.Contains(a))
_allocations.Remove(a);
_allocations.Add(a);
}
Below is some quick and dirty LINQ that gets me the logic I want, but I am guessing the HashSet remove based on the EqualityComparer would be significantly faster.
public virtual void ClockIn(Allocation a)
{
...
var found = _allocations.Where(x => x.StartTime.Equals(a.StartTime) && x.Resource.Equals(a.Resource)).FirstOrDefault();
if (found != null)
{
if (!Equals(found.Duration, a.Duration))
{
found.UpdateDurationTo(a.Duration);
}
}
else
{
_allocations.Add(a);
}
Can anyone suggest why the Remove would fail when the Contains succeeds?
Cheers,
Berryl
=== EDIT === the comparer
public class DurationExcludedEqualityComparer : EqualityComparer<Allocation>
{
public override bool Equals(Allocation lhs, Allocation rhs)
{
if (ReferenceEquals(null, rhs)) return false;
if (ReferenceEquals(lhs, null)) return false;
if (ReferenceEquals(lhs, rhs)) return true;
return
lhs.StartTime.Equals(rhs.StartTime) &&
lhs.Resource.Equals(rhs.Resource) &&
lhs.Activity.Equals(rhs.Activity);
}
public override int GetHashCode(Allocation obj) {
if (ReferenceEquals(obj, null)) return 0;
unchecked
{
var result = 17;
result = (result * 397) ^ obj.StartTime.GetHashCode();
result = (result * 397) ^ (obj.Resource != null ? obj.Resource.GetHashCode() : 0);
result = (result * 397) ^ (obj.Activity != null ? obj.Activity.GetHashCode() : 0);
return result;
}
}
}
=== UPDATE - FIXED ===
Well, the good news is that HashSet is not broken and works exactly as it should. The bad news, for me, is how incredibly stupid I can be when not being able to see the forest while examining the leaves on the trees!
The answer is actually in the posted code above, if you look at the class creating & owning the HashSet, and then taking another look at the Comparer to find out what is wrong with it. Easy points for the first person to spot it.
Thanks to all who looked at the code!
Well, your code that "works" appears to look at StartTime and Resource while ignoring Activity, whereas your IEqualityComparer<Allocation> implementation looks at all three. Could your problem be related to that?
Also: are your StartTime, Resource, and Activity properties unchanging? Otherwise, since they affect your GetHashCode result, I think you run the risk of breaking your HashSet<Allocation>.
I have some objects in List, let's say List<MyClass> and MyClass has several properties. I would like to create an index of the list based on 3 properties of of MyClass. In this case 2 of the properties are int's, and one property is a datetime.
Basically I would like to be able to do something like:
Dictionary< CompositeKey , MyClass > MyClassListIndex = Dictionary< CompositeKey , MyClass >();
//Populate dictionary with items from the List<MyClass> MyClassList
MyClass aMyClass = Dicitonary[(keyTripletHere)];
I sometimes create multiple dictionaries on a list to index different properties of the classes it holds. I am not sure how best to handle composite keys though. I considered doing a checksum of the three values but this runs the risk of collisions.
You should use tuples. They are equivalent to a CompositeKey class, but the Equals() and GetHashCode() are already implemented for you.
var myClassIndex = new Dictionary<Tuple<int, bool, string>, MyClass>();
//Populate dictionary with items from the List<MyClass> MyClassList
foreach (var myObj in myClassList)
myClassIndex.Add(Tuple.Create(myObj.MyInt, myObj.MyBool, myObj.MyString), myObj);
MyClass myObj = myClassIndex[Tuple.Create(4, true, "t")];
Or using System.Linq
var myClassIndex = myClassList.ToDictionary(myObj => Tuple.Create(myObj.MyInt, myObj.MyBool, myObj.MyString));
MyClass myObj = myClassIndex[Tuple.Create(4, true, "t")];
Unless you need to customize the computation of the hash, it's simpler to use tuples.
If there are a lot of properties you want to include in the composite key, the Tuple type name can become pretty long, but you can make the name shorter by creating your own class deriving from Tuple<...>.
** edited in 2017 **
There is a new option starting with C# 7: the value tuples. The idea is the same, but the syntax is different, lighter:
The type Tuple<int, bool, string> becomes (int, bool, string), and the value Tuple.Create(4, true, "t") becomes (4, true, "t").
With value tuples, it also becomes possible to name the elements. Note that performances are slightly different, so you may want to do some benchmarking if they matter for you.
The best way I could think of is to create a CompositeKey struct and make sure to override the GetHashCode() and Equals() methods in order to ensure speed and accuracy when working with the collection:
class Program
{
static void Main(string[] args)
{
DateTime firstTimestamp = DateTime.Now;
DateTime secondTimestamp = firstTimestamp.AddDays(1);
/* begin composite key dictionary populate */
Dictionary<CompositeKey, string> compositeKeyDictionary = new Dictionary<CompositeKey, string>();
CompositeKey compositeKey1 = new CompositeKey();
compositeKey1.Int1 = 11;
compositeKey1.Int2 = 304;
compositeKey1.DateTime = firstTimestamp;
compositeKeyDictionary[compositeKey1] = "FirstObject";
CompositeKey compositeKey2 = new CompositeKey();
compositeKey2.Int1 = 12;
compositeKey2.Int2 = 9852;
compositeKey2.DateTime = secondTimestamp;
compositeKeyDictionary[compositeKey2] = "SecondObject";
/* end composite key dictionary populate */
/* begin composite key dictionary lookup */
CompositeKey compositeKeyLookup1 = new CompositeKey();
compositeKeyLookup1.Int1 = 11;
compositeKeyLookup1.Int2 = 304;
compositeKeyLookup1.DateTime = firstTimestamp;
Console.Out.WriteLine(compositeKeyDictionary[compositeKeyLookup1]);
CompositeKey compositeKeyLookup2 = new CompositeKey();
compositeKeyLookup2.Int1 = 12;
compositeKeyLookup2.Int2 = 9852;
compositeKeyLookup2.DateTime = secondTimestamp;
Console.Out.WriteLine(compositeKeyDictionary[compositeKeyLookup2]);
/* end composite key dictionary lookup */
}
struct CompositeKey
{
public int Int1 { get; set; }
public int Int2 { get; set; }
public DateTime DateTime { get; set; }
public override int GetHashCode()
{
return Int1.GetHashCode() ^ Int2.GetHashCode() ^ DateTime.GetHashCode();
}
public override bool Equals(object obj)
{
if (obj is CompositeKey)
{
CompositeKey compositeKey = (CompositeKey)obj;
return ((this.Int1 == compositeKey.Int1) &&
(this.Int2 == compositeKey.Int2) &&
(this.DateTime == compositeKey.DateTime));
}
return false;
}
}
}
An MSDN article on GetHashCode():
http://msdn.microsoft.com/en-us/library/system.object.gethashcode.aspx
How about Dictionary<int, Dictionary<int, Dictionary<DateTime, MyClass>>>?
This would allow you to do:
MyClass item = MyData[8][23923][date];
You can store them in a struct and use that as the key:
struct CompositeKey
{
public int value1;
public int value2;
public DateTime value3;
}
Link to get hash code:
http://msdn.microsoft.com/en-us/library/system.valuetype.gethashcode.aspx
Now that VS2017/C#7 has come out, the best answer is to use ValueTuple:
// declare:
Dictionary<(string, string, int), MyClass> index;
// populate:
foreach (var m in myClassList) {
index[(m.Name, m.Path, m.JobId)] = m;
}
// retrieve:
var aMyClass = index[("foo", "bar", 15)];
I chose to declare the dictionary with an anonymous ValueTuple (string, string, int). But I could have given them names (string name, string path, int id).
Perfwise, the new ValueTuple is faster than Tuple at GetHashCode but slower at Equals. I think you'd need to do complete end-to-end experiments to figure out which is really fastest for your scenario. But the end-to-end niceness and language syntax for ValueTuple makes it win out.
// Perf from https://gist.github.com/ljw1004/61bc96700d0b03c17cf83dbb51437a69
//
// Tuple ValueTuple KeyValuePair
// Allocation: 160 100 110
// Argument: 75 80 80
// Return: 75 210 210
// Load: 160 170 320
// GetHashCode: 820 420 2700
// Equals: 280 470 6800
Two approaches immediately spring to mind:
Do as Kevin suggested and write a struct that will serve as your key. Be sure to make this struct implement IEquatable<TKey> and to override its Equals and GetHashCode methods*.
Write a class that utilizes nested dictionaries internally. Something like: TripleKeyDictionary<TKey1, TKey2, TKey3, TValue>... this class would internally have a member of type Dictionary<TKey1, Dictionary<TKey2, Dictionary<TKey3, TValue>>>, and would expose methods such as this[TKey1 k1, TKey2 k2, TKey3 k3], ContainsKeys(TKey1 k1, TKey2 k2, TKey3 k3), etc.
*A word on whether overriding the Equals method is necessary: while it's true that the Equals method for a struct compares the value of each member by default, it does so by using reflection -- which inherently entails performance costs -- and is therefore not a very appropriate implementation for something that is meant to be used as a key in a dictionary (in my opinion, anyway). According to the MSDN documentation on ValueType.Equals:
The default implementation of the
Equals method uses reflection to
compare the corresponding fields of
obj and this instance. Override the
Equals method for a particular type to
improve the performance of the method
and more closely represent the concept
of equality for the type.
If the key is part of the class then use KeyedCollection.
It is a Dictionary where the key is derived from the object.
Under the covers it is Dictionary
Don't have to repeat the key in the Key and Value.
Why take a chance the key is not the same in the Key as the Value.
Don't have to duplicate the same information in memory.
KeyedCollection Class
Indexer to expose the composite key
using System.Collections.ObjectModel;
namespace IntIntKeyedCollection
{
class Program
{
static void Main(string[] args)
{
Int32Int32DateO iid1 = new Int32Int32DateO(0, 1, new DateTime(2007, 6, 1, 8, 30, 52));
Int32Int32DateO iid2 = new Int32Int32DateO(0, 1, new DateTime(2007, 6, 1, 8, 30, 52));
if (iid1 == iid2) Console.WriteLine("same");
if (iid1.Equals(iid2)) Console.WriteLine("equals");
// that are equal but not the same I don't override = so I have both features
Int32Int32DateCollection int32Int32DateCollection = new Int32Int32DateCollection();
// dont't have to repeat the key like Dictionary
int32Int32DateCollection.Add(new Int32Int32DateO(0, 0, new DateTime(2008, 5, 1, 8, 30, 52)));
int32Int32DateCollection.Add(new Int32Int32DateO(0, 1, new DateTime(2008, 6, 1, 8, 30, 52)));
int32Int32DateCollection.Add(iid1);
//this would thow a duplicate key error
//int32Int32DateCollection.Add(iid2);
//this would thow a duplicate key error
//int32Int32DateCollection.Add(new Int32Int32DateO(0, 1, new DateTime(2008, 6, 1, 8, 30, 52)));
Console.WriteLine("count");
Console.WriteLine(int32Int32DateCollection.Count.ToString());
// reference by ordinal postion (note the is not the long key)
Console.WriteLine("oridinal");
Console.WriteLine(int32Int32DateCollection[0].GetHashCode().ToString());
// reference by index
Console.WriteLine("index");
Console.WriteLine(int32Int32DateCollection[0, 1, new DateTime(2008, 6, 1, 8, 30, 52)].GetHashCode().ToString());
Console.WriteLine("foreach");
foreach (Int32Int32DateO iio in int32Int32DateCollection)
{
Console.WriteLine(string.Format("HashCode {0} Int1 {1} Int2 {2} DateTime {3}", iio.GetHashCode(), iio.Int1, iio.Int2, iio.Date1));
}
Console.WriteLine("sorted by date");
foreach (Int32Int32DateO iio in int32Int32DateCollection.OrderBy(x => x.Date1).ThenBy(x => x.Int1).ThenBy(x => x.Int2))
{
Console.WriteLine(string.Format("HashCode {0} Int1 {1} Int2 {2} DateTime {3}", iio.GetHashCode(), iio.Int1, iio.Int2, iio.Date1));
}
Console.ReadLine();
}
public class Int32Int32DateCollection : KeyedCollection<Int32Int32DateS, Int32Int32DateO>
{
// This parameterless constructor calls the base class constructor
// that specifies a dictionary threshold of 0, so that the internal
// dictionary is created as soon as an item is added to the
// collection.
//
public Int32Int32DateCollection() : base(null, 0) { }
// This is the only method that absolutely must be overridden,
// because without it the KeyedCollection cannot extract the
// keys from the items.
//
protected override Int32Int32DateS GetKeyForItem(Int32Int32DateO item)
{
// In this example, the key is the part number.
return item.Int32Int32Date;
}
// indexer
public Int32Int32DateO this[Int32 Int1, Int32 Int2, DateTime Date1]
{
get { return this[new Int32Int32DateS(Int1, Int2, Date1)]; }
}
}
public struct Int32Int32DateS
{ // required as KeyCollection Key must be a single item
// but you don't really need to interact with Int32Int32DateS directly
public readonly Int32 Int1, Int2;
public readonly DateTime Date1;
public Int32Int32DateS(Int32 int1, Int32 int2, DateTime date1)
{ this.Int1 = int1; this.Int2 = int2; this.Date1 = date1; }
}
public class Int32Int32DateO : Object
{
// implement other properties
public Int32Int32DateS Int32Int32Date { get; private set; }
public Int32 Int1 { get { return Int32Int32Date.Int1; } }
public Int32 Int2 { get { return Int32Int32Date.Int2; } }
public DateTime Date1 { get { return Int32Int32Date.Date1; } }
public override bool Equals(Object obj)
{
//Check for null and compare run-time types.
if (obj == null || !(obj is Int32Int32DateO)) return false;
Int32Int32DateO item = (Int32Int32DateO)obj;
return (this.Int32Int32Date.Int1 == item.Int32Int32Date.Int1 &&
this.Int32Int32Date.Int2 == item.Int32Int32Date.Int2 &&
this.Int32Int32Date.Date1 == item.Int32Int32Date.Date1);
}
public override int GetHashCode()
{
return (((Int64)Int32Int32Date.Int1 << 32) + Int32Int32Date.Int2).GetHashCode() ^ Int32Int32Date.GetHashCode();
}
public Int32Int32DateO(Int32 Int1, Int32 Int2, DateTime Date1)
{
Int32Int32DateS int32Int32Date = new Int32Int32DateS(Int1, Int2, Date1);
this.Int32Int32Date = int32Int32Date;
}
}
}
}
As for using value type fpr the key Microsoft specifically recommends against it.
ValueType.GetHashCode
Tuple is technically not a value type but suffers from the same symptom (hash collisions) and is not good candidate for a key.
May I suggest an alternative - a anonymous object. It's the same we use in GroupBy LINQ method with multiple keys.
var dictionary = new Dictionary<object, string> ();
dictionary[new { a = 1, b = 2 }] = "value";
It may looks strange, but I've benchmarked Tuple.GetHashCode and new{ a = 1, b = 2 }.GetHashCode methods and the anonymous objects wins on my machine on .NET 4.5.1:
Object - 89,1732 ms for 10000 calls in 1000 cycles
Tuple - 738,4475 ms for 10000 calls in 1000 cycles
Another solution to the ones already mentioned would be to store some kind of list of all keys generated so far and when a new object is generated you generate it's hashcode (just as a starting point), check if it's already in the list, if it is, then add some random value etc to it until you've got a unique key, then store that key in the object itself and in the list and return that as the key at all times.
As an alternative:
Maybe this will help somebody with this necessity.
One option would be to use strings as a Composite Key for the dictionary. Example:
var myDict = new Dictionary<string, bool>();
myDict.Add($"{1}-{1111}-{true}", true);
myDict.Add($"{1}-{1111}-{false}", false);
This way you can store keys with any format. If you want you can always define a function that builds your key as this:
string BuildKey(int number, string name, bool disabled) => $"{number}-{name}-{disabled}";