【问题标题】:C# Sort List Based on Another ListC# 根据另一个列表对列表进行排序
【发布时间】:2011-03-22 07:40:41
【问题描述】:

我有一个包含多个 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"};

我希望能够从最旧到最新对列表一进行排序: 一 = {4/9/2006、4/13/2008、4/12/2010};

为此,我将元素 0 移到了末尾。

然后我想以相同的方式对列表 2 和 3 进行排序;将第一个移到最后一个。

所以当我对一个列表进行排序时,我希望其他列表中相应索引中的数据也根据一个列表的排序方式发生变化。

我猜我必须以某种方式重载 IComparer,但我觉得有一条我没有意识到的捷径。

【问题讨论】:

    标签: c# sorting


    【解决方案1】:

    我过去曾通过保留或创建单独的索引列表来处理此设计。您首先对索引列表进行排序,然后使用它对其他列表进行排序(或仅访问)。您可以通过为索引列表创建自定义 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。这是一个很好的方法。我的个人图书馆里正好有这种“间接清单”;它对很多事情都很有用。示例代码here.
    【解决方案2】:

    这是一种使用 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();
    

    【讨论】:

    • 我喜欢这个解决方案,但它有效吗?我对投影性能了解不多;
    • 只有一种方法可以找到 - :-)
    【解决方案3】:

    我很抱歉,但这感觉像是一个糟糕的设计。特别是因为 List 在您调用其中一种排序操作之前不保证元素顺序(因此插入时会出现问题):

    From MSDN

    列表不保证是 排序。您必须对列表进行排序 在执行操作之前(例如 BinarySearch) 需要列表 进行排序。

    在许多情况下,您不会因此而遇到麻烦,但您可能会遇到麻烦,如果您这样做了,这可能是一个很难追踪的错误。例如,我认为 List 的当前框架实现在调用 sort 之前保持插入顺序,但将来可能会改变。

    我会认真考虑重构以使用另一种数据结构。如果您仍想基于此数据结构实现排序,我将创建一个临时对象(可能使用匿名类型),对其进行排序,然后重新创建列表(see this excellent answer 解释如何)。

    【讨论】:

    • 我同意有更好的设计。我过去遇到的主要情况是我没有原始数组的所有权,而且它们太大而无法制作单独的副本。如果您拥有所有权,请务必将其重构为包含所有数据的 IComparable 类或结构。
    • 我无法真正改变这一点,但这是应用程序。基本上 List 对象包含数据并且该数据被传递给数学函数。然后数学函数将结果传回并附加到表类中。将这些东西按行存储并使用 LINQ 将列投影出来会更好吗?然而,这使得插入更加复杂。
    • 是的,我当然认为按行存储会更好。我看不出插入会因此变得更加复杂。但话又说回来,我不了解整个应用程序和您的限制。
    • 据我所知 List 不保证元素被排序,但它保证元素的顺序根据插入顺序(例如参见here),因此没有严格需要重构。
    【解决方案4】:

    首先,您应该创建一个 Data 对象来保存所有内容。

    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 - 您是否从无法控制的来源收到这些列表?
    • 是的,我收到的 List 对象无法更改;不过,它们可以就地排序。
    【解决方案5】:

    我为Nito.LINQ(尚未发布)编写了一个排序算法。它使用简单的 QuickSort 对列表进行排序,并使任意数量的相关列表保持同步。 Source code starts here, in the IList&lt;T&gt;.Sort extension method.

    或者,如果复制数据不是一个大问题,您可以使用 Zip 运算符(需要 .NET 4.0 或 Rx)将其投影到 LINQ 查询中,对其排序,然后提取每个结果:

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

    当然,最好的解决方案是一开始就不分离相关数据。

    【讨论】:

    【解决方案6】:

    使用泛型数组,这会有点麻烦。

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

    如果您愿意承担将您的 List&lt;T&gt;s 转换为数组(然后再返回)的成本,您可以利用此方法。

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

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

    【讨论】:

      【解决方案7】:

      希望对你有帮助:

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

      【讨论】:

        猜你喜欢
        • 2018-11-15
        • 1970-01-01
        • 2023-04-01
        • 2021-05-02
        • 1970-01-01
        • 2013-09-17
        • 1970-01-01
        • 2012-08-27
        相关资源
        最近更新 更多