5

I have a list of objects that contains different primitive data types:

List<object> list = new List<object>() { 
  3, "cat", 4.1, 'a', 5, "dog", "horse", 9.1, 'd', 1 
};

I want to be able to run a Linq query on the above list that groups all the element by data type and order as (string, integer,double,character) (if that makes sense?). Something like:

list = { "cat","dog", "horse", 3, 5, 1, 4.1, 9.1, 'a', 'd' }

I tried from

Using Linq to group a list of objects into a new grouped list of list of objects

code

lst
  .GroupBy(x => x.GetType())
  .Select(grp => grp.ToList())
  .ToList()
  .ForEach(group => group.ForEach(item => Console.WriteLine(item)));

works but does not give the desired output.

output

 { 3, 5, 1, "cat","dog", "horse",4.1, 9.1, 'a', 'd' }
Pavel Anikhouski
  • 21,776
  • 12
  • 51
  • 66
  • 3
    Your expression works in general, which output do you want? Maybe just order by type? – Pavel Anikhouski Apr 18 '20 at 08:03
  • Basically you want to aggregate your lists into one list. Of course the ForEach shows you that you have the correct gist of it. – Pac0 Apr 18 '20 at 08:07
  • 1
    @PavelAnikhouski i want to reorder the output as (string,integer,double,character) not like (integer , string, double , character) – Ali Ghanaatpisheh Apr 18 '20 at 08:12
  • 1
    So you need to use a custom comparer and use `Sort` – Pac0 Apr 18 '20 at 08:13
  • 3
    If (string,integer,double,character) are sorted by type name descending on purpose, `list.OrderByDescending(x => x.GetType().Name)` should do it – vc 74 Apr 18 '20 at 08:26

4 Answers4

2

If the problem is the order of the types in output, you could use a custom IComparer class and Sort you list.

The basic principle is you can map types to an integer score, and then return the comparison between the scores.

See documentation.

(dotnetFiddle )

public class StringThenIntThenDoubleThenChar  : Comparer<object> 
{
    public override int Compare(object x, object y)
    {
        return GetTypeScore(x).CompareTo(GetTypeScore(y));
    }

    private int GetTypeScore(object o) 
    {
        var type = o.GetType();
        if (type == typeof(string)) return 0;
        else if (type == typeof(int)) return 1;
        else if (type == typeof(double)) return 2;
        else if (type == typeof(char)) return 3;
        else return 4;

        /* Or, if you are using C# 8 :
        return o.GetType() switch
        {
            Type t when t == typeof(string) => 0,
            Type t when t == typeof(int) => 1,
            Type t when t == typeof(double) => 2,
            Type t when t == typeof(char) => 3,
            _ => 4
        };
        */
    }
}

Use it like this :

        List<object> list = new List<object>() { 3, "cat", 4.1, 'a', 5, "dog", "horse", 9.1, 'd', 1 };

        list.Sort(new StringThenIntThenDoubleThenChar());
        list.ForEach(x => Console.WriteLine(x));

The advantage over the simpler solution already given (sort by type name) is that you can customize it as you wish.

You can also refine the comparer, for instance if scores are equal, you can then compare them using their default comparison order (so that the string, int etc. are respectively sorted among them).

Pac0
  • 21,465
  • 8
  • 65
  • 74
2

You can just order type name by descending after Select, it'll give you the desired output - (string, integer, double, character) items

list.GroupBy(x => x.GetType())
    .OrderByDescending(g => g.Key.Name)
    .Select(grp => grp.ToList())
    .ToList()
    .ForEach(group => group.ForEach(Console.WriteLine));

Another option is order by IsPrimitive property OrderBy(g => g.Key.IsPrimitive), for your particular data it also gives the desired output

Pavel Anikhouski
  • 21,776
  • 12
  • 51
  • 66
2

Similar approach to @Pac0's answer, you could create a method to decide the sorting order on types:

private static int OrderByOnType(object o)
{
    var type = o.GetType();

    if (type == typeof(string))
        return 0;

    else if (type == typeof(int)) 
        return 1;

    else if (type == typeof(double)) 
        return 2;

    else if (type == typeof(char)) 
        return 3;

    return 4;
}

Then pass this method to Enumerable.OrderBy from LINQ to create a new sorted list:

var result = list.OrderBy(OrderByOnType);

Console.WriteLine("{ " + string.Join(", ", result) + " }");

Or sort the original list in-place with List<T>.Sort by passing your own Comparison<T> Delegate:

list.Sort((x, y) => OrderByOnType(x).CompareTo(OrderByOnType(y)));

Console.WriteLine("{ " + string.Join(", ", list) + " }");

Which can also be wrapped into a separate method:

private static int CompareTwoTypes(object x, object y)
{
    return OrderByOnType(x).CompareTo(OrderByOnType(y));
}

list.Sort(CompareTwoTypes);

Console.WriteLine("{ " + string.Join(", ", list) + " }");

All of which give:

{ cat, dog, horse, 3, 5, 1, 4.1, 9.1, a, d }
RoadRunner
  • 25,803
  • 6
  • 42
  • 75
0

I think the shorter version of @Pac0's and @RoadRunner answer is to use switch Expression in Linq query.

        list.OrderBy(x => x  switch
        {
             string xString=> 0,
             int xInt=> 1,
             double xDouble=> 2,
             char xChar=> 3,
            _ => -1,
        }).ToList().ForEach(item => Console.WriteLine(item));