Custom Collections & Sorting

M

moongirl

i have designed a class (MyClass) which contains 2 custom collections
of objects. One is a list of people which I want to display at all
times in order of name, and one is a list of the same people but this
time always displayed in order of their score.

Therefore as scores are entered via a datagridview against each person
I want the second datagridview to immediately reflect the new scores
entered and re-sort itself if necessary.

The class currently looks like this:

public class MyClass
{
private static PlayersCollection players = new
PlayersCollection();
private static PlayersCollection scoreboard = new
PlayersCollection();

}

The PlayersCollection class inherits from BindingList<T> and I have
added a method as follows which enables me to sort ok.


public void TotalScoreSort(ListSortDirection direction)
{
List<Player> itemsList = this.Items as List<Player>;
itemsList.Sort(delegate(Player t1, Player t2)
{
this.listSortDirection = direction;
this.isSorted = true;

int reverse = direction ==
ListSortDirection.Ascending ? 1 : -1;

object value1 = t1.TotalScore;
object value2 = t2.TotalScore;

IComparable comparable = value1 as IComparable;
if (comparable != null)
{
return reverse * comparable.CompareTo(value2);
}
else
{
comparable = value2 as IComparable;
if (comparable != null)
{
return -1 * reverse *
comparable.CompareTo(value1);
}
else
{
return 0;
}
}
});

this.OnListChanged(new
ListChangedEventArgs(ListChangedType.Reset, -1));
}


I have a couple of questions. Firstly I feel like it would be better
if when I initialise each playersCollection I also specifiy a
'sortType'. Secondly when I call the sort method from the ui the
initial list of players also gets sorted which doesn't make sense to
me as they are separate lists???? I have been looking for info about
this all morning
but feel like I'm getting nowhere. Can anyone help clear this up for
me?
 
J

Jon Skeet [C# MVP]

i have designed a class (MyClass) which contains 2 custom collections
of objects. One is a list of people which I want to display at all
times in order of name, and one is a list of the same people but this
time always displayed in order of their score.

I have a couple of questions. Firstly I feel like it would be better
if when I initialise each playersCollection I also specifiy a
'sortType'. Secondly when I call the sort method from the ui the
initial list of players also gets sorted which doesn't make sense to
me as they are separate lists???? I have been looking for info about
this all morning
but feel like I'm getting nowhere. Can anyone help clear this up for
me?

It's hard to say without seeing a code sample. Could you provide a
short but complete program which demonstrates the problem?

A few comments on the code:

1) You're setting listSortDirection and isSorted for every comparison,
rather than just once
2) Do you not know whether the TotalScore property implements
IComparable? What's the type of the TotalScore property?
3) Multiplying the result of IComparable.CompareTo by -1 to reverse
the sort is *slightly* dangerous, because multiplying Int32.MinValue
by -1 results in int.MinValue again. A better way is to reverse the
order of the call, so instead of first.CompareTo(second) use
second.CompareTo(first). You could easily write a ReverseComparison
which takes an existing Comparison and reverses the result, which
would make life simple.
4) It seems to me that there are two fundamental bits here -
converting a Player into a TotalScore, and using IComparable to
implement a comparison. These could be separated out quite easily.
Alternatively, if the TotalScore property *does* return something
guaranteed to implement IComparable, just do:

Comparison<Player> byScore = delegate(Player p1, Player p2)
{ return p1.TotalScore.CompareTo(p2.TotalScore); };

if (direction==ListSortDirection.Descending)
{
byScore = new ReversingComparison<Player>(byScore);
}

list.Sort(byScore);

Jon
 
B

Brian Gideon

3) Multiplying the result of IComparable.CompareTo by -1 to reverse
the sort is *slightly* dangerous, because multiplying Int32.MinValue
by -1 results in int.MinValue again.

I never would have thought about this particular edge case had you not
just pointed it out. Thanks for the tip.
 
I

