I have a simple domain object:
class FavoriteFood
{
public string Name;
public int Ordinal;
}
I want to have a collection of this domain object that maintains the correct ordinal. For example, given 4 favorite foods:
Name: Banana, Ordinal: 1
Name: Orange, Ordinal: 2
Name: Pear, Ordinal: 3
Name: Watermelon, Ordinal: 4
If I change Pear's ordinal to 4 it should shift Watermelon's ordinal down to 3.
If I add a new favorite food (Strawberry) with ordinal 3 it should shift Pear up to 4 and Watermelon up to 5.
If I change Pear's ordinal to 2 it should shift Orange up to 3.
If I change Watermelon's ordinal to 1, Banana would bump up to 2, Orange would bump up to 3, and Pear would bump up to 4.
What's the best way to accomplish this?
UPDATE: The name property of the domain object is dynamic and based on user input. The object has to have this Ordinal property because a user can change the order in which their favorite foods are displayed. This ordinal value is saved in a database and when populating the structure I cannot guarantee the items are added in order of their ordinals.
The trouble I am running into is when the underlying domain object is changed, there isn't a good way of updating the rest of the items in the list. For example:
var favoriteFoods = new List<FavoriteFood>();
var banana = new FavoriteFood { Name = "Banana", Ordinal = 1};
favoriteFoods.Add(banana);
favoriteFoods.Add(new FavoriteFood { Name = "Orange", Ordinal = 2 });
banana.Ordinal = 2;
// at this point both Banana and Orange have the same ordinal in the list. How can we make sure that Orange's ordinal gets updated too?
So far I have tried doing the following which works :
class FavoriteFood : INotifyPropertyChanging
{
public string Name;
public int Ordinal
{
get { return this.ordinal; }
set
{
var oldValue = this.ordinal;
if (oldValue != value && this.PropertyChanging != null)
{
this.PropertyChanging(new FavoriteFoodChangingObject { NewOrdinal = value, OldOrdinal = oldValue }, new PropertyChangingEventArgs("Ordinal"));
}
this.ordinal = value;
}
}
internal struct FavoriteFoodChangingObject
{
internal int NewOrdinal;
internal int OldOrdinal;
}
// THIS IS A TEMPORARY WORKAROUND
internal int ordinal;
public event PropertyChangingEventHandler PropertyChanging;
}
public class FavoriteFoodCollection : IEnumerable<FavoriteFood>
{
private class FavoriteFoodOrdinalComparer : IComparer<FavoriteFood>
{
public int Compare(FavoriteFood x, FavoriteFood y)
{
return x.Ordinal.CompareTo(y.Ordinal);
}
}
private readonly SortedSet<FavoriteFood> underlyingList = new SortedSet<FavoriteFood>(new FavoriteFoodOrdinalComparer());
public IEnumerator<FavoriteFood> GetEnumerator()
{
return this.underlyingList.GetEnumerator();
}
public void AddRange(IEnumerable<FavoriteFood> items)
{
foreach (var i in items)
{
this.underlyingList.Add(i);
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
private void UpdateOrdinalsDueToRemoving(FavoriteFood item)
{
foreach (var i in this.underlyingList.Where(x => x.Ordinal > item.Ordinal))
{
i.ordinal--;
}
}
public void Remove(FavoriteFood item)
{
this.underlyingList.Remove(item);
this.UpdateOrdinalsDueToRemoving(item);
}
public void Add(FavoriteFood item)
{
this.UpdateOrdinalsDueToAdding(item);
this.underlyingList.Add(item);
item.PropertyChanging += this.item_PropertyChanging;
}
private void item_PropertyChanging(object sender, PropertyChangingEventArgs e)
{
if (e.PropertyName.Equals("Ordinal"))
{
var ordinalsChanging = (FavoriteFood.FavoriteFoodChangingObject)sender;
this.UpdateOrdinalsDueToEditing(ordinalsChanging.NewOrdinal, ordinalsChanging.OldOrdinal);
}
}
private void UpdateOrdinalsDueToEditing(int newOrdinal, int oldOrdinal)
{
if (newOrdinal > oldOrdinal)
{
foreach (var i in this.underlyingList.Where(x => x.Ordinal <= newOrdinal && x.Ordinal > oldOrdinal))
{
//i.Ordinal = i.Ordinal - 1;
i.ordinal--;
}
}
else if (newOrdinal < oldOrdinal)
{
foreach (var i in this.underlyingList.Where(x => x.Ordinal >= newOrdinal && x.Ordinal < oldOrdinal))
{
//i.Ordinal = i.Ordinal + 1;
i.ordinal++;
}
}
}
private void UpdateOrdinalsDueToAdding(FavoriteFood item)
{
foreach (var i in this.underlyingList.Where(x => x.Ordinal >= item.Ordinal))
{
i.ordinal++;
}
}
}
This works alright, but the use of the internal Ordinal field is a strange workaround. It's needed so that the PropertyChangingEvent wont be infinitely raised.
Just use a List<string>:
List<string> foods = new List<string> { "Banana", "Orange", "Pear" };
int ordinalOfOrange = foods.IndexOf("Orange");
It's not a good idea to 'store' that ordinal if it has to change the way you describe.
Sounds like you want a SortedList. Add each item using it's Ordinal as the key.
I'd do something like the following:
public class FavoriteFoods
{
StringComparer comparer ;
List<string> list ;
public FavoriteFoods()
{
this.list = new List<string>() ;
this.comparer = StringComparer.InvariantCultureIgnoreCase ;
return ;
}
public void Add( string food , int rank )
{
if ( this.list.Contains(food,comparer ) ) throw new ArgumentException("food") ;
this.list.Insert(rank,food) ;
return ;
}
public void Remove( string food )
{
this.list.Remove( food ) ;
return ;
}
public void ChangeRank( string food , int newRank )
{
int currentRank = this.list.IndexOf(food) ;
if ( currentRank < 0 ) throw new ArgumentOutOfRangeException("food") ;
if ( newRank < 0 ) throw new ArgumentOutOfRangeException("newRank") ;
if ( newRank >= this.list.Count ) throw new ArgumentOutOfRangeException("newRank") ;
if ( newRank != currentRank )
{
this.Remove(food) ;
this.Add( food , newRank ) ;
}
return ;
}
public int GetRank( string food )
{
int rank = this.list.IndexOf(food) ;
if ( rank < 0 ) throw new ArgumentOutOfRangeException("food");
return rank ;
}
public IEnumerable<string> InRankOrder()
{
foreach ( string food in this.list )
{
yield return food ;
}
}
}
Let me restate your problem.
You have a collection of strings. You have a collection of ordinals.
You want to be able to quickly look up the ordinal of a string. And the string of an ordinal. You'd also like to be able to insert a string with a given ordinal. And change the ordinal of a string.
There are two ways to go. The first, simple, approach is to store a collection of the strings in order, with knowledge of their ordinal. You can scan the list in time O(n). You can also lookup, insert, move, and delete in time O(n) each. If you don't actually care about performance then I would strongly suggest going this way.
If you do care about performance, then you'll need to build a custom data structure. The simplest idea is to have two trees. One tree stores the strings in alphabetical order, and tells you where in the other tree the string is. The other tree stores the strings in order of the ordinals, and stores counts of how much stuff is in various subtrees.
Now here are your basic operations.
Insert. Insert in the second tree at the correct position (if you choose to move anything else in the process, updating those things in the first tree), then insert the string in the first tree.
Lookup by string. Search the first tree, find where it is in the second tree, walk back in the second tree to find its ordinal.
Lookup by ordinal. Search the second tree, find the string.
Delete. Delete from both trees.
Move ordinal. Remove from the second tree in the old position. Insert into the second tree in the new position. Update all appropriate nodes in the first tree.
For the simple version you can just use trees. If you want to get fancy, you can look up B-Trees, Red-Black trees and other types of self-balancing trees, then pick one of those.
If you program this correctly you can guarantee that all operations take time O(log(n)). However there will be a lot of constant overhead, and for small collections the effort to be clever may be a loss relative to the simple approach.
Related
I have four lists for four columns of a table. I want to print data as shown in the screenshot of the image below.
However, whenever I run the code below details of first user come according to the screenshot but after I input another user's data and try to print it, it comes in same line like this:
Jack Li Tom smith 10 5
This is what I have tried:
public void SummaryTable()
{
Console.WriteLine("\tSummary of Membership Fee");
Console.WriteLine("{0} {1,10} {2,10} {3,10}","Name", "Weeks","Discount","Charges");
//Console.Write("Weeks\t");
//Console.Write("Discount\t");
// Console.WriteLine("Charge");
//Console.WriteLine();
foreach (string namesl in nameList)
{
Console.Write("{0}",namesl);
//Console.Write("\t");
}
foreach (int weekl in weekList)
{
Console.Write("{0,10}",weekl);
//Console.Write("\t");
}
foreach (string discountl in discountList)
{
Console.Write("{0,10}", discountl);
// Console.Write("\t");
}
foreach (double chargel in chargeList)
{
Console.WriteLine("{0,10}",chargel);
}
You could just print them like this:
Console.WriteLine("\tSummary of Membership Fee");
Console.WriteLine(new String('=', 45));
Console.WriteLine("{0,-20} {1,-5} {2,-10} {3,-10}", "Name", "Weeks", "Discount", "Charges");
Console.WriteLine(new String('-', 45));
for (int count = 0; count < nameList.Count; count++)
{
Console.WriteLine("{0,-20}{1,5}{2,10}{3,10}", nameList[count], weekList[count], discountList[count], chargeList[count]);
Console.WriteLine(new String('-', 45));
}
These are parallel lists
I believe what you have here are called parallel lists. The first item in the first list goes with the first item in the second, third, and fourth lists; the second item in the first list goes with the second item in the other lists; etc. Is that correct?
What is needed here is to merge the lists into one, which you can do several ways. Here is a simple and intuitive way to do it. I'll show two:
Crayon method
This method is very simple and intuitive, although not all that fancy.
First, define a class that can contain the data in a single object:
public class Member
{
public string Name { get; set; }
public int Week { get; set; }
public string Discount { get; set; }
public double Charge { get; set; }
}
Then write a bit of code to merge your four lists into a single list of Member objects:
public List<Member> GetMergedList()
{
var results = new List<Member>();
for (int i=0; i<nameList.Count; i++)
{
results.Add(
new Member
{
Name = nameList[i],
Week = weekList[i],
Discount = discountList[i],
Charge = chargeList[i]
}
);
}
return results;
}
Now you can just iterate over (or DataBind against) the single list:
public void Run()
{
var members = GetMergedList();
foreach (var m in members)
{
Console.WriteLine("{0}\t{1}\r{2}\t{3:0.00}", m.Name, m.Week, m.Discount, m.Charge);
}
}
Output:
John 1 Discount1 1.23
Paul 2 Discount2 4.56
Dave 3 Discount3 7.89
Using LINQ
You could also merge your lists using LINQ (e.g. using the Zip method) to produce an anonymous type that contains everything:
var membersViaLinq = nameList
.Zip(weekList, (n, w) => new { Name = n, Week = w })
.Zip(discountList, (x, d) => new { Name = x.Name, Week = x.Week, Discount = d})
.Zip(chargeList, (x, c) => new { Name = x.Name, Week = x.Week, Discount = x.Discount, Charge = c });
foreach (var x in membersViaLinq)
{
Console.WriteLine("{0}\t{1}\r{2}\t{3:0.00}", x.Name, x.Week, x.Discount, x.Charge);
}
This approach is a little more confusing to read, but the benefit is you don't have to define a class just to hold the data.
The best solution
The best solution is to retrieve the data in a single list to begin with, if possible, and completely eliminate the parallel lists entirely. To suggest a way to do that, I'd have to see your data retrieval code.
Full example code on DotNetFiddle
This is easily solvable using OOP.
Create a following class:
public class Person
{
public string firstName;
public string lastName;
public int weeks;
public bool discount;
public int charge;
}
Now instead of creating multiple lists you will create only one list:
List<Person> People = new List<Person>();
You can populate it with Person objects.
Example:
People.Add(person1);
People.Add(person2);
People.Add(person3);
People.Add(person4);
Here I'm assuming you are going pass in a list of persons as an argument to this function:
public void SummaryTable(List<Person> list)
{
foreach (Person p in list)
{
Console.Write("{0}", p.firstName);
Console.Write("{0}", p.lastName);
Console.Write("{0}", p.weeks);
Console.Write("{0}", p.discount);
Console.Write("{0}", p.charge);
}
}
Now you simply call the function:
SummaryTable(People);
Also make sure you import the required namespaces using System.Collections;.
I'm trying to solve a problem in which it would be useful to have a data structure like
var list = new SortedList<int>();
list.Add(3); // list = { 3 }
list.Add(1); // list = { 1, 3 }
list.Add(2); // list = { 1, 2, 3 }
int median = list[list.Length / 2];
i.e.
O(n) insertion
O(1) lookup by index
but I can't see that such a thing exists? I see that there's some confusing SortedList<T,U> and then an interface SortedList, but neither of those are what I'm looking for.
The sorted list in the .NET framework is an associative list (that is it is for key/value pairs). You can use a regular List<T> if you use its binary search functionality, which works if you keep the list sorted at all times. You can encapsulate it in an extension method:
static class SortedListExtensions {
public static void SortedAdd<T>(this List<T> list, T value) {
int insertIndex = list.BinarySearch(value);
if (value < 0) {
value = ~value;
}
list.Insert(insertIndex, value);
}
//Added bonus: a faster Contains method
public static bool SortedContains<T>(this List<T> list, T value) {
return list.BinarySearch(value) >= 0;
}
}
List<int> values = new List<int>();
values.SortedAdd(3);
values.SortedAdd(1);
values.SortedAdd(2);
I've tried looking for an existing question but wasn't sure how to phrase this and this retrieved no results anywhere :(
Anyway, I have a class of "Order Items" that has different properties. These order items are for clothing, so they will have a size (string).
Because I am OCD about these sorts of things, I would like to have the elements sorted not by the sizes as alphanumeric values, but by the sizes in a custom order.
I would also like to not have this custom order hard-coded if possible.
To break it down, if I have a list of these order items with a size in each one, like so:
2XL
S
5XL
M
With alphanumeric sorting it would be in this order:
2XL
5XL
M
S
But I would like to sort this list into this order (from smallest size to largest):
S
M
2XL
5XL
The only way I can think of to do this is to have a hard-coded array of the sizes and to sort by their index, then when I need to grab the size value I can grab the size order array[i] value. But, as I said, I would prefer this order not to be hard-coded.
The reason I would like the order to be dynamic is the order items are loaded from files on the hard disk at runtime, and also added/edited/deleted by the user at run-time, and they may contain a size that I haven't hard-coded, for example I could hard code all the way from 10XS to 10XL but if someone adds the size "110cm" (aka a Medium), it will turn up somewhere in the order that I don't want it to, assuming the program doesn't crash and burn.
I can't quite wrap my head around how to do this.
Also, you could create a Dictionary<int, string> and add Key as Ordering order below. Leaving some gaps between Keys to accomodate new sizes for the future. Ex: if you want to add L (Large), you could add a new item as {15, "L"} without breaking the current order.
Dictionary<int, string> mySizes = new Dictionary<int, string> {
{ 20, "2XL" }, { 1, "S" },
{ 30, "5XL" }, { 10, "M" }
};
var sizes = mySizes.OrderBy(s => s.Key)
.Select(s => new {Size = s.Value})
.ToList();
You can use OrderByDescending + ThenByDescending directly:
sizes.OrderByDescending(s => s == "S")
.ThenByDescending( s => s == "M")
.ThenByDescending( s => s == "2XL")
.ThenByDescending( s => s == "5XL")
.ThenBy(s => s);
I use ...Descending since a true is similar to 1 whereas a false is 0.
I would implement IComparer<string> into your own TShirtSizeComparer. You might have to do some regular expressions to get at the values you need.
IComparer<T> is a great interface for any sorting mechanism. A lot of built-in stuff in the .NET framework uses it. It makes the sorting reusable.
I would really suggest parsing the size string into a separate object that has the size number and the size size then sorting with that.
You need to implement the IComparer interface on your class. You can google how to do that as there are many examples out there
you'll have to make a simple parser for this. You can search inside the string for elements like XS XL and cm" if you then filter that out you have your unit. Then you can obtain the integer that is the value. If you have that you can indeed use an IComparer object but it doesn't have that much of an advantage.
I would make a class out of Size, it is likely that you will need to add more functionality to this in the future. I added the full name of the size, but you could also add variables like width and length, and converters for inches or cm.
private void LoadSizes()
{
List<Size> sizes = new List<Size>();
sizes.Add(new Size("2X-Large", "2XL", 3));
sizes.Add(new Size("Small", "S", 1));
sizes.Add(new Size("5X-Large", "5XL", 4));
sizes.Add(new Size("Medium", "M", 2));
List<string> sizesShortNameOrder = sizes.OrderBy(s => s.Order).Select(s => s.ShortName).ToList();
//If you want to use the size class:
//List<Size> sizesOrder = sizes.OrderBy(s => s.Order).ToList();
}
public class Size
{
private string _name;
private string _shortName;
private int _order;
public string Name
{
get { return _name; }
}
public string ShortName
{
get { return _shortName; }
}
public int Order
{
get { return _order; }
}
public Size(string name, string shortName, int order)
{
_name = name;
_shortName = shortName;
_order = order;
}
}
I implemented TShirtSizeComparer with base class Comparer<object>. Of course you have to adjust it to the sizes and objects you have available:
public class TShirtSizeComparer : Comparer<object>
{
// Compares TShirtSizes and orders them by size
public override int Compare(object x, object y)
{
var _sizesInOrder = new List<string> { "None", "XS", "S", "M", "L", "XL", "XXL", "XXXL", "110 cl", "120 cl", "130 cl", "140 cl", "150 cl" };
var indexX = -9999;
var indexY = -9999;
if (x is TShirt)
{
indexX = _sizesInOrder.IndexOf(((TShirt)x).Size);
indexY = _sizesInOrder.IndexOf(((TShirt)y).Size);
}
else if (x is TShirtListViewModel)
{
indexX = _sizesInOrder.IndexOf(((TShirtListViewModel)x).Size);
indexY = _sizesInOrder.IndexOf(((TShirtListViewModel)y).Size);
}
else if (x is MySelectItem)
{
indexX = _sizesInOrder.IndexOf(((MySelectItem)x).Value);
indexY = _sizesInOrder.IndexOf(((MySelectItem)y).Value);
}
if (indexX > -1 && indexY > -1)
{
return indexX.CompareTo(indexY);
}
else if (indexX > -1)
{
return -1;
}
else if (indexY > -1)
{
return 1;
}
else
{
return 0;
}
}
}
To use it you just have a List or whatever your object is and do:
tshirtList.Sort(new TShirtSizeComparer());
The order you have "hard-coded" is prioritized and the rest is put to the back.
I'm sure it can be done a bit smarter and more generalized to avoid hard-coding it all. You could e.g. look for sizes ending with an "S" and then check how many X's (e.g. XXS) or the number before X (e.g. 2XS) and sort by that, and then repeat for "L" and perhaps other "main sizes".
I have a list that contains 3 items, two of type_1, and one of type_2. I want to return a second list that contains the type and number of that type that exists. When stepping through the breakpoints set at the foreach loop, the IF statement is never true. I assume there is something wrong with my attempt to use Contains() method.
The output should be something like:
type_1 2
type_2 1
Instead, it evaluates as:
type_1 1
type_1 1
type_2 1
Is my use of Contains() not correct?
public List<item_count> QueryGraphListingsNewAccountReport()
List<item> result = new List<items>();
var type_item1 = new item { account_type = "Type_1" };
var type_item2 = new item { account_type = "Type_1" };
var type_item3 = new item { account_type = "Type_2" };
result.Add(type_item1);
result.Add(type_item2);
result.Add(type_item3);
//Create a empty list that will hold the account_type AND a count of how many of that type exists:
List<item_count> result_count = new List<item_count>();
foreach (var item in result)
{
if (result_count.Contains(new item_count { account_type = item.account_type, count = 1 } ) == true)
{
var result_item = result_count.Find(x => x.account_type == item.account_type);
result_item.count += 1;
result_count.Add(result_item);
}
else
{
var result_item = new item_count { account_type = item.account_type, count = 1 };
result_count.Add(result_item);
}
}
return result_count;
}
public class item
{
public string account_type { get; set; }
}
public class item_count
{
public int count {get; set;}
public string account_type { get; set; }
}
I think your problem is that you don't want to use contains at all. You are creating a new object in your contains statement and, obviously, it isn't contained in your list already because you only just created it. The comparison is comparing references, not values.
Why not just use the find statement that you do in the next line instead? If it returns null, then you know there isn't an item already with that type.
So you could do something like this:
var result_item = result_count.Find(x => x.account_type == item.account_type);
if (result_item != null)
{
result_item.count++;
// note here you don't need to add it back to the list!
}
else
{
// create your new result_item here and add it to your list.
}
Note: Find is o(n), so this might not scale well if you have a really large set of types. In that case, you might be better off with Saeed's suggestion of grouping.
You can do:
myList.GroupBy(x=>x.type).Select(x=>new {x.Key, x.Count()});
If you want use for loop, it's better to use linq Count function to achieve this, If you want use Contains you should implement equal operator as the way you used.
I am trying to parse a rather long log file and creating a better more manageable listing of issues.
I am able to read and parse out the individual log line by line, but what I need to do is display only unique entries, as some errors occur more often than others and are always recorded with identical text.
What I was going to try to do was create a Dictionary object to hold each unique entry and as I work through the log file, search the Dictionary object to see if the same values are already in there.
Here is a crude sample of the code I have (a work in progress, I hope I have all syntax right) that does not work. For some reason this script never sees any distinct entries (if statement never passes):
string[] rowdta = new string[4];
Dictionary<string[], int> dict = new Dictionary<string[], int>();
int ctr = -1;
if (linectr == 1)
{
ctr++;
dict.Add(rowdta, ctr);
}
else
{
foreach (KeyValuePair<string[], int> pair in dict)
{
if ((pair.Key[1] != rowdta[1]) || (pair.Key[2] != rowdta[2])| (pair.Key[3] != rowdta[3]))
{
ctr++;
dict.Add(rowdta, ctr);
}
}
}
Some sample data:
First line
rowdta[0]="ErrorType";
rowdta[1]="Undefined offset: 0";
rowdta[2]="/url/routesDisplay2.svc.php";
rowdta[3]="Line Number 5";
2nd line
rowdta[0]="ErrorType";
rowdta[1]="Undefined offset: 0";
rowdta[2]="/url/routesDisplay2.svc.php";
rowdta[3]="Line Number 5";
3rd line
rowdta[0]="ErrorType";
rowdta[1]="Undefined variable: fvmsg";
rowdta[2]="/url/processes.svc.php";
rowdta[3]="Line Number 787";
So, with this, the Dictionary will have 2 items in it, first line and 3rd line.
I have also tried this with the following which nalso does not find any variations in the log file text.
if (!dict.ContainsKey(rowdta)) {}
Can someone please help me get this syntax right? I am just a newbie at C# but this should be relatively straightforward. As always, I am thinking that this should be enough information to get the conversation started. If you want/need more detail, please let me know.
Either create a wrapper for your strings which implements IEquatable.
public class LogFileEntry :IEquatable<LogFileEntry>
{
private readonly string[] _rows;
public LogFileEntry(string[] rows)
{
_rows = rows;
}
public override int GetHashCode()
{
return
_rows[0].GetHashCode() << 3 |
_rows[2].GetHashCode() << 2 |
_rows[1].GetHashCode() << 1 |
_rows[0].GetHashCode();
}
#region Implementation of IEquatable<LogFileEntry>
public override bool Equals(Object obj)
{
if (obj == null)
return base.Equals(obj);
return Equals(obj as LogFileEntry);
}
public bool Equals(LogFileEntry other)
{
if(other == null)
return false;
return _rows.SequenceEqual(other._rows);
}
#endregion
}
Then use that in your dictionary:
var d = new Dictionary<LogFileEntry, int>();
var entry = new LogFileEntry(rows);
if( d.ContainsKey(entry) )
{
d[entry] ++;
}
else
{
d[entry] = 1;
}
Or create a custom comparer similar to that proposed by #dasblinkenlight and use as follows
public class LogFileEntry
{
}
public class LogFileEntryComparer : IEqualityComparer<LogFileEntry>{ ... }
var d = new Dictionary<LogFileEntry, int>(new LogFileEntryComparer());
var entry = new LogFileEntry(rows);
if( d.ContainsKey(entry) )
{
d[entry] ++;
}
else
{
d[entry] = 1;
}
The reason that you see the problem is that an array of strings cannot be used as a key in a dictionary without supplying a custom IEqualityComparer<string[]> or writing a wrapper around it.
EDIT Here is a quick and dirty implementation of a custom comparer:
private class ArrayEq<T> : IEqualityComparer<T[]> {
public bool Equals(T[] x, T[] y) {
return x.SequenceEqual(y);
}
public int GetHashCode(T[] obj) {
return obj.Sum(o => o.GetHashCode());
}
}
Here is how you can use it:
var dd = new Dictionary<string[], int>(new ArrayEq<string>());
dd[new[] { "a", "b" }] = 0;
dd[new[] { "a", "b" }]++;
dd[new[] { "a", "b" }]++;
Console.WriteLine(dd[new[] { "a", "b" }]);
The problem is that array equality is reference equality. In other words, it does not depend on the values stored in the array, it depends only on the identity of the array.
Some solutions
use Tuple to hold the row data
use an anonymous type to hold the row data
create a custom type to hold the row data, and, if it is a class, override Equals and GetHashCode.
create a custom implementation of IEqualityComparer to compare the arrays according to their values, and pass that to the dictionary when you create it.