0
\$\begingroup\$

I want to sort entries in a given list on Entry column in most efficient way. Here is how my entries look:

#No Detail                                 Entry          Number          Rate
1   Carpool at 5$                          C              1               5
    Carpool at 5$                          H              2               5

2   Played Cricket at 2$                   X              1               2
    Played Cricket at 2$                   O              2               2

3   Done something at 4$                  ""              0               4

4   Done something else at 9$              M              1               9

5   Watched movie at 6$                    B              1               6
    Watched movie at 6$                    Z              2               6

Some explanation of the format:

  1. #No column is not present in table. I just mentioned here to give detail about entries.
  2. #1,2,5 are special "clubbed" entries and should be sorted together on Entry which have number column set to 1. These entries have specialty that their rate and description would be same and Number would be 1 and 2.
  3. There is possibility that there could be some entries which have empty Entry values; such entries will have number set to 0. Such as #3
  4. There is possibility that there could be some entries which have single Entry value and will not have any pair and Number column for such entries would be set to 1 and there wouldn't be any entry in list which have same description and rate with Number column set to 2. Such as #4.
  5. While sorting clubbed entries on Entry column only consider Entry with Number set to 1 and other entry should tag along.

I want to sort Entry column by ascending or descending order abiding above rules.

My Solution:

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApplication1
{
    internal interface IDBEntity
    {
        string Detail { get; set; }
        string Entry { get; set; }
        int Number { get; set; }
        int Rate { get; set; }
    }

    internal class Data : IDBEntity
    {
        public string Detail { get; set; }
        public string Entry { get; set; }
        public int Number { get; set; }
        public int Rate { get; set; }
    }

    internal enum SortingOrder
    {
        Ascending,
        Descending
    }

    internal enum SortingType
    {
        Detail,
        Entry,
        Number,
        Rate
    }

    internal class Comparer<T> : IComparer<T> where T : IDBEntity
    {
        public SortingType SortingColumn { get; set; }
        private int sortOrder = 1;

        public Comparer(SortingOrder order = SortingOrder.Ascending)
        {
            if (order == SortingOrder.Ascending)
            {
                sortOrder = 1;
            }
            else if (order == SortingOrder.Descending)
            {
                sortOrder = -1;
            }
            else
            {
                throw new NotImplementedException("Sorting Order is Undefined");
            }
        }

        public int Compare(T x, T y)
        {
            int result = sortOrder;

            if (x == null && y != null)
            {
                result *= sortOrder;
            }
            else if (x == null && y == null)
            {
                result = 0;
            }

            switch (SortingColumn)
            {
                case SortingType.Entry:
                    if (x.Detail == y.Detail && x.Rate == y.Rate)
                    {
                        result = x.Entry.CompareTo(y.Entry);
                    }
                    break;

                default:
                    break;
            }

            return result * sortOrder;
        }
    }

    internal class Program
    {
        private static void Main(string[] args)
        {
            List<Data> entries = new List<Data>();

            // Clubbed entry...While sorting only consider entry with Number set to 1. They will have same rate and Detail.
            entries.Add(new Data() { Detail = "Carpool at 5$", Entry = "C", Number = 1, Rate = 5 });
            entries.Add(new Data() { Detail = "Carpool at 5$", Entry = "H", Number = 2, Rate = 5 });

            // Clubbed entry
            entries.Add(new Data() { Detail = "Played Cricket at 2$", Entry = "X", Number = 1, Rate = 2 });
            entries.Add(new Data() { Detail = "Played Cricket at 2$", Entry = "O", Number = 2, Rate = 2 });

            // entry which have empty Entry value such entries will have Number set to 0
            entries.Add(new Data() { Detail = "Done something at 4$", Entry = "", Number = 0, Rate = 4 });

            // entry which will not have an pair and Number column for such entries would be set to 1 and 
            // there wouldn't be any entry in list which have same detail and rate with Number coloumn set to 2
            entries.Add(new Data() { Detail = "Done something else at 9$", Entry = "M", Number = 1, Rate = 9 });

            // Clubbed entry
            entries.Add(new Data() { Detail = "Watched movie at 6$", Entry = "B", Number = 1, Rate = 6 });
            entries.Add(new Data() { Detail = "Watched movie at 6$", Entry = "Z", Number = 2, Rate = 6 });
            entries.Add(new Data() { Detail = "BlahBlah", Entry = "", Number = 0, Rate = 0 });

            // Sorting on Entry Coloumn
            var sortedList = entries.GroupBy(x => new { x.Detail, x.Rate }) // Grouping entries which have same Detail and Rate
                        .OrderBy(x => x.FirstOrDefault(y => y.Number <= 1)?.Entry) // Sort the group based on Entry which has Number set to <=1
                        .SelectMany(x => x.OrderBy(z => z.Number)) // Un-Group entries and make sure order is maintained using Number property
                        .ToList();

            var compObj = new Comparer<Data>() { SortingColumn = SortingType.Entry };
            entries.Sort(compObj);
        }
    }
}

