Fatias de matriz em C #

228

Como você faz isso? Dada uma matriz de bytes:

byte[] foo = new byte[4096];

Como eu obteria os primeiros x bytes da matriz como uma matriz separada? (Especificamente, eu preciso dele como um IEnumerable<byte>)

Isto é para trabalhar com Sockets. Eu acho que a maneira mais fácil seria fatiar o array, semelhante à sintaxe Perls:

@bar = @foo[0..40];

O que retornaria os primeiros 41 elementos na @barmatriz. Existe alguma coisa em C # que estou perdendo ou há outra coisa que devo fazer?

O LINQ é uma opção para mim (.NET 3.5), se isso ajudar alguma.

Matthew Scharley
fonte
3
Matriz de corte é uma proposta para c # 7.2 github.com/dotnet/csharplang/issues/185
Mark
3
O C # 8.0 verá a introdução do fatiamento de matriz nativa. Veja a resposta para mais detalhes
Remy
1
Você pode estar interessado em ArraySlice <T>, que implementa o fatiamento de matrizes com etapa como uma exibição sobre os dados originais: github.com/henon/SliceAndDice
henon

Respostas:

196

As matrizes são enumeráveis, então você foojá é uma pessoa IEnumerable<byte>. Simplesmente use métodos de sequência LINQ como Take()obter o que você deseja (não se esqueça de incluir o Linqespaço para nome using System.Linq;):

byte[] foo = new byte[4096];

var bar = foo.Take(41);

Se você realmente precisar de uma matriz de qualquer IEnumerable<byte>valor, poderá usar o ToArray()método para isso. Esse não parece ser o caso aqui.

peSHIr
fonte
5
Se vamos copiar para outra matriz, use o método estático Array.Copy. No entanto, acho que as outras respostas interpretaram a intenção corretamente, outra matriz não é necessária apenas um IEnumberable <byte> que nos primeiros 41 bytes.
AnthonyWJones
2
Observe que apenas matrizes unidimensionais e irregulares são enumeráveis, matrizes multidimensionais não são.
Abel
11
Observe que o Array.Copy é muito mais rápido do que os métodos Take ou Skip do LINQ.
Michael
4
@ Abel Isso é realmente muito incorreto. Matrizes multidimensionais são enumeráveis, mas eles enumerar assim: [2,3] => [1,1], [1,2], [1,3], [2,1], [2,2], [2,3]. Matrizes irregulares também são enumeráveis, mas em vez de retornar um valor quando enumeradas, elas retornam sua matriz interna. Assim:type[][] jaggedArray; foreach (type[] innerArray in jaggedArray) { }
Aidiakapi
3
@Aidiakapi "very incorect"? ;). Mas você está parcialmente certo, eu deveria ter escrito "matrizes multidim não implementadas IEnumerable<T>", então minha declaração teria sido mais clara. Veja também: stackoverflow.com/questions/721882/…
Abel
211

Você poderia usar ArraySegment<T>. É muito leve, pois não copia a matriz:

string[] a = { "one", "two", "three", "four", "five" };
var segment = new ArraySegment<string>( a, 1, 2 );
Mike Scott
fonte
5
Infelizmente não é IEnumerable.
recursivo
1
É verdade, mas seria fácil escrever um invólucro de iterador em torno dele que implemente IEnumerable.
Mike Scott
22
Alguém sabe por que não é IEnumerable? Eu não. Parece que deveria ser.
Fantius
39
ArraySegment é IList e IEnumerable a partir de .Net 4.5. Muito ruim para os usuários da versão mais velhos ..
Todd Li
6
@Zyo Eu quis dizer que ArraySegment <T> implementa IEnumerable <T> a partir do .Net 4.5, não o IEnumerable <T> em si é novo.
Todd Li
137

Você pode usar o CopyTo()método matrizes .

Ou com o LINQ você pode usar Skip()e Take()...

byte[] arr = {1, 2, 3, 4, 5, 6, 7, 8};
var subset = arr.Skip(2).Take(2);
Arjan Einbu
fonte
1
+1 para uma boa ideia, mas preciso usar a matriz retornada como entrada para outra função, o que faz com que CopyTo exija uma variável temporária. Vou esperar por outras respostas ainda.
Matthew Scharley
4
Ainda não estou familiarizado com o LINQ, talvez seja mais uma evidência de que realmente deveria estar.
Matthew Scharley 02/01/09
11
essa abordagem é pelo menos 50x mais lenta que Array.Copy. Isso não é um problema em muitas situações, mas ao fatiar a matriz em um ciclo, a queda de desempenho é muito óbvia.
Valentin Vasilyev
3
Estou fazendo uma única ligação, então o desempenho não é um problema para mim. Isso é ótimo para facilitar a leitura ... obrigado.
Rich
2
Obrigado por Skip(). Só Take()não vai te dar uma fatia arbitrária. Além disso, eu estava procurando uma solução LINQ de qualquer maneira (fatia IEnumerable, mas sabia que os resultados sobre a matriz seriam mais fáceis de encontrar).
Tomasz Gandor
55
static byte[] SliceMe(byte[] source, int length)
{
    byte[] destfoo = new byte[length];
    Array.Copy(source, 0, destfoo, 0, length);
    return destfoo;
}

