Obtendo uma sub-matriz de uma matriz existente

335

Eu tenho uma matriz X de 10 elementos. Eu gostaria de criar uma nova matriz contendo todos os elementos de X que começam no índice 3 e terminam no índice 7. Claro, posso escrever facilmente um loop que fará isso por mim, mas gostaria de manter meu código o mais limpo possível . Existe um método em c # que pode fazer isso por mim?

Algo como (pseudo código):

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

Array.Copynão se encaixa nas minhas necessidades . Eu preciso que os itens na nova matriz sejam clones. Array.copyé apenas um memcpyequivalente do estilo C , não é o que estou procurando.

user88637
fonte
7
@Kirtan - esse "dup" quer especificamente o IEnumerable <T> - que é diferente e tem soluções ótimas diferentes; IMO
Marc Gravell
Portanto, as duas linhas que seriam necessárias para declarar a nova matriz e chamar .Copy () não são "código limpo"?
Ed S.
2
@Ed Swangren - não se você precisa fazê-lo no meio de uma expressão acorrentado, não ;-p
Marc Gravell
2
A resposta de ShaggyUk provavelmente é a correta: stackoverflow.com/questions/943635/…
Dykam 11/07/2009

Respostas:

469

Você pode adicioná-lo como um método de extensão:

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

Atualize a re-clonagem (o que não era óbvio na pergunta original). Se você realmente quer um clone 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);
    }
}

Isso requer que os objetos sejam serializáveis ​​( [Serializable]ou ISerializable). Você poderia facilmente substituir qualquer outro serializer conforme o caso - XmlSerializer, DataContractSerializer, protobuf-net, etc.

Observe que o clone profundo é complicado sem serialização; em particular, ICloneableé difícil confiar na maioria dos casos.

Marc Gravell
fonte
11
(obviamente usando um índice final, em vez de um comprimento é uma simples mudança, eu postei "como é" porque esse é o uso mais "típico")
Marc Gravell
11
Então ... difícil; ele não faz isso .... você provavelmente precisará usar serialização para alcançar algo semelhante
Marc Gravell
11
veja minha resposta para algumas alternativas e um link para várias implementações. a parte de fazer isso em uma sub-matriz é realmente bastante trivial, o que você realmente quer é o bit de clonagem e essa é uma pergunta complexa e um tanto aberta que depende inteiramente de suas expectativas sobre o que o comportamento "correto" deve ser .
ShuggyCoUk
2
Isso é legal. E é especialmente bom ressaltar que o ICloneable não é confiável, porque, oh, é sempre.
Marcus Griep
11
Obrigado por sublinhar os problemas com a clonagem profunda em C #. É realmente uma pena, pois a cópia profunda é uma operação fundamental .
Dimitri C.
316

Você pode usar Array.Copy(...)para copiar para a nova matriz depois de criá-la, mas não acho que exista um método que crie a nova matriz e copie uma série de elementos.

Se você estiver usando o .NET 3.5, poderá usar o LINQ:

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

mas isso será um pouco menos eficiente.

Veja esta resposta a uma pergunta semelhante para opções para situações mais específicas.

Jon Skeet
fonte
+1 Eu também gosto dessa variação. Jon, você pode expandir por que isso é considerado menos eficiente?
Ian Roke
@ Jon: Para combinar com a pergunta, isso não seria "Take (5)"? @Ian: a abordagem Array.Copy não envolve um enumerador, e mais provável será um memcopy reta ...
Marc Gravell
@ Marc: Sim, de fato. Demasiada questão desnatação :)
Jon Skeet
11
@Ian: A abordagem LINQ introduz dois níveis de indireção (os iteradores), precisa pular explicitamente os itens e não sabe o tamanho da matriz final de antemão. Considere usar a segunda metade de uma matriz de dois milhões de elementos: uma abordagem simples "criar matriz de destino, copiar" copiará apenas o bloco necessário sem tocar nos outros elementos e de uma só vez. A abordagem LINQ percorrerá a matriz até atingir o ponto inicial e, em seguida, começará a receber valores, criando um buffer (aumentando o tamanho do buffer e copiando periodicamente). Muito menos eficiente.
Jon Skeet
se 5 for o EndIndexm, a pergunta correta será array.Skip (3) .Take (5-3 + 1) .ToArray (); ie Array.Skip (StartIndex) .Take (EndIndex-StartIndex + 1) .ToArray ();
precisa saber é o seguinte
73

Você já pensou em usar ArraySegment?

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

