需要帮助以复杂的方式对数组进行排序 c# linq

Need help to sorting an array in a complicated way c# linq

我有一个严格的类似的:

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

我有一个这个结构的数组。

我需要的:

我需要对其进行排序,以便第一个元素是一个非活动块,它也是离玩家最远的,而下一个元素是活动的,也是离玩家最近的,这就是模式,

  1. 不活动,最远
  2. 活跃,最近
  3. 不活动,最远
  4. 活跃,最近
  5. 不活动,最远 等等...

目前我正在使用 LINQ, 我正在这样做:

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

但是我不知道怎么让它一个接一个地交替出现

您似乎想将其拆分为两个列表,对它们进行排序,然后交错排列。

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);

不确定是否只用一行代码就可以做到。

我写了一个只需要对数组进行一次排序的方法。然后,它根据 for 循环的当前索引进入下一个最近或最远的块(奇数 = 最近,偶数 = 最远)。我从排序列表中删除该项目以确保它不会重新进入结果列表。最后,我return将结果作为数组。

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();
}

可能有更好的方法,但希望它能有所帮助。请注意,我没有测试此方法。

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

如果您对数组进行排序,使所有非活动元素都在开头,降序排列,所有活动元素都在结尾,降序排列..

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

然后你可以从开始拿一个项目,然后从最后拿一个项目,然后从开始 + 1,最后 - 1 向内工作

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--];

    }

}

里面有点东西,让我们解压一下。这是一个作用于块数组的扩展方法。这是一个自定义枚举器方法,因此您可以调用 foreach 来使用它

foreach(var c in chunk.Interleave())

它包含一个跟踪两个变量的 for 循环,一个用于起始索引,一个用于结束。开始递增,结束递减。在某个时候他们会相遇并且 s 将不再小于 e,这就是我们停止的时候:

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

我们需要在 return 之前查看该块,如果它在开始附近处于非活动状态,yield return 它将开始增加一个。 s++ 递增 s,但解析为 s 递增之前的值。因此,它在概念上就像做 chunks[s]; s += 1; 但在一个班轮中

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

然后我们查看接近末尾的块,如果它是 active 则 return 结束一个并将结束索引向下移动

非活动块由 s 跟踪,如果 s 到达活动块,它会停止 returning(跳过循环的每一遍),这意味着 e 将朝着 s return 的方向努力 return 只有活动

类似地,如果非活跃数多于活跃数,e 将首先停止递减,s 将逐渐增加到 e


如果您之前从未接触过 yield return,请将其视为一种让您从中断处继续学习而不是重新开始该方法的方法。它与枚举一起使用,为枚举提供一种方法 return 一个项目,然后移动到一个项目并 return 下一个项目。它的工作方式有点像保存您的游戏并继续做其他事情,然后返回,实现您的保存游戏并从您离开的地方继续。向枚举器询问 Next 使其加载游戏,玩一会儿,然后保存并停止.. 然后你再次 Next 并加载最新的保存 ID,再玩一些,保存并停止。通过这种方式,您可以逐渐一点一点地完成游戏。如果你通过再次调用 Interleave 来开始一个新的枚举,这就像从头开始一个新的游戏

如果您想深入了解,MSDN 将获得 more detailed on yield return


编辑:

您可以通过自定义比较器执行 in-place 类型的块[]:

public class InactiveThenDistancedDescending : IComparer
{
   public int Compare(object x, object y)
   {
       var a = (Chunk)x;
       var b = (Chunk)y;
       if(a.Active == b.Active)
         return -a.Distance.CompareTo(b.Distance);
       else
         return a.Active.CompareTo(b.Active);
   }
}

并且:

Array.Sort(chunkArray, _someInstanceOfThatComparerAbove);