Home > front end >  Need help to sorting an array in a complicated way c# linq
Need help to sorting an array in a complicated way c# linq

Time:02-10

I have a strict similar to this :

struct Chunk
 {
   public float d; // distance from player
   public bool active; // is it active
 }

I have an array of this struct.

What I need:

I need to sort it so the first element is an inactive chunk that is also the furthest from the player, than the next element is active and is also the closest to the player and that is the pattern,

  1. Inactive, furthest
  2. Active, closest
  3. Inactive, furthest
  4. Active, closest
  5. Inactive, furthest And so on...

Currently I'm using LINQ, And I'm doing this :

chunk = chunk.AsParallel().OrderBy(x => x.active ? x.d : -x.d).ToArray();

But I don't know how to make it alternate one after another.

CodePudding user response:

It looks like you want to split this into two lists, sort them, then interleave them.

var inactive = chunk.Where(x => !x.active).OrderByDescending(x => x.d);
var active = chunk.Where(x => x.active).OrderBy(x => x.d);
var interleaved = inactive.Zip(active, (a, b) => new [] { a, b }).SelectMany(x => x);

CodePudding user response:

But I don't know how to make it alternate one after another.

If you sort the array so that all the inactives are at the start, descending and all the actives are at the end, descending..

OrderBy(x => x.active?1:0).ThenBy(x=>-x.d)

then you can take an item from the start, then an item from the end, then from the start 1, then the end - 1 working your way inwards

public static IEnumerable<Chunk> Interleave(this Chunk[] chunks){
  
    for(int s = 0, e = chunks.Length - 1; s<e;){

        if(!chunks[s].active)
            yield return chunks[s  ]; 

        if(chunks[e].active)
            yield return chunks[e--];

    }

}

There's a bit in this so let's unpack it. This is an extension method that acts on an array of chunk. It's a custom enumerator method, so you'd call foreach on it to use it

foreach(var c in chunk.Interleave())

It contains a for loop that tracks two variables, one for the start index and one for the end. The start increments and the end decrements. At some point they'll meet and s will no longer be less than e, which is when we stop:

for(int s = 0, e = chunks.Length - 1; s<e;){

We need to look at the chunk before we return it, if it's an inactive near the start, yield return it and bump the start on by one. s increments s, but resolves to the value s was before it incremented. It's thus conceptually like doing chunks[s]; s = 1; but in a one liner

if(!chunks[s].active)
    yield return chunks[s  ]; 

Then we look at the chunk near the end, if it's active then return the ending one and bump the end index down

The inactive chunks are tracked by s, and if s reaches an active chunk it stops returning (every pass of the loop it is skipped), which means e will work its way down towards s returning only the actives

Similarly if there are more inactives than actives, e will stop decrementing first and s will work its way up towards e


If you never came across yield return before think of it as a way to allow you to resume from where you left off rather than starting the method over again. It's used with enumerations to provide a way for the enumeration to return an item, then be moved on one and return the next item. It works a bit like saving your game and going doing something else, then coming back, realising your save game and carrying on from where you left off. Asking an enumerator for Next makes it load the game, play a bit, then save and stop.. Then you Next again and the latest save id loaded, play some more, save and stop. This way you gradually get through the game a bit at a time. If you started a new enumeration by calling Interleave again, that's like starting a new game over from the beginning

MSDN will get more detailed on yield return if you want to dig in more

CodePudding user response:

Not sure if you can do it with only one line of code.

I wrote a method that would only require the Array to be sorted once. Then, it enters either the next closest or furthest chunk based on the current index of the for loop (odd = closest, even = furthest). I remove the item from the sorted list to ensure that it will not be reentered in the results list. Finally, I return the results as an Array.

public Chunk[] SortArray(List<Chunk> list_to_sort)
{
    //Setup Variables
    var results = new List<Chunk>();
    int count = list_to_sort.Count;

    //Tracking the sorting list so that we only need to sort the list once
    list_to_sort = list_to_sort.OrderBy(x => x.active).ThenBy(x => x.d).ToList();

    //Loop through the list
    for (int i = 0; i < count; i  )
    {
        results.Add(list_to_sort[i % 2 == 0 ? list_to_sort.Count - 1 : 0]);
        list_to_sort.RemoveAt(i % 2 == 0 ? list_to_sort.Count - 1 : 0);
    }

    // Return Results
    return results.ToArray();
}

There is probably a better way of doing this but hopefully it helps. Please note that I did not test this method.

  • Related