I have a imprmented sort method for a colection in my code and today i noticed something strange. When i tried to add new enum values to the enum the sort method crashed with this error.
Unable to sort because the IComparer.Compare() method returns inconsistent results. Either a value does not compare equal to itself, or one value repeatedly compared to another value yields different results. x: '', x's type: 'Texture2D', IComparer: 'System.Array+FunctorComparer`1[Microsoft.Xna.Framework.Graphics.Texture2D]'.
This seems really strange seens the sort is in now way dependent on earlyer result and all it should do is sort after the index of the enum insteed of alfabatic order.
Here is the code.
availableTiles.Sort(CompareTilesToEnum);
private static int CompareTilesToEnum(Texture2D x, Texture2D y)
{
int xValue = (int) (Enum.Parse(typeof(TileTyp), x.Name, true));
int yValue = (int) (Enum.Parse(typeof(TileTyp), y.Name, true));
if (xValue > yValue)
{
return 1;
}
else
{
return -1;
}
}
public enum TileTyp
{
Nothing = -1,
Forest,
Grass,
GrassSandBottom,
GrassSandLeft,
GrassSandRight,
GrassSandTop,
Mounten,
Sand,
Snow,
Water,
GrassSandTopLeft,
GrassSandAll,
GrassSandBottomLeft,
GrassSandBottomRightLeft,
GrassSandBottomRightTop,
GrassSandBottomTopLeft,
GrassSandRightLeft,
GrassSandRightTop,
GrassSandRightTopLeft,
GrassSandBottomRight,
GrassSandBottomTop
}
The values i added was
GrassSandBottomRight,
GrassSandBottomTop
Your comparison never returns 0 - even if the values are equal. Any reason you don't just ask int.CompareTo to compare the values?
private static int CompareTilesToEnum(Texture2D x, Texture2D y)
{
int xValue = (int) (Enum.Parse(typeof(TileTyp), x.Name, true));
int yValue = (int) (Enum.Parse(typeof(TileTyp), y.Name, true));
return xValue.CompareTo(yValue);
}
Simpler and more importantly, it should actually work :)
As the error clearly states, your comparer is broken.
You need to return 0 if the values are equal.
There are some rules you must follow with any comparison method:
If A == B, then B == A (return zero both times).
If A < B and B < C, then A < C.
If A < B, then B > A
A == A (return zero if compared with itself).
(Note, the == above means that nether < nor > is true. It is permissable for two objects to be equivalent in a sort-order without being true for a corresponding Equals. We could for instance have a rule that sorted all strings containing numbers in numerical order, put all other strings and the end, but didn't care about what order those other strings were in).
These rules follow for any language (they're not programming rules, they're logic rules), there is a .NET specific one too:
5: If A != null, then A > null.
You're breaking all of the first four rules. Since Texture2D is a reference type you risk breaking rule 5 too (will throw a different exception though).
You're also lucky that .NET catches it. A different sort algorithm could well have crashed with a more confusing error or fallen into an infinite loop as it e.g found that item 6 was reported as greater than item 7 and swapped them, then soon after found that item 6 was reported as greater than item 7 and swapped them, then soon after found...
private static int CompareTilesToEnum(Texture2D x, Texture2D y)
{
//Let's deal with nulls first
if(ReferenceEquals(x, y))//both null or both same item
return 0;
if(x == null)
return -1;
if(y == null)
return 1;
//Enum has a CompareTo that works on integral value, so why not just use that?
return Enum.Parse(typeof(TileTyp), x.Name, true)).CompareTo(Enum.Parse(typeof(TileTyp), y.Name, true)));
}
(This assumes a failure in the parsing is impossible and doesn't have to be considered).
Related
I'm trying to run a sort as below but am running into an issue with the Start properties being of type Long in the Lambda expression. When they were of type int this was working correctly but I need this to work over larger values. I've tried casting the a.Start - b.Start to int but this seems to provide an incorrect sort result.
Is there a different method by which I should be sorting or should I change datatypes?
ranges.Sort((a, b) => a.Start - b.Start);
private readonly List<Range> ranges = new List<Range>();
public class Range
{
public Range(long startEnd) : this(startEnd, startEnd)
{
}
public Range(long start, long end)
{
if (end >= start)
{
Start = start;
End = end;
}
else
{
Start = end;
End = start;
}
}
public long Start { get; private set; }
public long End { get; private set; }
public void Update(long newStart, long newEnd)
{
Start = newStart;
End = newEnd;
}
public static implicit operator Range(long i)
{
return new Range(i);
}
}
Function you pass to Sort should:
return anything negative if a < b (can be always -1 for example)
zero if a == b
anything positive if a > b (can be always 1 for example)
Your current function satisfies this criteria (but not safe to use because of potential overflow), but returns long. There are many other functions that satisfy this criteria though. One is already existing comparer for longs:
ranges.Sort((a, b) => a.Start.CompareTo(b.Start));
You can do the same yourself if you'd like (though no reason to):
ranges.Sort((a, b) => a.Start > b.Start ? 1 : a.Start < b.Start ? -1 : 0);
The delegate you pass to the Sort method is a Comparison<T> which must always return an int, whatever the type T it is comparing.
The int returned from this delegate should be:
A signed integer that indicates the relative values of x and y, as
shown in the following table.
Value Meaning
Less than 0 x is less than y.
0 x equals y.
Greater than 0 x is greater than y.
Therefore the fact that it worked when your Start was an int is actually purely coincidental.
You can fix your case by having your delegate return
a.Start.CompareTo(b.Start)
A comparison is supposed to return an int so you need to convert your long to an int somehow. You can either Convert.ToInt32 or, if that might be out of range, simply return -1 for any negative value and 1 for any positive value.
Another, probably better alternative, would be to use the CompareTo method of one of the values for both int and long, which would be functionally equivalent to the second option.
Casting a.Start - b.Start to int seems to work here, however by doing that you expose yourself to overflow errors (what if a.Start is 0 and b.Start is long.MaxValue, for example?). Since Sort only checks if your lambda is returning a positive value, a negative value or zero, you can do just this:
ranges.Sort((a, b) => a.Start > b.Start ? 1 : a.Start < b.Start ? -1 : 0);
Alternatively, LINQ's OrderBy works just fine (and is not limited to Lists), but be aware that it returns a new object rather than modifying the original one, which may or may not be ok for you:
ranges = ranges.OrderBy(r => r.Start).ToList()
This question already has answers here:
Why does .NET use int instead of uint in certain classes?
(7 answers)
Closed 4 years ago.
The C# IEnumerable.Count(IEnumerable<TSource>) and IEnumerable.Count(IEnumerable<TSource>, Func<TSource,Boolean>) both return type int implying it can return a value less than zero. It doesn't make sense for it to do so, but if the type is int it's theoretically possible for the result to be a negative value.
IEnumerable<string> list = GetMyList();
int listCount = list.Count();
// is it more correct to do this:
if(listCount <= 0)
{
DoSomething();
}
else
{
DoSomethingElse();
}
// or this:
if(listCount == 0)
{
DoSomething();
}
else if (listCount > 0)
{
DoSomethingElse();
}
else
{
// but this branch will never be hit
throw new Exception();
}
I can't find any information online about whether or not that can actually happen, and the Documentation for Enumerable.Count does not specify any cases in which it might.
Just wondering if anyone has any experience with this happening or any information on this.
Thanks
The purpose of the return data type is not to imply a range of numbers. Although it naturally does set a hard upper and lower limit (sort of... see LongCount), that is just a side effect of type compatibility and generalizability.
Consider the Array's Rank property. The maximum value is 32. But we don't store it in a byte or short. We store it in an int. Why? We don't need all that range. But it's recommended: they're fast (they align well to register size and memory maps) and, by convention, It is easier to work with other libraries if you work with ints. Also, the int datatype is CLS-compliant (meaning that any language that implements the CLR must support it) but uint32 is not.
Returning a numeric data type that has a particular range in no way implies that the full range will be used. And returning a negative value from IEnumerable.Count() would not only be poor form, but it would be semantically incorrect, as a count must obviously return a cardinal number, which must be and integer and non-negative.
Actually there´s no such method Count defined on neither IEnumerable nor IEnumerable<T>, but on the static class System.Linq.Enumerable. As it´s a (static) extension-method, you can´t override nor modify this at all. So let´s look into the extension-method:
public static int Count<TSource>(this IEnumerable<TSource> source)
{
if (source == null) throw Error.ArgumentNull("source");
ICollection<TSource> collectionoft = source as ICollection<TSource>;
if (collectionoft != null) return collectionoft.Count;
ICollection collection = source as ICollection;
if (collection != null) return collection.Count;
int count = 0;
using (IEnumerator<TSource> e = source.GetEnumerator()) {
checked {
while (e.MoveNext()) count++;
}
}
return count;
}
As you can see the only way Count will ever return a negative number is by implelementing ICollection.Count, which is called by Enumerable.Count() (as you can see above), or by creating your own extension-method with the exact same name and relying on extension-method resolution in order to "hide" the method from System.Linq.Enumerable:
public static class MyClass
{
public static int Count(this IEnumerable<T> src) { return -1; }
}
However I can´t see any reason why one should do this at all.
So in short the method can return a negative number. Doing this however breaks the principle of least astonishment and thus is a bad idea.
I have a node class which is contained in a SortedDictionary:
SortedDictionary<Node, bool> openList = new SortedDictionary<Node, bool>();
I need to write the CompareTo method on the node, so that the nodes are sorted from lowest to highest based on an int value(F). I also need to be able to check if a node already exists in the dictionary based on their position (this position is what makes the node Unique). The CompareTo looks like this:
public int CompareTo(Node other)
{
if (GridPosition != other.GridPosition)
{
if (F > other.F)
{
return 1;
}
if (F < other.F)
{
return -1;
}
return -1;
}
return 0;
}
The problem is, that it doesn't always return the correct result. For example, the following line of code will return false even though the node is in the dictionary. However it sorts all the nodes that I add to the dictionary exactly as I want it.
Node node = new Node(); //It has a grid position and a f value
openList.add(node, false);
if(openList.Keys.Contains(node)) //this returns false
{
}
To fix this I created an EqualityComparer and used it while comparing the values. The equality comparer looks like this:
class NodeEqualityComparer : IEqualityComparer<Node>
{
public bool Equals(Node x, Node y)
{
return x.GridPosition == y.GridPosition;
}
public int GetHashCode(Node obj)
{
return obj.GridPosition.GetHashCode();
}
}
I'm using this equality comparer as a parameter in the Contains method on the SortedDictionary, this works fine and it returns the correct result based on the content in the Dictionary:
if (openList.Keys.Contains(currentNode,new NodeEqualityComparer())) //This will return true if the node is in the dictionary
{
}
The problem occours when I need to remove a node from the Dictionary. The remove function is using the CompareTo method on the Node to find the node to remove, and as I stated earlier, this function appreantly doesn't return the correct result when comparing the objects. This means that the code below will not remove the node from the dictionary 100% of the time. You can't pass an equalityComparer to the remove function, so that can't fix my problem.
Node node = new Node(); //It has a grid position and a f value
openList.add(node, false);
openList.Remove(node);
Maybe I'm handeling this in a wrong way, so if anyone have some suggestions for solving this problem I'll be very happy to hear them. Maybe I can write a CompareTo function that solves the problem?
This problem is probably a result of you comparing GridPosition in the comparer.
C# distinguishes between value types and reference types. If you compare two int variables the comparer will base equality on the value. For instantiated classes however the comparisson is based upon the reference, meaning you are comparing whether an object is the object in memory.
This:
if (GridPosition != other.GridPosition)
{
will probably almost always return true, thus resulting in your sort order being correct, because you are providing the order int he if statment nested inside:
if (F > other.F)
{
return 1;
}
if (F < other.F)
{
return -1;
}
If however (F == other.F) you assign -1, so the ContainsKey will not recognize these objects as equal.
Depending on what type GridPosition is, you should provide an appropriate comparer, for instance something like
if (GridPosition.X != other.GridPosition.X && GridPosition.Y != other.GridPosition.Y)
And depending on what F is, you are violating the mathematical principal of symmetry, as an object with different GridPosition and equal F will alway be sorted before any other object fulfilling this. Thus for two objects you could get A == B ==> -1 and B == A ==> -1 which isnt useful in any case.
So you would have to do this in the nested if:
if (GridPosition != other.GridPosition)
{
if (F > other.F)
{
return 1;
}
if (F < other.F)
{
return -1;
}
/////////////////////////////////
return 0;
/////////////////////////////////
}
or expand the comparission to a third condition beyond GridPosition and F
EDIT: Based on the information you provided in the comments, you don't actually want a dictionary in this case, just a list with proper sorting. For instance:
List<Node> myList = new List<Node>();
myList.Sort((node1, node2) => node1.F > node2.F);//Sort the list based on their node value
As for your bool; I don't know what this represents, but I'm under the strong impression that you want a regular Dictionary<Node, bool> for this. Then use the list myList to handle whatever node you need as next waypoint and lookup the bool corresponding to a node in the dictionary whenever you need it.
I am relatively new to programming. I have an array of objects which isn't necessarily full (may include null rows). And I want to sort it by one of the class parameters "int moveScore".
This is my array (currently holds only 32 entries)
Score[] firstPlyScore = new Score[1000];
I tried 2 things for sorting
1
In the "Score" class, i inherited "IComparable" and used the "CompareTo" method as follows
public int CompareTo(object obj)
{
Score x = (Score)obj;
if (this.moveScore < x.moveScore)
return -1;
if (this.moveScore > x.moveScore)
return 1;
return 0;
}
I called it using;
Array.Sort(firstPlyScore);
The problem is that it does sort correctly but at the end of the array. Meaning rows 0-966 are "null" and 967-999 are sorted correctly (967 with highest "int", 999 with lowest).
Is there any way to fix this.
2
I also tried this
Array.Sort(firstPlyScore, delegate
(Score x, Score y) { return x.moveScore.CompareTo(y.moveScore); });
Here the problem was that it crashed when it reached a "null" row.
Help most appreciated!
The default comparison behavior is for null values to be ordered before non-null values. If you want to override this behavior, a custom Comparison<Score> like in your second example would be the way to go.
delegate (Score x, Score y) {
if (x == null)
return y == null ? 0 : 1;
if (y == null)
return -1;
return x.moveScore.CompareTo(y.moveScore);
}
This will keep the null items at the end of the array.
To sort in descending order, just swap the x and y references in the last line.
firstPlyScore = firstPlyScore
.Where(x => x != null)
.OrderByDescending(x => x.moveScore)
.ToArray();
You can use Linq to Entities to sort and then convert back to an array it will re-size your array to the correct length needed without null issue
var list = firstPlyScore.OrderByDescending(x => x.MoveScore).ToList();
//here how you can get always 1000 array length as you asked
for (int i = list.Count-1; i < 1000; i++)
{
list.Add(null);
}
firstPlyScore = list.ToArray();
}
In the beginning of your compare method
if(obj == null) return 0;
The problem is that it does sort correctly but at the end of the
array. Meaning rows 0-966 are "null" and 967-999 are sorted correctly
(967 with highest "int", 999 with lowest). Is there any way to fix
this.
If you need something, whose size can change, you are looking for a List<int>, instead of using arrays.
Arrays should be used for Lists that do not change (e.g. a chess board).
The List also provides you a method called Sort.
As mentioned by others, you can also use LINQ to achieve what you seek for.
Do you really need to keep 1000 items in the list even if most of them are null ?
I would suggest to check the logic behind it to see if you can prevent that because that's taking a lot of space for nothing.
Otherwise, 2 possibilities:
Check for Obj == null in your compare function. But it might still fail if the comparing item is null
Create a custom class for your score and make Icomparable (Check this link for several example about how to sort arrays
There is a class
public class Camera
{
...
public bool live;
...
}
This is the sorting class
public class CameraSortByLive : IComparer<Camera>
{
private bool asc;
public CameraSortByLive(bool a)
{
this.asc = a;
}
public int Compare(Camera x, Camera y)
{
if (x.live != y.live)
return asc ? 0 : 1;
else
return asc ? 1 : 0;
}
}
This is how I use it:
List<Camera> CameraList = new List<Camera>();
CameraList.Sort(new CameraSortByLive(sortAsc));
Now, I beside live member I have other members int, string type. With those types I have similar sorting class implementing IComparer. There is no problem with them. The only problem with this live member. It simply doesn't sort. I expect it to go either on top of list or bottom, but it goes somewhere in the middle. What am I missing?
The problem is with your Compare function. You should state some ordering, like false < true or true < false. In your function, sometimes true < false and sometimes false < true.
public int Compare(Camera x, Camera y)
{
return (asc && x.live) ? 1 : 0;
}
The problem with your code is that you can't be really sure the order in which the list elements are compared to each other. So, you are comparing two cams and if their live member is equal you consider the first is "greater" than second. So, if your first cam have is "dead" and second is "live", the first is still greater. That definitely not what you want.
With this code, if the left cam is live - it's considered greater than right, regardless of right's live value. Since we're not concerned about sorting by other features, we really don't care of "internal" order of live cams (i.e. all live cams are considered equal, as well as all deadcams considered equal too)