Удалить элемент регулярного массива

135

У меня есть массив объектов Foo. Как удалить второй элемент массива?

Мне нужно что-то подобное, RemoveAt()но для обычного массива.

Лиора
источник
1
Использование System.Collections.ObjectModel.Collection<Foo>.
abatishchev
1
В своей игре я использовал структуру данных с нулевым индексом. По сути, внутренний массив (буфер) имеет статический размер, и вместо удаления индекса и изменения размера массива я просто делаю индекс нулевым. Когда мне нужно добавить элемент, я просто нахожу первый ненулевой индекс и помещаю его туда. Работает неплохо, но не для всего.
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
Первый пример, приведенный в этом ответе, намного менее эффективен, чем второй. Для этого требуется две копии массива и сдвиг всего после индекса, а не одна выборочная копия массива.
Мартин Браун
2
+1, конечно, но мы также можем использовать список OR List <Foo> list = new List <Foll> (GetFoos ()); list.Remove (my_foo); list.RemoveAt (2); где GetFoos () вернет массив Foos !!!!
Shahjapan
2
Первая строка внутри метода должна содержать «source.Length» вместо «array.Length».
Нельсон
1
Кроме того, имейте в виду, что любая переменная, хранящая ссылку на исходный массив, будет по-прежнему содержать исходные данные и что любое сравнение равенства ссылок между массивом в источнике и выходным массивом будет возвращать отрицательное значение.
bkqc 05
1
@MartinBrown На самом деле преобразование списка в \ from и массив происходит намного медленнее, чем копирование массива (которое может копировать данные с максимальной скоростью, разрешенной ЦП, всего с несколькими инструкциями ASM). Кроме того, смещение списка происходит очень быстро, потому что это просто вопрос замены нескольких указателей и удаления данных узла (в данном случае это всего 8 байт [плюс еще 16 для указателей головы / хвоста]).
krowe2
66

Природа массивов такова, что их длина неизменна. Вы не можете добавлять или удалять какие-либо элементы массива.

Вам нужно будет создать новый массив на один элемент короче и скопировать старые элементы в новый массив, за исключением элемента, который вы хотите удалить.

Так что, вероятно, лучше использовать список вместо массива.

Себастьян Дитц
источник
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;
}
EdHellyer
источник
7
Лично мне этот ответ нравится больше, чем принятый. Он должен быть таким же эффективным, и его намного легче читать. Я могу смотреть на него и знать, что это правильно. Мне пришлось бы протестировать другой, чтобы убедиться, что эти копии написаны правильно.
oillio 08
1
Очень жаль, что этот ответ настолько низкий, хотя он намного лучше, чем два выше.
Sepulchritude
Aaarhg, это тот ответ, который я искал! Это лучший способ без списков.
Хорди Уэртас,
47

Однострочное решение LINQ:

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

В 1этом примере это индекс удаляемого элемента - в этом примере, согласно исходному вопросу, это 2-й элемент ( 1который является вторым элементом в индексировании массива C # с отсчетом от нуля).

Более полный пример:

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 Это честный комментарий. Выполнение этого решения тысячи раз в жестком цикле не так хорошо, как у некоторых других решений на этой странице, получивших большое количество голосов
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);
}
infografnet
источник
2
«без копирования в другой массив» - в связанной документации, Array.Resize фактически делает выделить новый массив за кулисами, и копирует элементы из старого массива в новый. Тем не менее, мне нравится лаконичность этого решения.
Jon Schneider
Очень красиво и понятно, если вы уверены, что это относительно небольшой массив.
Даррен
1
Продолжая комментарий @JonSchneider, это не «тот же экземпляр массива». Вот почему вам нужно использовать refпри вызове Resizeметода. Длина экземпляра массива фиксирована и неизменна.
Джеппе Стиг Нильсен
2
Если порядок элементов не важен, вместо перемещения всех элементов вниз вы можете поменять местами элемент по индексу с последним элементом, а затем изменить размер: arr [index] = arr [arr.Length - 1]; Array.Resize (ref arr, arr.Length - 1);
Бартель
5

Вот моя старая версия, которая работает с версией 1.0 платформы .NET и не требует общих типов.

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

Не совсем так, но если ситуация тривиальна и вы цените свое время, вы можете попробовать это для типов, допускающих значение NULL.

Foos[index] = null

а затем проверьте наличие нулевых записей в вашей логике.

Навфал
источник
Вот как я сделал это для своей игры. Используйте буферы, допускающие значение 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();
NovatechGuy
источник
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, а затем изменить его размер с помощью метода Resize. Возможно, вам лучше использовать ArrayList.

gkrogers
источник
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
источник
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полагается, IndexOfи новый массив создается для каждого элемента, который вы хотите удалить с помощью вызова RemoveAt.

IndexOf- единственный метод расширения, поскольку ему не нужно возвращать исходный массив. Newпринимает несколько элементов определенного типа для создания нового массива указанного типа. Все другие методы должны принимать исходный массив в качестве ссылки, поэтому нет необходимости назначать результат позже, поскольку это уже происходит внутри.

Я бы определил Merge метод слияния двух массивов; однако это уже может быть выполнено с помощью Addметода путем передачи фактического массива вместо нескольких отдельных элементов. Следовательно, Addдля соединения двух наборов элементов могут использоваться следующие два способа:

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

Или

Arr.Add<string>(ref myArray, anotherArray);
Джеймс М
источник
-1

Я знаю, что этой статье десять лет и, следовательно, она, вероятно, мертва, но вот что я попробую сделать:

Используйте метод IEnumerable.Skip (), найденный в System.Linq . Он пропустит выбранный элемент из массива и вернет другую копию массива, которая содержит только все, кроме выбранного объекта. Затем просто повторите это для каждого элемента, который вы хотите удалить, и после этого сохраните его в переменной.

Например, если у нас есть массив с именем «Sample» (типа int []) с 5 числами. Мы хотим удалить второй, поэтому пробуем "Sample.Skip (2);" должен возвращать тот же массив, за исключением второго числа.

commandertuna
источник
Разве этот метод не просто обходит указанное количество элементов в последовательности, а затем возвращает оставшиеся элементы ? В вашем примере вы «пропустите» первые два элемента общего списка, а не только второй!
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