Obtener una submatriz de una matriz existente


335

Tengo una matriz X de 10 elementos. Me gustaría crear una nueva matriz que contenga todos los elementos de X que comienzan en el índice 3 y terminan en el índice 7. Claro que puedo escribir fácilmente un bucle que lo hará por mí, pero me gustaría mantener mi código lo más limpio posible . ¿Hay algún método en C # que pueda hacerlo por mí?

Algo así (pseudocódigo):

Array NewArray = oldArray.createNewArrayFromRange(int BeginIndex , int EndIndex)

Array.Copyno se ajusta a mis necesidades . Necesito que los elementos de la nueva matriz sean clones. Array.copyes solo un memcpyequivalente de C-Style , no es lo que estoy buscando.



77
@Kirtan: ese "dup" específicamente quiere IEnumerable <T>, que es diferente y tiene diferentes soluciones óptimas; OMI
Marc Gravell

Entonces, ¿las dos líneas que se necesitarían para declarar la nueva matriz y llamar a .Copy () no es "código limpio"?
Ed S.

2
@Ed Swangren: no si necesitas hacerlo en medio de una expresión encadenada, no ;-p
Marc Gravell

2
La respuesta de ShaggyUk es probablemente la correcta: stackoverflow.com/questions/943635/…
Dykam

Respuestas:


469

Puede agregarlo como un método de extensión:

public static T[] SubArray<T>(this T[] data, int index, int length)
{
    T[] result = new T[length];
    Array.Copy(data, index, result, 0, length);
    return result;
}
static void Main()
{
    int[] data = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    int[] sub = data.SubArray(3, 4); // contains {3,4,5,6}
}

Actualizar la clonación (que no era obvio en la pregunta original). Si realmente quieres un clon profundo; algo como:

public static T[] SubArrayDeepClone<T>(this T[] data, int index, int length)
{
    T[] arrCopy = new T[length];
    Array.Copy(data, index, arrCopy, 0, length);
    using (MemoryStream ms = new MemoryStream())
    {
        var bf = new BinaryFormatter();
        bf.Serialize(ms, arrCopy);
        ms.Position = 0;
        return (T[])bf.Deserialize(ms);
    }
}

Sin embargo, esto requiere que los objetos sean serializables ( [Serializable]o ISerializable). Desde aquí se puede sustituir a cualquier otro serializador en su caso - XmlSerializer, DataContractSerializer, protobuf-net, etc.

Tenga en cuenta que el clon profundo es complicado sin serialización; en particular, ICloneablees difícil de confiar en la mayoría de los casos.


1
(obviamente, usar un índice final en lugar de una longitud es un cambio simple; he publicado "tal cual" porque ese es el uso más "típico")
Marc Gravell

1
Entonces ... duro; no hace eso ... probablemente necesitará usar la serialización para lograr algo similar
Marc Gravell

1
Vea mi respuesta para algunas alternativas y un enlace a varias implementaciones. la parte de hacerlo en una submatriz es realmente bastante trivial, lo que realmente quieres es el bit de clonación y esa es una pregunta compleja y algo abierta que depende completamente de tus expectativas sobre cuál debería ser el comportamiento 'correcto' .
ShuggyCoUk

2
Esto es bonito. Y es especialmente bueno señalar que ICloneable no es confiable, porque oh, lo es alguna vez.
Marcus Griep

1
Gracias por subrayar los problemas con la clonación profunda en C #. Realmente es una pena, ya que la copia profunda es una operación fundamental .
Dimitri C.

317

Puede usar Array.Copy(...)para copiar en la nueva matriz después de haberla creado, pero no creo que haya un método que cree la nueva matriz y copie una variedad de elementos.

Si está utilizando .NET 3.5, podría usar LINQ:

var newArray = array.Skip(3).Take(5).ToArray();

pero eso será algo menos eficiente.

Vea esta respuesta a una pregunta similar para opciones para situaciones más específicas.


+1 También me gusta esta variación. Jon, ¿puedes explicar por qué esto se considera menos eficiente?
Ian Roke

