Czy istnieje sposób LINQ do zamiany pozycji dwóch elementów w list<T>
?
Czy istnieje sposób LINQ do zamiany pozycji dwóch elementów w list<T>
?
Odpowiedzi:
Sprawdź odpowiedź Marca z C #: Dobra / najlepsza implementacja metody Swap .
public static void Swap<T>(IList<T> list, int indexA, int indexB)
{
T tmp = list[indexA];
list[indexA] = list[indexB];
list[indexB] = tmp;
}
który może być podobny do linq-i-fied
public static IList<T> Swap<T>(this IList<T> list, int indexA, int indexB)
{
T tmp = list[indexA];
list[indexA] = list[indexB];
list[indexB] = tmp;
return list;
}
var lst = new List<int>() { 8, 3, 2, 4 };
lst = lst.Swap(1, 2);
Może ktoś wymyśli sprytny sposób na zrobienie tego, ale nie powinieneś. Zamiana dwóch elementów na liście jest z natury obciążona efektem ubocznym, ale operacje LINQ powinny być wolne od skutków ubocznych. Dlatego po prostu użyj prostej metody rozszerzenia:
static class IListExtensions {
public static void Swap<T>(
this IList<T> list,
int firstIndex,
int secondIndex
) {
Contract.Requires(list != null);
Contract.Requires(firstIndex >= 0 && firstIndex < list.Count);
Contract.Requires(secondIndex >= 0 && secondIndex < list.Count);
if (firstIndex == secondIndex) {
return;
}
T temp = list[firstIndex];
list[firstIndex] = list[secondIndex];
list[secondIndex] = temp;
}
}
List<T>
ma Reverse()
metodę, jednak odwraca tylko kolejność dwóch (lub więcej) kolejnych pozycji.
your_list.Reverse(index, 2);
Tam, gdzie 2
wskazuje drugi parametr , odwracamy kolejność 2 pozycji, zaczynając od pozycji podanej index
.
Źródło: https://msdn.microsoft.com/en-us/library/hf2ay11y(v=vs.110).aspx
Nie ma istniejącej metody Swap, więc musisz ją stworzyć samodzielnie. Oczywiście możesz to zrobić linqify, ale należy to zrobić mając na uwadze jedną (niepisaną?) Regułę: operacje LINQ nie zmieniają parametrów wejściowych!
W innych odpowiedziach "linqify" lista (wejściowa) jest modyfikowana i zwracana, ale to działanie łamie tę regułę. Jeśli byłoby dziwne, gdybyś miał listę z nieposortowanymi pozycjami, wykonaj operację LINQ „OrderBy” i odkryj, że lista wejściowa jest również posortowana (tak jak wynik). To nie może się zdarzyć!
Więc jak to robimy?
Moją pierwszą myślą było przywrócenie kolekcji po zakończeniu iteracji. Ale to jest brudne rozwiązanie, więc nie używaj go:
static public IEnumerable<T> Swap1<T>(this IList<T> source, int index1, int index2)
{
// Parameter checking is skipped in this example.
// Swap the items.
T temp = source[index1];
source[index1] = source[index2];
source[index2] = temp;
// Return the items in the new order.
foreach (T item in source)
yield return item;
// Restore the collection.
source[index2] = source[index1];
source[index1] = temp;
}
To rozwiązanie jest brudny, ponieważ robi zmodyfikować listę wejściowego, nawet jeśli przywraca go do stanu pierwotnego. Może to spowodować kilka problemów:
Jest lepsze (i krótsze) rozwiązanie: po prostu zrób kopię oryginalnej listy. (Umożliwia to również użycie IEnumerable jako parametru zamiast IList):
static public IEnumerable<T> Swap2<T>(this IList<T> source, int index1, int index2)
{
// Parameter checking is skipped in this example.
// If nothing needs to be swapped, just return the original collection.
if (index1 == index2)
return source;
// Make a copy.
List<T> copy = source.ToList();
// Swap the items.
T temp = copy[index1];
copy[index1] = copy[index2];
copy[index2] = temp;
// Return the copy with the swapped items.
return copy;
}
Wadą tego rozwiązania jest to, że kopiuje całą listę, co zużywa pamięć, co powoduje, że rozwiązanie jest raczej powolne.
Możesz rozważyć następujące rozwiązanie:
static public IEnumerable<T> Swap3<T>(this IList<T> source, int index1, int index2)
{
// Parameter checking is skipped in this example.
// It is assumed that index1 < index2. Otherwise a check should be build in and both indexes should be swapped.
using (IEnumerator<T> e = source.GetEnumerator())
{
// Iterate to the first index.
for (int i = 0; i < index1; i++)
yield return source[i];
// Return the item at the second index.
yield return source[index2];
if (index1 != index2)
{
// Return the items between the first and second index.
for (int i = index1 + 1; i < index2; i++)
yield return source[i];
// Return the item at the first index.
yield return source[index1];
}
// Return the remaining items.
for (int i = index2 + 1; i < source.Count; i++)
yield return source[i];
}
}
A jeśli chcesz, aby parametr wejściowy był IEnumerable:
static public IEnumerable<T> Swap4<T>(this IEnumerable<T> source, int index1, int index2)
{
// Parameter checking is skipped in this example.
// It is assumed that index1 < index2. Otherwise a check should be build in and both indexes should be swapped.
using(IEnumerator<T> e = source.GetEnumerator())
{
// Iterate to the first index.
for(int i = 0; i < index1; i++)
{
if (!e.MoveNext())
yield break;
yield return e.Current;
}
if (index1 != index2)
{
// Remember the item at the first position.
if (!e.MoveNext())
yield break;
T rememberedItem = e.Current;
// Store the items between the first and second index in a temporary list.
List<T> subset = new List<T>(index2 - index1 - 1);
for (int i = index1 + 1; i < index2; i++)
{
if (!e.MoveNext())
break;
subset.Add(e.Current);
}
// Return the item at the second index.
if (e.MoveNext())
yield return e.Current;
// Return the items in the subset.
foreach (T item in subset)
yield return item;
// Return the first (remembered) item.
yield return rememberedItem;
}
// Return the remaining items in the list.
while (e.MoveNext())
yield return e.Current;
}
}
Swap4 tworzy również kopię (podzbioru) źródła. Tak więc najgorszy scenariusz jest tak powolny i pochłania pamięć jak funkcja Swap2.
Jeśli porządek ma znaczenie, powinieneś zachować właściwość na obiektach „T” na swojej liście, która oznacza sekwencję. Aby je zamienić, po prostu zamień wartość tej właściwości, a następnie użyj jej w .Sort ( porównanie z właściwością sequence )