Custom c# collection class sorting by multiple properties - c#

I am sorting a custom class as follows, which is sorted by FirstHalfPoints:
var firstHalfTables = new FirstHalfTableCollection();
firstHalfTables.PopulateForSeason(SeasonId);
firstHalfTables.Sort((t1, t2) => t1.FirstHalfPoints.CompareTo(t2.FirstHalfPoints));
firstHalfTables.Reverse();
FirstHalfTableRepeater.DataSource = firstHalfTables;
FirstHalfTableRepeater.DataBind();
I also need to sort by a two further fields 'GD' - Goal Difference and 'GF' - Goals For- after this initial sort by 'FirstHalfPoints'
So the finished collection will be sorted by FirstHalfPoints, then by GD, then by GF.
Can anyone help me out with multiple sorting?

You could use LINQ, which often makes the code more readable:
var firstHalfTables = new FirstHalfTableCollection();
firstHalfTables.PopulateForSeason(SeasonId);
var firstHalfTablesProcessed = (from table in firstHalfTables
orderby table.FirstHalfPoints, table.GD, table.GF
select table).Reverse()
FirstHalfTableRepeater.DataSource = firstHalfTablesProcessed;
Or you could make your comparison delegate return the proper sorting (probably better as normal function than lambda):
int FirstHalfTableComparison(FirstHalfTable t1, FirstHalfTable t2)
{
int result = t1.FirstHalfPoints.CompareTo(t2.FirstHalfPoints);
if (result == 0)
{
result = t1.GD.CompareTo(t2.GD);
if (result == 0)
result = t1.GF.CompareTo(t2.GF);
}
return result;
}
You can use that like this:
firstHalfTables.Sort(FirstHalfTableComparison);

you can use direct LINQ expressions: "OrderBy" and "ThenBy" too: MSDN Sorting Data
OR
you have to extent your comparison. You have to remember: a.CompareTo(b) should return -1 if a is "smaller" than b, 0 if they are equal and +1 if a is "bigger" than b.
So try the following:
private void Compare(YourRow a, YourRow b)
{
let v = a.FirstHalfPoints.CompareTo(b.FirstHalfPoints);
if (v != 0) return v;
v = a.GD.CompareTo(b.GD);
if (v != 0) return v;
return a.GF.CompareTo(b.GF);
}
and call Sort with this function.

Related

Compare multiple string between them according to different conditions

is it possible to compare several strings between them according to different conditions ?
I have 4 strings to compare between them:
If the four are the same => one condition
(example : A,A,A,A)
If only one different value and the rest null => a condition
(example : A, A, null, A or A, null, null, null )
If there are at least two different strings other than null => another condition
(example : A,B,A,A or A,B,Null, Null)
do you know how I can do it, in a clean way, without making an "if" for each possible combination?
Put the strings in a collection and start the LINQ engine :)
If the four are the same => one condition (example : A,A,A,A)
bool allSame = !strings.Distinct().Skip(1).Any(); // or Distinct().Count() == 1
If only one different value and the rest null
bool oneNonNull = strings.Count(s => s != null).Count() == 1;
If there are at least two different strings other than null
bool twoNonNull = strings.Where(s => s != null).Take(2).Count() == 2;
If you absolutely don't want to use if statements, you could use delegates with anonymous functions like so:
public void CompareStrings(List<string> stringsToCompare) {
Func<List<string>, bool> allSame = delegate (List<string> input)
{
return input.Distinct().Count() == stringsToCompare.Count;
};
Func<List<string>, bool> oneDifferent = delegate (List<string> input)
{
return input.Distinct().Count() == stringsToCompare.Count - 1;
};
Func<List<string>, bool> twoDifferent = delegate (List<string> input)
{
return input.Distinct().Count() == stringsToCompare.Count - 2;
};
Console.WriteLine(allSame);
Console.WriteLine(oneDifferent);
Console.WriteLine(twoDifferent);
}