LINQ solution

var sortedList = entries.GroupBy(x => new { x.Detail, x.Rate }) // Grouping entries which have same Detail and Rate
                            .OrderBy(x => x.FirstOrDefault(y => y.Number <= 1)?.Entry) // Sort the group based on Entry which has Number set to <=1
                            .SelectMany(x => x.OrderBy(z => z.Number)) // Un-Group entries and make sure order is maintained using Number property
                            .ToList();

Output:

Detail                                 Entry          Number          Rate  
Done something at 4$                  ""              0               4

Watched movie at 6$                    B              1               6
Watched movie at 6$                    Z              2               6

Carpool at 5$                          C              1               5
Carpool at 5$                          H              2               5

Done something else at 9$              M              1               9

Played Cricket at 2$                   X              1               2
Played Cricket at 2$                   O              2               2

Questions:

  • I have Linq solution working, Any comments on that?
  • Can it be done using IComparer as in rest of my code had sorting done using IComparer? In Compare method, I have switch case on column where I compare two entries but I don't know how to group while comparing.
\$\endgroup\$
3
  • 1
    \$\begingroup\$ Your question is unfortuantelly off-topic because they are mostly about features that are not implemented. \$\endgroup\$
    – t3chb0t
    Commented Oct 1, 2018 at 20:33
  • \$\begingroup\$ @t3chb0t I have Linq solution working with all use cases.Question is changed now. Unfortunately i am still not able figure out to do it using IComparer.Will update the question once its done \$\endgroup\$
    – A.Learn
    Commented Oct 2, 2018 at 4:24
  • \$\begingroup\$ IComparer can only compare two single instances. It can't possibly compare groups of instances as in the first OrderBy in your LINQ statement. The comparer isn't even aware of collections that single instances happen to be in. \$\endgroup\$ Commented Dec 31, 2018 at 13:38

1 Answer 1

1
\$\begingroup\$

Can it be done using IComparer

of course.

Here is a code review answer that shows how one could sort on multiple criteria. I think it will be useful to read the entire thread.

but I don't know how to group while comparing.

I suspect explicit grouping is not necessary at all. Grouping is a natural consequence of sorting. This is because while 2 things are equal for a given criterion the sort continues down the criteria chain until a non-equal comparison is made or the last criterion is done.

\$\endgroup\$
3
  • \$\begingroup\$ Thanks for link. I have updated the question with IComparer code which i had but i am still not able to figure out rational logic for my scenario while comparing. \$\endgroup\$
    – A.Learn
    Commented Oct 2, 2018 at 4:28
  • \$\begingroup\$ I am still not able to figure out how to do it using IComparer.I could really use your help on this \$\endgroup\$
    – A.Learn
    Commented Oct 10, 2018 at 5:49
  • \$\begingroup\$ I am able to OrderBy().ThenBy equivalent using IComparer but not able to group them. \$\endgroup\$
    – A.Learn
    Commented Oct 10, 2018 at 6:36

Not the answer you're looking for? Browse other questions tagged or ask your own question.