@ Jon: Para que coincida con la pregunta, ¿no sería "Take (5)"? @Ian: el enfoque de Array.Copy no involucra un enumerador, y lo más probable es que sea una memoria directa ...
Marc Gravell

@Marc: Sí, de hecho. Demasiado pregunta descremado :)
Jon Skeet

11
@Ian: el enfoque LINQ introduce dos niveles de indirección (los iteradores), tiene que omitir explícitamente los elementos y no sabe qué tan grande será la matriz final de antemano. Considere tomar la segunda mitad de una matriz de dos millones de elementos: un enfoque simple de "crear matriz de destino, copiar" simplemente copiará el bloque requerido sin tocar los otros elementos, y de una vez. El enfoque LINQ recorrerá la matriz hasta que alcance el punto de inicio, luego comenzará a tomar valores, construyendo un búfer (aumentando el tamaño del búfer y copiando periódicamente). Mucho menos eficiente.
Jon Skeet

si 5 es EndIndexm, entonces la pregunta correcta es array.Skip (3) .Take (5-3 + 1) .ToArray (); es decir. array.Skip (StartIndex) .Take (EndIndex-StartIndex + 1) .ToArray ();
Klaus78

74

¿Has considerado usar ArraySegment?

http://msdn.microsoft.com/en-us/library/1hsbd92d.aspx


1
Probablemente haga lo que desee, pero no admite la sintaxis de matriz predeterminada, ni admite IEnumerable, por lo que no es especialmente limpio.
Alex Black el

55
Esto necesita más votos a favor. En mi propia exp, la copia de ArraySegment también es un poco más rápida (después de todo, uso matrices para cosas críticas de velocidad) ..
nawfal

55
@AlexBlack Se parece a .NET 4.5 , implementa IEnumerable<T>y una variedad de otras interfaces útiles.
pswg

1
¿Cómo usarías ArraySegmentpara responder la pregunta original?
Craig McQueen

2
@CraigMcQueen - Pruebe el siguiente enfoque de línea única:IList<T> newArray = (IList<T>)new ArraySegment<T>(oldArray, beginIndex, endIndex);
skia.heliou

36

Veo que quieres hacer clonación, no solo copiar referencias. En este caso, puede usar .Selectpara proyectar miembros de la matriz en sus clones. Por ejemplo, si sus elementos se implementaron IClonable, podría hacer algo como esto:

var newArray = array.Skip(3).Take(5).Select(eachElement => eachElement.Clone()).ToArray();

Nota: Esta solución requiere .NET Framework 3.5.


Esto es mas elegante.
smwikipedia

Esto es exactamente lo que estaba buscando. Esto funciona para cualquiera IEnumerable. Puedo obtener una IEnumerable, IList, IArray, etc ... con un mínimo esfuerzo, en línea si necesito. Si no necesito la copia profunda, simplemente elimino el Select. Caer Skipo Takeme permite controlar el rango. Alternativamente, puedo mezclarlo con SkipWhiley / o TakeWhile.
Mike

33

El siguiente código lo hace en una línea:

// Source array
string[] Source = new string[] { "A", "B", "C", "D" };
// Extracting a slice into another array
string[] Slice = new List<string>(Source).GetRange(2, 2).ToArray();

Singe line y no es necesario agregar Linq. Es mi forma preferida.
Dimitris

Todavía no clona la fuente ... pero de todos modos es un buen enfoque
IG Pascual

1
Debería clonar la fuente porque ToArray: (1) crea una nueva matriz y (2) ejecuta Array.Copy. Al final, Source y Slice son dos objetos separados. El enfoque es correcto, sin embargo, prefiero Array.Copy: referencesource.microsoft.com/#mscorlib/system/collections/…
Krauss

13

En C # 8 han introducido un nuevo Rangey Indextipo

int[] a = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
Index i1 = 3;  // number 3 from beginning
Index i2 = ^4; // number 4 from end
var slice = a[i1..i2]; // { 3, 4, 5 }

12
string[] arr = { "Parrot" , "Snake" ,"Rabbit" , "Dog" , "cat" };

arr = arr.ToList().GetRange(0, arr.Length -1).ToArray();

