Behroz Sikander
Behroz Sikander

Reputation: 4039

C# LINQ Orderby on parent only in hierarchical parent-child relationship data

I have an interesting problem in LINQ and I am not sure how to solve it. Here is what my data looks like

I have a list of Send objects(List<Send>) where Send object has the following properties

public class Send
{   
    public string messageName { get; set; }
    public string Port { get; set; }        
    public string Type { get; set; }
}

where Port can be PortA, PortB, etc. Type can only be "receive" or "transmit" and messageName can be

0_firstmessage
1_secondmessage
2_thirdmessage

messageName always have an identifier at the start 0,1,2....N.

My current list has data like the following. A few things to note in data

  1. My data is already sorted according to Ports. So, PortA data comes first followed by PortB.
  2. Each message of type "receive" is followed by 0 or N transmit messages group.
  3. Each transmit message always has a parent receive.

My data:

MESSAGENAME, PORT  , TYPE  
    - 0_message , PortA , receive
       - 1_message , PortA , transmit
       - 3_message , PortA , transmit
       - 7_message , PortA , transmit
    - 8_message , PortA , receive
       - 9_message , PortA , transmit

    - 2_message , PortB , receive
    - 4_message , PortB , receive
       - 5_message , PortB , transmit
       - 6_message , PortB , transmit
    - 10_message, PortB , receive
       - 11_message , PortB , transmit

My final output should be like this.

 MESSAGENAME, PORT  , TYPE  
- 0_message , PortA , receive
   - 1_message , PortA , transmit
   - 3_message , PortA , transmit
   - 7_message , PortA , transmit
- 2_message , PortB , receive
- 4_message , PortB , receive
   - 5_message , PortB , transmit
   - 6_message , PortB , transmit
- 8_message , PortA , receive
   - 9_message , PortA , transmit
- 10_message, PortB , receive
   - 11_message , PortB , transmit

I want to ORDERBY based on MESSAGE_NAME for "receive" type messages only. The child "transmit" messages should stay intact.

I searched alot online but I am not sure how to write this LINQ query.

Here is a example: You can play around here. https://dotnetfiddle.net/DKOOk2

Upvotes: 4

Views: 678

Answers (4)

Katia
Katia

Reputation: 629

I have another one suggestion for you. You can add a Dictionary and put your list there where key - will be your Send with message type = "receive" Then you can simply order Dictionary by key. Check this code:

        List<Send> messages = new List<Send>();

    messages.Add(new Send() {messageName ="0_message" , Port = "PortA", Type="Receive" });
    messages.Add(new Send() {messageName ="1_message" , Port = "PortA", Type="transmit" });
    messages.Add(new Send() {messageName ="3_message" , Port = "PortA", Type="transmit" });
    messages.Add(new Send() {messageName ="7_message" , Port = "PortA", Type="transmit" });
    messages.Add(new Send() {messageName ="8_message" , Port = "PortA", Type="Receive" });
    messages.Add(new Send() {messageName ="9_message" , Port = "PortA", Type="transmit" });
    messages.Add(new Send() {messageName ="2_message" , Port = "PortB", Type="Receive" });
    messages.Add(new Send() {messageName ="4_message" , Port = "PortB", Type="Receive" });
    messages.Add(new Send() {messageName ="5_message" , Port = "PortB", Type="transmit" });
    messages.Add(new Send() {messageName ="6_message" , Port = "PortB", Type="transmit" });
    messages.Add(new Send() {messageName ="10_message" , Port = "PortB", Type="Receive" });
    messages.Add(new Send() {messageName ="11_message" , Port = "PortB", Type="transmit" });


     Dictionary<Send, List<Send>> myDict = new Dictionary<Send, List<Send>>();
        List<Send> mylist2 = new List<Send>();
        Send messagename = new Send();
        int i = 0;


        foreach (Send s in messages)
        {
            if (s.Type == "Receive" || s.Type == "receive")
            {
                if (i != 0)
                {
                    myDict.Add(messagename, mylist2);
                }
                messagename = s;
                mylist2 = new List<Send>();
            }
            else
            {
                mylist2.Add(s);
            }

            if(i== messages.Count()-1)
            {
                myDict.Add(messagename, mylist2);
            }

            i++;
        }

        var q = myDict.OrderBy(x => int.Parse(x.Key.messageName.Split('_')[0]));

If you want, you can convert dictionary back to list:

        List<Send> newlist = new List<Send>();

        foreach (KeyValuePair<Send, List<Send>> k in q)
        {
            newlist.Add(k.Key);

            foreach (Send s in k.Value)
            {
                newlist.Add(s);
            }
        }

Upvotes: 0

Hari Prasad
Hari Prasad

Reputation: 16956

I have a solution with an assumption that you receive receive first followed by transmit type messages.

int gid=0;
var results = messages.Select(m => new 
                     {                                 // Rank each message 
                        m.Type.Equals("Receive", StringComparison.InvariantCultureIgnoreCase ) ? ++gid: gid, 
                        message=m 
                     })
    .GroupBy(g=>g.groupid) // Group them on Rank
    .OrderBy(g=>int.Parse(g.First().message.messageName.Split('_')[0]))  // apply Sort
    .SelectMany(c=>c.Select(x=>x.message)) // flatten structure .
    .ToList() ; 

Check working example

Upvotes: 4

Katia
Katia

Reputation: 629

I hope this LINQ can help you:

var a = mylist.OrderBy(x => x.MESSAGENAME).Where(y => y.TYPE == "receive")
                      .Concat(mylist.Where(z => z.TYPE!= "receive"));

As a result you will get IEnumerable where first go ordered Sends by message type receive and second - all the rest Sends unordered.

Upvotes: 0

jdweng
jdweng

Reputation: 34421

Try this code below.

    public class Send : IComparable<Send>
    {
        public string messageName { get; set; }
        public string Port { get; set; }
        public string Type { get; set; }


        public int CompareTo(Send other)
        {
            int results = 0;
            if (this.messageName != other.messageName)
            {
                results = this.messageName.CompareTo(other.messageName);
            }
            else
            {
                if (this.Port != other.Port)
                {
                    results = this.Port.CompareTo(other.Port);
                }
                else
                {
                    results = this.Type.CompareTo(other.Type);
                }
            }
            return results;
        }
    }

Upvotes: 0

Related Questions