//

var myslice = SliceMe(sourcearray,41);
WOPR
fonte
11
Eu acho que o Buffer.BlockCopy () é mais eficiente e alcança os mesmos resultados.
Matt Davis
28

A partir do C # 8.0 / .Net Core 3.0

O corte de matriz será suportado, junto com os novos tipos Indexe Rangeserá adicionado.

Documentos do Range Struct documentos do
Struct

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 };
Console.WriteLine($"{a[i1]}, {a[i2]}"); // "3, 6"

var slice = a[i1..i2]; // { 3, 4, 5 }

Exemplo de código acima, retirado do blog C # 8.0 .

observe que o ^prefixo indica a contagem a partir do final da matriz. Como mostrado no exemplo de documentos

var words = new string[]
{
                // index from start    index from end
    "The",      // 0                   ^9
    "quick",    // 1                   ^8
    "brown",    // 2                   ^7
    "fox",      // 3                   ^6
    "jumped",   // 4                   ^5
    "over",     // 5                   ^4
    "the",      // 6                   ^3
    "lazy",     // 7                   ^2
    "dog"       // 8                   ^1
};              // 9 (or words.Length) ^0

Rangee Indextambém trabalha fora de matrizes de fatiamento, por exemplo, com loops

Range range = 1..4; 
foreach (var name in names[range])

Percorrerá as entradas 1 a 4


Observe que, no momento em que escrevemos esta resposta, o C # 8.0 ainda não havia sido lançado oficialmente, o
C # 8.xe o .NET Core 3.x já estavam disponíveis no Visual Studio 2019 e posteriores.

Remy
fonte
alguma idéia se isso cria ou não uma cópia da matriz?
Tim Pohlmann
2
parece que é uma cópia: codejourney.net/2019/02/csharp-8-slicing-indexes-ranges
Tim Pohlmann
22

No C # 7.2 , você pode usar Span<T>. O benefício do novo System.Memorysistema é que ele não precisa copiar dados.

O método que você precisa é Slice:

Span<byte> slice = foo.Slice(0, 40);

Muitos métodos agora suportam Spane IReadOnlySpan, portanto, será muito simples usar esse novo tipo.

Observe que, no momento da escrita, o Span<T>tipo ainda não está definido na versão mais recente do .NET (4.7.1). Para usá-lo, é necessário instalar o pacote System.Memory do NuGet.

Patrick Hofman
fonte
1
Observe que o Span<T>tipo ainda não está definido na versão mais recente do .Net (4.7.1). Para usá-lo, você precisa instalar o System.MemoryNuGet (e lembre-se de marcar "incluir pré-lançamento" ao procurá-lo no NuGet)
Matthew Watson
@MatthewWatson Thanks. Reescrevi seu comentário e o adicionei à minha resposta.
22818 Patrick Hofman
16

Outra possibilidade que eu não vi mencionada aqui: Buffer.BlockCopy () é um pouco mais rápida que Array.Copy () e tem o benefício adicional de poder converter on-the-fly a partir de uma matriz de primitivas (por exemplo, []) para uma matriz de bytes, o que pode ser útil quando você possui matrizes numéricas que precisam transmitir por soquetes.

Ken Smith
fonte
2
Buffer.BlockCopyproduziu resultados diferentes do que Array.Copy()mesmo que aceitem os mesmos parâmetros - havia muitos elementos vazios. Por quê?
jocull
7
@ jocull - Eles realmente não seguem os mesmos parâmetros. Array.Copy () leva seus parâmetros de comprimento e posição em elementos. Buffer.BlockCopy () leva seus parâmetros de comprimento e posição em bytes. Em outras palavras, se você deseja copiar uma matriz de 10 elementos com números inteiros, você usaria Array.Copy(array1, 0, array2, 0, 10), mas Buffer.BlockCopy(array1, 0, array2, 0, 10 * sizeof(int)).
8279 Ken Smith
14

Se você quiser IEnumerable<byte>, então apenas

IEnumerable<byte> data = foo.Take(x);
Marc Gravell
fonte
14