8

Basándose en la respuesta de Marc pero agregando el comportamiento de clonación deseado

public static T[] CloneSubArray<T>(this T[] data, int index, int length)
    where T : ICloneable
{
    T[] result = new T[length];
    for (int i = 0; i < length; i++)
    { 
        var original = data[index + i];
        if (original != null)
            result[i] = (T)original.Clone();            
    return result;
}

Y si implementar ICloneable se parece demasiado al trabajo duro, uno reflexivo usando la biblioteca Copiable de Håvard Stranden para hacer el trabajo pesado requerido.

using OX.Copyable;

public static T[] DeepCopySubArray<T>(
    this T[] data, int index, int length)
{
    T[] result = new T[length];
    for (int i = 0; i < length; i++)
    { 
        var original = data[index + i];
        if (original != null)
            result[i] = (T)original.Copy();            
    return result;
}

Tenga en cuenta que la implementación OX.Copyable funciona con cualquiera de:

Sin embargo, para que la copia automatizada funcione, una de las siguientes declaraciones debe contener, por ejemplo:

  • Su tipo debe tener un constructor sin parámetros, o
  • Debe ser una copiable, o
  • Debe tener un IInstanceProvider registrado para su tipo.

Entonces esto debería cubrir casi cualquier situación que tenga. Si está clonando objetos en los que el gráfico secundario contiene elementos como conexiones db o identificadores de archivo / secuencia, obviamente tiene problemas, pero es cierto para cualquier copia profunda generalizada.

Si desea utilizar algún otro enfoque de copia profunda, este artículo enumera varios otros, por lo que le sugiero que no intente escribir el suyo.


La primera es probablemente la solución deseada, ya que está pidiendo clonación. Tenga en cuenta que con el método Copiar, probablemente ni siquiera tenga que verificar si es nulo, ya que es un método de extensión, si el método en sí ya lo hace. Vale la pena intentarlo.
Dykam

Sí, noté el cheque nulo, pero no quería confundir al OP en caso de que no leyera la fuente.
ShuggyCoUk

2
Solo una nota al margen: la última versión de Copiable en GitHub no requiere que los objetos tengan un constructor sin parámetros. :) Ver github.com/havard/copyable
Håvard S

8

Puedes hacer esto bastante fácil;

    object[] foo = new object[10];
    object[] bar = new object[7];   
    Array.Copy(foo, 3, bar, 0, 7);  

No, la barra seguirá siendo nula. Array.Copy no crea mágicamente una nueva matriz, especialmente porque la barra no se pasa con ref o out.
Zr40

2
oh ya oye, tienes razón, hice esto a toda prisa, pero oye, tal vez cuando escribas una crítica deberías corregir, el critisismo constructivo es mucho más útil para todos. así que antes de ese array.copy haces una "barra = nuevo objeto [7];
RandomNickName42

4

Creo que el código que estás buscando es:

Array.Copy(oldArray, 0, newArray, BeginIndex, EndIndex - BeginIndex)


Creo que he estado haciendo buenos amigos aquí ... ¡la misma respuesta que tú;) y me votaron mucho! hah !! De todos modos, buenos tiempos buenos tiempos.
RandomNickName42

3

Como alternativa a la copia de los datos, puede crear un contenedor que le dé acceso a una parte de la matriz original como si fuera una copia de la parte de la matriz. La ventaja es que no obtiene otra copia de los datos en la memoria, y el inconveniente es una ligera sobrecarga al acceder a los datos.

public class SubArray<T> : IEnumerable<T> {

   private T[] _original;
   private int _start;

   public SubArray(T[] original, int start, int len) {
      _original = original;
      _start = start;
      Length = len;
   }

   public T this[int index] {
      get {
         if (index < 0 || index >= Length) throw new IndexOutOfRangeException();
         return _original[_start + index];
      }
   }

   public int Length { get; private set; }

   public IEnumerator<T> GetEnumerator() {
      for (int i = 0; i < Length; i++) {
        yield return _original[_start + i];
      }
   }

   IEnumerator IEnumerable.GetEnumerator() {
      return GetEnumerator();
   }

}

