在对列表进行排序时,保持相同值的项的相对顺序

时间:2017-10-14 11:05:11

标签: c# list sorting

假设您需要保留一个更改列表,该列表可能包含重复值的项目,已排序。例如,您有[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

1 个答案:

答案 0 :(得分:0)

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

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中的位置进行排序 - 从而保持相对顺序。