基于另一个列表对C#列表进行排序

9

我有一个类,其中包含多个List<>。它基本上是一个表格,每列都作为一个List<>存储。每一列不包含相同的类型。每个列表也具有相同的长度(具有相同数量的元素)。

例如:

我有3个List<>对象:一个List,两个List和三个List。

//Not syntactically correct
List<DateTime> one = new List...{4/12/2010, 4/9/2006, 4/13/2008};
List<double> two = new List...{24.5, 56.2, 47.4};
List<string> three = new List...{"B", "K", "Z"};

我希望能对列表进行按时间先后排序: one = {4/9/2006, 4/13/2008, 4/12/2010};
为了实现这一点,我将第一个元素移动到了列表末尾。
然后我希望以相同的方式对列表二和三进行排序;将第一个移到最后。
因此,当我对一个列表进行排序时,我希望在其他列表中与之对应的索引数据也会按照这种排序方式进行更改。
我猜想我必须以某种方式重载IComparer,但我感觉可能还有一些我还没有意识到的捷径。
7个回答

7

我曾经通过保留或创建一个单独的索引列表来处理这个设计。首先对索引列表进行排序,然后使用它来对其他列表进行排序(或仅访问)。您可以通过为索引列表创建自定义IComparer来实现此操作。在该IComparer内部所做的是根据关键字列表中的索引进行比较。换句话说,您是间接地对索引列表进行排序。类似于以下内容:

// This is the compare function for the separate *index* list.
int Compare (object x, object y)
{
  KeyList[(int) x].CompareTo(KeyList[(int) y])
}

所以,您正在根据键列表中的值对索引列表进行排序。 然后,您可以使用该已排序的键列表来重新排序其他列表。 如果不清楚,请等待我有机会发布更完整的示例。


1
+1. 这是一个非常好的方法。我个人的代码库中也有这种“间接列表”,它对很多事情都很有用。示例代码在这里 - Stephen Cleary
我知道这是一个老问题,但能否添加一些上下文来澄清KeyList的来源? - ryanwebjackson

5
这里有一种使用LINQ和投影的方法。第一个查询生成一个数组,其中原始索引按照日期时间值重新排序; 在您的示例中,newOrdering数组将包含成员: { 4/9/2006, 1 }, { 4/13/2008, 2 }, { 4/12/2010, 0 }。第二组语句通过使用重新排序的索引选择项目来生成新列表(换句话说,按顺序选择项目1、2和0)。
var newOrdering = one
    .Select((dateTime, index) => new { dateTime, index })
    .OrderBy(item => item.dateTime)
    .ToArray();

// now, order each list
one = newOrdering.Select(item => one[item.index]).ToList();
two = newOrdering.Select(item => two[item.index]).ToList();
three = newOrdering.Select(item => three[item.index]).ToList();

我喜欢这个解决方案,但它是否高效呢?我对投影性能不是很了解。 - John Gitzlsan
只有一种方法可以找出来 - :-) - Ben M

3
很抱歉,但这似乎是一个糟糕的设计。特别是因为List<T>在调用排序操作之前不保证元素顺序(所以插入时会有问题):来自MSDN

List没有保证排序。您必须在执行需要对List进行排序的操作(例如BinarySearch)之前对其进行排序。

在许多情况下,您不会因此遇到麻烦,但如果遇到问题,可能很难跟踪到错误。例如,我认为当前的框架实现List<T>维护插入顺序,直到调用排序操作,但这可能会在将来更改。 我会认真考虑重构以使用另一种数据结构。如果您仍然想要基于此数据结构实现排序,则可以创建一个临时对象(可能使用匿名类型),对其进行排序,并重新创建列表(请参见此优秀答案以获取说明)。

我同意有更好的设计。我过去遇到这种情况的主要情况是,我没有原始数组的所有权,并且它们太大而无法制作单独的副本。如果您拥有所有权,请务必将其重构为具有所有数据的IComparable类或结构。 - TechNeilogy
我实际上不能改变这个,但是这是应用程序。基本上,List<>对象包含数据,并且将该数据传递给数学函数。然后,数学函数将结果传递回来,并追加到表类中。把这些东西按行存储并使用LINQ投影出列会更好吗?不过这样会使插入操作变得更复杂。 - John Gitzlsan
是的,我肯定认为按行存储会更好。我不明白为什么插入操作会因此变得更加复杂;但是,我也不了解整个应用程序和您的限制条件。 - driis
2
据我所知,List<T> 不保证元素排序,但确保元素按插入顺序排序(例如,请参见此处),因此没有严格的重构需求。 - Mauro Ganswer

2

首先,您应该创建一个数据对象来存储所有内容。

private class Data
{
    public DateTime DateTime { get; set; }
    public int Int32 { get; set; }
    public string String { get; set; }
}

然后你可以像这样排序。
var l = new List<Data>();
l.Sort(
    (a, b) =>
    {
        var r = a.DateTime.CompareTo(b);
        if (r == 0)
        {
            r = a.Int32.CompareTo(b);
            if (r == 0)
            {
                r = a.String.CompareTo(b);
            }
        }
        return r;
    }
);

所以我遍历每个列表的全部内容并从数据创建一个Data结构。然后我对数据进行排序,再将其放回到列表中。这很有道理,但似乎不太高效。它至少能工作,这比我目前拥有的要好。谢谢。编辑:我不确定是否有3个项目,因此无法使用结构体。列表可能可行,但在大型排序时需要担心转换吗? - John Gitzlsan
@John - 你是从一个不可控的来源接收这些列表吗? - ChaosPandion
是的,我收到的List<>对象是不可更改的;但它们可以在原地排序。 - John Gitzlsan

2
我编写了一个排序算法,它可以用于Nito.LINQ(尚未发布)。 它使用简单的快速排序算法对列表进行排序,并使任何数量的相关列表保持同步。源代码从这里开始,在 IList<T>.Sort 扩展方法中。 或者,如果复制数据不是一个巨大的问题,您可以将其投影到一个LINQ查询中,使用Zip操作符(需要.NET 4.0或Rx)对其进行排序,然后提取每个结果:
List<DateTime> one = ...;
List<double> two = ...;
List<string> three = ...;
var combined = one.Zip(two, (first, second) => new { first, second })
    .Zip(three, (pair, third) => new { pair.first, pair.second, third });
var ordered = combined.OrderBy(x => x.first);
var orderedOne = ordered.Select(x => x.first);
var orderedTwo = ordered.Select(x => x.second);
var orderedThree = ordered.Select(x => x.third);

自然而然的,最好的解决方案是一开始就不要将相关数据分开。

这里是更新后的源代码链接。 - anon

1

希望这能有所帮助:

one = one.Sort(delegate(DateTime d1, DateTime d2)
{
    return Convert.ToDateTime(d2).CompareTo(Convert.ToDateTime(d1));
});

1

使用通用数组可能会变得有些麻烦。

另一种选择是使用 Array.Sort() 方法,该方法接受一个键数组和一个值数组进行排序。它首先将键数组按升序排序,并确保值数组重新组织以匹配此排序顺序。

如果您愿意承担将您的 List<T> 转换为数组(然后再转回)的成本,您可以利用这种方法。

或者,您可以使用 LINQ 将多个数组中的值组合成单个匿名类型,使用 Zip() 对匿名类型列表进行排序,然后将其拆分为单独的数组。

如果您想要在原地进行此操作,则必须编写自定义比较器并创建一个单独的索引数组来维护项目的新排序。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接