Linq: round-robin ordering based on segments

Linq: round-robin ordering based on segments

Linq is a wonderful way to work with lists in C#. This article focuses on how you can create a round-robin ordering for segments of your list. It will distribute items of each segment evenly over the list.

A round robin is an arrangement of choosing all elements in a group equally in some rational order (...) A simple way to think of round robin is that it is about "taking turns." Used as an adjective, round robin becomes "round-robin. Source: definition by What Is

Source: definition by What Is

The problem

Let's say we have a list of download URLs and we need to create a program that downloads a file from each URL. The list might look a little bit like this:

var urls = new string[] {
  "https://a.com?download=1.mp3",     "https://a.com?download=2.mp3", 
  "https://a.com?download=3.mp3",     "https://a.com?download=4.mp3", 
  "https://b.nl?download=alpha.mp3",  "https://b.nl?download=beta.mp3", 
  "https://b.nl?download=gamma.mp3",  "https://b.nl?download=delta.mp3", 
  "https://c.es?download=i.mp3",      "https://c.es?download=ii.mp3", 
  "https://c.es?download=iii.mp3",    "https://c.es?download=iv.mp3"
};

Question: How can we process this list in such a way that we put the least pressure on the hosts? Answer: Order the list in such a way that the program will download: file 1 from a.com first, next file 1 from b.nl, next file 1 from c.es before continuing with file 2 form a.com until all the files are downloaded.

But how?

The basic idea is as follows:

Show me some code!

Let's put the idea into code:

var x = urls

    //convert the urls to uri's
    .Select(u => new Uri(u))

    //create groups of unique hosts
    .GroupBy(u => u.Host)

    //select them back together into a single list
    .SelectMany((group, groupIndex) => group.Select((item, index) => new { Index = index, GroupIndex = groupIndex, Value = item }))

    //order by the group item index
    .OrderBy(u => u.Index)

    //order by the group index
    .ThenBy(u => u.GroupIndex)

    //show position (or one could select u.Value.ToString() for just the URL)
    .Select((u, position) => new { Position = position, Index = u.Index, GroupIndex = u.GroupIndex, Value = u.Value });

//write it to the console       
x.ToList().ForEach(u => Console.WriteLine(u));

The Select and SelectMany methods provide indexes that we leveraged to do the actual ordering. The console will show the following feedback:

{ Position = 0, Index = 0, GroupIndex = 0, Value = https://a.com/?download=1.mp3 }
{ Position = 1, Index = 0, GroupIndex = 1, Value = https://b.nl/?download=alpha.mp3 }
{ Position = 2, Index = 0, GroupIndex = 2, Value = https://c.es/?download=i.mp3 }
{ Position = 3, Index = 1, GroupIndex = 0, Value = https://a.com/?download=2.mp3 }
{ Position = 4, Index = 1, GroupIndex = 1, Value = https://b.nl/?download=beta.mp3 }
{ Position = 5, Index = 1, GroupIndex = 2, Value = https://c.es/?download=ii.mp3 }
{ Position = 6, Index = 2, GroupIndex = 0, Value = https://a.com/?download=3.mp3 }
{ Position = 7, Index = 2, GroupIndex = 1, Value = https://b.nl/?download=gamma.mp3 }
{ Position = 8, Index = 2, GroupIndex = 2, Value = https://c.es/?download=iii.mp3 }
{ Position = 9, Index = 3, GroupIndex = 0, Value = https://a.com/?download=4.mp3 }
{ Position = 10, Index = 3, GroupIndex = 1, Value = https://b.nl/?download=delta.mp3 }
{ Position = 11, Index = 3, GroupIndex = 2, Value = https://c.es/?download=iv.mp3 }

Source: When the code runs .Net Fiddle

Conclusion

A round-robin ordering based on segments is easy to make in C# Linq. Use GroupBy to create the segments, use SelectMany to create a simple list with indexes and use OrderBy and ThenOrderBy to sort the list on the index and groupIndex.