Ignacio Machin \( .NET/ C# MVP \)

Hi Moongirl,



moongirl said:
i have designed a class (MyClass) which contains 2 custom collections
of objects. One is a list of people which I want to display at all
times in order of name, and one is a list of the same people but this
time always displayed in order of their score.

Most probably you should keep only one list, and just sort and return the
list when needed, unless the list do not change never (or not that often) in
that case is better to hold the two lists for performance.

Below you will find a class that can sort any kind of collection, I use
reflection to be able to sort using any property of such a class (you can
even use a property''s property like Employee.Address.ZIP ).


The way of use it is extremely simple, let say you have a List<Employee> you
want to sort and you want to sort it using the Lastname property.
myList.Sort( new ClassSorter<Employee>( "LastName", SortByType.Property,
SortDirection.Ascending));

If you want to sort it by state (considering that Employee.Address exist)
myList.Sort( new ClassSorter<Employee>( "Address.State",
SortByType.Property, SortDirection.Ascending));

Hope this solve your problem


public class ClassSorter<T> : IComparer<T>
{
protected string sortBy;
protected SortByType sortByType;
protected SortDirection sortDirection;


#region Constructors
public ClassSorter(string sortBy, SortByType sortByType,
SortDirection
sortDirection)
{
this.sortBy = sortBy;
this.sortByType = sortByType;
this.sortDirection = sortDirection;
}
#endregion

int Compare(object x, object y, string comparer)
{
if (comparer.IndexOf(".") != -1)
{
//split the string
string[] parts = comparer.Split(new char[] { '.' });
return Compare(x.GetType().GetProperty(parts[0]).GetValue(x,
null),
y.GetType().GetProperty(parts[0]).GetValue(y, null),
parts[1]
);
}
else
{
IComparable icx, icy;
icx =
(IComparable)x.GetType().GetProperty(comparer).GetValue(x,
null);
icy =
(IComparable)y.GetType().GetProperty(comparer).GetValue(y,
null);

if (x.GetType().GetProperty(comparer).PropertyType ==
typeof(System.String))
{
icx = (IComparable)icx.ToString().ToUpper();
icy = (IComparable)icy.ToString().ToUpper();
}

if (this.sortDirection == SortDirection.Descending)
return icy.CompareTo(icx);
else
return icx.CompareTo(icy);
}

}

public int Compare(T x, T y)
{
return Compare(x, y, sortBy);
}

}

public enum SortByType
{
Method = 0,
Property = 1
}

public enum SortDirection
{
Ascending = 0,
Descending = 1
}
 
E

eliza sahoo

First create a class that implements [IComparer] interface and define the [Compare] method.Following is the class that we will use for sorting Clients order by last name.
[VB.NET CODE STARTS]

Public Class ClientComparer
Implements IComparer

Public Function Compare(ByVal x As Object, ByVal y As Object) As Integer Implements System.Collections.IComparer.Compare
Dim objClientX As Client = CType(x, Client) ' Client is the class having FirtsName, LastName as properties
Dim objClientY As Client = CType(y, Client)

Dim sX As String = UCase(objClientX.LastName) ' comparision is made using the LAST NAME OF CLIENT
Dim sY As String = UCase(objClientY.LastName)

If sX = sY Then
Return 0
End If

If sX.Length > sY.Length Then
sX = sX.Substring(0, sY.Length)
If sX = sY Then
Return 1
End If
ElseIf sX.Length < sY.Length Then
sY = sY.Substring(0, sX.Length)
If sX = sY Then
Return -1
End If
End If

For i As Integer = 0 To sX.Length
If Not sX.Substring(i, 1) = sY.Substring(i, 1) Then
Return Asc(CType(sX.Substring(i, 1), Char)) - Asc(CType(sY.Substring(i, 1), Char))
End If
Next
End Function

End Class

[VB.NET CODE ENDS]

http://www.mindfiresolutions.com/Sorting-a-user-defined-collection-class-877.php



moongirl wrote:

Custom Collections & Sorting
02-Jul-07

i have designed a class (MyClass) which contains 2 custom collection
of objects. One is a list of people which I want to display at al
times in order of name, and one is a list of the same people but thi
time always displayed in order of their score

Therefore as scores are entered via a datagridview against each perso
I want the second datagridview to immediately reflect the new score
entered and re-sort itself if necessary

The class currently looks like this

public class MyClas

private static PlayersCollection players = ne
PlayersCollection()
private static PlayersCollection scoreboard = ne
PlayersCollection()



The PlayersCollection class inherits from BindingList<T> and I hav
added a method as follows which enables me to sort ok

public void TotalScoreSort(ListSortDirection direction

List<Player> itemsList = this.Items as List<Player>
itemsList.Sort(delegate(Player t1, Player t2

this.listSortDirection = direction
this.isSorted = true

int reverse = direction =
ListSortDirection.Ascending ? 1 : -1

object value1 = t1.TotalScore
object value2 = t2.TotalScore

IComparable comparable = value1 as IComparable
if (comparable != null

return reverse * comparable.CompareTo(value2)

els

comparable = value2 as IComparable
if (comparable != null

return -1 * reverse
comparable.CompareTo(value1)

els

return 0


})

this.OnListChanged(ne
ListChangedEventArgs(ListChangedType.Reset, -1))


I have a couple of questions. Firstly I feel like it would be bette
if when I initialise each playersCollection I also specifiy
'sortType'. Secondly when I call the sort method from the ui th
initial list of players also gets sorted which doesn't make sense t
me as they are separate lists???? I have been looking for info abou
this all mornin
but feel like I'm getting nowhere. Can anyone help clear this up fo
me?

Previous Posts In This Thread:

Custom Collections & Sorting
i have designed a class (MyClass) which contains 2 custom collection
of objects. One is a list of people which I want to display at all
times in order of name, and one is a list of the same people but this
time always displayed in order of their score.

Therefore as scores are entered via a datagridview against each person
I want the second datagridview to immediately reflect the new scores
entered and re-sort itself if necessary.

The class currently looks like this:

public class MyClass
{
private static PlayersCollection players = new
PlayersCollection();
private static PlayersCollection scoreboard = new
PlayersCollection();

}

The PlayersCollection class inherits from BindingList<T> and I have
added a method as follows which enables me to sort ok.


public void TotalScoreSort(ListSortDirection direction)
{
List<Player> itemsList = this.Items as List<Player>;
itemsList.Sort(delegate(Player t1, Player t2)
{
this.listSortDirection = direction;
this.isSorted = true;

int reverse = direction ==
ListSortDirection.Ascending ? 1 : -1;

object value1 = t1.TotalScore;
object value2 = t2.TotalScore;

IComparable comparable = value1 as IComparable;
if (comparable != null)
{
return reverse * comparable.CompareTo(value2);
}
else
{
comparable = value2 as IComparable;
if (comparable != null)
{
return -1 * reverse *
comparable.CompareTo(value1);
}
else
{
return 0;
}
}
});

this.OnListChanged(new
ListChangedEventArgs(ListChangedType.Reset, -1));
}


I have a couple of questions. Firstly I feel like it would be better
if when I initialise each playersCollection I also specifiy a
'sortType'. Secondly when I call the sort method from the ui the
initial list of players also gets sorted which doesn't make sense to
me as they are separate lists???? I have been looking for info about
this all morning
but feel like I'm getting nowhere. Can anyone help clear this up for
me?

Re: Custom Collections & Sorting

<snip>


It's hard to say without seeing a code sample. Could you provide a
short but complete program which demonstrates the problem?

A few comments on the code:

1) You're setting listSortDirection and isSorted for every comparison,
rather than just once
2) Do you not know whether the TotalScore property implements
IComparable? What's the type of the TotalScore property?
3) Multiplying the result of IComparable.CompareTo by -1 to reverse
the sort is *slightly* dangerous, because multiplying Int32.MinValue
by -1 results in int.MinValue again. A better way is to reverse the
order of the call, so instead of first.CompareTo(second) use
second.CompareTo(first). You could easily write a ReverseComparison
which takes an existing Comparison and reverses the result, which
would make life simple.
4) It seems to me that there are two fundamental bits here -
converting a Player into a TotalScore, and using IComparable to
implement a comparison. These could be separated out quite easily.
Alternatively, if the TotalScore property *does* return something
guaranteed to implement IComparable, just do:

