ลบองค์ประกอบของอาร์เรย์ปกติ


135

ฉันมีอาร์เรย์ของวัตถุ Foo ฉันจะลบองค์ประกอบที่สองของอาร์เรย์ได้อย่างไร

ฉันต้องการสิ่งที่คล้ายกับRemoveAt()แต่สำหรับอาร์เรย์ปกติ


1
ใช้System.Collections.ObjectModel.Collection<Foo>.
abatishchev

1
สำหรับเกมของฉันฉันใช้โครงสร้างข้อมูล "null at index" โดยทั่วไปอาร์เรย์ภายใน (บัฟเฟอร์) มีขนาดคงที่และแทนที่จะลบดัชนีและปรับขนาดอาร์เรย์ฉันแค่ทำให้ดัชนีเป็นโมฆะ เมื่อฉันต้องการเพิ่มรายการฉันเพียงแค่ค้นหาดัชนีแรกที่ไม่ใช่ค่าว่างและวางไว้ที่นั่น ใช้งานได้ดี แต่ไม่ใช่สำหรับทุกอย่าง
Krythic

คำตอบ:


202

หากคุณไม่ต้องการใช้รายการ:

var foos = new List<Foo>(array);
foos.RemoveAt(index);
return foos.ToArray();

คุณสามารถลองวิธีการขยายนี้ที่ฉันยังไม่ได้ทดสอบ:

public static T[] RemoveAt<T>(this T[] source, int index)
{
    T[] dest = new T[source.Length - 1];
    if( index > 0 )
        Array.Copy(source, 0, dest, 0, index);

    if( index < source.Length - 1 )
        Array.Copy(source, index + 1, dest, index, source.Length - index - 1);

    return dest;
}

และใช้มันเช่น:

Foo[] bar = GetFoos();
bar = bar.RemoveAt(2);

8
ตัวอย่างแรกที่ให้ในคำตอบนี้มีประสิทธิภาพน้อยกว่าตัวอย่างที่สองมาก ต้องใช้สำเนาอาร์เรย์สองชุดและเปลี่ยนทุกอย่างหลังดัชนีมากกว่าหนึ่งสำเนาอาร์เรย์ที่เลือก
Martin Brown

2
+1 แน่นอน แต่เราสามารถใช้ list ได้เช่นกันหรือ List <Foo> list = new List <Foll> (GetFoos ()); list.Remove (my_foo); list.RemoveAt (2); โดยที่ GetFoos () จะส่งคืนอาร์เรย์ของ Foos !!!!
shahjapan

2
บรรทัดแรกในเมธอดควรพูดว่า 'source.Length' แทน 'array.Length'
Nelson

1
นอกจากนี้โปรดทราบว่าตัวแปรใด ๆ ที่จัดเก็บการอ้างอิงไปยังอาร์เรย์เดิมจะยังคงมีข้อมูลเดิมอยู่และการเปรียบเทียบความเท่าเทียมกันของการอ้างอิงระหว่างอาร์เรย์ในแหล่งที่มาและอาร์เรย์เอาต์พุตจะส่งคืนค่าลบ
bkqc

1
@MartinBrown ที่จริงแล้วการแปลงรายการเป็น \ from และ array นั้นช้ากว่าการคัดลอกอาร์เรย์มาก (ซึ่งสามารถคัดลอกข้อมูลด้วยความเร็วสูงสุดที่ CPU อนุญาตโดยใช้คำสั่ง ASM เพียงไม่กี่คำ) นอกจากนี้การเปลี่ยนรายการยังทำได้เร็วมากเพราะเป็นเพียงการสลับตัวชี้สองสามตัวและลบข้อมูลโหนดออก (ซึ่งมีขนาดเพียง 8 ไบต์ [บวกอีก 16 สำหรับ head \ tail pointers] ในกรณีนี้)
krowe2

67

ธรรมชาติของอาร์เรย์คือความยาวไม่เปลี่ยนรูป คุณไม่สามารถเพิ่มหรือลบรายการอาร์เรย์ใด ๆ

คุณจะต้องสร้างอาร์เรย์ใหม่ที่สั้นกว่าหนึ่งองค์ประกอบและคัดลอกรายการเก่าไปยังอาร์เรย์ใหม่โดยไม่รวมองค์ประกอบที่คุณต้องการลบ

