【问题标题】:Custom sorting with LINQ使用 LINQ 进行自定义排序
【发布时间】:2010-11-29 10:45:48
【问题描述】:

似乎我错过了一些微不足道的东西。

不管怎样,就这样吧:

var order = new[]{1,3,2};
var foos = new[]{new Foo{Id=1}, new Foo{Id=2}, new Foo{Id=3}};

如何使用 Linq 按顺序数组对 foos 进行排序?

想要的结果:

foos == new[]{new Foo{Id=1}, new Foo{Id=3}, new Foo{Id=2}};

编辑:
订单包含 Foo id。对不起,我没有提到这一点。有时,正确地提问比回答问题更难。 :)

【问题讨论】:

  • order 数组是否指定了数组索引或Ids 的Foos?
  • 确实 - 请澄清问题。我已经在我的回答中解释了我认为您所要求的内容,但我只是在猜测。 (例如,Dale Halliwell 猜到了不同的意思。)

标签: c# linq sorting


【解决方案1】:
from o in order.Select((o, i) => new { o, i })
join f in foos on o.o equals f.Id
orderby o.i
select f;

【讨论】:

  • 您为什么要通过o.i 订购?它已经按照这个顺序了。
  • 详细说明:如果我不订购,我必须知道哪些序列(如果有)是保留顺序的。对于 LINQ to 对象中的此运算符,它可能已经很好地定义了,但如果它是 LINQ to SQL,则不会。另外,如果我误读了文档,我可以通过省略 orderby 来引入一个难以发现的错误(如果我只用 3 个项目测试它,我假设有 1/6 的机会意外通过,因为我不知道实现细节)。通过添加 orderby,我可以在不伤害任何人的情况下摆脱这些问题。
  • erikkallen:“……不伤害任何人。”除了执行不必要的排序的性能影响。
【解决方案2】:

好的,这个问题对我来说似乎完全不清楚,所以我会尝试澄清我认为你在问什么:

  • 您有一个按所需顺序排列的 ID 序列
  • 您有一个对象集合,不是按正确的顺序,但有相应的 ID
  • 您希望按照 ID 序列的顺序获取对象的集合

正确吗?

那就是:

var orderedFoos = from orderedId in order
                  join foo in foos on orderedId equals foo.Id into groups
                  select groups.Single();

您需要一个join ... into 来验证您在foos 中没有任何丢失或重复的ID。但是,它不会检测您是否在 order 中丢失或重复 ID。如果您知道一切都会正确(即,foos 中的每个条目都只有一个条目,order 中的每个条目,反之亦然),那么简单的连接就可以了:

var orderedFoos = from orderedId in order
                  join foo in foos on orderedId equals foo.Id
                  select foo;

可以用点表示法表示为:

var orderedFoos = order.Join(foos, order => order, foo => foo.ID, (o, f) => f);

【讨论】:

  • 我认为这不能解决问题。我认为 OP 基本上想抓住在特定位置具有特定 IdFoos。
  • 抱歉 Skeet,不能使用 .NET 4.0
  • @Arnis:这就是我链接到 MoreLINQ 的原因——但我误解了这个问题。
  • (我现在使用 GroupJoin 重写了答案。)
【解决方案3】:
var order = new[] { 1, 3, 2 };
var foos = new[] { new Foo { Id = 1 }, new Foo { Id = 2 }, new Foo { Id = 3 } };

var query = from o in order
            join foo in foos on o equals foo.Id
            select foo;

var foos2 = query.ToArray();

【讨论】:

    【解决方案4】:

    如果我有很多这些事情要做,我可能会使用 id/ordering 对的Dictionary<int,int> 来进行订单查找 O(1)。请注意,您还需要处理订单中缺少您的值的情况——我选择将它们移到最后。

    var order = new Dictionary<int,int>();
    order.Add( 1, 1 );
    order.Add( 2, 3 );
    order.Add( 3, 2 );
    
    var orderedFoos = foos.OrderBy( f => order.Contains(f.Id) ? order[f.Id] : int.MaxValue );
    

    【讨论】:

    • 性能在这里不是问题,值应该匹配(如果它们不匹配则例外)但是谢谢。 :)
    【解决方案5】:

    这是你想要做的吗?

    foos.OrderBy(f => order[f.Id-1]);
    

    如果你现在在输出上 foreach,打印 ID,你会得到:1,3,2

    【讨论】:

    • 其中一些基于连接的答案比这个使用比较器的小 LINQ 的大集合要贵得多。
    • 真的吗?订单应该使用快速排序或更好的东西,所以是的,O(n log n),但直觉告诉我连接(在 LINQ 的情况下是等值连接)必须在 O(n^2)左右。你从哪里得到O(n) 来自?
    • 是的,它做了一些假设,OP 需要澄清
    • @Dale:它将在“内部”部分创建一个哈希,然后使用它进行匹配。假设我们有一个 1-1 对应关系(即我们没有 N 个条目都具有相同的键)并且哈希是合理的(即查找是 O(1),创建每个项目是 O(1))那么结果是O(N)。
    • 谢谢乔恩,我不知道!
    【解决方案6】:

    您可以使用嵌套查询来执行此操作,但使用O(n²) 效率非常低。

    var result = order.Select(o => foos.Single(f => f.Id == o));
    

    如果 'order' 可能包含 'foos' 中不存在的 id,则应使用 SingleOrDefault()。如果foos 可能包含重复的 id,则应使用First()FirstOrDefault()

    var result = order
        .Select(o => foos.FirstOrDefault(f => f.Id == o))
        .Select(f => f != null);
    

    也许即使加入也可以,但我不确定它是否会保留顺序。

    var result = Enumerable.Join(order, foos, o => o, f => f.Id, (o, f) => f);
    

    正如 Jon 所提到的,只有当输入的格式与我的第一个建议所要求的相同时,连接才能正常工作。

    【讨论】:

    • 联接确实保留了 LINQ to Objects 中的顺序...但您最终可能会出现重复或丢失的行。
    • "var 结果 = order.Select(o => foos.Single(f => f.Id == o));"这是 Mehrdid 在开始时发布的(并已删除)。实际上 - 在这种情况下这是一种有效的方法,因为性能在这里根本不是问题。无论如何 - 谢谢。
    猜你喜欢
    • 1970-01-01
    • 2020-03-24
    • 2014-09-10
    • 2014-06-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多