Comparison<Player> byScore = delegate(Player p1, Player p2)
{ return p1.TotalScore.CompareTo(p2.TotalScore); };

if (direction==ListSortDirection.Descending)
{
byScore = new ReversingComparison<Player>(byScore);
}

list.Sort(byScore);

Jon

Re: Custom Collections & Sorting
I never would have thought about this particular edge case had you not
just pointed it out. Thanks for the tip.

Re: Custom Collections & Sorting
Hi Moongirl,




Most probably you should keep only one list, and just sort and return the
list when needed, unless the list do not change never (or not that often) in
that case is better to hold the two lists for performance.

Below you will find a class that can sort any kind of collection, I use
reflection to be able to sort using any property of such a class (you can
even use a property''s property like Employee.Address.ZIP ).


The way of use it is extremely simple, let say you have a List<Employee> you
want to sort and you want to sort it using the Lastname property.
myList.Sort( new ClassSorter<Employee>( "LastName", SortByType.Property,
SortDirection.Ascending));

If you want to sort it by state (considering that Employee.Address exist)
myList.Sort( new ClassSorter<Employee>( "Address.State",
SortByType.Property, SortDirection.Ascending));

Hope this solve your problem


public class ClassSorter<T> : IComparer<T>
{
protected string sortBy;
protected SortByType sortByType;
protected SortDirection sortDirection;


#region Constructors
public ClassSorter(string sortBy, SortByType sortByType,
SortDirection
sortDirection)
{
this.sortBy = sortBy;
this.sortByType = sortByType;
this.sortDirection = sortDirection;
}
#endregion

int Compare(object x, object y, string comparer)
{
if (comparer.IndexOf(".") != -1)
{
//split the string
string[] parts = comparer.Split(new char[] { '.' });
return Compare(x.GetType().GetProperty(parts[0]).GetValue(x,
null),
y.GetType().GetProperty(parts[0]).GetValue(y, null),
parts[1]
);
}
else
{
IComparable icx, icy;
icx =
(IComparable)x.GetType().GetProperty(comparer).GetValue(x,
null);
icy =
(IComparable)y.GetType().GetProperty(comparer).GetValue(y,
null);

if (x.GetType().GetProperty(comparer).PropertyType ==
typeof(System.String))
{
icx = (IComparable)icx.ToString().ToUpper();
icy = (IComparable)icy.ToString().ToUpper();
}

if (this.sortDirection == SortDirection.Descending)
return icy.CompareTo(icx);
else
return icx.CompareTo(icy);
}

}

public int Compare(T x, T y)
{
return Compare(x, y, sortBy);
}

}