Uso:

int[] original = { 1, 2, 3, 4, 5 };
SubArray<int> copy = new SubArray<int>(original, 2, 2);

Console.WriteLine(copy.Length); // shows: 2
Console.WriteLine(copy[0]); // shows: 3
foreach (int i in copy) Console.WriteLine(i); // shows 3 and 4

@Robert: No, no lo es. Intente usar un ArraySegment en su lugar, y verá que no puede acceder a los elementos por índice, ni iterar a través de los elementos.
Guffa

2

Array.ConstrainedCopy funcionará.

public static void ConstrainedCopy (
    Array sourceArray,
    int sourceIndex,
    Array destinationArray,
    int destinationIndex,
    int length
)

2
Eso solo copia los datos; no creará la nueva matriz, etc. y si la matriz es nueva, podríamos usar Array.Copy, que es más eficiente (sin necesidad de controles / retrocesos adicionales).
Marc Gravell

Eso es correcto, pero crear una nueva matriz es solo una línea de código y no se requiere ningún método nuevo. Estoy de acuerdo en que Array.Copy también funcionará.
crauscher 03 de

1

No hay un método único que haga lo que quieras. Deberá poner a disposición un método de clonación para la clase en su matriz. Entonces, si LINQ es una opción:

Foo[] newArray = oldArray.Skip(3).Take(5).Select(item => item.Clone()).ToArray();

class Foo
{
    public Foo Clone()
    {
        return (Foo)MemberwiseClone();
    }
}

1

¿Qué hay de usar Array.ConstrainedCopy :

int[] ArrayOne = new int[8] {1,2,3,4,5,6,7,8};
int[] ArrayTwo = new int[5];
Array.ConstrainedCopy(ArrayOne, 3, ArrayTwo, 0, 7-3);

A continuación se muestra mi publicación original. No funcionará

Puedes usar Array.CopyTo :

int[] ArrayOne = new int[8] {1,2,3,4,5,6,7,8};
int[] ArrayTwo = new int[5];
ArrayOne.CopyTo(ArrayTwo,3); //starts copy at index=3 until it reaches end of
                             //either array

1

Qué tal esto:

public T[] CloneCopy(T[] array, int startIndex, int endIndex) where T : ICloneable
{
    T[] retArray = new T[endIndex - startIndex];
    for (int i = startIndex; i < endIndex; i++)
    {
        array[i - startIndex] = array[i].Clone();
    }
    return retArray;

}

Luego debe implementar la interfaz ICloneable en todas las clases en las que necesita usar esto, pero eso debería hacerlo.


1

No estoy seguro de cuán profundo es realmente, pero:

MyArray.ToList<TSource>().GetRange(beginningIndex, endIndex).ToArray()

Es un poco de sobrecarga, pero podría eliminar un método innecesario.


1

C # 8 ha proporcionado una función llamada como Rango para obtener subarreglos desde el índice inicial hasta el final. Puedes usarlo así.

Index i1 = 3; // number 3 from beginning  
Index i2 = ^4; // number 4 from end  
int[] a = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };  
var slice = a[i1..i2]; // { 3, 4, 5 }

Eso es un poco de mierda pitón loco. Me encanta.
Ch3shire hace

Sí, es una característica muy agradable
vivek nuna hace

0

En cuanto a la clonación, no creo que la serialización llame a sus constructores. Esto puede romper invariantes de clase si estás haciendo cosas interesantes en el ctor's.

Parece que la apuesta más segura son los métodos de clonación virtual que llaman constructores de copias.

protected MyDerivedClass(MyDerivedClass myClass) 
{
  ...
}

public override MyBaseClass Clone()
{
  return new MyDerivedClass(this);
}

Si la serialización llama a sus constructores depende del serializador específico. Algunos lo hacen, otros no. Pero aquellos que generalmente no ofrecen soporte de devolución de llamada para permitirle hacer las reparaciones necesarias.
Marc Gravell

Esto resalta otro punto de fricción de la serialización: debe proporcionar constructores predeterminados.
Hans Malherbe

0

