Загальний список - переміщення елемента в списку


155

Отже, у мене є загальний список, і значення, oldIndexі newIndexзначення.

Я хочу перемістити предмет на oldIndex, до newIndex... якомога простіше.

Будь-які пропозиції?

Примітка

Елемент повинен бути в кінці між елементами на (newIndex - 1)і newIndex до його вилучення.


1
Ви повинні змінити відповідь, яку ви поставили. Той, хто newIndex--має, не призводить до поведінки, яку ви сказали, що хочете.
Мірал

1
@Miral - на вашу думку, яка відповідь, на вашу думку, повинна бути прийнятою?
Річард Єв

4
jpierson's. Це призводить до того, що об'єкт, який раніше знаходився у oldIndex, перед переміщенням був у newIndex після переміщення. Це найменш дивна поведінка (і це те, що мені було потрібно, коли я писав якийсь код переупорядкування drag'n'drop). Зрозуміло, він говорить про те, що ObservableCollectionне є загальним List<T>, але банально просто поміняти виклики методу, щоб отримати той самий результат.
Міраль

Запрошена поведінка (і правильно реалізована в цій відповіді ) для переміщення предмета між елементами в [newIndex - 1]і [newIndex]не є зворотною. Move(1, 3); Move(3, 1);не повертає список у початковий стан. У той же час існує інша поведінка передбачено ObservableCollectionі згадується в цій відповіді , який є оборотним .
Лайтман

Відповіді:


138

Я знаю, що ви сказали "загальний список", але ви не вказали, що вам потрібно використовувати клас List (T), тож ось вистрілив щось інше.

Клас ObservableCollection (T) має метод Move, який робить саме те, що ви хочете.

public void Move(int oldIndex, int newIndex)

Внизу це в основному реалізовано так.

T item = base[oldIndex];
base.RemoveItem(oldIndex);
base.InsertItem(newIndex, item);

Отже, як ви можете бачити метод swap, який запропонували інші, - це по суті те, що OservableCollection робить у своєму власному Move методі.

ОНОВЛЕННЯ 2015-12-30: Ви можете побачити вихідний код методів Move і MoveItem в corefx тепер для себе без використання Reflector / ILSpy, оскільки .NET є відкритим кодом.


28
Цікаво, чому це також не реалізовано у Список <T>, а хтось пролічує на це світло?
Андреас

