从常规数组中删除元素

157

我有一个 Foo 对象的数组。如何删除这个数组的第二个元素?

我需要类似于 RemoveAt() 但适用于普通数组的方法。


1
使用 System.Collections.ObjectModel.Collection<Foo> - abatishchev
2
对于我的游戏,我选择了“索引处为空”的数据结构。基本上,内部数组(缓冲区)是静态大小的,而不是删除索引并调整数组大小,我只是将索引设置为null。当我需要添加一个项目时,我只需找到第一个非空索引并将其放置在那里。效果还不错,但显然并不适用于所有情况。 - Krythic
16个回答

235
如果您不想使用列表:
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);

10
这个答案中给出的第一个示例比第二个示例效率低得多。它需要进行两次数组复制和一次整体向后移位,而不是仅进行一次有选择性的数组复制。 - Martin Brown
2
当然可以使用 +1,但我们也可以使用列表。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 实际上,将列表转换为/从数组转换比数组复制慢得多(数组复制可以使用几个ASM指令以最大速度复制CPU允许的数据)。此外,移动列表非常快,因为只涉及交换几个指针并删除节点数据(在这种情况下,节点数据仅为8字节[加上头/尾指针的另外16字节])。 - krowe2
显示剩余3条评论

81

数组的本质是长度不可变的,不能添加或删除其中的任何项。

如果要删除数组中的某个元素,您需要创建一个新的比原数组长度少1的数组,并将旧的元素复制到新数组中(不包括要删除的元素)。

因此,使用List可能会更好,而不是使用数组。


4
将数组转换为列表:List<mydatatype> array = new List<mydatatype>(arrayofmydatatype) - Immortal Blue
1
使用System.Linq命名空间中的Enumerable.ToList()方法,将@ImmortalBlue或var myList = myArray.ToList();转换为列表。 - Dyndrilliac

72

我使用这种方法从对象数组中移除元素。在我的情况下,我的数组长度很小。如果您有大型数组,则可能需要另一种解决方案。

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

8
个人而言,我更喜欢这个答案而非被采纳的那个答案。它应该同样有效,而且更易于阅读。我看一眼就知道它是正确的。但要测试另一个答案以确保那些副本是正确书写的。 - oillio
1
这个答案比上面的两个答案好得多,但它的评分却很低,真是遗憾。 - Sepulchritude
啊啊啊,这就是我在寻找的答案!这是最好的方法,不需要使用列表。 - Jordi Huertas

58

LINQ 一行解决方案:

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

在这个例子中,1 是要移除的元素的索引--在本例中,根据原始问题,是第二个元素 (在 C# 的从零开始的数组索引中,1 是第二个元素)。

一个更完整的例子:

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

执行该代码片段后,myArray 的值将为 { "a", "c", "d", "e" }


2
对于需要高性能/频繁访问的区域,不建议使用LINQ。 - Krythic
3
@Krythic 这是一个公正的评论。如果在一个紧密循环中运行成千上万次,这个解决方案的性能不如这个页面上其他得票率较高的一些解决方案:https://dotnetfiddle.net/z9Xkpn - Jon Schneider

12

这是一种在 .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);
}

3
根据链接文档,Array.Resize实际上会在后台分配一个新数组,并将旧数组的元素复制到新数组中。因此,"without copying to another array"是不正确的。尽管如此,我仍然喜欢这个解决方案的简洁性。 - Jon Schneider
如果你确定它是一个相对较小的数组,那么非常好和清晰。 - Darren
1
继续@JonSchneider的评论,这不是“相同的数组实例”。这就是为什么调用Resize方法时需要使用ref的原因。数组实例的长度是固定且不可变的。 - Jeppe Stig Nielsen
2
如果元素的顺序不重要,你可以通过交换索引处的元素和最后一个元素,然后调整大小来代替将所有元素向下移动:arr[index] = arr[arr.Length - 1]; Array.Resize(ref arr, arr.Length - 1); - Bartel

6

这是我拥有的旧版本,适用于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]);
    }
}

5

虽然这不是最佳解决方式,但如果情况比较琐碎且你很看重时间,你可以在可空类型中尝试这种方法。

Foos[index] = null

然后在您的逻辑中检查空条目。


这是我为我的游戏所做的。对于那些经常更改的区域,请使用可空缓冲区。 - Krythic

3

尝试下面的代码:

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

或者

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

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"
             */
        }
    }
}

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

请注意,原问题是通过索引删除一个元素,而此解决方案是通过值删除元素。 - AntonK

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接