Usuń element zwykłej tablicy

137

Mam tablicę obiektów Foo. Jak usunąć drugi element tablicy?

Potrzebuję czegoś podobnego, RemoveAt()ale dla zwykłej tablicy.

leora
źródło
1
Użyj System.Collections.ObjectModel.Collection<Foo>.
abatishchev
1
W mojej grze korzystałem z danych strukturalnych „zero na indeksie”. Zasadniczo wewnętrzna tablica (bufor) ma rozmiar statyczny i zamiast usuwać indeks i zmieniać rozmiar tablicy, po prostu ustawiam indeks na null. Kiedy muszę dodać element, po prostu znajduję pierwszy niezerowy indeks i umieszczam go tam. Działa całkiem dobrze, ale oczywiście nie do wszystkiego.
Krythic

Odpowiedzi:

204

Jeśli nie chcesz korzystać z listy:

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

Możesz wypróbować tę metodę rozszerzenia, której tak naprawdę nie testowałem:

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;
}

I używaj go jak:

Foo[] bar = GetFoos();
bar = bar.RemoveAt(2);
Andrew Kennan
źródło
8
Pierwszy przykład podany w tej odpowiedzi jest znacznie mniej skuteczny niż drugi. Wymaga dwóch kopii tablicy i przesunięcia wszystkiego po indeksie, a nie jednej selektywnej kopii tablicy.
Martin Brown,
2
+1 oczywiście, ale możemy też użyć list LUB List <Foo> list = new List <Foll> (GetFoos ()); list.Remove (my_foo); list.RemoveAt (2); gdzie GetFoos () zwróci tablicę Foos !!!!
shahjapan
2
Pierwsza linia w metodzie powinna zawierać tekst „source.Length” zamiast „array.Length”.
Nelson
1
Należy również pamiętać, że każda zmienna przechowująca odniesienie do oryginalnej tablicy będzie nadal zawierać oryginalne dane i że każde porównanie równości odniesienia między tablicą w źródle a tablicą wyjściową zwróci wartość ujemną.
bkqc
1
@MartinBrown W rzeczywistości konwersja listy na \ from i array jest znacznie wolniejsza niż kopia tablicy (która jest w stanie skopiować dane z maksymalną prędkością dozwoloną przez procesor za pomocą zaledwie kilku instrukcji ASM). Ponadto przesuwanie listy jest bardzo szybkie, ponieważ jest to tylko kwestia zamiany kilku wskaźników i usunięcia danych węzła (w tym przypadku jest to tylko 8 bajtów [plus kolejne 16 dla wskaźników nagłówka / końca]).
krowe2
68

Naturą tablic jest to, że ich długość jest niezmienna. Nie możesz dodawać ani usuwać żadnych elementów tablicy.

Będziesz musiał utworzyć nową tablicę, która jest o jeden element krótsza, i skopiować stare elementy do nowej tablicy, z wyłączeniem elementu, który chcesz usunąć.

Więc prawdopodobnie lepiej jest użyć listy zamiast tablicy.

Sebastian Dietz
źródło
4
Konwertuj tablicę na listęList<mydatatype> array = new List<mydatatype>(arrayofmydatatype)
Immortal Blue
1
@ImmortalBlue lub po prostu var myList = myArray.ToList();używając Enumerable.ToList()metody z System.Linqprzestrzeni nazw.
Dyndrilliac
59

Używam tej metody do usuwania elementu z tablicy obiektów. W mojej sytuacji moje tablice mają małą długość. Więc jeśli masz duże tablice, możesz potrzebować innego rozwiązania.

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;
}
EdHellyer
źródło
7
Osobiście podoba mi się ta odpowiedź bardziej niż zaakceptowana odpowiedź. Powinien być równie wydajny i znacznie łatwiejszy do odczytania. Mogę na to spojrzeć i wiem, że jest poprawna. Musiałbym przetestować drugą, aby upewnić się, że te kopie zostały napisane poprawnie.
oillio
1
Naprawdę szkoda, że ​​ta odpowiedź jest tak niska, podczas gdy jest o wiele lepsza niż dwie powyżej.
Sepulchritude
Aaarhg, to jest odpowiedź, której szukałem! To najlepsza metoda bez list.
Jordi Huertas
47

Rozwiązanie jednowierszowe LINQ:

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

W 1tym przykładzie jest to indeks elementu do usunięcia - w tym przykładzie, zgodnie z oryginalnym pytaniem, drugi element ( 1będący drugim elementem w C # indeksowaniu tablicy od zera).

Bardziej kompletny przykład:

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

Po uruchomieniu tego fragmentu wartość myArraywill będzie { "a", "c", "d", "e" }.

Jon Schneider
źródło
1
W przypadku obszarów wymagających wysokiej wydajności / częstego dostępu LINQ nie jest zalecane.
Krythic
3
@Krythic To uczciwy komentarz. Uruchomione tysiące razy w ciasnej pętli, wydajność tego rozwiązania nie jest tak dobra, jak niektórych innych wysoko ocenianych
Jon Schneider
9

Jest to sposób na usunięcie elementu tablicy, począwszy od .Net 3.5, bez kopiowania do innej tablicy - używając tej samej instancji tablicy z 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);
}
infografnet
źródło
2
„bez kopiowania do innej tablicy” - za połączonego dokumentacji Array.Resize faktycznie robi przydzielić nową tablicę za kulisy, a kopie elementów ze starej tablicy na nową. Mimo wszystko podoba mi się zwięzłość tego rozwiązania.
Jon Schneider,
Bardzo ładne i jasne, jeśli masz pewność, że jest to stosunkowo mała tablica.
Darren,
1
Kontynuując komentarz @ JonSchneider, nie jest to „ta sama instancja tablicy”. Dlatego musisz użyć reftej Resizemetody. Długość wystąpienia tablicy jest stała i niezmienna.
Jeppe Stig Nielsen
2
Jeśli kolejność elementów nie jest ważna, zamiast przesuwać wszystkie elementy w dół, można zamienić element pod indeksem na ostatni element, a następnie zmienić rozmiar: arr [index] = arr [arr.Length - 1]; Array.Resize (ref arr, arr.Length - 1);
Bartel
5

