Como classificar um IEnumerable <string>

98

Como posso classificar em IEnumerable<string>ordem alfabética. Isso é possível?

Edit: Como escrever uma solução in-loco?

CatZilla
fonte

Respostas:

154

Da mesma forma que você classificaria qualquer outro enumerável:

var result = myEnumerable.OrderBy(s => s);

ou

var result = from s in myEnumerable
             orderby s
             select s;

ou (ignorando maiúsculas e minúsculas)

var result = myEnumerable.OrderBy(s => s,
                                  StringComparer.CurrentCultureIgnoreCase);

Observe que, como é normal com LINQ, isso cria um novo IEnumerable <T> que, quando enumerado, retorna os elementos do IEnumerable <T> original na ordem de classificação. Ele não classifica o IEnumerable <T> no local.


Um IEnumerable <T> é somente leitura, ou seja, você só pode recuperar os elementos dele, mas não pode modificá-lo diretamente. Se você deseja classificar uma coleção de strings no local, você precisa classificar a coleção original que implementa IEnumerable <string> ou transformar um IEnumerable <string> em uma coleção classificável primeiro:

List<string> myList = myEnumerable.ToList();
myList.Sort();

Com base em seu comentário:

_components = (from c in xml.Descendants("component")
               let value = (string)c
               orderby value
               select value
              )
              .Distinct()
              .ToList();

ou

_components = xml.Descendants("component")
                 .Select(c => (string)c)
                 .Distinct()
                 .OrderBy(v => v)
                 .ToList();

ou (se quiser adicionar mais itens à lista posteriormente e mantê-la classificada)

_components = xml.Descendants("component")
                 .Select(c => (string)c)
                 .Distinct()
                 .ToList();

_components.Add("foo");
_components.Sort();
dtb
fonte
ou myEnumerable.OrderByDescending (s => s).
Grozz,
Portanto, _components = _components.OrderBy (s => s); seria ótimo?
CatZilla,
@CatZilla: Isso deve funcionar, mas pode não ser a melhor maneira de resolver seu problema real. O que é _components, de onde você o tira, como você o usa?
dtb
1
@dtb: Oh, _components é preenchido a partir de um arquivo XML exatamente desta forma: _components = (de c em xml.Descendants ("component") select c.Value.ToString ()). Distinct (). ToList (); E eu preciso resolver isso.
CatZilla
2
OrderByretorna IOrderedEnumerable<T>. IOrderedEnumerable<T>deriva de IEnumerable<T>para que possa ser usado como IEnumerable<T>, mas estende o tipo, permitindo por exemplo, o uso de ThenBy.
Maciej Hehl,
12

É impossível, mas não é.

Basicamente, qualquer método de classificação irá copiar seu IEnumerableem um List, classificar o Liste, em seguida, retornar para você a lista classificada, que é tanto um IEnumerablequanto um IList.

Isso significa que você perde a propriedade "continuar infinitamente" de um IEnumerable, mas não pode classificar um assim de qualquer maneira.

James Curran
fonte
7
Pode apostar. O propósito de IEnumerable é apresentar a você um identificador para uma série que você pode iterar, do início ao fim, continuando a pedir o "próximo" item. Isso significa que um IEnumerable pode ser parcialmente iterado antes que todo o conteúdo seja conhecido; você não precisa saber quando passou por todos eles até que tenha. A classificação (como muitas coisas que o Linq permite) requer o conhecimento de toda a série como uma lista ordenada; o item que apareceria primeiro em uma série classificada pode ser o último retornado por uma série, e você não saberá disso a menos que saiba quais são todos os itens.
KeithS
8
myEnumerable = myEnumerable.OrderBy(s => s);
Larsenal
fonte
2

Nem sempre podemos fazer isso no local, mas detectamos quando é possível:

IEnumerable<T> SortInPlaceIfCan(IEnumerable<T> src, IComparer<T> cmp)
{
  List<T> listToSort = (src is List<T>) ? (List<T>)src : new List<T>(src);
  listToSort.Sort(cmp);
  return listToSort;
}
IEnumerable<T> SortInPlaceIfCan(IEnumerable<T> src, Comparison<T> cmp)
{
  return SortInPlaceIfCan(src, new FuncComparer<T>(cmp));
}
IEnumerable<T> SortInPlaceIfCan(IEnumerable<T> src)
{
  return SortInPlaceIfCan(src, Comparer<T>.Default);
}

Isso usa a seguinte estrutura útil:

internal struct FuncComparer<T> : IComparer<T>
{
  private readonly Comparison<T> _cmp;
  public FuncComparer(Comparison<T> cmp)
  {
      _cmp = cmp;
  }
  public int Compare(T x, T y)
  {
      return _cmp(x, y);
  }
}
Jon Hanna
fonte
Não tenho certeza se recomendaria isso. Se você tiver um IEnumerable <T>, mas não souber o tipo real que o implementa, provavelmente não deveria modificá-lo. Btw, Array.FunctorComparer <T> é interno.
dtb
Ao modificar o que temos, assumi que isso estava implícito na questão de procurar no local; que isso implica. É um motivo para ter "InPlaceInCan" no nome do método; nomes de métodos podem ser ainda mais diretos sobre os riscos do que a melhor documentação;) Sim, Array.FunctorComparer <T> é interno, mas é trivial. Eu o coloquei porque é a melhor maneira que poderia imaginar em um exemplo para "seu comparador de functor que você tem em seu conjunto de classes auxiliares".
Jon Hanna,
@dtb pensando bem, mudou para usar o meu próprio (dei uma olhada em Array.FunctorComparer novamente e eu prefiro o meu de qualquer maneira!)
Jon Hanna
Idéia inteligente e nomenclatura! Mas por que a dupla lançando anti padrão em listToSort = (src is List<T>) ? (List<T>)src : new List<T>(src);? Que tal ter comolistToSort = (src as List<T>); if (null == listToSort) listToSort = new List<T>(src);
Jeroen Wiert Pluimers,
1
@JeroenWiertPluimers sempre há o problema com o código de exemplo também; talvez eu simplesmente não tenha me incomodado porque o texto acima é um pouco mais curto e eu queria me concentrar no assunto em questão, mas é bom desencorajar os maus hábitos, mesmo em exemplos.
Jon Hanna,