How to sort a collection based on type in LINQ
Asked Answered
E

8

9

I have a collection like this,

Class Base{}
Class A : Base {}
Class B : Base {}

List<Base> collection = new List<Base>();
collection.Add(new A());
collection.Add(new B());
collection.Add(new A());
collection.Add(new A());
collection.Add(new B());

Now I want to sort the collection based on type (A/B). How I can do this? Please help me.

Electrophysiology answered 6/10, 2010 at 16:39 Comment(0)
M
8

You can use the type information itself:

collection.Sort( (a,b) => 
  {
      bool aType = a.GetType() == typeof(A);
      bool bType = b.GetType() == typeof(A);
      return aType.CompareTo(bType);
  });

This will work for the two types you specified, but doesn't scale beyond them. It does allow you to specify the order explicitly (ie: if you want "B" elements before "A", you could make it work using this technique).

If you need to support many types, and the ordering doesn't need to be specified in advance, you could do something like:

collection.Sort( (a,b) => a.GetType().FullName.CompareTo(b.GetType().FullName) );

This would handle any number of types (ie: a C and a D subtype, too), and order them by their full type name.

Matador answered 6/10, 2010 at 16:43 Comment(7)
@Anthony: Yes - there's no checking in here for null. Right now, anything other than A or B will be treated as B, too...Matador
Yes, I took another look at it, and it turns out it's not exactly what I would go far because of not accounting for type C : Base. I would likely do a.GetType().Name.CompareTo(b.GetType().Name) (and whatever null checks, should they be relevant). But who knows, maybe there are only two types, and maybe A is really Foo and B is really Bar and Foo should come before Bar.Slavin
@Anthony: There is really not enough info in the original question to completely determine this - this gives you more control over how the sort occurs, but it's tough to know what the OP really wants...Matador
@Reed, I completely agree. It's all hypothetical.Slavin
@Anthony: I reworked it - sound better now? (I used FullName, btw, in case there are namespace conflicts, too...)Matador
@Reed, it's good for me (and apparently Jai, too). Note to Jai: If there's a possibility that null references could be in this list, you want to handle those or filter them out beforehand.Slavin
Ya. As of now it wont have null references :)Electrophysiology
M
10
private static int OrderOnType(Base item)
{
  if(item is A)
    return 0;
  if(item is B)
    return 1;
  return 2;
}

Then take your pick from:

collection.OrderBy(OrderOnType)

or

collection.Sort((x, y) => OrderOnType(x).CompareTo(OrderOnType(y)));

Depending on whether you want in-place sorting or not. You could put OrderOnType into the lambda if you really wanted, but this seems more readable to me, and I prefer to keep lambdas for when they add rather than reduce readability.

Manson answered 6/10, 2010 at 16:48 Comment(0)
M
8

You can use the type information itself:

collection.Sort( (a,b) => 
  {
      bool aType = a.GetType() == typeof(A);
      bool bType = b.GetType() == typeof(A);
      return aType.CompareTo(bType);
  });

This will work for the two types you specified, but doesn't scale beyond them. It does allow you to specify the order explicitly (ie: if you want "B" elements before "A", you could make it work using this technique).

If you need to support many types, and the ordering doesn't need to be specified in advance, you could do something like:

collection.Sort( (a,b) => a.GetType().FullName.CompareTo(b.GetType().FullName) );

This would handle any number of types (ie: a C and a D subtype, too), and order them by their full type name.

Matador answered 6/10, 2010 at 16:43 Comment(7)
@Anthony: Yes - there's no checking in here for null. Right now, anything other than A or B will be treated as B, too...Matador
Yes, I took another look at it, and it turns out it's not exactly what I would go far because of not accounting for type C : Base. I would likely do a.GetType().Name.CompareTo(b.GetType().Name) (and whatever null checks, should they be relevant). But who knows, maybe there are only two types, and maybe A is really Foo and B is really Bar and Foo should come before Bar.Slavin
@Anthony: There is really not enough info in the original question to completely determine this - this gives you more control over how the sort occurs, but it's tough to know what the OP really wants...Matador
@Reed, I completely agree. It's all hypothetical.Slavin
@Anthony: I reworked it - sound better now? (I used FullName, btw, in case there are namespace conflicts, too...)Matador
@Reed, it's good for me (and apparently Jai, too). Note to Jai: If there's a possibility that null references could be in this list, you want to handle those or filter them out beforehand.Slavin
Ya. As of now it wont have null references :)Electrophysiology
S
5
collection.OrderBy(i => i.GetType() == typeof(A) ? 0 : 1);

Will give you a sequence with all the As then all the Bs

Shatzer answered 6/10, 2010 at 16:48 Comment(3)
What if there is type C : Base ?Slavin
@Anthony Pegram - Then this approach won't work. I suppose you could order on type Name, but then what if you wanted the order to be A,C,B in that case? If this method is too simplistic, the question needs more details added.Shatzer
I agree. It was a thought exercise. Based on Jai's comment to Reed's answer, there are just the 2 types, which makes a number of these answers appropriate.Slavin
E
2

This is going to order so A will be the first and B the second.