La clonación de elementos en una matriz no es algo que se pueda hacer de manera universal. ¿Desea una clonación profunda o una copia simple de todos los miembros?

Vayamos al enfoque del "mejor esfuerzo": clonar objetos usando la interfaz ICloneable o la serialización binaria:

public static class ArrayExtensions
{
  public static T[] SubArray<T>(this T[] array, int index, int length)
  {
    T[] result = new T[length];

    for (int i=index;i<length+index && i<array.Length;i++)
    {
       if (array[i] is ICloneable)
          result[i-index] = (T) ((ICloneable)array[i]).Clone();
       else
          result[i-index] = (T) CloneObject(array[i]);
    }

    return result;
  }

  private static object CloneObject(object obj)
  {
    BinaryFormatter formatter = new BinaryFormatter();

    using (MemoryStream stream = new MemoryStream())
    {
      formatter.Serialize(stream, obj);

      stream.Seek(0,SeekOrigin.Begin);

      return formatter.Deserialize(stream);
    }
  }
}

Esta no es una solución perfecta, porque simplemente no hay ninguna que funcione para cualquier tipo de objeto.


¿No debería ser algo así como el resultado [i-index] = (T) ...?
Donald Byrd

sí :) Y no solo eso. El límite del bucle está mal. Lo arreglaré. ¡Gracias!
Philippe Leybaert el

0

Puedes tomar clases hechas por Microsoft:

internal class Set<TElement>
{
    private int[] _buckets;
    private Slot[] _slots;
    private int _count;
    private int _freeList;
    private readonly IEqualityComparer<TElement> _comparer;

    public Set()
        : this(null)
    {
    }

    public Set(IEqualityComparer<TElement> comparer)
    {
        if (comparer == null)
            comparer = EqualityComparer<TElement>.Default;
        _comparer = comparer;
        _buckets = new int[7];
        _slots = new Slot[7];
        _freeList = -1;
    }

    public bool Add(TElement value)
    {
        return !Find(value, true);
    }

    public bool Contains(TElement value)
    {
        return Find(value, false);
    }

    public bool Remove(TElement value)
    {
        var hashCode = InternalGetHashCode(value);
        var index1 = hashCode % _buckets.Length;
        var index2 = -1;
        for (var index3 = _buckets[index1] - 1; index3 >= 0; index3 = _slots[index3].Next)
        {
            if (_slots[index3].HashCode == hashCode && _comparer.Equals(_slots[index3].Value, value))
            {
                if (index2 < 0)
                    _buckets[index1] = _slots[index3].Next + 1;
                else
                    _slots[index2].Next = _slots[index3].Next;
                _slots[index3].HashCode = -1;
                _slots[index3].Value = default(TElement);
                _slots[index3].Next = _freeList;
                _freeList = index3;
                return true;
            }
            index2 = index3;
        }
        return false;
    }

    private bool Find(TElement value, bool add)
    {
        var hashCode = InternalGetHashCode(value);
        for (var index = _buckets[hashCode % _buckets.Length] - 1; index >= 0; index = _slots[index].Next)
        {
            if (_slots[index].HashCode == hashCode && _comparer.Equals(_slots[index].Value, value))
                return true;
        }
        if (add)
        {
            int index1;
            if (_freeList >= 0)
            {
                index1 = _freeList;
                _freeList = _slots[index1].Next;
            }
            else
            {
                if (_count == _slots.Length)
                    Resize();
                index1 = _count;
                ++_count;
            }
            int index2 = hashCode % _buckets.Length;
            _slots[index1].HashCode = hashCode;
            _slots[index1].Value = value;
            _slots[index1].Next = _buckets[index2] - 1;
            _buckets[index2] = index1 + 1;
        }
        return false;
    }

    private void Resize()
    {
        var length = checked(_count * 2 + 1);
        var numArray = new int[length];
        var slotArray = new Slot[length];
        Array.Copy(_slots, 0, slotArray, 0, _count);
        for (var index1 = 0; index1 < _count; ++index1)
        {
            int index2 = slotArray[index1].HashCode % length;
            slotArray[index1].Next = numArray[index2] - 1;
            numArray[index2] = index1 + 1;
        }
        _buckets = numArray;
        _slots = slotArray;
    }

