Есть ли способ LINQ поменять местами два элемента внутри list<T>
?
Поменяйте местами два элемента в списке ‹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;
}
}
List<T>
имеет Reverse()
метод, однако он меняет порядок только двух (или более) последовательных элементов.
your_list.Reverse(index, 2);
Второй параметр 2
указывает, что мы меняем порядок 2 элементов на обратный, начиная с элемента в данном index
.
Источник: https://msdn.microsoft.com/en-us/library/hf2ay11y(v=vs.110).aspx
Существующего Swap-метода не существует, поэтому его придется создать самостоятельно. Конечно, вы можете его модифицировать, но это должно быть сделано с одним (неписаным?) Правилом: LINQ-операции не изменяют входные параметры!
В других ответах "linqify" (входной) список изменяется и возвращается, но это действие нарушает это правило. Было бы странно, если бы у вас есть список с несортированными элементами, выполните 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).
Есть лучшее (и более короткое) решение: просто сделайте копию исходного списка. (Это также позволяет использовать 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;
}
}
Swap4 также делает копию (подмножество) источника. В худшем случае это так же медленно и потребляет много памяти, как и функция Swap2.
Если порядок имеет значение, вы должны сохранить свойство для объектов «T» в вашем списке, которое обозначает последовательность. Чтобы поменять их местами, просто поменяйте местами значение этого свойства, а затем используйте его в .Sort (сравнение со свойством последовательности)