Alex Black
fonte
11
Provavelmente, ele faz o que você deseja, mas não suporta a sintaxe padrão da matriz nem o IEnumerable; portanto, não é especialmente limpo.
811 Alex Black
5
Isso precisa de mais votos. Em meu próprio exp, ArraySegment cópia é ligeiramente mais rápido também (após todas as matrizes que eu uso para materiais críticos de velocidade) ..
Nawfal
5
@AlexBlack Parece que a partir do .NET 4.5 , implementa IEnumerable<T>e uma variedade de outras interfaces úteis.
PSWG
11
Como você usaria ArraySegmentpara responder à pergunta original?
Craig McQueen
2
@CraigMcQueen - Tente a seguinte abordagem em linha única:IList<T> newArray = (IList<T>)new ArraySegment<T>(oldArray, beginIndex, endIndex);
skia.heliou
36

Vejo que você deseja fazer Clonagem, não apenas copiar referências. Nesse caso, você pode usar .Selectpara projetar membros da matriz em seus clones. Por exemplo, se seus elementos foram implementados, IClonablevocê poderia fazer algo assim:

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

Nota: Esta solução requer o .NET Framework 3.5.

zvolkov
fonte
Isso é mais elegante.
smwikipedia
Era exatamente isso que eu estava procurando. Isso funciona para qualquer um IEnumerable. Posso obter um IEnumerable, IList, IArray, etc ... com o mínimo de barulho, inline se eu precisar. Se eu não precisar da cópia em profundidade, apenas removo a Select. Deixar cair Skipou Takeme permite controlar o alcance. Alternativamente, eu posso misturar com SkipWhilee / ou TakeWhile.
Mike
33

O código a seguir faz isso em uma linha:

// 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();
Volker
fonte
Singe line e não há necessidade de adicionar Linq. É o meu caminho preferido.
Dimitris
Ainda não clonar a fonte ... mas é uma abordagem bem assim mesmo
IG Pascual
11
Ele deve clonar a fonte porque ToArray: (1) cria uma nova matriz e (2) executa Array.Copy. No final, Source e Slice são dois objetos separados. A abordagem é correta, no entanto, eu prefiro Array.Copy: referencesource.microsoft.com/#mscorlib/system/collections/...
Krauss
13

No C # 8, eles introduziram um novo Rangee Indexdigite

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 }
Prasanth Louis
fonte
12
string[] arr = { "Parrot" , "Snake" ,"Rabbit" , "Dog" , "cat" };

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

Com base na resposta de Marc, mas adicionando o comportamento de clonagem desejado

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

E se a implementação do ICloneable é muito parecida com o trabalho árduo, é necessário usar a biblioteca Copyable da Håvard Stranden para fazer o trabalho pesado necessário.

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

Observe que a implementação OX.Copyable funciona com qualquer um dos seguintes:

Para que a cópia automatizada funcione, uma das seguintes instruções deve ser mantida, por exemplo:

  • Seu tipo deve ter um construtor sem parâmetros, ou
  • Deve ser um copiável ou
  • Ele deve ter um IInstanceProvider registrado para seu tipo.

Portanto, isso deve cobrir quase todas as situações que você tiver. Se você estiver clonando objetos em que o subgrafo contém itens como conexões db ou identificadores de arquivo / fluxo, obviamente você tem problemas, mas isso é verdade para qualquer cópia profunda generalizada.

Se você quiser usar alguma outra abordagem de cópia profunda, este artigo lista várias outras, então eu sugiro que não tente escrever sua própria.

ShuggyCoUk
fonte
A primeira é provavelmente a solução desejada, pois ele está pedindo clonagem. Observe que, com o método Copy, você provavelmente nem precisa procurar nulo, pois é um método de extensão, se o próprio método já faz isso. Vale a pena tentar.
Dykam 11/07/2009
Sim, observei a verificação nula, mas não quis confundir o OP, caso ele não lesse a fonte.
ShuggyCoUk
2
Apenas uma nota de rodapé: A versão mais recente do Copyable no GitHub não exige que os objetos tenham um construtor sem parâmetros. :) Veja github.com/havard/copyable
Håvard S
8

Você pode fazer isso facilmente;

    object[] foo = new object[10];
    object[] bar = new object[7];   
    Array.Copy(foo, 3, bar, 0, 7);  
RandomNickName42
fonte
Não, a barra ainda será nula. Array.Copy não cria magicamente uma nova matriz, especialmente porque a barra não é passada com ref ou out.
Zr40 08/07/09
2
oh sim, seu direito, eu fiz isso às pressas, mas ei, talvez quando sua crítica escrita deve colocar a correção, críticas construtivas são muito mais úteis para todos. então, antes desse array.copy, você faz um "bar = new object [7];"
RandomNickName42
4

