基于另一个列表排序的LINQ方法

16
List<int> _lstNeedToOrder = new List<int>();
_lstNeedToOrder.AddRange(new int[] { 1, 5, 6, 8 });

//I need to sort this based on the below list.

List<int> _lstOrdered = new List<int>();//to order by this list
_lstOrdered.AddRange(new int[] { 13, 5, 11, 1, 4, 9, 2, 7, 12, 10, 3, 8, 6 });

order will be -->_lstNeedToOrder = 5,1,8,6

我该怎么做呢?

6个回答

19

那么一种简单但效率低下的方式是:

var result = _lstNeedToOrder.OrderBy(x => _lstOrdered.IndexOf(x));

另一种方法是找到一种更好的方式来获取所需值的索引。如果您的值始终在范围[1...n]内,您可以将该“有序”列表反转为“按值排序的索引列表”。此时,您可以使用:

var result = _lstNeedToOrder.OrderBy(x => indexes[x]);

(其中indexes会在开头增加一个额外的值0,只是为了让事情变得更简单。)

或者,您可以创建一个从值到索引的Dictionary<int, int>。这更加通用,因为它可以处理非常广泛的值范围,而不需要占用大量内存。但是,与数组或列表查找相比,字典查找效率明显较低。

顺便说一句,这不适合作为注释格式,您可以使用集合初始化程序简化初始化:

var listToOrder = new List<int> { 1, 5, 6, 8 };
var orderedList = new List<int> { 13, 5, 11, 1, 4, 9, 2, 7, 12, 10, 3, 8, 6 };

嗨Jon,抱歉如果这是一个愚蠢的问题,但为什么第一个不高效呢? - Dimitar Dimitrov
1
@DimitarDimitrov:它使用IndexOf来查找每个条目的所需索引。这在_lstOrdered的大小上是一个O(n)的操作,是不必要的。 - Jon Skeet
@DimitarDimitrov 可能是因为使用了 IndexOf - King King
@JonSkeet 尴尬是的,显然...哦,好吧,这真的非常非常尴尬。 - Dimitar Dimitrov
@JonSkeet,谢谢Jon!我希望有一天能像你一样! - Rami Alshareef

13
    List<int> results = _lstOrdered.Where(item => _lstNeedToOrder.Contains(item)).ToList();

有趣的想法。它不会处理重复项,但我们当然不知道是否需要处理。 - Jon Skeet
嗯,我没有考虑到那种情况。 :) 我想这取决于原帖作者是否要求这样做。 - Vaughan Hilts
@VaughanHilts,我这里不会有重复项,谢谢! - Rami Alshareef

4
您可以像这样构建一个自定义比较器:
public class SequenceComparer<T> : IComparer<T> {
    private readonly Dictionary<T, int> indexes;

    public SequenceComparer(IEnumerable<T> sequence) {
        this.indexes =
            sequence
                .Select((item, index) => new { Item = item, Index = index })
                .ToDictionary(x => x.Item, x => x.Index);
    }

    public int Compare(T x, T y) {
        return indexes[x].CompareTo(indexes[y]);
    }
}

现在您可以说

var result = _lstNeedToOrder.OrderBy(x => x, new SequenceComparer(_lstOrdered));

еҰӮдҪ•дҪҝз”Ёзұ»SequenceComparer<T>пјҢжҲ‘дёҚжҳҺзҷҪе®ғеңЁOrderByжҹҘиҜўдёӯзҡ„з”Ёжі•гҖӮ - King King
1
@King King:谢谢。我错过了在“OrderBy”调用中创建它。现在已经有了。再次感谢。 - jason

4

这个很好用:

var lookup = _lstOrdered
    .Select((x, n) => new { x, n })
    .ToLookup(x => x.x, x => x.n);

var query =
    from x in _lstNeedToOrder
    let rank = lookup[x]
        .DefaultIfEmpty(int.MaxValue)
        .First()
    orderby rank
    select x;

2
另一个选项是使用Intersect,它保证按照第一个序列中元素的顺序返回元素。
因此,在这个例子中:
var result = _lstOrdered.Intersect(_lstNeedToOrder);

产生所需的结果为 { 5, 1, 8, 6}

1

将顺序保存在一个中间字典中...

// dict key will be the values of _lstOrdered, value will be the index of the
// key in _lstOrdered
// I'm using a seldom used .Select overload that returns the current value 
// plus its index (ix)
var dict = _lstOrdered.Select((p, ix) => new { Value = p, Ix = ix })
                      .ToDictionary(p => p.Value, p => p.Ix);

// note that this will explode if _lstNeedToOrder contains values outside
// _lstOrdered.
_lstNeedToOrder.Sort((p, q) => dict[p] - dict[q]);
< p > .Sort 方法会就地排序,因此_lstNeedToOrder将被排序。 < /p >

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