var xx = list.OrderBy(x => x.GetType() == typeof(B)).ToList();

This following console project confirms:

class Program
{
    public class X { }
    public class A : X { }
    public class B : X { }
    static void Main()
    {
        List<X> list = new List<X>();
        list.Add(new B());
        list.Add(new A());
        list.Add(new B());
        list.Add(new A());
        list.Add(new A());

        // A.GetType() == typeof(B) will be "0" making the type A go first
        // B.GetType() == typeof(B) will be "1" making the type B go last
        var xx = list.OrderBy(x => x.GetType() == typeof(B)).ToList();

        Console.ReadLine();
    }
}

In this case I am assuming you only have A and B. If you have more types you would have to create a comparer to return a value for each type. You could also have a property on the base class which would set the order of the elements, then you could sort the list with this property.

Entrammel answered 6/10, 2010 at 16:45 Comment(4)
What if there is type C : Base ?Slavin
@Anthony: He would have to handle each type with a Comparer. Unless the base class have a property called Order and you could order by this property. Since he stated only A and B I believe this is the shortest solution for him.Entrammel
@Jon Hanna provides an interesting way to handle the order, with another function and you can provide whatever index you want. Of course, that function would have to be modified with each new type. Or, in your case, you could order by x => x.GetType().Name, which would put A before B before C. Of course, it could be that A and B are not really A and B and an alphabetic sort wouldn't work. So, yes, barring more information, it's difficult to say what answer is appropriate. I merely throw out class C as a thought.Slavin
@Anthony, if you wanted a code-defined sort order, but to also have an order (guaranteeing objects of same type are together) with other types you could do a OrderBy as per Bruno's approach or my approach followed by a ThenBy on type full name.Manson
L
1

EDIT: I think this is what you want:

If you don't mind sorting 'out of place' and reassigning the list, this should work:

collection = collection.GroupBy(item => item.GetType())
                       .SelectMany(g => g)
                       .ToList();

or depending on your needs, something like:

collection = collection.OrderBy(item => item.GetType().FullName)
                       .ToList();

If it must be in-place, then writing a custom comparer and list.Sort is probably the best choice.


To group the items by type, you can use GroupBy:

var groupedItems = collection.GroupBy(item => item.GetType());

This uses deferred execution.

Alternatively, you can put the 'groups' into a data-structure like this:

var itemsByTypeLookUp = collection.ToLookup(item => item.GetType());

foreach(A a in itemsByTypeLookUp[typeof(A)])
{
   ...
}

If you are only looking for a certain type:

var itemsOfTypeA = collection.OfType<A>();
Legate answered 6/10, 2010 at 16:43 Comment(0)
V
1

Does

collection.Where(entry => entry is A).Concat(collection.Where(entry => entry is B))

do what you need?

Vaughn answered 6/10, 2010 at 16:44 Comment(0)
P
0

Something like this works for me.

collection.OrderBy(p => p.GetType().Equals(typeof(B))).ThenBy(p => p.GetType().Equals(typeof(A))).ToList();

My code:

class Employer;
class Doctor : Employer
class Administrator : Employer
class Nurse : Employer
List<Employer> collection = new List<Employer>();
collection.Add(new Doctor());
collection.Add(new Administrator());
collection.Add(new Doctor());
collection.Add(new Nurse());
collection.Add(new Administrator());
collection = collection.OrderBy(p => p.GetType().Equals(typeof(Nurse))).ThenBy(p => p.GetType().Equals(typeof(Doctor))).ThenBy(p => p.GetType().Equals(typeof(Administrator))).ToList();
Pitiful answered 15/12, 2016 at 13:42 Comment(0)
P
0

it's a later answer but something easy that might work for others and worked during my testing to implement something like this

var orderedList = animalList.OrderBy(x=> x.GetType().Name);

foreach(var animal in orderedList)
{
    System.Console.WriteLine(animal.Name);
}

output would look like:

Ape
Ape
Ape
Ape
Cat
Cat
Cat
Cat
Cat
Dog
Dog
Dog
Dog
Dog

where the animalList looks something like:

animalList = new List<IAnimal>();

and IAnimal interface/implementations:

public class Ape : IAnimal 
{
    public string Name => "Ape";
    public AnimalType AnimalType => AnimalType.Ape;
}


public class Dog : IAnimal 
{
    public string Name => "Dog";
    public AnimalType AnimalType => AnimalType.Dog;
}

public class Cat : IAnimal
{
    public string Name => "Cat";
    public AnimalType AnimalType => AnimalType.Cat;
}

public interface IAnimal 
{
     string Name {get;}
     AnimalType AnimalType {get;}
}

further more, something like specifying an enum type that represents your class order might help. Example:

public enum MyOrderType 
{
    Ape = 1,
    Cat = 2,
    Dog = 3
}

then it is as easy as

var orderedList = animalList.OrderBy(x=> x.AnimalType).ToList();
Pepsinate answered 11/12, 2018 at 4:29 Comment(1)
ive updated my response for another example and more clarityPepsinate

© 2022 - 2024 — McMap. All rights reserved.