ดังนั้นจึงน่าจะดีกว่าที่จะใช้ List แทนอาร์เรย์


4
แปลงอาร์เรย์เป็นรายการList<mydatatype> array = new List<mydatatype>(arrayofmydatatype)
Immortal Blue

1
@ImmortalBlue หรือvar myList = myArray.ToList();ใช้Enumerable.ToList()วิธีการจากSystem.Linqเนมสเปซ
Dyndrilliac

58

ฉันใช้วิธีนี้เพื่อลบองค์ประกอบออกจากอาร์เรย์วัตถุ ในสถานการณ์ของฉันอาร์เรย์ของฉันมีความยาวน้อย ดังนั้นหากคุณมีอาร์เรย์ขนาดใหญ่คุณอาจต้องการโซลูชันอื่น

private int[] RemoveIndices(int[] IndicesArray, int RemoveAt)
{
    int[] newIndicesArray = new int[IndicesArray.Length - 1];

    int i = 0;
    int j = 0;
    while (i < IndicesArray.Length)
    {
        if (i != RemoveAt)
        {
            newIndicesArray[j] = IndicesArray[i];
            j++;
        }

        i++;
    }

    return newIndicesArray;
}

7
โดยส่วนตัวแล้วฉันชอบคำตอบนี้ดีกว่าคำตอบที่ได้รับการยอมรับ ควรมีประสิทธิภาพพอ ๆ กันและอ่านง่ายกว่ามาก ฉันสามารถดูและรู้ว่ามันถูกต้อง ฉันจะต้องทดสอบอีกอันเพื่อให้แน่ใจว่าสำเนาเหล่านั้นเขียนถูกต้อง
oillio

1
เป็นเรื่องที่น่าเสียดายคำตอบนี้ต่ำมากเมื่อมันดีกว่าสองข้อข้างต้น
Sepulchritude

Aaarhg นั่นคือคำตอบที่ฉันกำลังมองหา! นี่เป็นวิธีที่ดีที่สุดโดยไม่มีรายการ
Jordi Huertas

47

LINQ โซลูชันหนึ่งบรรทัด:

myArray = myArray.Where((source, index) => index != 1).ToArray();