public enum SortByType
{
Method = 0,
Property = 1
}

public enum SortDirection
{
Ascending = 0,
Descending = 1
}


Submitted via EggHeadCafe - Software Developer Portal of Choice
LINQ With Strings
http://www.eggheadcafe.com/tutorial...47db-adb9-db7fe2c6ab8c/linq-with-strings.aspx
 
M

Mr. Arnold

eliza said:
I have a couple of questions. Firstly I feel like it would be better
if when I initialise each playersCollection I also specifiy a
'sortType'. Secondly when I call the sort method from the ui the
initial list of players also gets sorted which doesn't make sense to
me as they are separate lists???? I have been looking for info about
this all morning
but feel like I'm getting nowhere. Can anyone help clear this up for
me?


You have a collection of objects with properties. Why don't you just use
Linq-2-Objects and sort the objects by their property?


<http://www.beansoftware.com/ASP.NET-Tutorials/Linq-Objects-Collections-Arrays.aspx>

<copied>

Sorting and Ordering with LINQ

The sorting functionality is achieved by using the orderby opertaor. The
below query outputs the products ordered by the product name field.

var sortedProducts =
from p in products
orderby p.ProductName
select p;

The sorting can be ascending or descending. By default the sorting will
be ascending on the field name specified by orderby operator. If we want
to order them in descending order we need to explicitly use the
descending keyword followed by the field name on which we want to apply
the ordering. We can apply a compound sort means the sort will be
applied on multiple field from left to right. A simple query using
compound sorting is shown below.

var sortedProducts =
from p in products
orderby p.Category, p.UnitPrice descending
select p;


<end copy>
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top