按枚举排序列表<T>,其中枚举无序

jpi*_*olo 26 c# sorting

我有一个消息列表.每条消息都有一个类型.

public enum MessageType
{
    Foo = 0,
    Bar = 1,
    Boo = 2,
    Doo = 3
}
Run Code Online (Sandbox Code Playgroud)

枚举名称是任意的,无法更改.

我需要返回列表排序为:Boo,Bar,Foo,Doo

我目前的解决方案是创建一个tempList,按我想要的顺序添加值,返回新列表.

List<Message> tempList = new List<Message>();
tempList.AddRange(messageList.Where(m => m.MessageType == MessageType.Boo));
tempList.AddRange(messageList.Where(m => m.MessageType == MessageType.Bar));
tempList.AddRange(messageList.Where(m => m.MessageType == MessageType.Foo));
tempList.AddRange(messageList.Where(m => m.MessageType == MessageType.Doo));
messageList = tempList;
Run Code Online (Sandbox Code Playgroud)

我怎么能用IComparer做到这一点?

voi*_*hos 39

使用的另一种方法IComparer是构建排序字典.

var orderMap = new Dictionary<MessageType, int>() {
    { MessageType.Boo, 0 },
    { MessageType.Bar, 1 },
    { MessageType.Foo, 2 },
    { MessageType.Doo, 3 }
};