    internal int InternalGetHashCode(TElement value)
    {
        if (value != null)
            return _comparer.GetHashCode(value) & int.MaxValue;
        return 0;
    }

    internal struct Slot
    {
        internal int HashCode;
        internal TElement Value;
        internal int Next;
    }
}

y entonces

public static T[] GetSub<T>(this T[] first, T[] second)
    {
        var items = IntersectIteratorWithIndex(first, second);
        if (!items.Any()) return new T[] { };


        var index = items.First().Item2;
        var length = first.Count() - index;
        var subArray = new T[length];
        Array.Copy(first, index, subArray, 0, length);
        return subArray;
    }

    private static IEnumerable<Tuple<T, Int32>> IntersectIteratorWithIndex<T>(IEnumerable<T> first, IEnumerable<T> second)
    {
        var firstList = first.ToList();
        var set = new Set<T>();
        foreach (var i in second)
            set.Add(i);
        foreach (var i in firstList)
        {
            if (set.Remove(i))
                yield return new Tuple<T, Int32>(i, firstList.IndexOf(i));
        }
    }

0

Esta es la forma óptima, encontré, de hacer esto:

private void GetSubArrayThroughArraySegment() {
  int[] array = { 10, 20, 30 };
  ArraySegment<int> segment = new ArraySegment<int>(array,  1, 2);
  Console.WriteLine("-- Array --");
  int[] original = segment.Array;
  foreach (int value in original)
  {
    Console.WriteLine(value);
  }
  Console.WriteLine("-- Offset --");
  Console.WriteLine(segment.Offset);
  Console.WriteLine("-- Count --");
  Console.WriteLine(segment.Count);

  Console.WriteLine("-- Range --");
  for (int i = segment.Offset; i <= segment.Count; i++)
  {
    Console.WriteLine(segment.Array[i]);
  }
}

¡Espero eso ayude!


0

utilice el método de extensión:

public static T[] Slice<T>(this T[] source, int start, int end)
    {
        // Handles negative ends.
        if (end < 0)
        {
            end = source.Length + end;
        }
        int len = end - start;

        // Return new array.
        T[] res = new T[len];
        for (int i = 0; i < len; i++)
        {
            res[i] = source[i + start];
        }
        return res;
    }

y puedes usarlo

var NewArray = OldArray.Slice(3,7);

0

Código del System.Private.CoreLib.dll:

public static T[] GetSubArray<T>(T[] array, Range range)
{
    if (array == null)
    {
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
    }
    (int Offset, int Length) offsetAndLength = range.GetOffsetAndLength(array.Length);
    int item = offsetAndLength.Offset;
    int item2 = offsetAndLength.Length;
    if (default(T) != null || typeof(T[]) == array.GetType())
    {
        if (item2 == 0)
        {
            return Array.Empty<T>();
        }
        T[] array2 = new T[item2];
        Buffer.Memmove(ref Unsafe.As<byte, T>(ref array2.GetRawSzArrayData()), ref Unsafe.Add(ref Unsafe.As<byte, T>(ref array.GetRawSzArrayData()), item), (uint)item2);
        return array2;
    }
    T[] array3 = (T[])Array.CreateInstance(array.GetType().GetElementType(), item2);
    Array.Copy(array, item, array3, 0, item2);
    return array3;
}



0

No cumple con su requisito de clonación, pero parece más simple que muchas respuestas:

Array NewArray = new ArraySegment(oldArray,BeginIndex , int Count).ToArray();

-1
public   static   T[]   SubArray<T>(T[] data, int index, int length)
        {
            List<T> retVal = new List<T>();
            if (data == null || data.Length == 0)
                return retVal.ToArray();
            bool startRead = false;
            int count = 0;
            for (int i = 0; i < data.Length; i++)
            {
                if (i == index && !startRead)
                    startRead = true;
                if (startRead)
                {

                    retVal.Add(data[i]);
                    count++;

                    if (count == length)
                        break;
                }
            }
            return retVal.ToArray();
        }
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.