Am Facing a problem in Dictionaries.
Whether an Array can be a Key of a Value???
Dictionary<string[], int> di = new Dictionary<string[], int>();
di.Add(new string[]
{
"1","2"
}, 1);
di.Add(new string[]
{
"2","3"
}, 2);
MessageBox.Show(di[new string[] { "2", "3" }].ToString()); // Here KeyNotFoundException occurred.
Why Exception?
By default only references of the arrays would be compared, so you either have to
provide a custom IEqualityComparer<string[]> or
use a Tuple<string, string> as key instead ( since you only have two strings)
Here's a similar question's answer which shows how to create a custom comparer for the Dictionary- constructor.
No, actually you should not use arrays as a Dictionary<> Key; Dictionary<> when works with keys uses their hash codes which are computed as addresses:
String[] a = new[]{"1", "2"};
String[] b = new[]{"1", "2"};
a.GetHashCode() == b.GetHashCode(); // <- false
Arrays a and b have different addresses, and so different hash codes that's why
di.Add(a, 1);
di[b]; // <- error since a.GetHashCode() != b.GetHashCode()
Because a the Equals and GetHashCode functions of an array don't compare the content but the reference of the array himself.
Related
This question already has answers here:
Whether a Dictionary can have Array as Key?
(3 answers)
An integer array as a key for Dictionary
(3 answers)
Closed 5 months ago.
I have a concurrent dictionary which maps an array of characters to an integer
var dictionary = new ConcurrentDictionary<char[], int>();
It works fine if I map an array
var key = new char[] { 'A', 'B' };
var value = 8;
dictionary.TryAdd(key, value);
Console.WriteLine(dictionary[key]);
gives
8
But if I use an array created from a queue it doesn't work
var qu = new Queue<char>();
qu.Enqueue('A');
qu.Enqueue('B');
Console.WriteLine(dictionary[qu.ToArray()]);
gives
Unhandled exception. System.Collections.Generic.KeyNotFoundException: The given key 'System.Char[]' was not present in the dictionary.
at System.Collections.Concurrent.ConcurrentDictionary`2.ThrowKeyNotFoundException(TKey key)
at System.Collections.Concurrent.ConcurrentDictionary`2.get_Item(TKey key)
at Program.<Main>$(String[] args) in C:\Users\User\Desktop\SubstitutionCipher\Program.cs:line 65
C:\Users\User\Desktop\SubstitutionCipher\bin\Debug\net6.0\SubstitutionCipher.exe (process 18688) exited with code -532462766.
What is wrong and how to fix it?
In order to find the item in the dictionary, the dictionary gets a hash code and does an equality check. For the char-arrays, this leads to a reference comparison. Even though the arrays do contain the same items, they are different objects, so the reference comparison fails. Hence, the item is not found.
There are several ways to solve this:
Either you help the dictionary to find the item by implementing a custom equality comparer - that is a class that implements IEqualityComparer<T> and is used as a constructor parameter when creating the dictionary.
Or you use a type that already implements a comparison that fits your needs. In the case of a char-Array, you could use a string as a simple alternative, e.g.
var key = new string(new char[] { 'A', 'B' });
var value = 8;
dictionary.TryAdd(key, value);
Console.WriteLine(dictionary[key]);
var qu = new Queue<char>();
qu.Enqueue('A');
qu.Enqueue('B');
key = new string(qu.ToArray());
Console.WriteLine(dictionary[key]);
See this fiddle to test.
I have two sets of dictionaries that each contain the same keys and have initialized values.
Using unsafe code, I would like to swap their addresses:
Dictionary<string, List<object>> d1 = ...
Dictionary<string, List<object>> d2 = ...
unsafe void SwapEntries(string index)
{
int* tmp = &d1[index];
&d1[index] = &d2[index]
&d2[index] = tmp;
}
Assuming I've recalled my pointer arithmetic properly, the output I'm looking for would be this:
d1 = new Dictionary<string, List<int>>() { "a", { 1, 2, 3 } };
d2 = new Dictionary<string, List<int>>() { "a", { 4, 5, 6 } };
SwapEntries("a");
Console.WriteLine(d1["a"]); //4,5,6
Console.WriteLine(d2["a"]); //1,2,3
However, when I try to write this, I get the compile error "Cannot take the address of the given expression."
1) Is there a faster way of performing the address swap that I've missed? Performance is the only priority here.
2) Is my pointer arithmetic correct?
3) Do I need to move to a wrapper or a different data structure entirely in order to be able to perform the address swap as described?
I agree with Martin Ullrich's answer.
The expression d1[index] is not a variable. It is an invocation of the get accessor of the indexer defined by Dictionary<,>. You cannot take a pointer to that with the & operator.
Besides, in this case, the type of it is List<object>. You can only take pointers to value types, and List<> is a class type.
Even if you did have the true storage location, and it was of type object[], it would still be impossible since the element type of the array is object. So arr[0] (corresponding to d1[index][0]) would be a class type again, and you cannot take the address of that.
Scott Chamberlain's comment to your question gives an easy approach. Just use
void SwapEntries(string index)
{
var tmp = d1[index];
d1[index] = d2[index];
d2[index] = tmp;
}
This just involves passing around references to the two existing List<object> instances in question.
Automatic pointers to dictionary members aren't supported - they only work for Arrays or data types that use C# 7's "ref return" feature for indexers, properties or methods.
If you wanted to actually take the ref addresses of the two locations, there is now an option for it
CollectionsMarshal.GetValueRefOrNullRef(d1, "a")
So if you had a Swap function which accepted pointers:
void Swap<T>(ref T a, ref T b)
{
var tmp = a;
a = b;
b = tmp;
}
You could call it like this
Swap(ref CollectionsMarshal.GetValueRefOrNullRef(d1, "x"),
ref CollectionsMarshal.GetValueRefOrNullRef(d2, "x"));
shaplab
The benefit of this over just using normal dictionary indexers is that you only look up each location once, rather than once for get and once for set.
I have a file filled with text and numbers and need to import it into a Tuple<string, int> array (Tuple<string, int>[] vowels = new Tuple<string, int>[81]). The file looks something like this
a,2,e,6,i,3,o,8,u,2,y,5
The current method I use initially imports it into a string array using
string[] vowelsin = File.ReadAllText("path.txt").Split(',');
After importing, I turn the data into Tuples using
for (int x = 0; x < 81; x++)
vowels[x] = Tuple.Create(vowelin[x*2], int.Parse(vowelin[(x*2) + 1]));
While it works, it's a bit hard to read and during tests, takes around 100ms to complete. Are there any potential one-liners, faster methods, or more readable methods that could pull off the same thing?
string[] vowelsin = File.ReadAllText("path.txt").Split(',');
vowles = vowelsin.Zip(vowelsin.Skip(1),
(a, b) => new Tuple<string, string>(a, b))
.Where((x, i) => i % 2 == 0)
.ToArray();
You can use a KeyValuePair or a Dictionary instead of Tuples.
According to this article Tuples are faster than KeyValuePair. You can find more points of view here.
On the other hand, a comparison between Dictionaries and Tuples were made here.
The good news comes here:
As of C#7.0 a new feature about Tuples was introduced:
(string, string, string) LookupName(long id) // tuple return type
{
... // retrieve first, middle and last from data storage
return (first, middle, last); // tuple literal
}
This is the new way for using tuples: (type, ..., type) and it means that the method will return more than one value(three in this case).
The method now effectively returns three strings, wrapped up as elements in a tuple value.
The caller of the method will now receive a tuple, and can access the elements individually:
var names = LookupName(id);
WriteLine($"found {names.Item1} {names.Item3}.");
Further information can be found here What is new in C# 7.0 There you will find the advantages of these new Tuples over System.Tuple<,>
I am storing large number of arrays of data into a List, however, I don't want to store the data if it already exists in my list - the order of the data doesn't matter. I figured using GetHashCode to generate a hashcode would be appropriate because it was supposed to not care about order. However, what I found with a simple test below is that for the first two string[] a1 and a2 it generates a different hashcode.
Can I not utilize this method of checking? Can someone suggest a better way to check please?
string[] a1 = { "cat", "bird", "dog" };
string[] a2 = { "cat", "dog", "bird" };
string[] a3 = { "cat", "fish", "dog" };
Console.WriteLine(a1.GetHashCode());
Console.WriteLine(a2.GetHashCode());
Console.WriteLine(a3.GetHashCode());
the results from the above test produces three different hashcode results.
Ideally, I would have liked to see the same Hashcode for a1 and a2...so I am looking for something that would allow me to quickly check if those strings already exist.
Your arrays aren't equal, by the standard used by arrays for determining equality. The standard used by arrays for determining equality is that two separately created arrays are never equal.
If you want separately created collections with equal elements to compare as equal, then use a collection type which supports that.
I recommend HashSet<T>, in your case HashSet<string>. It doesn't provide the GetHashCode() and Equals() behaviour you want directly, but it has a CreateSetComparer() method that provides you with a helper class that does give you hash code and comparer methods that do what you want.
Just remember that you cannot use this for a quick equality check. You can only use this for a quick inequality check. Two objects that are not equal may still have the same hash code, basically by random chance. It's only when the hash codes aren't equal that you can skip the equality check.
If you say a1.GetHashCode(), this will always generate a new hash code for you:
using System;
public class Program
{
public static void Main()
{
string[] a1 = { "cat", "bird", "dog" };
string[] a2 = { "cat", "dog", "bird" };
string[] a3 = { "cat", "fish", "dog" };
Console.WriteLine(a1.GetHashCode());
Console.WriteLine(a2.GetHashCode());
Console.WriteLine(a3.GetHashCode());
}
}
I have two string arrays, newArray and oldArray, and I want to use Enumberable.Except method to remove all items that are in newArray that are also in oldArray and then write the result to a csv file.
However, I need to use a custom comparer in order to check for formatting similarities(if there is a new line character in one array and not the other, I don't want this item being written to the file).
My code as of now:
string newString = File.ReadAllText(csvOutputFile1);
string[] newArray = newString.Split(new string[] {sentinel}, StringSplitOptions.RemoveEmptyEntries);
string oldString = File.ReadAllText(csvOutputFile2);
string[] oldArray = oldString.Split(new string[] { sentinel }, StringSplitOptions.None);
IEnumerable<string> differnceQuery = newArray.Except(oldArray, new Comparer());
using (var wtr = new StreamWriter(diffFile))
{
foreach (var s in differnceQuery)
{
wtr.WriteLine(s.Trim() + "#!#");
}
}
and the custom comparer class:
class Comparer : IEqualityComparer<string>
{
public bool Equals(string x, string y)
{
x = x.ToString().Replace(" ", "").Replace("\n", "").Replace("\r", "");
y = y.ToString().Replace(" ", "").Replace("\n", "").Replace("\r", "");
if (x == y)
return true;
else
return false;
}
public int GetHashCode(string row)
{
int hCode = row.GetHashCode();
return hCode;
}
}
The resulting file is not omitting the formatting difference items between the two arrays. So although it catches items that are in the newArray but not in the oldArray(like it should), it is also putting in items that are only different because of a \n or something even though in my custom comparer I am removing them.
The thing I really don't understand is when I debug and step through my code, I can see each pair of items being analyzed in my custom comparer class, but only when they are equal terms. If for example the string "This is\nthe 1st term" is in newArray and the string "This is the first array" is in oldArray, the debugger doesn't even enter the comparer class and instead jumps straight to the writeline part of my code in the main class.
simply: your hash-code does not correctly mirror your equality method. Strings like "a b c" and "abc" would return different values from GetHashCode, so it would never get around to testing Equals. GetHashCode must return the same result for any two values that could be equal. It is not, however, necessary that two strings that are not equal return different hash-codes (although it is highly desirable, otherwise everything will go into the same hash-bucket).
I guess you could use:
// warning: probably not very efficient
return x.Replace(" ", "").Replace("\n", "").Replace("\r", "").GetHashCode();
but that looks pretty expensive (lots of potential for garbage strings to be generated all the time)