Поменять местами два элемента в списке
есть ли LINQ способ поменять положение двух элементов внутри list<T>
?
5 ответов:
Проверьте ответ от Марка от C#: хорошая / лучшая реализация метода Swap.
public static void Swap<T>(IList<T> list, int indexA, int indexB) { T tmp = list[indexA]; list[indexA] = list[indexB]; list[indexB] = tmp; }
который может быть 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);
возможно, кто-то придумает умный способ сделать это, но вы не должны. замена двух элементов в списке по своей сути является побочным эффектом, но операции LINQ должны быть свободными от побочных эффектов. Таким образом, просто используйте простой метод расширения:
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; } }
нет никакого существующего Swap-метода, поэтому вы должны создать его самостоятельно. Конечно, вы можете линкифицировать его, но это должно быть сделано с одним (неписаным?) правила в виду: LINQ-операции не изменяют входные параметры!
в других ответах "linqify" список (input) изменяется и возвращается, но это действие тормозит это правило. Если было бы странно, если у вас есть список с несортированными элементами, выполните операцию LINQ "OrderBy" - и затем обнаружите, что входной список также отсортирован (просто как и результат). Этого нельзя допустить!
так... как нам это сделать?
моей первой мыслью было просто восстановить коллекцию после его завершения итерации. Но это же грязный решение, так что не используйте его:
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; }
это решение грязно, потому что это тут изменить список ввода, даже если она восстанавливает его в исходное состояние. Это может вызвать несколько проблем:
- список может быть только для чтения, который будет вызывать исключение.
- если список является общим для нескольких потоков, список изменится для других потоков во время выполнения этой функции.
- если во время итерации возникнет исключение, список не будет восстановлен. (Это можно было бы решить, чтобы написать try-finally внутри Swap-функции и поместить код восстановления внутри finally-block).
есть лучшее (и более короткое) решение: просто сделайте копию первоначальный список. (Это также позволяет использовать IEnumerable в качестве параметра, а не 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; }
одним из недостатков этого решения является то, что он копирует весь список, который будет потреблять память, и это делает решение довольно медленным.
вы можете рассмотреть следующее решение:
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]; } }
и если вы хотите, чтобы входной параметр был 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; } }
своп 4 также делает копию (подмножество) источник. Так что хуже всего сценарий случая, он так же медленен и потребляет память, как функция Swap2.
список имеет обратный метод.
your_list.Reverse(i, 2) // will swap elements with indexs i, i + 1.
Источник:https://msdn.microsoft.com/en-us/library/hf2ay11y (v=vs. 110). aspx