Яка різниця між загальним списком і класом List (T)? Я подумав, що вони однакові :(
BKSpurgeon

А «загальний список» може означати будь-який тип списку або колекцію , як структура даних в .NET , які можуть включати в себе ObservableCollection (T) або інші класи , які можуть реалізувати Listy інтерфейсу , такі як IList / ICollection / IEnumerable.
jpierson

6
Чи не може хтось пояснити, будь ласка, чому насправді немає зсуву індексу призначення (якщо він більший за вихідний індекс)?
Владіус

@vladius Я вважаю, що ідея полягає в тому, що вказане значення newIndex повинно просто вказати потрібний індекс, який має бути після переміщення, і оскільки використовується вставка, немає ніяких причин для коригування. Якби newIndex був позицією відносно вихідного індексу, це була б інша історія, я думаю, але це не так, як це працює.
jpierson

129
var item = list[oldIndex];

list.RemoveAt(oldIndex);

if (newIndex > oldIndex) newIndex--; 
// the actual index could have shifted due to the removal

list.Insert(newIndex, item);

9
Ваше рішення розбивається, якщо у списку є дві копії елемента, причому одна відбувається перед oldIndex. Ви повинні використовувати RemoveAt, щоб переконатися, що ви отримали правильний.
Аарон Маенпаа

6
Дійсно, підлий край справи
Гаррі Шутлер

1
@GarryShutler Я не бачу, як може змінитися індекс, якщо ми видаляємо та вставляємо один елемент. Зниження newIndexфактично порушує мій тест (див. Мою відповідь нижче).
Бен Фостер

1
Примітка: якщо безпека потоку важлива, це все повинно бути всередині lockтвердження.
rory.ap

1
Я б не використовував це, оскільки це заплутано з кількох причин. Визначення методу Move (oldIndex, newIndex) у списку та виклик Move (15,25), а потім Move (25,15) - це не ідентичність, а своп. Також Move (15,25) змушує елемент переміститись до індексу 24, а не до 25, якого я б очікував. Окрім того, свопінг може бути реалізований за допомогою temp = item [oldindex]; item [oldindex] = предмет [newindex]; item [newindex] = temp; що здається більш ефективним на великих масивах. Також Move (0,0) і Move (0,1) були б однаковими, що також є дивним. А також Move (0, Count -1) не переміщує елемент до кінця.
Wouter

12

Я знаю, що це питання давнє, але я адаптував ЦЕ відповідь коду JavaScript на C #. Сподіваюся, це допомагає

 public static void Move<T>(this List<T> list, int oldIndex, int newIndex)
{

    // exit if possitions are equal or outside array
    if ((oldIndex == newIndex) || (0 > oldIndex) || (oldIndex >= list.Count) || (0 > newIndex) ||
        (newIndex >= list.Count)) return;
    // local variables
    var i = 0;
    T tmp = list[oldIndex];
    // move element down and shift other elements up
    if (oldIndex < newIndex)
    {
        for (i = oldIndex; i < newIndex; i++)
        {
            list[i] = list[i + 1];
        }
    }
        // move element up and shift other elements down
    else
    {
        for (i = oldIndex; i > newIndex; i--)
        {
            list[i] = list[i - 1];
        }
    }
    // put element from position 1 to destination
    list[newIndex] = tmp;
}

9

Список <T>. Видалити () та Список <T> .RemoveAt () не повертає вилучений елемент.

Тому ви повинні використовувати це:

var item = list[oldIndex];
list.RemoveAt(oldIndex);
list.Insert(newIndex, item);

5

Вставте елемент, який зараз oldIndexзнаходиться у, newIndexа потім видаліть початковий екземпляр.

list.Insert(newIndex, list[oldIndex]);
if (newIndex <= oldIndex) ++oldIndex;
list.RemoveAt(oldIndex);

Ви повинні врахувати, що індекс елемента, який ви хочете вилучити, може змінюватися через вставку.


1
Ви повинні видалити, перш ніж вставляти ... Ваше замовлення може спричинити розподіл списку.
Джим Балтер

4

Я створив метод розширення для переміщення елементів у списку.

Індекс не повинен зміщуватися, якщо ми переміщуємо існуючий елемент, оскільки ми переміщуємо елемент до існуючого позиції індексу у списку.

Крайній випадок, на який @Oliver посилається нижче (переміщення елемента до кінця списку) насправді призведе до збою тестів, але це за задумом. Щоб вставити новий елемент в кінці списку, ми просто зателефонували List<T>.Add. не list.Move(predicate, list.Count) має права, оскільки ця позиція індексу не існує до переміщення.

У будь-якому випадку я створив два додаткові методи розширення, MoveToEndі MoveToBeginningджерело яких можна знайти тут .

/// <summary>
/// Extension methods for <see cref="System.Collections.Generic.List{T}"/>
/// </summary>
public static class ListExtensions
{
    /// <summary>
    /// Moves the item matching the <paramref name="itemSelector"/> to the <paramref name="newIndex"/> in a list.
    /// </summary>
    public static void Move<T>(this List<T> list, Predicate<T> itemSelector, int newIndex)
    {
        Ensure.Argument.NotNull(list, "list");
        Ensure.Argument.NotNull(itemSelector, "itemSelector");
        Ensure.Argument.Is(newIndex >= 0, "New index must be greater than or equal to zero.");

        var currentIndex = list.FindIndex(itemSelector);
        Ensure.That<ArgumentException>(currentIndex >= 0, "No item was found that matches the specified selector.");

        // Copy the current item
        var item = list[currentIndex];

        // Remove the item
        list.RemoveAt(currentIndex);

        // Finally add the item at the new index
        list.Insert(newIndex, item);
    }
}

[Subject(typeof(ListExtensions), "Move")]
public class List_Move
{
    static List<int> list;

    public class When_no_matching_item_is_found
    {
        static Exception exception;

        Establish ctx = () => {
            list = new List<int>();
        };

        Because of = ()
            => exception = Catch.Exception(() => list.Move(x => x == 10, 10));

        It Should_throw_an_exception = ()
            => exception.ShouldBeOfType<ArgumentException>();
    }

    public class When_new_index_is_higher
    {
        Establish ctx = () => {
            list = new List<int> { 1, 2, 3, 4, 5 };
        };

        Because of = ()
            => list.Move(x => x == 3, 4); // move 3 to end of list (index 4)

        It Should_be_moved_to_the_specified_index = () =>
            {
                list[0].ShouldEqual(1);
                list[1].ShouldEqual(2);
                list[2].ShouldEqual(4);
                list[3].ShouldEqual(5);
                list[4].ShouldEqual(3);
            };
    }

    public class When_new_index_is_lower
    {
        Establish ctx = () => {
            list = new List<int> { 1, 2, 3, 4, 5 };
        };

        Because of = ()
            => list.Move(x => x == 4, 0); // move 4 to beginning of list (index 0)

        It Should_be_moved_to_the_specified_index = () =>
        {
            list[0].ShouldEqual(4);
            list[1].ShouldEqual(1);
            list[2].ShouldEqual(2);
            list[3].ShouldEqual(3);
            list[4].ShouldEqual(5);
        };
    }
}

Де Ensure.Argumentвизначено?
Олівер

1
Зазвичай List<T>ви можете зателефонувати, Insert(list.Count, element)щоб розмістити щось у кінці списку. Тож вам When_new_index_is_higherслід зателефонувати, list.Move(x => x == 3, 5)що насправді не вдається.
Олівер

3
@Oliver в звичайному, List<T>я просто закликаю .Addвставити новий елемент в кінець списку. Під час переміщення окремих елементів ми ніколи не збільшуємо початковий розмір індексу, оскільки ми видаляємо лише один елемент і знову вставляємо його. Якщо ви натиснете посилання у моїй відповіді, ви знайдете код для Ensure.Argument.
Бен Фостер

Ви розраховуєте, що індекс призначення - це позиція, а не між двома елементами. Хоча для деяких випадків використання це добре, для інших це не працює. Крім того, ваш хід не підтримує переміщення до кінця (як зауважив Олівер), але там, де у вашому коді не вказано це обмеження. Це також є протиінтуїтивним, якщо я маю список з 20 елементами і хочу перемістити елемент 10 до кінця, я б очікував, що метод Move впорається з цим, замість того, щоб знайти збереження посилання на об'єкт, видалити об'єкт зі списку і додати об’єкт.
Trisped

1
@Trisped на насправді , якщо ви читали мою відповідь, переміщення елемента в кінець / початок списку буде підтримуватися. Ви можете побачити характеристики тут . Так, мій код очікує, що індекс буде дійсною (існуючою) позицією у списку. Ми переміщуємо предмети, не вставляючи їх.
Бен Фостер

1

Я очікував би будь-якого:

// Makes sure item is at newIndex after the operation
T item = list[oldIndex];
list.RemoveAt(oldIndex);
list.Insert(newIndex, item);

... або:

// Makes sure relative ordering of newIndex is preserved after the operation, 
// meaning that the item may actually be inserted at newIndex - 1 
T item = list[oldIndex];
list.RemoveAt(oldIndex);
newIndex = (newIndex > oldIndex ? newIndex - 1, newIndex)
list.Insert(newIndex, item);

... зробив би трюк, але я не маю VS на цій машині для перевірки.


1
@GarryShutler Це залежить від ситуації. Якщо ваш інтерфейс дозволяє користувачеві вказати позицію в списку за індексом, вони будуть заплутані, коли вони скажуть пункт 15 перейти до 20, але замість цього він переміститься на 19. Якщо ваш інтерфейс дозволяє користувачеві перетягувати елемент між іншими у списку, тоді було б сенс декрементувати, newIndexякщо він буде після oldIndex.
Trisped

-1

Найпростіший спосіб:

list[newIndex] = list[oldIndex];
list.RemoveAt(oldIndex);

EDIT

Питання не дуже зрозуміло ... Оскільки нам все одно, куди list[newIndex]йде елемент, я думаю, що найпростіший спосіб зробити це наступним чином (із методом розширення або без нього):

    public static void Move<T>(this List<T> list, int oldIndex, int newIndex)
    {
        T aux = list[newIndex];
        list[newIndex] = list[oldIndex];
        list[oldIndex] = aux;
    }

Це рішення є найшвидшим, оскільки воно не включає вставки / видалення списків.


4
Це замінить елемент у newIndex, а не вставить.
Гаррі Шутлер

@Garry Чи не кінцевий результат буде однаковим?
Ozgur Ozcitak

4
Ні, ви втратите значення в newIndex, що не відбудеться, якщо ви вставили.
Гаррі Шутлер

-2

Більш прості хлопці просто роблять це

    public void MoveUp(object item,List Concepts){

        int ind = Concepts.IndexOf(item.ToString());

        if (ind != 0)
        {
            Concepts.RemoveAt(ind);
            Concepts.Insert(ind-1,item.ToString());
            obtenernombres();
            NotifyPropertyChanged("Concepts");
        }}

Зробіть те ж саме з MoveDown, але змініть if на "if (ind! = Concepts.Count ())" та Concepts.Insert (ind + 1, item.ToString ());


-3

Ось як я реалізував метод розширення елемента переміщення. Він досить добре справляється з переміщенням до / після і до крайностей для елементів.

public static void MoveElement<T>(this IList<T> list, int fromIndex, int toIndex)
{
  if (!fromIndex.InRange(0, list.Count - 1))
  {
    throw new ArgumentException("From index is invalid");
  }
  if (!toIndex.InRange(0, list.Count - 1))
  {
    throw new ArgumentException("To index is invalid");
  }

  if (fromIndex == toIndex) return;

  var element = list[fromIndex];

  if (fromIndex > toIndex)
  {
    list.RemoveAt(fromIndex);
    list.Insert(toIndex, element);
  }
  else
  {
    list.Insert(toIndex + 1, element);
    list.RemoveAt(fromIndex);
  }
}

2
Це дублікат відповіді від Франциско.
nivs1978
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.