Чи існує LINQ-спосіб поміняти місцями два елементи всередині a list<T>
?
Чи існує LINQ-спосіб поміняти місцями два елементи всередині a list<T>
?
Відповіді:
Перевірте відповідь від Марка з C #: Хороша / найкраща реалізація методу обміну .
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
Не існує методу обміну, тому вам доведеться створити його самостійно. Звичайно, ви можете його зв’язати, але це потрібно робити з урахуванням одного (неписаного?) Правила: 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;
}
Це рішення забруднено , тому що він робить зміна списку введення, навіть якщо він відновлює його в початковому стані. Це може спричинити кілька проблем:
Є краще (і коротше) рішення: просто зробіть копію оригінального списку. (Це також дозволяє використовувати 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 ( порівняння з властивістю послідовності )