Oto moja stara wersja, która działa na wersji 1.0 platformy .NET i nie wymaga typów ogólnych.

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;
}

Jest to używane w następujący sposób:

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]);
    }
}
Martin Brown
źródło
3

Nie do końca jest to sposób na zrobienie tego, ale jeśli sytuacja jest trywialna i cenisz swój czas, możesz spróbować tego dla typów dopuszczających wartość null.

Foos[index] = null

a później sprawdź puste wpisy w logice.

nawfal
źródło
Tak zrobiłem to w mojej grze. Korzystaj z buforów dopuszczających wartość null dla obszarów, które są zmieniane bardzo często.
Krythic
2

Jak zwykle spóźniam się na imprezę ...

Chciałbym dodać kolejną opcję do już istniejącej listy fajnych rozwiązań. =)
Uznałbym to za dobrą okazję do rozszerzeń.

Źródła: http://msdn.microsoft.com/en-us/library/bb311042.aspx

Dlatego definiujemy pewną statyczną klasę, aw niej naszą metodę.
Potem możemy użyć naszej rozszerzonej metody chcąc nie chcąc. =)

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"
             */
        }
    }
}
Duncan
źródło
2

Wypróbuj poniższy kod:

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

lub

myArray = myArray.Where(s => (s != "not_this")).ToArray();
NovatechGuy
źródło
1

Oto jak to zrobiłem ...

    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;
    }
Paul Mitchell
źródło
1

W normalnej tablicy musisz przetasować wszystkie wpisy tablicy powyżej 2, a następnie zmienić ich rozmiar za pomocą metody Resize. Lepiej byłoby użyć ArrayList.

gkrogers
źródło
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;
    }
user2884232
źródło
0

Oto mały zbiór metod pomocniczych, które stworzyłem na podstawie niektórych istniejących odpowiedzi. Wykorzystuje zarówno rozszerzenia, jak i metody statyczne z parametrami referencyjnymi dla maksymalnej idealności:

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;
    }
}

Pod względem wydajności jest przyzwoity, ale prawdopodobnie można by go poprawić. Removeopiera się IndexOfi tworzona jest nowa tablica dla każdego elementu, który chcesz usunąć przez wywołanie RemoveAt.

IndexOfjest jedyną metodą rozszerzającą, ponieważ nie musi zwracać oryginalnej tablicy. Newakceptuje wiele elementów pewnego typu, aby utworzyć nową tablicę tego typu. Wszystkie inne metody muszą akceptować oryginalną tablicę jako odniesienie, więc nie ma potrzeby późniejszego przypisywania wyniku, ponieważ dzieje się to już wewnętrznie.

Zdefiniowałbym Mergemetodę łączenia dwóch tablic; Jednak można to już osiągnąć za pomocą Addmetody, przekazując rzeczywistą tablicę w porównaniu z wieloma pojedynczymi elementami. Dlatego Addmożna go użyć na dwa sposoby, aby połączyć dwa zestawy elementów:

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

Lub

Arr.Add<string>(ref myArray, anotherArray);
James M.
źródło
-2

Wiem, że ten artykuł ma dziesięć lat i dlatego prawdopodobnie nie żyje, ale oto, co spróbuję zrobić:

Użyj metody IEnumerable.Skip (), którą można znaleźć w System.Linq . Pominie wybrany element z tablicy i zwróci kolejną kopię tablicy, która zawiera tylko wszystko oprócz zaznaczonego obiektu. Następnie powtórz to dla każdego elementu, który chcesz usunąć, a następnie zapisz go w zmiennej.

Na przykład, jeśli mamy tablicę o nazwie „Próbka” (typu int []) z 5 liczbami. Chcemy usunąć drugi, więc próbujemy "Sample.Skip (2);" powinien zwrócić tę samą tablicę, ale bez drugiej liczby.

commandertuna
źródło
1
Czy ta metoda nie pomija określoną liczbę elementów w sekwencji, a następnie zwraca pozostałe elementy ? W twoim przykładzie „pominiesz” pierwsze dwa elementy listy ogólnej, a nie tylko drugi!
xnr_z
-5

Pierwszy krok
Musisz przekonwertować tablicę na listę, możesz napisać taką metodę rozszerzającą

// 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;
} 

Drugi krok
Napisz metodę rozszerzenia, aby przekonwertować listę z powrotem na tablicę

// 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;
}

Ostatnie kroki
Napisz ostateczną metodę, ale pamiętaj, aby usunąć element w indeksie przed konwersją z powrotem do tablicy, takiej jak w kodzie

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

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

Przykładowe kody można znaleźć na moim blogu , śledźcie dalej.

Bamara Coulibaly
źródło
14
Jest to trochę szalone, biorąc pod uwagę istnienie .ToArray()i List<T>konstruktora, który bierze istniejącą sekwencję ...
user7116