1ในตัวอย่างที่เป็นดัชนีขององค์ประกอบในการลบ - ในตัวอย่างนี้ต่อคำถามเดิมองค์ประกอบที่ 2 (มี1เป็นองค์ประกอบที่สองใน C # zero-based การจัดทำดัชนีอาร์เรย์)

ตัวอย่างที่สมบูรณ์ยิ่งขึ้น:

string[] myArray = { "a", "b", "c", "d", "e" };
int indexToRemove = 1;
myArray = myArray.Where((source, index) => index != indexToRemove).ToArray();

หลังจากใช้ข้อมูลโค้ดที่ค่าของจะmyArray{ "a", "c", "d", "e" }


1
สำหรับพื้นที่ที่ต้องการการเข้าถึงที่มีประสิทธิภาพสูง / บ่อยครั้งไม่แนะนำให้ใช้ LINQ
Krythic

3
@Krythic นั่นเป็นความคิดเห็นที่ยุติธรรม ทำงานเป็นพัน ๆ ครั้งในวง จำกัด ประสิทธิภาพของโซลูชันนี้ไม่ดีเท่ากับโซลูชันที่ได้รับการโหวตสูงอื่น ๆ ในหน้านี้: dotnetfiddle.net/z9Xkpn
Jon Schneider

9

นี่เป็นวิธีการลบองค์ประกอบอาร์เรย์ ณ . Net 3.5 โดยไม่ต้องคัดลอกไปยังอาร์เรย์อื่นโดยใช้อินสแตนซ์อาร์เรย์เดียวกันกับArray.Resize<T>:

public static void RemoveAt<T>(ref T[] arr, int index)
{
    for (int a = index; a < arr.Length - 1; a++)
    {
        // moving elements downwards, to fill the gap at [index]
        arr[a] = arr[a + 1];
    }
    // finally, let's decrement Array's size by one
    Array.Resize(ref arr, arr.Length - 1);
}

2
"โดยไม่ต้องคัดลอกไปยังอาร์เรย์อื่น" - ตามเอกสารที่เชื่อมโยง Array.Resize จะจัดสรรอาร์เรย์ใหม่เบื้องหลังและคัดลอกองค์ประกอบจากอาร์เรย์เก่าไปยังอาร์เรย์ใหม่ ถึงกระนั้นฉันก็ชอบความกระชับของการแก้ปัญหานี้
Jon Schneider

ดีมากและชัดเจนถ้าคุณแน่ใจว่าเป็นอาร์เรย์ที่ค่อนข้างเล็ก
Darren

1
ต่อจากความคิดเห็นของ @ JonSchneider ไม่ใช่ "อินสแตนซ์อาร์เรย์เดียวกัน" ซึ่งเป็นเหตุผลที่คุณต้องใช้refเมื่อคุณเรียกใช้Resizeเมธอด ความยาวของอินสแตนซ์อาร์เรย์ได้รับการแก้ไขและไม่เปลี่ยนรูป
Jeppe Stig Nielsen

2
หากลำดับขององค์ประกอบไม่สำคัญแทนที่จะย้ายองค์ประกอบทั้งหมดลงคุณสามารถสลับองค์ประกอบที่ดัชนีกับองค์ประกอบสุดท้ายแล้วปรับขนาด: arr [index] = arr [arr.Length - 1]; Array.Resize (อ้างอิง arr, arr.Length - 1);
Bartel

5

นี่คือเวอร์ชันเก่าที่ฉันมีซึ่งใช้งานได้กับ. NET framework เวอร์ชัน 1.0 และไม่จำเป็นต้องใช้ประเภททั่วไป

public static Array RemoveAt(Array source, int index)
{
    if (source == null)
        throw new ArgumentNullException("source");

    if (0 > index || index >= source.Length)
        throw new ArgumentOutOfRangeException("index", index, "index is outside the bounds of source array");

    Array dest = Array.CreateInstance(source.GetType().GetElementType(), source.Length - 1);
    Array.Copy(source, 0, dest, 0, index);
    Array.Copy(source, index + 1, dest, index, source.Length - index - 1);

    return dest;
}

ใช้ในลักษณะนี้:

class Program
{
    static void Main(string[] args)
    {
        string[] x = new string[20];
        for (int i = 0; i < x.Length; i++)
            x[i] = (i+1).ToString();

        string[] y = (string[])MyArrayFunctions.RemoveAt(x, 3);

        for (int i = 0; i < y.Length; i++)
            Console.WriteLine(y[i]);
    }
}

3

ไม่ใช่วิธีที่จะดำเนินต่อไปอย่างแน่นอน แต่ถ้าสถานการณ์ไม่สำคัญและคุณให้ความสำคัญกับเวลาของคุณคุณสามารถลองสิ่งนี้สำหรับประเภทที่เป็นโมฆะได้

Foos[index] = null

และตรวจสอบรายการว่างในตรรกะของคุณในภายหลัง ..


นี่คือวิธีที่ฉันทำเพื่อเกมของฉัน ใช้บัฟเฟอร์ที่เป็นโมฆะสำหรับพื้นที่ที่มีการเปลี่ยนแปลงบ่อยมาก
Krythic

2

ตามปกติฉันไปงานปาร์ตี้สาย ...

ฉันต้องการเพิ่มตัวเลือกอื่นในรายการโซลูชันที่ดีที่มีอยู่แล้ว =)
ฉันเห็นว่านี่เป็นโอกาสที่ดีสำหรับส่วนขยาย

อ้างอิง: http://msdn.microsoft.com/en-us/library/bb311042.aspx

ดังนั้นเราจึงกำหนดคลาสคงที่และในนั้นวิธีการของเรา
หลังจากนั้นเราสามารถใช้วิธีการเพิ่มเติมของเราโดยเต็มใจ =)

using System;

namespace FunctionTesting {

    // The class doesn't matter, as long as it's static
    public static class SomeRandomClassWhoseNameDoesntMatter {

        // Here's the actual method that extends arrays
        public static T[] RemoveAt<T>( this T[] oArray, int idx ) {
            T[] nArray = new T[oArray.Length - 1];
            for( int i = 0; i < nArray.Length; ++i ) {
                nArray[i] = ( i < idx ) ? oArray[i] : oArray[i + 1];
            }
            return nArray;
        }
    }

