รายการทั่วไป - ย้ายรายการภายในรายการ


155

ดังนั้นฉันมีรายการทั่วไปและoldIndexและnewIndexค่า

ฉันต้องการย้ายรายการoldIndexไปที่newIndex... อย่างง่ายดายที่สุด

ข้อเสนอแนะใด ๆ

บันทึก

รายการควรจะสิ้นสุดระหว่างรายการที่(newIndex - 1)และnewIndex ก่อนที่จะถูกลบออก


1
คุณควรเปลี่ยนคำตอบที่คุณเลือก คนที่มีnewIndex--ไม่ส่งผลให้เกิดพฤติกรรมที่คุณบอกว่าคุณต้องการ
Miral

1
@Miral - คำตอบใดที่คุณคิดว่าควรเป็นคำตอบที่ยอมรับได้
Richard Ev

4
jpierson ของ มันส่งผลให้วัตถุที่เคยเป็นที่ oldIndex ก่อนที่จะย้ายไปที่ newIndex หลังจากการย้าย นี่เป็นพฤติกรรมที่น่าแปลกใจน้อยที่สุด (และเป็นสิ่งที่ฉันต้องการเมื่อฉันเขียนโค้ดการเรียงลำดับใหม่ของ drag'n'drop) ให้สิทธิ์เขากำลังพูดถึงObservableCollectionและไม่ใช่แบบทั่วไปList<T>แต่มันเล็กน้อยที่จะเพียงแค่สลับการเรียกเมธอดเพื่อให้ได้ผลลัพธ์เดียวกัน
Miral

พฤติกรรมที่ร้องขอ (และดำเนินการอย่างถูกต้องในคำตอบนี้ ) เพื่อย้ายรายการระหว่างรายการที่[newIndex - 1]และ[newIndex]ไม่สามารถย้อนกลับได้ Move(1, 3); Move(3, 1);จะไม่ส่งคืนรายการกลับสู่สถานะเริ่มต้น ในขณะเดียวกันมีพฤติกรรมที่แตกต่างให้ไว้ในObservableCollectionและกล่าวถึงในคำตอบนี้ซึ่งเป็นตัวผกผัน
Lightman

คำตอบ:


138

ฉันรู้ว่าคุณพูดว่า "รายการทั่วไป" แต่คุณไม่ได้ระบุว่าคุณจำเป็นต้องใช้รายการ (T)คลาสดังนั้นนี่คือภาพที่แตกต่างออกไป

ObservableCollection (T)ชั้นมีวิธีการย้ายที่ไม่ตรงกับสิ่งที่คุณต้องการ

public void Move(int oldIndex, int newIndex)

ภายใต้การใช้งานโดยทั่วไปเช่นนี้

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

ดังนั้นในขณะที่คุณสามารถเห็นวิธีการสลับที่คนอื่นแนะนำเป็นหลักสิ่งที่ObservableCollectionทำในวิธีการย้ายของตัวเอง

UPDATE 2015-12-30:คุณสามารถดูซอร์สโค้ดของเมธอดMove and MoveItemใน corefx ได้ด้วยตนเองโดยไม่ต้องใช้ Reflector / ILSpy เนื่องจาก. NET เป็นโอเพ่นซอร์ส


28
ฉันสงสัยว่าทำไมสิ่งนี้จึงไม่ถูกนำไปใช้กับรายการ <T> เช่นกันใครที่จะทำให้เข้าใจถึงสิ่งนี้
Andreas

