Jak sortować IEnumerable <string>


Odpowiedzi:


155

W ten sam sposób, w jaki posortowałbyś inne wyliczalne:

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

lub

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

lub (ignorując wielkość liter)

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

Należy zauważyć, że, jak zwykle w przypadku LINQ, tworzy to nowy IEnumerable <T>, który po wyliczeniu zwraca elementy oryginalnego IEnumerable <T> w kolejności posortowanej. Nie sortuje IEnumerable <T> w miejscu.


IEnumerable <T> jest tylko do odczytu, co oznacza, że ​​można pobrać z niego tylko elementy, ale nie można go bezpośrednio modyfikować. Jeśli chcesz posortować kolekcję ciągów w miejscu, musisz najpierw posortować oryginalną kolekcję, która implementuje IEnumerable <string>, lub zamienić IEnumerable <string> w kolekcję, którą można sortować:

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

Na podstawie Twojego komentarza:

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

lub

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

lub (jeśli chcesz później dodać więcej pozycji do listy i zachować ją posortowaną)

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

_components.Add("foo");
_components.Sort();

lub myEnumerable.OrderByDescending (s => s).
Grozz

Tak więc _components = _components.OrderBy (s => s); będzie dobrze?
CatZilla

@CatZilla: To powinno działać, ale może nie być najlepszym sposobem na rozwiązanie twojego rzeczywistego problemu. Co to jest _components, skąd go bierzesz, jak go używasz?
dtb

1
@dtb: Och, _components jest wypełniane z pliku XML dokładnie w ten sposób: _components = (z c w xml.Descendants ("komponent") wybierz c.Value.ToString ()). Distinct (). ToList (); I muszę to uporządkować.
CatZilla

2
OrderByzwraca IOrderedEnumerable<T>. IOrderedEnumerable<T>wywodzi się z, IEnumerable<T>więc może być używany jak IEnumerable<T>, ale rozszerza typ, pozwalając na przykład na użycie ThenBy.
Maciej Hehl

12

To niemożliwe, ale tak nie jest.

Zasadniczo każda metoda sortowania skopiuje twoją IEnumerabledo a List, posortuje, Lista następnie zwróci do ciebie posortowaną listę, która jest IEnumerablezarówno plikiem IList.

Oznacza to, że tracisz właściwość „kontynuuj w nieskończoność” IEnumerable, ale i tak nie możesz tak posortować.


7
Tak jest. Celem IEnumerable jest przedstawienie ci uchwytu do serii, którą możesz iterować od początku do końca, pytając o „następny” element. Oznacza to, że IEnumerable można częściowo iterować, zanim cała zawartość będzie znana; nie musisz wiedzieć, kiedy przeszedłeś przez nie wszystkie, dopóki tego nie zrobisz. Sortowanie (podobnie jak wiele rzeczy, na które pozwala Linq) wymaga znajomości całej serii jako uporządkowanej listy; pozycja, która pojawiłaby się jako pierwsza w posortowanej serii, może być ostatnią zwróconą przez serię i nie będziesz tego wiedzieć, chyba że wiesz, jakie są wszystkie elementy.
KeithS


2

Nie zawsze możemy to zrobić na miejscu, ale wykrywamy, kiedy jest to możliwe:

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

Używa to następującej przydatnej struktury:

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

Nie jestem pewien, czy poleciłbym to. Jeśli masz IEnumerable <T>, ale nie znasz rzeczywistego typu, który implementuje, prawdopodobnie nie powinieneś go modyfikować. Btw, Array.FunctorComparer <T> jest wewnętrzna.
dtb

Modyfikując to, co mamy, przyjąłem, że jest to implikowane w pytaniu dotyczącym poszukiwania miejsca; że to implikuje. Jest to powód, dla którego w nazwie metody należy umieścić „InPlaceInCan”; nazwy metod mogą być nawet bardziej zorientowane na ryzyko niż najlepsza dokumentacja;) Tak, Array.FunctorComparer <T> jest wewnętrzna, ale jest trywialna. Umieściłem to, ponieważ jest to najlepszy sposób, jaki mogłem wymyślić na przykładzie „Twojego funktora porównującego, który masz w swoim podstawowym zestawie klas pomocniczych”.
Jon Hanna

@dtb po przemyśleniu, zmieniono na własne (ponownie spojrzałem na Array.FunctorComparer i i tak wolę moje!)
Jon Hanna

Sprytny pomysł i nazewnictwo! Ale dlaczego podwójnie odlewania anty wzór w listToSort = (src is List<T>) ? (List<T>)src : new List<T>(src);? A co powiesz na tolistToSort = (src as List<T>); if (null == listToSort) listToSort = new List<T>(src);
Jeroen Wiert Pluimers

1
@JeroenWiertPluimers zawsze jest problem z przykładowym kodem; może po prostu nie zawracałem sobie głowy, bo powyższe jest nieco krótsze i chciałem się skoncentrować na bieżącej sprawie, ale złych nawyków dobrze jest zniechęcać nawet w przykładach.
Jon Hanna,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.