    // Sample usage...
    class Program {
        static void Main( string[] args ) {
            string[] myStrArray = { "Zero", "One", "Two", "Three" };
            Console.WriteLine( String.Join( " ", myStrArray ) );
            myStrArray = myStrArray.RemoveAt( 2 );
            Console.WriteLine( String.Join( " ", myStrArray ) );
            /* Output
             * "Zero One Two Three"
             * "Zero One Three"
             */

            int[] myIntArray = { 0, 1, 2, 3 };
            Console.WriteLine( String.Join( " ", myIntArray ) );
            myIntArray = myIntArray.RemoveAt( 2 );
            Console.WriteLine( String.Join( " ", myIntArray ) );
            /* Output
             * "0 1 2 3"
             * "0 1 3"
             */
        }
    }
}

2

ลองใช้รหัสด้านล่าง:

myArray = myArray.Where(s => (myArray.IndexOf(s) != indexValue)).ToArray();

หรือ

myArray = myArray.Where(s => (s != "not_this")).ToArray();

1

นี่คือวิธีที่ฉันทำ ...

    public static ElementDefinitionImpl[] RemoveElementDefAt(
        ElementDefinition[] oldList,
        int removeIndex
    )
    {
        ElementDefinitionImpl[] newElementDefList = new ElementDefinitionImpl[ oldList.Length - 1 ];

        int offset = 0;
        for ( int index = 0; index < oldList.Length; index++ )
        {
            ElementDefinitionImpl elementDef = oldList[ index ] as ElementDefinitionImpl;
            if ( index == removeIndex )
            {
                //  This is the one we want to remove, so we won't copy it.  But 
                //  every subsequent elementDef will by shifted down by one.
                offset = -1;
            }
            else
            {
                newElementDefList[ index + offset ] = elementDef;
            }
        }
        return newElementDefList;
    }

1

ในอาร์เรย์ปกติคุณต้องสลับรายการอาร์เรย์ทั้งหมดที่อยู่เหนือ 2 จากนั้นปรับขนาดโดยใช้วิธีการปรับขนาด คุณอาจจะดีกว่าการใช้ ArrayList


1
    private int[] removeFromArray(int[] array, int id)
    {
        int difference = 0, currentValue=0;
        //get new Array length
        for (int i=0; i<array.Length; i++)
        {
            if (array[i]==id)
            {
                difference += 1;
            }
        }
        //create new array
        int[] newArray = new int[array.Length-difference];
        for (int i = 0; i < array.Length; i++ )
        {
            if (array[i] != id)
            {
                newArray[currentValue] = array[i];
                currentValue += 1;
            }
        }

        return newArray;
    }

0

นี่คือชุดวิธีช่วยเหลือเล็ก ๆ น้อย ๆ ที่ฉันสร้างขึ้นจากคำตอบที่มีอยู่บางส่วน ใช้ทั้งส่วนขยายและวิธีการแบบคงที่พร้อมพารามิเตอร์อ้างอิงเพื่อความเหมาะสมสูงสุด:

public static class Arr
{
    public static int IndexOf<TElement>(this TElement[] Source, TElement Element)
    {
        for (var i = 0; i < Source.Length; i++)
        {
            if (Source[i].Equals(Element))
                return i;
        }

        return -1;
    }

    public static TElement[] Add<TElement>(ref TElement[] Source, params TElement[] Elements)
    {
        var OldLength = Source.Length;
        Array.Resize(ref Source, OldLength + Elements.Length);

        for (int j = 0, Count = Elements.Length; j < Count; j++)
            Source[OldLength + j] = Elements[j];

        return Source;
    }

    public static TElement[] New<TElement>(params TElement[] Elements)
    {
        return Elements ?? new TElement[0];
    }

    public static void Remove<TElement>(ref TElement[] Source, params TElement[] Elements)
    {
        foreach (var i in Elements)
            RemoveAt(ref Source, Source.IndexOf(i));
    }

    public static void RemoveAt<TElement>(ref TElement[] Source, int Index)
    {
        var Result = new TElement[Source.Length - 1];

        if (Index > 0)
            Array.Copy(Source, 0, Result, 0, Index);

        if (Index < Source.Length - 1)
            Array.Copy(Source, Index + 1, Result, Index, Source.Length - Index - 1);

        Source = Result;
    }
}

ประสิทธิภาพที่ดีเป็นสิ่งที่ดี แต่มันอาจจะดีขึ้น Removeอาศัยและอาร์เรย์ใหม่ถูกสร้างขึ้นสำหรับองค์ประกอบที่คุณต้องการที่จะลบโดยการเรียกแต่ละIndexOfRemoveAt

