Question

I'm trying to get a list of string ordered such that the longest are on either end of the list and the shortest are in the middle. For example:

A
BB
CCC
DDDD
EEEEE
FFFFFF

would get sorted as:

FFFFFF
DDDD
BB
A
CCC
EEEEE

EDIT: To clarify, I was specifically looking for a LINQ implementation to achieve the desired results because I wasn't sure how/if it was possible to do using LINQ.

Was it helpful?

Solution 2

Don't ask how and why... ^^

list.Sort(); // In case the list is not already sorted.

var length = list.Count;

var result = Enumerable.Range(0, length)
                       .Select(i => length - 1 - 2 * i)
                       .Select(i => list[Math.Abs(i - (i >> 31))])
                       .ToList();

Okay, before I forget how it works, here you go.

A list with 6 items for example has to be reordered to this; the longest string is at index 5, the shortest one at index 0 of the presorted list.

5 3 1 0 2 4

We start with Enumerable.Range(0, length) yielding

0 1 2 3 4 5

then we apply i => length - 1 - 2 * i yielding

5 3 1 -1 -3 -5

and we have the non-negative part correct. Now note that i >> 31 is an arithmetic left shift and will copy the sign bit into all bits. Therefore non-negative numbers yield 0 while negative numbers yield -1. That in turn means subtracting i >> 31 will not change non-negative numbers but add 1 to negative numbers yielding

5 3 1 0 -2 -4

and now we finally apply Math.Abs() and get

5 3 1 0 2 4

which is the desired result. It works similarly for lists of odd length.

OTHER TIPS

You could create two ordered groups, then order the first group descending(already done) and the second group ascending:

var strings = new List<string> { 
        "A",
        "BB",
        "CCC",
        "DDDD",
        "EEEEE",
        "FFFFFF"};
var two = strings.OrderByDescending(str => str.Length)
        .Select((str, index) => new { str, index })
        .GroupBy(x => x.index % 2)
        .ToList(); // two groups, ToList to prevent double execution in following query
List<string> ordered = two.First() 
    .Concat(two.Last().OrderBy(x => x.str.Length))
    .Select(x => x.str)
    .ToList();

Result:

[0] "FFFFFF"    string
[1] "DDDD"      string
[2] "BB"        string
[3] "A"         string
[4] "CCC"       string
[5] "EEEEE"     string

Just another option, which I find more readable and easy to follow: You have an ordered list:

var strings = new List<string> { 
        "A",
        "BB",
        "CCC",
        "DDDD",
        "EEEEE",
        "FFFFFF"};

Create a new list and simply alternate where you add items::

var new_list = new List<string>();  // This will hold your results
bool start = true;                  // Insert at head or tail

foreach (var s in strings)
{
    if (start)
        new_list.Insert(0,s);
    else        
        new_list.Add(s);

    start = !start;             // Flip the insert location
}

Sweet and simple :)

As for Daniel Bruckner comment, if you care about which strings comes first, you could also change the start condition to:

 // This will make sure the longest strings is first
 bool start= strings.Count()%2 == 1;  
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top