- Home /
Problem using IComparer to sort list of GameObjects
Hello everyone,
I've been trying to understand how to sort a list of GameObjects by a custom attribute, but without success so far. I must be doing something wrong. I'm trying to do this for a* pathfinding. I have a list of tiles, each with F, G and H scores. Now I want to sort them by F score, so, in my class TileStatus attached to the Tile GameObject I have this :
using UnityEngine;
using System;
using System.Collections;
public class TileStatus : MonoBehaviour,IComparable {
public double F;
public double G;
public double H;
public GameObject parent = null;
int IComparable.CompareTo ( object obj)
{
TileStatus tile = ( TileStatus )obj;
if ( this . getFScore()< tile . getFScore() )
{
return -1;
}
if ( this . getFScore() > tile . getFScore() )
{
return 1;
}
else
{
return 0;
}
}
public void setParent(GameObject parentTile)
{
parent = parentTile;
}
public GameObject getParent()
{
return parent;
}
public double getFScore ()
{
return F;
}
public void setFScore (double x)
{
F = x;
}
public double getHScore()
{
return H;
}
public double getGScore()
{
return G;
}
public void setGScore (double x)
{
G = x;
}
public void setHScore (double x)
{
H = x;
}
}
void Start () {
if (gameObject.tag == "Free square")
{
isOccupied = false;
}
else
{
isOccupied = true;
}
setFScore(0);
setGScore(0);
setHScore(0);
}
// Update is called once per frame
void Update () {
}
}
but for some reason when it comes to the sorting itself Unity sends me this error :
"ArgumentException: does not implement right interface System.Collections.Generic.Comparer`1+DefaultComparer[UnityEngine.GameObject].Compare (UnityEngine.GameObject x, UnityEngine.GameObject y) (at /Applications/buildAgent/work/b59ae78cff80e584/mcs/class/corlib/System.Collections.Generic/Comparer.cs:86) System.Array.compare[GameObject] (UnityEngine.GameObject value1, UnityEngine.GameObject value2, IComparer`1 comparer) (at /Applications/buildAgent/work/b59ae78cff80e584/mcs/class/corlib/System/Array.cs:1744) System.Array.qsort[GameObject,GameObject] (UnityEngine.GameObject[] keys, UnityEngine.GameObject[] items, Int32 low0, Int32 high0, IComparer`1 comparer) (at /Applications/buildAgent/work/b59ae78cff80e584/mcs/class/corlib/System/Array.cs:1721) System.Array.Sort[GameObject,GameObject] (UnityEngine.GameObject[] keys, UnityEngine.GameObject[] items, Int32 index, Int32 length, IComparer`1 comparer) (at /Applications/buildAgent/work/b59ae78cff80e584/mcs/class/corlib/System/Array.cs:1674) Rethrow as InvalidOperationException: The comparer threw an exception. System.Array.Sort[GameObject,GameObject] (UnityEngine.GameObject[] keys, UnityEngine.GameObject[] items, Int32 index, Int32 length, IComparer`1 comparer) (at /Applications/buildAgent/work/b59ae78cff80e584/mcs/class/corlib/System/Array.cs:1677) System.Array.Sort[GameObject] (UnityEngine.GameObject[] array, Int32 index, Int32 length, IComparer`1 comparer) (at /Applications/buildAgent/work/b59ae78cff80e584/mcs/class/corlib/System/Array.cs:1623) System.Collections.Generic.List`1[UnityEngine.GameObject].Sort () (at /Applications/buildAgent/work/b59ae78cff80e584/mcs/class/corlib/System.Collections.Generic/List.cs:568)"
I'd be very happy if anyone could help me out because I don't see what's wrong here. Thanks !!
I dorked around with this for a little bit in Visual Studio(not mono) as it would seem like everything is implemented correctly. They way you have that code right now is explicitly implemented, i found explicitly to be not forgiving with accessors(public) and a couple other quirks. When i implicitly implemented it, there seemed to be more of a forgiving nature of the compiler.
Explicit:
int IComparable.CompareTo(object obj)
{
throw new NotImplementedException();
}
Implicit(more forgiving):
public int CompareTo(object obj)
{
throw new NotImplementedException();
}
Test your code with an implicit implementation and see if mono is more loving. Otherwise it seems buggy, also note that it seems to be trying to use IComparable which is odd. Anyways, just stabs in the dark.
Unfortunately I first tried with public int CompareTo and it gav me the same error. It is trying to use IComparable because it's the way you can define a "custom" sort for lists in c#, although I do not master that aspect, and that's probably where my error is, but i don't get where even if I've read dozens of threads about IComparable.
Answer by whydoidoit · Mar 19, 2013 at 01:33 PM
I think you want to implement an object using the IComparer interface rather than IComparable. However, normally you can just do the sort using the lambda version of Sort:
E.g:
children.Sort((c1, c2) => (int) (c1.topLeft.x - c2.topLeft.x));
Sure you can do that - if it's an array of GameObject you can do this:
children.Sort((c1,c2)=> (int) (c1.GetComponent<TileStatus>().getFScore() - c2.GetComponent<TileStatus>().getFScore()));
@laoril - only when you have 15 karma (which you do now because I upvoted your question - seemed like a sensible one to me :)
You can upvote comments (but that awards no karma, just indicates the ones that are relevant). You can upvote answers (15 karma) and you can tick answers.
You can accept an answer (20 karma) and you can post your own answer and tick that (no karma for that I'm afraid) if you feel you did something different.
I would also suggest to use an array / List of TileStatus and not GameObject. The IComparable interface would only work when you compare two objects of this type. When you have a list of GameObjects the sort function will compare GameObjects and not a component that might be attached to it.
Also there's a generic version of IComparable so you don't need to type-cast manually. Also inside the CompareTo function you usually use the CompareTo function of the variable type you want to compare.
eg:
public int CompareTo(TileStatus aOther)
{
return getFScore().CompareTo(aOther.getFScore());
}
Good point @Bunny83!
I would also point out, for completeness that you can also use Linq:
using System.Linq;
...
var tileStatusSortedByFscore = gameObjectArray.Select(g=>g.GetComponent<TileStatus>()).OrderBy(ts=>ts.getFScore()).ToList(); //Or ToArray
var gameObjectSortedByFscore = gameObjectArray.OrderBy(g=>g.GetComponent<TileStatus>().getFScore()).ToList();
Or perhaps
foreach(var tile in tileStatusArrayOrList.OrderBy(ts=>ts.getFScore())
{
//Do something
}
@laoril we were talking techy sorry - that's pretty advance stuff.
The practical upshot is that using Linq is actually not a good idea in an Update loop because it allocates memory that will be reclaimed causing a garbage collection performance spike (until I release my version of Linq as an asset that doesn't do that!)
To summarise our suggestions:
$$anonymous$$eep your TileStatus in a List< TileStatus> to remove the need for continual GetComponent calls.
Use Linq in a setup phase as it's faster than the IComparer versions that require the execution of the comparison O(log(n)) times.
Answer by vexe · Jul 07, 2014 at 01:15 PM
I ran into the same issue with IComparable
- Here are the workarounds.