IndexOfเป็นวิธีการขยายเพียงวิธีเดียวเนื่องจากไม่จำเป็นต้องส่งคืนอาร์เรย์เดิม Newยอมรับหลายองค์ประกอบบางประเภทเพื่อสร้างอาร์เรย์ใหม่ของประเภทดังกล่าว วิธีการอื่น ๆ ทั้งหมดต้องยอมรับอาร์เรย์ดั้งเดิมเป็นข้อมูลอ้างอิงดังนั้นจึงไม่จำเป็นต้องกำหนดผลลัพธ์ในภายหลังเนื่องจากเกิดขึ้นภายในแล้ว

ฉันจะกำหนดMergeวิธีการรวมสองอาร์เรย์ อย่างไรก็ตามสามารถทำได้ด้วยAddวิธีการอยู่แล้วโดยส่งผ่านอาร์เรย์จริงกับหลายองค์ประกอบแต่ละรายการ ดังนั้นAddอาจใช้สองวิธีต่อไปนี้เพื่อรวมสองชุดขององค์ประกอบ:

Arr.Add<string>(ref myArray, "A", "B", "C");

หรือ

Arr.Add<string>(ref myArray, anotherArray);

-1

ฉันรู้ว่าบทความนี้มีอายุสิบปีและอาจถึงแก่ความตาย แต่นี่คือสิ่งที่ฉันจะลองทำ:

ใช้ IEnumerable.Skip () วิธีการที่พบใน System.Linq มันจะข้ามองค์ประกอบที่เลือกจากอาร์เรย์และส่งคืนสำเนาของอาร์เรย์อีกชุดที่มีเฉพาะทุกอย่างยกเว้นวัตถุที่เลือก จากนั้นทำซ้ำทุกองค์ประกอบที่คุณต้องการลบและหลังจากนั้นให้บันทึกลงในตัวแปร

ตัวอย่างเช่นถ้าเรามีอาร์เรย์ชื่อ "ตัวอย่าง" (ประเภท int []) ที่มีตัวเลข 5 ตัว เราต้องการลบอันที่ 2 ออกจึงลอง "Sample.Skip (2);" ควรส่งคืนอาร์เรย์เดียวกันยกเว้นไม่มีหมายเลขที่ 2


วิธีนี้ไม่เพียงแค่ข้ามองค์ประกอบตามจำนวนที่ระบุในลำดับแล้วส่งคืนองค์ประกอบที่เหลือ ? ในตัวอย่างของคุณคุณจะ "ข้าม" สององค์ประกอบแรกของรายการทั่วไปและไม่ใช่แค่องค์ประกอบที่ 2!
xnr_z

-4

ขั้นตอนแรก
คุณต้องแปลงอาร์เรย์เป็นรายการคุณสามารถเขียนวิธีการขยายได้เช่นนี้

// Convert An array of string  to a list of string
public static List<string> ConnvertArrayToList(this string [] array) {

    // DECLARE a list of string and add all element of the array into it

    List<string> myList = new List<string>();
    foreach( string s in array){
        myList.Add(s);
    }
    return myList;
} 

ขั้นตอนที่สอง
เขียนวิธีการขยายเพื่อแปลงกลับรายการเป็นอาร์เรย์

// convert a list of string to an array 
public static string[] ConvertListToArray(this List<string> list) {

    string[] array = new string[list.Capacity];
    array = list.Select(i => i.ToString()).ToArray();
    return array;
}

ขั้นตอนสุดท้าย
เขียนวิธีสุดท้ายของคุณ แต่อย่าลืมลบองค์ประกอบที่ดัชนีก่อนที่จะแปลงกลับเป็นอาร์เรย์เช่นการแสดงโค้ด

public static string[] removeAt(string[] array, int index) {

    List<string> myList = array.ConnvertArrayToList();
    myList.RemoveAt(index);
    return myList.ConvertListToArray();
} 

โค้ดตัวอย่างสามารถพบได้ในบล็อกของฉันติดตามต่อไป


13
นี่เป็นเรื่องบ้าเล็กน้อยเมื่อพิจารณาถึงการมีอยู่ของ.ToArray()และตัวList<T>สร้างซึ่งใช้ลำดับที่มีอยู่ ...
user7116
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.