อะไรคือความแตกต่างระหว่างรายการทั่วไปและคลาส List (T) ฉันคิดว่าพวกเขาเหมือนกัน :(
BKSpurgeon

รายการ "ทั่วไป" อาจหมายถึงประเภทของรายการหรือคอลเลกชันเช่นโครงสร้างข้อมูลใด ๆ ใน .NET ซึ่งอาจรวมถึงการ ObservableCollection (T) หรือชั้นเรียนอื่น ๆ ซึ่งอาจใช้listyอินเตอร์เฟซเช่น IList / ICollection / IEnumerable
jpierson

6
มีใครช่วยอธิบายได้โปรดทำไมไม่มีการเปลี่ยนดัชนีปลายทาง (ในกรณีที่มันใหญ่กว่าดัชนีต้นทาง) แน่นอน
Vladius

@ 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 เพื่อให้แน่ใจว่าคุณได้รับสิ่งที่ถูกต้อง
Aaron Maenpaa

6
อันที่จริงกรณีขอบลับ ๆ ล่อๆ
Garry Shutler

1
@ GarryShutler ฉันไม่สามารถดูว่าดัชนีสามารถเปลี่ยนได้อย่างไรถ้าเราลบออกแล้วแทรกรายการเดียว การลดการnewIndexทดสอบของฉันจริง ๆ (ดูคำตอบของฉันด้านล่าง)
เบ็นฟอสเตอร์

1
หมายเหตุ: หากความปลอดภัยของเธรดนั้นสำคัญสิ่งนี้ควรอยู่ภายในlockข้อความ
rory.ap

1
ฉันจะไม่ใช้สิ่งนี้เนื่องจากมันทำให้สับสนด้วยเหตุผลหลายประการ การกำหนดวิธีการย้าย (oldIndex, newIndex) ในรายการและโทรย้าย (15,25) แล้วย้าย (25,15) ไม่ใช่ตัวตน แต่สลับกัน ย้ายด้วย (15,25) ทำให้การย้ายรายการเป็นดัชนี 24 และไม่ใช่ที่ 25 ซึ่งฉันคาดหวัง นอกจากนี้การสลับสามารถนำมาใช้โดย temp = item [oldindex]; รายการ [oldindex] = รายการ [newindex]; รายการ [newindex] = อุณหภูมิ; ซึ่งดูเหมือนว่ามีประสิทธิภาพมากขึ้นในอาร์เรย์ขนาดใหญ่ เช่นกันย้าย (0,0) และย้าย (0,1) จะเหมือนกันซึ่งก็แปลกด้วย และย้าย (0, นับ -1) ไม่ย้ายรายการไปยังจุดสิ้นสุด
Wouter

12

ฉันรู้ว่าคำถามนี้เป็นรุ่นเก่า แต่ผมดัดแปลงมานี้การตอบสนองของโค้ดจาวาสคริปต์เพื่อ 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

List <T> .Remove () และ List <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
คุณควรลบก่อนที่จะแทรก ... คำสั่งซื้อของคุณอาจทำให้รายการทำการจัดสรร
Jim Balter

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กำหนด?
Oliver

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ถ้าคุณคลิกลิงก์ในคำตอบของฉันที่คุณจะพบรหัสสำหรับ
เบ็นฟอสเตอร์

โซลูชันของคุณคาดว่าดัชนีปลายทางจะเป็นตำแหน่งไม่ใช่ระหว่างสององค์ประกอบ แม้ว่าจะใช้งานได้ดีในบางกรณี แต่ก็ไม่ได้ผลสำหรับคนอื่น นอกจากนี้การย้ายของคุณไม่สนับสนุนการย้ายไปยังจุดสิ้นสุด (ตามที่ระบุไว้โดย Oliver) แต่ไม่มีรหัสใดที่คุณระบุข้อ จำกัด นี้ นอกจากนี้ยังเป็นเรื่องง่ายหากฉันมีรายการที่มีองค์ประกอบ 20 รายการและต้องการย้ายองค์ประกอบ 10 ไปยังจุดสิ้นสุดฉันคาดว่าวิธีการย้ายจะจัดการกับสิ่งนี้แทนที่จะต้องค้นหาบันทึกการอ้างอิงวัตถุเอาวัตถุออกจากรายการ และเพิ่มวัตถุ
ริป

1
@Trisped จริงถ้าคุณอ่านคำตอบของฉันย้ายรายการไปยังจุดสิ้นสุด / จุดเริ่มต้นของรายการจะได้รับการสนับสนุน ท่านสามารถเข้าดูรายละเอียดที่นี่ ใช่รหัสของฉันคาดว่าดัชนีจะเป็นตำแหน่งที่ถูกต้อง (มีอยู่) ภายในรายการ เรากำลังเคลื่อนย้ายสิ่งของไม่ได้ใส่เข้าไป
Ben Foster

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
ริป

-1

วิธีที่ง่ายที่สุด:

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

แก้ไข

คำถามไม่ชัดเจนมาก ... เนื่องจากเราไม่สนใจว่า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 Shutler

@Garry ผลลัพธ์สุดท้ายจะไม่เหมือนเดิมหรือไม่
Ozgur Ozcitak

4
ไม่คุณจะต้องเสียค่าที่ newIndex ซึ่งจะไม่เกิดขึ้นหากคุณแทรก
Garry Shutler

-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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.