Aqui está um método de extensão simples que retorna uma fatia como uma nova matriz:

public static T[] Slice<T>(this T[] arr, uint indexFrom, uint indexTo) {
    if (indexFrom > indexTo) {
        throw new ArgumentOutOfRangeException("indexFrom is bigger than indexTo!");
    }

    uint length = indexTo - indexFrom;
    T[] result = new T[length];
    Array.Copy(arr, indexFrom, result, 0, length);

    return result;
}

Então você pode usá-lo como:

byte[] slice = foo.Slice(0, 40);
Vladimir Mitrovic
fonte
8

Se você não deseja adicionar LINQ ou outras extensões, basta:

float[] subArray = new List<float>(myArray).GetRange(0, 8).ToArray();
Dimitris
fonte
Error CS0246: The type or namespace name 'List<>' could not be found (are you missing a using directive or an assembly reference?) A documentação da Microsoft é inútil com centenas de entradas "Lista" indexadas. Qual é o caminho certo aqui?
wallyk
1
System.Collections.Generic.List
Tetralux 22/11/19
7

Você pode usar um wrapper em torno da matriz original (que é IList), como neste pedaço de código (não testado).

public class SubList<T> : IList<T>
{
    #region Fields

private readonly int startIndex;
private readonly int endIndex;
private readonly int count;
private readonly IList<T> source;

#endregion

public SubList(IList<T> source, int startIndex, int count)
{
    this.source = source;
    this.startIndex = startIndex;
    this.count = count;
    this.endIndex = this.startIndex + this.count - 1;
}

#region IList<T> Members

public int IndexOf(T item)
{
    if (item != null)
    {
        for (int i = this.startIndex; i <= this.endIndex; i++)
        {
            if (item.Equals(this.source[i]))
                return i;
        }
    }
    else
    {
        for (int i = this.startIndex; i <= this.endIndex; i++)
        {
            if (this.source[i] == null)
                return i;
        }
    }
    return -1;
}

public void Insert(int index, T item)
{
    throw new NotSupportedException();
}

public void RemoveAt(int index)
{
    throw new NotSupportedException();
}

public T this[int index]
{
    get
    {
        if (index >= 0 && index < this.count)
            return this.source[index + this.startIndex];
        else
            throw new IndexOutOfRangeException("index");
    }
    set
    {
        if (index >= 0 && index < this.count)
            this.source[index + this.startIndex] = value;
        else
            throw new IndexOutOfRangeException("index");
    }
}

#endregion

#region ICollection<T> Members

public void Add(T item)
{
    throw new NotSupportedException();
}

public void Clear()
{
    throw new NotSupportedException();
}

public bool Contains(T item)
{
    return this.IndexOf(item) >= 0;
}

public void CopyTo(T[] array, int arrayIndex)
{
    for (int i=0; i<this.count; i++)
    {
        array[arrayIndex + i] = this.source[i + this.startIndex];
    }
}

public int Count
{
    get { return this.count; }
}

public bool IsReadOnly
{
    get { return true; }
}

public bool Remove(T item)
{
    throw new NotSupportedException();
}

#endregion

#region IEnumerable<T> Members

public IEnumerator<T> GetEnumerator()
{
    for (int i = this.startIndex; i < this.endIndex; i++)
    {
        yield return this.source[i];
    }
}

#endregion

#region IEnumerable Members

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

#endregion

}

Rauhotz
fonte
4
Eu sugiro usar o EqualityComparer.Default para IndexOf - dessa forma, você não precisa de nenhum invólucro especial.
Jon Skeet
1
Eu esperaria que estivesse absolutamente bem. Eu certamente usaria o código mais simples primeiro.
91110 Jon Skeet
Algo assim é, na minha opinião, o melhor caminho a percorrer. Mas, obviamente, é mais trabalho (a primeira vez) do que simples Array.Copy, embora isso possa ter muitas vantagens, como o SubList literalmente sendo uma região na Lista pai, em vez de uma cópia das entradas na Lista.
Aidiakapi
7
byte[] foo = new byte[4096]; 

byte[] bar = foo.Take(40).ToArray();
linha cinza
fonte
6

Para matrizes de bytes, o System.Buffer.BlockCopy oferece o melhor desempenho.

Simon Giles
fonte
1
O que realmente importa se você está fazendo isso em loop milhares ou milhões de vezes. Em um aplicativo de soquetes, você provavelmente está pegando alguma entrada e dividindo-a em partes. Se você está fazendo isso apenas uma vez, o melhor desempenho é o que o próximo programador entenderá com mais facilidade.
Michael Blackburn
5

Você pode usar o método Take extension

var array = new byte[] {1, 2, 3, 4};
var firstTwoItems = array.Take(2);
aku
fonte
3

