Подтвердить что ты не робот

Сортировка списка <T> по перечислению, где перечисление не соответствует порядку

У меня есть список сообщений. Каждое сообщение имеет тип.

public enum MessageType
{
    Foo = 0,
    Bar = 1,
    Boo = 2,
    Doo = 3
}

Имена перечислений произвольны и не могут быть изменены.

Мне нужно вернуть список, отсортированный как: 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;

Как я могу сделать это с помощью IComparer?

4b9b3361

Ответ 1

Альтернативой использованию 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]);

Ответ 2

Итак, напишите наш собственный компаратор:

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

    public MyMessageComparer() {
        // you can reorder it 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);
    }
};

Как использовать:

messages.OrderBy(m => m.MessageType, new MyMessageComparer())

Существует более простой способ: просто создайте список ordereTypes и используйте другую перегрузку OrderBy:

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

messages.OrderBy(m => orderedTypes.IndexOf(m.MessageType)).ToList();

Hm.. Попробуем воспользоваться преимуществами написания собственного IComparer. Идея: напишите это как наш последний пример, но в какой-то другой семантике. Вот так:

messages.OrderBy(
      m => m.MessageType, 
      new EnumComparer<MessageType>() { 
          MessageType.Boo, 
          MessageType.Foo }
);

Или это:

messages.OrderBy(m => m.MessageType, EnumComparer<MessageType>());

Хорошо, так что нам нужно. Наш собственный компаратор:

  • Необходимо принять перечисление как общий тип (как решить)
  • Должен использоваться с синтаксисом инициализатора коллекции (как)
  • Должен сортировать по умолчанию порядок, когда у нас нет значений перечисления в нашем компаменте (или некоторые значения перечисления не находятся в нашем компаменте)

Итак, вот код:

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

Итак, давайте сделаем сортировку быстрее. Нам нужно переопределить метод 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;
            }
        }
    }
}

Да, это лениво. Вы можете google, как работает доход. Ну, пусть скорость тестирования. Простой эталон: 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

Мы видим, что наш собственный заказБолее ленив, что стандартный порядок (да, ему не нужно сортировать все). И быстрее даже для полной.

Проблемы в этом коде: он не поддерживает ThenBy(). Если вам это нужно, вы можете написать собственное расширение linq, которое возвращает IOrderedEnumerable. серия блога блога Jon Skeet, которая входит в LINQ к объектам в некоторой степени, обеспечивая полную альтернативную реализацию. Основание IOrderedEnumerable описано в часть 26a и 26b, с более подробной информацией и оптимизацией в 26c и 26d.

Ответ 3

Вместо использования 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();

Ответ 4

Вы можете избежать написания совершенно нового типа только для реализации IComparable. Вместо этого используйте класс Comparer:

IComparer<Message> comparer = Comparer.Create<Message>((message) =>
    {
    // lambda that compares things
    });
tempList.Sort(comparer);

Ответ 5

Вы можете динамически строить словарь сопоставления из значений Enum с помощью LINQ следующим образом:

  var mappingDIctionary = new List<string>((string[])Enum.GetNames(typeof(Hexside)))
                    .OrderBy(label => label )
                    .Select((i,n) => new {Index=i, Label=n}).ToList();

Теперь любые новые значения, добавленные в будущее Enum, автоматически будут правильно отображаться.

Кроме того, если кто-то решает изменить нумерацию, рефакторинг или изменить порядок перечисления, все будет обрабатываться автоматически.

Update: Как указано ниже, Алфавитный порядок не призывался; скорее полу-алфавитным упорядочением, так что существенно случайным. Хотя это и не ответ на этот конкретный вопрос, этот метод может быть полезен будущим посетителям, поэтому я оставлю его стоящим.

Ответ 6

Нет необходимости иметь сопоставление. Это должно дать вам список и порядок на основе перечисления. Вам не нужно ничего изменять, даже если вы изменяете порядок перечисления и/или новые элементы...

var result = (from x in tempList
              join y in Enum.GetValues(typeof(MessageType)).Cast<MessageType>()
              on x equals y
              orderby y
              select y).ToList();