What to do to get only one List?

Hello i have a method that compares the objects of 2 Lists for differences. Right now this works but only for one property at a time.
Here is the Method:
public SPpowerPlantList compareTwoLists(string sqlServer, string database, DateTime timestampCurrent, string noteCurrent, DateTime timestampOld, string noteOld)
{
int count = 0;
SPpowerPlantList powerPlantListCurrent = loadProjectsAndComponentsFromSqlServer(sqlServer, database, timestampCurrent, noteCurrent);
SPpowerPlantList powerPlantListOld = loadProjectsAndComponentsFromSqlServer(sqlServer, database, timestampOld, noteOld);
SPpowerPlantList powerPlantListDifferences = new SPpowerPlantList();
count = powerPlantListOld.Count - powerPlantListCurrent.Count;
var differentObjects = powerPlantListCurrent.Where(p => !powerPlantListOld.Any(l => p.mwWeb == l.mwWeb)).ToList();
foreach (var differentObject in differentObjects)
{
powerPlantListDifferences.Add(differentObject);
}
return powerPlantListDifferences;
}
This works and i get 4 Objects in the new List. The Problem is that i have a few other properties that i need to compare. Instead of mwWeb for example name. When i try to change it i need for every new property a new List and a new Foreach-Loop.
e.g.
int count = 0;
SPpowerPlantList powerPlantListCurrent = loadProjectsAndComponentsFromSqlServer(sqlServer, database, timestampCurrent, noteCurrent);
SPpowerPlantList powerPlantListOld = loadProjectsAndComponentsFromSqlServer(sqlServer, database, timestampOld, noteOld);
SPpowerPlantList powerPlantListDifferences = new SPpowerPlantList();
SPpowerPlantList powerPlantListDifferences2 = new SPpowerPlantList();
count = powerPlantListOld.Count - powerPlantListCurrent.Count;
var differentObjects = powerPlantListCurrent.Where(p => !powerPlantListOld.Any(l => p.mwWeb == l.mwWeb)).ToList();
var differentObjects2 = powerPlantListCurrent.Where(p => !powerPlantListOld.Any(l => p.shortName == l.shortName)).ToList();
foreach (var differentObject in differentObjects)
{
powerPlantListDifferences.Add(differentObject);
}
foreach (var differentObject in differentObjects2)
{
powerPlantListDifferences2.Add(differentObject);
}
return powerPlantListDifferences;
Is there a way to prevent this? or to make more querys and get only 1 List with all different Objects back?
I tried it with except and intersect but that didnt worked.
So any help or advise would be great and thx for your time.
PS: If there is something wrong with my question-style please say it to me becouse i try to learn to ask better questions.
You may be able to simply chain the properties that you wanted to compare within your Where() clause using OR statements :
// This should get you any elements that have different A properties, B properties, etc.
var different = current.Where(p => !old.Any(l => p.A == l.A || p.B == l.B))
.ToList();
If that doesn't work and you really want to use the Except() or Intersect() methods to properly compare the objects, you could write your own custom IEqualityComparer<YourPowerPlant> to use to properly compare them :
class PowerPlantComparer : IEqualityComparer<YourPowerPlant>
{
// Powerplants are are equal if specific properties are equal.
public bool Equals(YourPowerPlant x, YourPowerPlant y)
{
// Check whether the compared objects reference the same data.
if (Object.ReferenceEquals(x, y)) return true;
//Check whether any of the compared objects is null.
if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
return false;
// Checks the other properties to compare (examples using mwWeb and shortName)
return x.mwWeb == y.mwWeb && x.shortName == y.shortName;
}
// If Equals() returns true for a pair of objects
// then GetHashCode() must return the same value for these objects.
public int GetHashCode(YourPowerPlant powerPlant)
{
// Check whether the object is null
if (Object.ReferenceEquals(powerPlant, null)) return 0;
// Get hash code for the mwWeb field if it is not null.
int hashA = powerPlant.mwWeb == null ? 0 : powerPlant.mwWeb.GetHashCode();
// Get hash code for the shortName field if it is not null.
int hashB = powerPlant.shortName == null ? 0 : powerPlant.shortName.GetHashCode();
// Calculate the hash code for the product.
return hashA ^ hashB;
}
}
and then you could likely use something like one of the following depending on your needs :
var different = current.Except(old,new PowerPlantComparer());
or :
var different = current.Intersect(old,new PowerPlantComparer());
One way is to use IEqualityComparer as Rion Williams suggested, if you'd like a more flexible solution you can split logic in to two parts. First create helper method that accepts two lists, and function where you can define what properties you wish to compare. For example :
public static class Helper
{
public static SPpowerPlantList GetDifference(this SPpowerPlantList current, SPpowerPlantList old, Func<PowerPlant, PowerPlant, bool> func)
{
var diff = current.Where(p => old.All(l => func(p, l))).ToList();
var result = new SPpowerPlantList();
foreach (var item in diff) result.Add(item);
return result;
}
}
And use it :
public SPpowerPlantList compareTwoLists(string sqlServer, string database,
DateTime timestampCurrent, string noteCurrent,
DateTime timestampOld, string noteOld)
{
var powerPlantListCurrent = ...;
var powerPlantListOld = ...;
var diff = powerPlantListCurrent.GetDifference(
powerPlantListOld,
(x, y) => x.mwWeb != y.mwWeb ||
x.shortName != y.shortName);
return diff;
}
P.S. if it better suits your needs, you could move method inside of existing class :
public class MyClass
{
public SPpowerPlantList GetDifference(SPpowerPlantList current, SPpowerPlantList old, Func<PowerPlant, PowerPlant, bool> func)
{
...
}
}
And call it (inside of class) :
var result = GetDifference(currentValues, oldValues, (x, y) => x.mwWeb != y.mwWeb);
The easiest way to do this would be to compare some unique identifier (ID)
var differentObjects = powerPlantListCurrent
.Where(p => !powerPlantListOld.Any(l => p.Id == l.Id)
.ToList();
If the other properties might have been updated and you want to check that too, you'll have to compare all of them to detect changes made to existing elements:
Implement a camparison-method (IComparable, IEquatable, IEqualityComparer, or override Equals) or, if that's not possible because you didn't write the class yourself (code generated or external assembly), write a method to compare two of those SPpowerPlantList elements and use that instead of comparing every single property in Linq. For example:
public bool AreThoseTheSame(SPpowerPlantList a,SPpowerPlantList b)
{
if(a.mwWeb != b.mwWeb) return false;
if(a.shortName != b.shortName) return false;
//etc.
return true;
}
Then replace your difference call with this:
var differentObjects = powerPlantListCurrent
.Where(p => !powerPlantListOld.Any(l => AreThoseTheSame(p,l))
.ToList();

How to Check All Values in Dictionary is same in C#?

I have a Dictionary, I want to write a method to check whether all values are same in this Dictionary.
Dictionary Type:
Dictionary<string, List<string>>
List {1,2,3}`and {2,1,3} are same in my case.
I have done this previously for simple datatype values, but I can not find logic for new requirement, please help me.
For simple values:
MyDict.GroupBy(x => x.Value).Where(x => x.Count() > 1)
I have also written a Generic Method to compare two datatypes in this way.
// 1
// Require that the counts are equal
if (a.Count != b.Count)
{
return false;
}
// 2
// Initialize new Dictionary of the type
Dictionary<T, int> d = new Dictionary<T, int>();
// 3
// Add each key's frequency from collection A to the Dictionary
foreach (T item in a)
{
int c;
if (d.TryGetValue(item, out c))
{
d[item] = c + 1;
}
else
{
d.Add(item, 1);
}
}
// 4
// Add each key's frequency from collection B to the Dictionary
// Return early if we detect a mismatch
foreach (T item in b)
{
int c;
if (d.TryGetValue(item, out c))
{
if (c == 0)
{
return false;
}
else
{
d[item] = c - 1;
}
}
else
{
// Not in dictionary
return false;
}
}
// 5
// Verify that all frequencies are zero
foreach (int v in d.Values)
{
if (v != 0)
{
return false;
}
}
// 6
// We know the collections are equal
return true;
Implement an IEqualityComparer for List<string> that compares two list based on their content. Then just use Distinct on Values and check the count:
dictionary.Values.Distinct(new ListEqualityComparer()).Count() == 1
This should do the trick
var lists = dic.Select(kv => kv.Value.OrderBy(x => x)).ToList();
var first = lists.First();
var areEqual = lists.Skip(1).All(hs => hs.SequenceEqual(first));
You'll need to add some checks to make this work for the empty case.
...or if you want to take #Selman's approach here's an implementation of the IEqualityComparer:
class SequenceComparer<T>:IEqualityComparer<IEnumerable<T>>
{
public bool Equals(IEnumerable<T> left, IEnumerable<T> right)
{
return left.OrderBy(x => x).SequenceEqual(right.OrderBy(x => x));
}
public int GetHashCode(IEnumerable<T> item)
{
//no need to sort because XOR is commutative
return item.Aggregate(0, (acc, val) => val.GetHashCode() ^ acc);
}
}
You could make a variant of this combining the best of both approaches using a HashSet<T> that might be considerably more efficient in the case that you have many candidates to test:
HashSet<IEnumerable<int>> hs = new HashSet<IEnumerable<int>>(new SequenceComparer<int>());
hs.Add(dic.First().Value);
var allEqual = dic.All(kvp => !hs.Add(kvp.Value));
This uses the feature of HashSets that disallows adding more than one item that is considered equal with an item already in the set. We make the HashSet use the custom IEqualityComparer above...
So we insert an arbitrary item from the dictionary before we start, then the moment another item is allowed into the set (i.e. hs.Add(kvp.Value) is true), we can say that there's more than one item in the set and bail out early. .All does this automatically.
Selman22's answer works perfectly - you can also do this for your Dictionary<string, List<string>> without having to implement an IEqualityComparer yourself:
var firstValue = dictionary.Values.First().OrderBy(x => x);
return dictionary.Values.All (x => x.OrderBy(y => y).SequenceEqual(firstValue));
We compare the first value to every other value, and check equality in each case. Note that List<string>.OrderBy(x => x) simply sorts the list of strings alphabetically.
Its not the fastest sdolution, but its works for me:
bool AreEqual = l1.Intersect(l2).ToList().Count() == l1.Count() && l1.Count() == l2.Count();

Searching with Linq

I have a collection of objects, each with an int Frame property. Given an int, I want to find the object in the collection that has the closest Frame.
Here is what I'm doing so far:
public static void Search(int frameNumber)
{
var differences = (from rec in _records
select new { FrameDiff = Math.Abs(rec.Frame - frameNumber), Record = rec }).OrderBy(x => x.FrameDiff);
var closestRecord = differences.FirstOrDefault().Record;
//continue work...
}
This is great and everything, except there are 200,000 items in my collection and I call this method very frequently. Is there a relatively easy, more efficient way to do this?
var closestRecord = _records.MinBy(rec => Math.Abs(rec.Frame - frameNumber));
using MinBy from MoreLINQ.
What you might want to try is to store the frames in a datastructure that's sorted by Frame. Then you can do a binary search when you need to find the closest one to a given frameNumber.
I don't know that I would use LINQ for this, at least not with an orderby.
static Record FindClosestRecord(IEnumerable<Record> records, int number)
{
Record closest = null;
int leastDifference = int.MaxValue;
foreach (Record record in records)
{
int difference = Math.Abs(number - record.Frame);
if (difference == 0)
{
return record; // exact match, return early
}
else if (difference < leastDifference)
{
leastDifference = difference;
closest = record;
}
}
return closest;
}
you can combine your statements into one ala:
var closestRecord = (from rec in _records
select new { FrameDiff = Math.Abs(rec.Frame - frameNumber),
Record = rec }).OrderBy(x => x.FrameDiff).FirstOrDefault().Record;
Maybe you could divide your big itemlist in 5 - 10 smaller lists that are ordered by their Framediff or something ?
this way the search is faster if you know in which list you need to search

How do I do a non-standard sort on an Array in C#?

Array.Sort(test);
Comes out to be
_A
_B
_C
A
B
C
I reverse it
C
B
A
_C
.. You know
I want it to be
_C
_B
_A
C
B
A
So how could I do that?
If the built-in Array.Sort() does not sort the way you intend, you can override the IComparable.CompareTo method.
See this MSDN Article: How to: Sort Arrays Using Custom Criteria
Both the most efficient and simplest method would make use of the Comparison<T> delegate to do it all in one statement.
var items = new string[] { "_A", "_B", "_C", "A", "B", "C" };
Array.Sort(items, (a, b) =>
{
var aUnderscore = a.StartsWith("_");
var bUnderscore = b.StartsWith("_");
if (!(aUnderscore ^ bUnderscore))
return string.Compare(b, a);
else if (aUnderscore)
return -1;
else if (bUnderscore)
return +1;
return 0;
});
In uses lambda expressions, although not LINQ, so the performance hit should not be at all large, I believe.
You could create a comparer class that implements IComparer(Of String), then pass an instance of this class as the second parameter of Array.Sort.
Here's a sample code (in VB, sorry for that, but C# code would be similar):
Dim str = New String() {"_A", "_B", "_C", "A", "B", "C"}
Array.Sort(str, New SpecialComparer())
And here's my SpecialComparer:
Private Class SpecialComparer Implements IComparer(Of String)
Public Function Compare(ByVal x As String, ByVal y As String) As Integer
If x.StartsWith("_") And Not y.StartsWith("_") Then
Return -1
ElseIf y.StartsWith("_") And Not x.StartsWith("_") Then
Return 1
Else
Return y.CompareTo(x)
End If
End Function
End Class
You could implement your own Comparer like this:
public class MyOwnComparer: IComparer<string>
{
public int Compare(string x, string y)
{
if (x.StarsWith("_") && !y.StartsWith("_"))
return 1;
else if (!x.StartsWith("_") && y.StartsWith("_"))
return -1;
else
return x.CompareTo(y);
}
}
and then use it like this:
Array.Sort(test, new MyOwnComparer()); //sort ascending
var result = test.Reverse(); //now its descending
Read this article from MSDN about the Sort(Comparison) method, which includes a code example on how to write a custom sorter. I'd recommend copy/pasting it and tweaking it until you get what you're looking for.
Probably not the fastest way...
test = test.Where(x => x.StartsWith("_")).OrderByDescending(x => x).Concat(
test.Where(x => !x.StartsWith("_")).OrderByDescending(x => x)).ToArray();
Given your criteria I'd just do this (null or empty strings would cause a problem though):
List<string> items = new List<string>();
items.Sort();
List<string> result = items.FindAll(x => x[0] == '_');
result.AddRange(items.FindAll(x => x[0] != '_'));
A sort algorithm that doesn't compare all items may run into issues if you write a comparer that compares "_" strings from non-"_" strings. I recall having issues where the resulting order was incorrect because I wrote a comparer that did something similar.
Regardless, a custom comparer:
List<string> items = new List<string>();
items.Sort(
delegate(string a, string b)
{
if (a[0] == '_' && b[0] == '_')
{
return -a.CompareTo(b);
}
else if (a[0] == '_')
{
return 1;
}
else if (b[0] == '_')
{
return -1;
}
else
{
return a.CompareTo(b);
}
}
);
I also find the former to be more readable, but YMMV. (Note the minus signs to reverse sort.)

Categories