【问题标题】:Keep the relative order of items of same value when keep sorting a list保持排序列表时保持相同值项目的相对顺序
【发布时间】:2017-10-14 19:33:53
【问题描述】:

假设您需要对一个可能包含重复值项的变化列表进行排序。例如,您有 [99, 99, 90, 89],但第 4 个项目的值已更改为 91。您希望它是 [99, 99, 91, 90],但您不希望第一个项目的顺序以及要更改的第二项。

我使用了Sort() 方法,但似乎它可能会改变上面示例的第一项和第二项的顺序。有什么办法可以防止它并保持具有相同值的项目的相对顺序?

如果您想不出为什么需要这样做,请假设一个进度列表。列表中的项目每秒更新一次。当项目按进度排序时,您会希望相同进度的项目不断改变它们的相对位置。

我创建了一个示例代码来测试它。目标是达到Debug.WriteLine("No change");

public void Start()
{
    var list = new List<Item>();
    var ran = new Random();
    const int nItems = 30;
    for (int i = 0; i < nItems; i++)
    {
        var name = "Item " + (list.Count + 1);
        var item = new Item(name, ran.Next(0, 10));
        list.Add(item);
    }

    var sorter = new ItemComparer();
    var snapshot = new Item[nItems];
    for (int nSort = 0; nSort < 10000; nSort++)
    {
        list.CopyTo(snapshot);
        list.Sort(sorter);

        if (nSort == 0)
        {
            //Sorted for the first time, so the snapshot is invalid.
            continue;
        }

        for (int pos = 0; pos < nItems; pos++)
        {
            if (snapshot[pos] != list[pos])
            {
                Debug.WriteLine($"Order changed at position {pos} after {nSort} sorts.");
                PrintChangedLocation(list, snapshot, pos);
                return;
            }
        }
    }

    Debug.WriteLine("No change");
}

private static void PrintChangedLocation(List<Item> list, Item[] snapshot, int changedLocation)
{
    Debug.WriteLine($"Before\t\t\tAfter");
    for (int pos = 0; pos < list.Count; pos++)
    {
        var before = snapshot[pos];
        var after = list[pos];
        Debug.Write($"{before.Name} = {before.Progress}");
        Debug.Write($"\t\t{after.Name} = {after.Progress}");

        if (pos == changedLocation)
        {
            Debug.Write(" <----");
        }
        Debug.WriteLine("");
    }
}

class Item
{
    public string Name;
    public float Progress;

    public Item(string name, float progress)
    {
        Name = name;
        Progress = progress;
    }
}

class ItemComparer : IComparer<Item>
{
    int Direction = -1; 

    public int Compare(Item x, Item y)
    {
        return Direction * (int)(x.Progress - y.Progress);
    }      
}

示例输出:

Order changed at position 12 after 1 sorts.
Before          After
Item 7 = 9      Item 7 = 9
Item 24 = 9     Item 24 = 9
Item 30 = 8     Item 30 = 8
Item 4 = 8      Item 4 = 8
Item 19 = 8     Item 19 = 8
Item 27 = 7     Item 27 = 7
Item 5 = 7      Item 5 = 7
Item 25 = 7     Item 25 = 7
Item 20 = 7     Item 20 = 7
Item 26 = 6     Item 26 = 6
Item 14 = 6     Item 14 = 6
Item 1 = 6      Item 1 = 6
Item 28 = 5     Item 2 = 5 <----
Item 2 = 5      Item 12 = 5
Item 12 = 5     Item 28 = 5
Item 11 = 4     Item 11 = 4
Item 6 = 4      Item 6 = 4
Item 13 = 3     Item 13 = 3
Item 3 = 3      Item 3 = 3
Item 21 = 3     Item 21 = 3
Item 10 = 3     Item 10 = 3
Item 18 = 3     Item 18 = 3
Item 22 = 2     Item 22 = 2
Item 29 = 2     Item 29 = 2
Item 23 = 1     Item 23 = 1
Item 8 = 1      Item 8 = 1
Item 17 = 1     Item 17 = 1
Item 16 = 0     Item 9 = 0
Item 9 = 0      Item 16 = 0
Item 15 = 0     Item 15 = 0

【问题讨论】:

  • 您要查找的术语是“稳定”排序。有些算法是,有些不是。
  • 啊,谢谢你的提示。现在,我正在阅读有关此主题的文章。

标签: c# list sorting


【解决方案1】:

要考虑的一个选项是改变:

list.Sort(sorter);

到:

list = list
    .Select((item, index) => new { item, index})
    .OrderBy(z => z.item, sorter)
    .ThenBy(z => z.index)
    .Select(z => z.item)
    .ToList();

这允许您按比较器排序,然后按其在List 中的位置 - 从而保持相对顺序。

【讨论】:

  • 谢谢。正如 Crowcoder 建议的那样,我通过使用插入排序(稳定)而不是使用快速排序(不稳定)的内置 Sort() 解决了这个问题,但这似乎也是一个解决方案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-11-01
  • 1970-01-01
  • 2020-04-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多