Eu acho que o código que você está procurando é:

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

Sean
fonte
Acho que venho fazendo um bom amigo aqui .... a mesma resposta que você;) e fui bastante criticada !! hah !! De qualquer forma, bons tempos bons tempos.
RandomNickName42 14/07/2009
3

Como alternativa à cópia dos dados, você pode criar um wrapper que dê acesso a uma parte da matriz original como se fosse uma cópia da parte da matriz. A vantagem é que você não recebe outra cópia dos dados na memória, e a desvantagem é uma pequena sobrecarga ao acessar os dados.

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
Guffa
fonte
@ Robert: Não, não é. Tente usar um ArraySegment e verá que não é possível acessar os itens por índice, nem iterar pelos itens.
Guffa
2

Array.ConstrainedCopy funcionará.

public static void ConstrainedCopy (
    Array sourceArray,
    int sourceIndex,
    Array destinationArray,
    int destinationIndex,
    int length
)
crauscher
fonte
2
Isso apenas copia os dados; não criará a nova matriz etc; e se a matriz for nova, poderíamos usar Array.Copy, que é mais eficiente (sem necessidade de verificações / reversões adicionais).
Marc Gravell
Isso mesmo, mas criar uma nova matriz é apenas uma linha de código e nenhum novo método é necessário. Concordo que o Array.Copy também funcionará.
crauscher
1

Não existe um método único que faça o que você deseja. Você precisará disponibilizar um método clone para a classe em sua matriz. Então, se LINQ é uma opção:

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

class Foo
{
    public Foo Clone()
    {
        return (Foo)MemberwiseClone();
    }
}
Thorarin
fonte
1

Que tal 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);

Abaixo está o meu post original. Isso não vai funcionar

Você poderia 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
Mike
fonte
1

Que tal agora:

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;

}

Você precisa implementar a interface ICloneable em todas as classes nas quais você precisa usá-lo, mas isso deve ser feito.

RCIX
fonte
1

Não tenho certeza de quão profundo é realmente, mas:

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

É um pouco caro, mas pode resultar em um método desnecessário.

SCNerd
fonte
1

O C # 8 forneceu o recurso chamado Range para obter subárvores do início ao fim do índice. você pode usá-lo assim.

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 }
vivek nuna
fonte
0

No que diz respeito à clonagem, não acho que a serialização chame seus construtores. Isso pode quebrar os invariantes de classe se você estiver fazendo coisas interessantes no ctor.

Parece que a aposta mais segura são os métodos de clone virtual que chamam construtores de cópias.

protected MyDerivedClass(MyDerivedClass myClass) 
{
  ...
}

public override MyBaseClass Clone()
{
  return new MyDerivedClass(this);
}
Hans Malherbe
fonte
Se a serialização chama seus construtores depende do serializador específico. Alguns fazem, outros não. Mas aqueles que normalmente não oferecem suporte a retorno de chamada para permitir que você faça as correções necessárias.
Marc Gravell
Isso destaca outro ponto de atrito da serialização: você deve fornecer construtores padrão.
21779 Hans Malherbe
0

A clonagem de elementos em uma matriz não é algo que pode ser feito de maneira universal. Deseja clonagem profunda ou uma cópia simples de todos os membros?

Vamos seguir a abordagem do "melhor esforço": clonar objetos usando a interface ICloneable ou serialização binária:

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 não é uma solução perfeita, porque simplesmente não existe uma que funcione para qualquer tipo de objeto.

Philippe Leybaert
fonte
Não deveria ser algo como resultado [i-index] = (T) ...?
227 Donald Byrd
sim :) E não é só isso. O limite do loop está errado. Eu resolvo isso. Obrigado!
Philippe Leybaert 13/07/2009
0

Você pode assistir às aulas da 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;
    }
}

e depois

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));
        }
    }
Smagin Alexey
fonte
0

Descobri que esta é a maneira ideal:

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 que ajude!

OscarMas
fonte
0

use o método de extensão:

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

e você pode usá-lo

var NewArray = OldArray.Slice(3,7);
Erwin Draconis
fonte
0

Código do 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;
}


Ezequiel Maygua
fonte
0

Ele não atende aos seus requisitos de clonagem, mas parece mais simples do que muitas respostas:

Array NewArray = new ArraySegment(oldArray,BeginIndex , int Count).ToArray();
Mr. Boy
fonte
-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();
        }
Binay Rana
fonte