Esta pode ser uma solução que:

var result = foo.Slice(40, int.MaxValue);

Em seguida, o resultado é um IEnumerable <IEnumerable <byte >> com um primeiro IEnumerable <byte> contém os primeiros 40 bytes de foo e um segundo IEnumerable <byte> mantém o restante.

Eu escrevi uma classe de wrapper, toda a iteração é preguiçosa, espero que ajude:

public static class CollectionSlicer
{
    public static IEnumerable<IEnumerable<T>> Slice<T>(this IEnumerable<T> source, params int[] steps)
    {
        if (!steps.Any(step => step != 0))
        {
            throw new InvalidOperationException("Can't slice a collection with step length 0.");
        }
        return new Slicer<T>(source.GetEnumerator(), steps).Slice();
    }
}

public sealed class Slicer<T>
{
    public Slicer(IEnumerator<T> iterator, int[] steps)
    {
        _iterator = iterator;
        _steps = steps;
        _index = 0;
        _currentStep = 0;
        _isHasNext = true;
    }

    public int Index
    {
        get { return _index; }
    }

    public IEnumerable<IEnumerable<T>> Slice()
    {
        var length = _steps.Length;
        var index = 1;
        var step = 0;

        for (var i = 0; _isHasNext; ++i)
        {
            if (i < length)
            {
                step = _steps[i];
                _currentStep = step - 1;
            }

            while (_index < index && _isHasNext)
            {
                _isHasNext = MoveNext();
            }

            if (_isHasNext)
            {
                yield return SliceInternal();
                index += step;
            }
        }
    }

    private IEnumerable<T> SliceInternal()
    {
        if (_currentStep == -1) yield break;
        yield return _iterator.Current;

        for (var count = 0; count < _currentStep && _isHasNext; ++count)
        {
            _isHasNext = MoveNext();

            if (_isHasNext)
            {
                yield return _iterator.Current;
            }
        }
    }

    private bool MoveNext()
    {
        ++_index;
        return _iterator.MoveNext();
    }

    private readonly IEnumerator<T> _iterator;
    private readonly int[] _steps;
    private volatile bool _isHasNext;
    private volatile int _currentStep;
    private volatile int _index;
}
Li Zhen
fonte
2

Eu não acho que o C # suporta a semântica do intervalo. Você pode escrever um método de extensão, como:

public static IEnumerator<Byte> Range(this byte[] array, int start, int end);

Mas, como outros já disseram, se você não precisa definir um índice inicial, Takeé tudo o que precisa.

bleevo
fonte
1

Aqui está uma função de extensão que usa um genérico e se comporta como a função PHP array_slice . Deslocamento negativo e comprimento são permitidos.

public static class Extensions
{
    public static T[] Slice<T>(this T[] arr, int offset, int length)
    {
        int start, end;

        // Determine start index, handling negative offset.
        if (offset < 0)
            start = arr.Length + offset;
        else
            start = offset;

        // Clamp start index to the bounds of the input array.
        if (start < 0)
            start = 0;
        else if (start > arr.Length)
            start = arr.Length;

        // Determine end index, handling negative length.
        if (length < 0)
            end = arr.Length + length;
        else
            end = start + length;

        // Clamp end index to the bounds of the input array.
        if (end < 0)
            end = 0;
        if (end > arr.Length)
            end = arr.Length;

        // Get the array slice.
        int len = end - start;
        T[] result = new T[len];
        for (int i = 0; i < len; i++)
        {
            result[i] = arr[start + i];
        }
        return result;
    }
}
Brendan Taylor
fonte
1
Muito bom, apesar de algumas coisas do mundo .NET. Se startnão estiver entre 0 e arr.Length, provavelmente deve gerar uma exceção fora dos limites. Além disso, end >= start >= 0para que você não precise verificar end < 0, não é possível que isso aconteça. Você provavelmente poderia fazê-lo ainda mais sucintamente verificando isso length >= 0e depois em len = Math.min(length, arr.Length - start)vez de se enganar end.
Matthew Scharley
0
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace data_seniens
{
    class Program
    {
        static void Main(string[] args)
        {
            //new list
            float [] x=new float[]{11.25f,18.0f,20.0f,10.75f,9.50f, 11.25f, 18.0f, 20.0f, 10.75f, 9.50f };

            //variable
            float eat_sleep_area=x[1]+x[3];
            //print
            foreach (var VARIABLE in x)
            {
                if (VARIABLE < x[7])
                {
                    Console.WriteLine(VARIABLE);
                }
            }



            //keep app run
        Console.ReadLine();
        }
    }
}
Ahmad AlSaloum
fonte