var orderedList = messageList.OrderBy(m => orderMap[m.MessageType]);
Run Code Online (Sandbox Code Playgroud)

  • 有趣的是,他现在所拥有的是一个桶式排序,它比"OrderBy"渐近地快(因为我很确定它必须基于比较).像这样的东西更具可扩展性,更易于维护,但如果他处理大型数据集,时间复杂度可能值得注意. (6认同)
  • @WayneO:在这种情况下,它会出错。因为 `OrderBy` 直接从字典访问值(而不是使用 [this GetValueOrDefault 扩展方法](http://stackoverflow.com/a/2601501/716118) 之类的东西),所以它会抛出 `KeyNotFoundException`当查找失败时。为了解决这个问题,您可以更改 lambda 以使用类似于上面提到的 `GetValueOrDefault` 的内容:`m =&gt; orderMap.GetValueOrDefault(m.MessageType, -1)`。 (2认同)

Vik*_*ova 17

那么,让我们编写自己的比较器:

public class MyMessageComparer : IComparer<MessageType> {
    protected IList<MessageType> orderedTypes {get; set;}

    public MyMessageComparer() {
        // you can reorder it's all as you want
        orderedTypes = new List<MessageType>() {
            MessageType.Boo,
            MessageType.Bar,
            MessageType.Foo,
            MessageType.Doo,
        };
    }

    public int Compare(MessageType x, MessageType y) {
        var xIndex = orderedTypes.IndexOf(x);
        var yIndex = orderedTypes.IndexOf(y);

        return xIndex.CompareTo(yIndex);
    }
};
Run Code Online (Sandbox Code Playgroud)

如何使用:

messages.OrderBy(m => m.MessageType, new MyMessageComparer())
Run Code Online (Sandbox Code Playgroud)

有一种更简单的方法:只需创建ordereTypes列表并使用OrderBy的另一个重载:

var orderedTypes = new List<MessageType>() {        
            MessageType.Boo,
            MessageType.Bar,
            MessageType.Foo,
            MessageType.Doo,
    };

messages.OrderBy(m => orderedTypes.IndexOf(m.MessageType)).ToList();
Run Code Online (Sandbox Code Playgroud)

嗯..让我们尝试通过编写我们自己的IComparer来获得优势.想法:把它写成我们的最后一个例子,但是在其他一些语义中.像这样:

messages.OrderBy(
      m => m.MessageType, 
      new EnumComparer<MessageType>() { 
          MessageType.Boo, 
          MessageType.Foo }
);
Run Code Online (Sandbox Code Playgroud)

或这个:

messages.OrderBy(m => m.MessageType, EnumComparer<MessageType>());
Run Code Online (Sandbox Code Playgroud)

好的,我们需要什么.我们自己的比较器:

  1. 必须接受enum作为泛型类型(如何解决)
  2. 必须可用于集合初始化程序语法(如何)
  3. 当我们的比较器中没有枚举值时(或者某些枚举值不在我们的比较器中),必须按默认顺序排序

所以,这是代码:

public class EnumComparer<TEnum>: IComparer<TEnum>, IEnumerable<TEnum> where TEnum: struct, IConvertible {
    protected static IList<TEnum> TypicalValues { get; set; }

    protected IList<TEnum> _reorderedValues;

    protected IList<TEnum> ReorderedValues { 
        get { return _reorderedValues.Any() ? _reorderedValues : TypicalValues; } 
        set { _reorderedValues = value; }
    } 

    static EnumComparer() {
        if (!typeof(TEnum).IsEnum) 
        {
            throw new ArgumentException("T must be an enumerated type");
        }

        TypicalValues = new List<TEnum>();
        foreach (TEnum value in Enum.GetValues(typeof(TEnum))) {
            TypicalValues.Add(value);
        };            
    }

    public EnumComparer(IList<TEnum> reorderedValues = null) {
        if (_reorderedValues == null ) {
            _reorderedValues = new List<TEnum>();

            return;
        }

        _reorderedValues = reorderedValues;
    }

    public void Add(TEnum value) {
        if (_reorderedValues.Contains(value))
            return;

        _reorderedValues.Add(value);
    }

    public int Compare(TEnum x, TEnum y) {
        var xIndex = ReorderedValues.IndexOf(x);
        var yIndex = ReorderedValues.IndexOf(y);

        // no such enums in our order list:
        // so this enum values must be in the end
        //   and must be ordered between themselves by default

        if (xIndex == -1) {
            if (yIndex == -1) {
                xIndex = TypicalValues.IndexOf(x);
                yIndex = TypicalValues.IndexOf(y);
                return xIndex.CompareTo(yIndex);                
            }

           return -1;
        }

        if (yIndex == -1) {
            return -1; //
        }

        return xIndex.CompareTo(yIndex);
    }

    public void Clear() {
        _reorderedValues = new List<TEnum>();
    }

    private IEnumerable<TEnum> GetEnumerable() {
        return Enumerable.Concat(
            ReorderedValues,
            TypicalValues.Where(v => !ReorderedValues.Contains(v))
        );
    }

    public IEnumerator<TEnum> GetEnumerator() {
        return GetEnumerable().GetEnumerator();            
    }

    IEnumerator IEnumerable.GetEnumerator() {
        return GetEnumerable().GetEnumerator();            
    }
}
Run Code Online (Sandbox Code Playgroud)

所以,好吧,让我们更快地进行排序.我们需要为我们的枚举覆盖默认的OrderBy方法:

public static class LinqEnumExtensions
{
    public static IEnumerable<TSource> OrderBy<TSource, TEnum>(this IEnumerable<TSource> source, Func<TSource, TEnum> selector, EnumComparer<TEnum> enumComparer) where TEnum : struct, IConvertible
    {
        foreach (var enumValue in enumComparer)
        {
            foreach (var sourceElement in source.Where(item => selector(item).Equals(enumValue)))
            {
                yield return sourceElement;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

是的,那很懒.你可以谷歌收益率如何工作.好吧,让我们测试速度吧.简单的基准:http://pastebin.com/P8qaU20Y.结果为n = 1000000;

Enumerable orderBy, elementAt: 00:00:04.5485845
       Own orderBy, elementAt: 00:00:00.0040010
Enumerable orderBy, full sort: 00:00:04.6685977
       Own orderBy, full sort: 00:00:00.4540575
Run Code Online (Sandbox Code Playgroud)

我们看到,我们自己的订单通过更为懒惰的标准顺序(是的,它不需要排序所有内容).甚至对于fullsort也更快.

此代码中的问题:它不支持ThenBy().如果你需要这个,你可以编写自己的linq扩展返回IOrderedEnumerable有一个由Jon Skeet撰写的博客文章系列,它深入LINQ to Objects,提供了一个完整的替代实现.部分26a26bIOrderedEnumerable涵盖了基础,26c26d中有更多细节和优化.


rec*_*ive 8

如果您有固定数量的消息类型IComparer,您也可以使用一种SelectMany方法,而不是使用一种方法,该方法应该具有更好的大型消息列表性能.

var messageTypeOrder = new [] {
    MessageType.Boo,
    MessageType.Bar,
    MessageType.Foo,
    MessageType.Doo,
};

List<Message> tempList = messageTypeOrder
    .SelectMany(type => messageList.Where(m => m.MessageType == type))
    .ToList();
Run Code Online (Sandbox Code Playgroud)