Sorting custom list with bool - c#

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)

Related

Lambda Sort on Long Types

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()

C# Ordering an Array

I am looking to order an Array. You can compare 2 objects and they will give a return either -1 (which means the object should come before that one), 0 object is the same or 1 and the object should be behind that one in the array. I've looked up multiple ways to order an Array but could not find a solution for this one. I hope someone could help me out on this one. Thanks!
EDIT:
This is what I tried
public virtual void Add(NAW item)
{
_nawArray[ItemCount()] = item; //igonore this
for (int x = 0; x < _nawArray.Size; x++)
{
for (int y = 0; y < _nawArray.Size; y++)
{
if(_nawArray[x].CompareTo(_nawArray[y]) == 1){
//x should go on the place of y
} else if(_nawArray[x].CompareTo(_nawArray[y]) == -1){
//y should go on the place of x
}
}
}
}
ItemCount() gets the amount of used places in the Array.
But when ObjectX goes in the place of ObjectY I can not set ObjectX on the place of ObjectY (unless I copy it but that is not what I need). Anyone an idea?
Well, just use Array.Sort:
https://msdn.microsoft.com/en-us/library/cxt053xf(v=vs.110).aspx
something like this:
MyType[] data = ...
...
Array.Sort(data, (left, right) => YourFunction(left, right));
If you mean that you want to sort your Array, you could take a look at this page, it may help you:
https://msdn.microsoft.com/en-us/library/6tf1f0bc(v=vs.110).aspx
Array.Sort(yourArrayNameHere)
In case you mean anything else, please do share and be more precise in your question.
You need to use Array.Sort (https://msdn.microsoft.com/en-us/library/6tf1f0bc(v=vs.110).aspx)
Array.Sort(someArray)
But you need to implement interface IComparable in your class (https://msdn.microsoft.com/en-us/library/4d7sx9hd(v=vs.110).aspx)
Example:
class IntHolder : IComparable<IntHolder>
{
public int SomeInt { get; set; }
public int CompareTo(IntHolder other)
{
return SomeInt.CompareTo(other.SomeInt);
}
}

C# Sort object array by specific parameter

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

Sort stop working after enum values added

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).

Is there a class in C# to handle a couple of INT (range of 2 INT- 1-10)

I am quite new to C# and I was wondering if there is a Class or a data structure or the best way to handle the following requirement...
I need to handle a COUPLE of int that represent a range of data (eg. 1 - 10 or 5-245) and I need a method to verify if an Int value is contained in the range...
I believe that in C# there is a class built in the framework to handle my requirement...
what I need to do is to verify if an INT (eg. 5) is contained in the range of values Eg (1-10) ...
in the case that I should discover that there is not a class to handle it, I was thinking to go with a Struct that contain the 2 numbers and make my own Contain method to test if 5 is contained in the range 1-10)
in the case that I should discover that there is not a class to handle
it, I was thinking to go with a Struct that contain the 2 numbers and
make my own Contain method to test if 5 is contained in the range
1-10)
That's actually a great idea as there's no built-in class for your scenario in the BCL.
You're looking for a range type; the .Net framework does not include one.
You should make an immutable (!) Int32Range struct, as you suggested.
You may want to implement IEnumerable<int> to allow users to easily loop through the numbers in the range.
You need to decide whether each bound should be inclusive or exclusive.
[Start, End) is probably the most obvious choice.
Whatever you choose, you should document it clearly in the XML comments.
Nothing exists that meets your requirements exactly.
Assuming I understood you correctly, the class is pretty simple to write.
class Range
{
public int Low {get; set;}
public int High {get; set;}
public bool InRange(int val) { return val >= Low && val <= High; }
}
A Tuple<int,int> would get you part of the way but you'd have to add an extension method to get the extra behavior. The downside is that the lower- and upper-bounds are implicitly Item1 and Item2 which could be confusing.
// written off-the-cuff, may not compile
public static class TupleExtension
{
public static bool InRange(Tuple<int, int> this, int queryFor)
{
return this.Item1 >= queryFor && this.Item2 <= queryFor;
}
}
You could create an extension if you want to avoid making a new type:
public static class Extensions
{
public static bool IsInRange(this int value, int min, int max)
{
return value >= min && value <= max;
}
}
Then you could do something like:
if(!value.IsInRange(5, 545))
throw new Exception("Value is out of range.");
i think you can do that with an array.
some nice examples and explanation can be found here:
http://www.dotnetperls.com/int-array
Nothing built in AFAIK, but (depending on the size of the range) an Enumerable.Range would work (but be less than optimal, as you're really storing every value in the range, not just the endpoints). It does allow you to use the LINQ methods (including Enumerable.Contains), though - which may come in handy.
const int START = 5;
const int END = 245;
var r = Enumerable.Range(START, (END - START)); // 2nd param is # of integers
return r.Contains(100);
Personally, I'd probably go ahead and write the class, since it's fairly simple (and you can always expose an IEnumerable<int> iterator via Enumerable.Range if you want to do LINQ over it)

Categories

Resources