Rebanadas de matriz en C #


228

¿Cómo lo haces? Dado un conjunto de bytes:

byte[] foo = new byte[4096];

¿Cómo obtendría los primeros x bytes de la matriz como una matriz separada? (Específicamente, lo necesito como un IEnumerable<byte>)

Esto es para trabajar con Sockets. Me imagino que la forma más fácil sería el corte de matriz, similar a la sintaxis de Perls:

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

Lo que devolvería los primeros 41 elementos en la @barmatriz. ¿Hay algo en C # que solo me estoy perdiendo, o hay alguna otra cosa que debería estar haciendo?

LINQ es una opción para mí (.NET 3.5), si eso ayuda.


3
El corte de matriz es una propuesta para c # 7.2 github.com/dotnet/csharplang/issues/185
Mark

3
C # 8.0 verá la introducción de la segmentación de matriz nativa. Ver respuesta para más detalles
Remy

1
Tal vez le interese ArraySlice <T>, que implementa el corte de matrices con paso como una vista sobre los datos originales: github.com/henon/SliceAndDice
henon

Respuestas:


196

Las matrices son enumerables, por lo que su fooya es un IEnumerable<byte>sí mismo. Simplemente use métodos de secuencia LINQ como Take()obtener lo que desea de él (no olvide incluir el Linqespacio de nombres con using System.Linq;):

byte[] foo = new byte[4096];

var bar = foo.Take(41);

Si realmente necesita una matriz de cualquier IEnumerable<byte>valor, puede usar el ToArray()método para eso. Ese no parece ser el caso aquí.


55
Si vamos a copiar a otra matriz, simplemente use el método estático Array.Copy. Sin embargo, creo que las otras respuestas han interpretado la intención correctamente, no se requiere otra matriz solo un <byte> IEnumberable que en los primeros 41 bytes.
AnthonyWJones

2
Tenga en cuenta que solo las matrices unidimensionales e irregulares son enumerables, las matrices multidimensionales no lo son.
Abel

11
Tenga en cuenta que el uso de Array.Copy funciona mucho más rápido que el uso de los métodos Take o Skip de LINQ.
Michael

44
@Abel Eso es realmente muy incorrecto. Matrices multidimensionales son enumerables pero enumeran así: [2,3] => [1,1], [1,2], [1,3], [2,1], [2,2], [2,3]. Las matrices irregulares también son enumerables, pero en lugar de devolver un valor cuando se enumeran, devuelven su matriz interna. Así:type[][] jaggedArray; foreach (type[] innerArray in jaggedArray) { }
Aidiakapi

3
@Aidiakapi "muy incorect"? ;). Pero tiene razón en parte, debería haber escrito "las matrices multidim no se implementan IEnumerable<T>", entonces mi declaración habría sido más clara. Vea también esto: stackoverflow.com/questions/721882/…
Abel

211

Podrías usar ArraySegment<T>. Es muy liviano ya que no copia la matriz:

string[] a = { "one", "two", "three", "four", "five" };
var segment = new ArraySegment<string>( a, 1, 2 );

55
Lamentablemente no es IEnumerable.
recursivo el

1
Es cierto, pero sería fácil escribir un envoltorio iterador alrededor que implemente IEnumerable.
Mike Scott

22
¿Alguien sabe POR QUÉ no es IEnumerable? Yo no. Parece que debería ser.
Fantius

39
ArraySegment es IList e IEnumerable a partir de .Net 4.5. Demasiado malo para los usuarios de versiones anteriores ..
Todd Li

66
@Zyo Quise decir que ArraySegment <T> implementa IEnumerable <T> a partir de .Net 4.5, no IEnumerable <T> en sí mismo es nuevo.
Todd Li

137

Podrías usar el CopyTo()método de matrices .

O con LINQ puede usar Skip()y Take()...

byte[] arr = {1, 2, 3, 4, 5, 6, 7, 8};
var subset = arr.Skip(2).Take(2);

1
+1 para una buena idea, pero necesito usar la matriz devuelta como entrada para otra función, lo que hace que CopyTo requiera una variable temporal. Esperaré otras respuestas todavía.
Matthew Scharley el

44
Todavía no estoy familiarizado con LINQ, tal vez esto sea una prueba más de que realmente debería estarlo.
Matthew Scharley el

11
este enfoque es al menos 50 veces más lento que Array.Copy. Esto no es un problema en muchas situaciones, pero cuando se hace un corte de matriz en un ciclo, la caída del rendimiento es muy obvia.
Valentin Vasilyev

3
Estoy haciendo una sola llamada, por lo que el rendimiento no es un problema para mí. Esto es genial para la legibilidad ... gracias.
Rico

2
Gracias por Skip(). Simplemente Take()no te dará una porción arbitraria. Además, estaba buscando una solución LINQ de todos modos (rebanada IEnumerable, pero sabía que los resultados sobre la matriz serían más fáciles 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);

11
Creo que Buffer.BlockCopy () es más eficiente y logra los mismos resultados.
Matt Davis

28

A partir de C # 8.0 / .Net Core 3.0

Se admitirá el corte de matriz, junto con los nuevos tipos Indexy la Rangeadición.

Range Struct docs
Index Struct docs

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 }

Ejemplo de código anterior tomado del blog C # 8.0 .

tenga en cuenta que el ^prefijo indica contar desde el final de la matriz. Como se muestra en el ejemplo de documentación

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

Rangey Indextambién funcionan fuera de las matrices de corte, por ejemplo con bucles

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

Recorrerá las entradas 1 a 4


tenga en cuenta que al momento de escribir esta respuesta, C # 8.0 aún no se ha lanzado oficialmente
C # 8.xy .Net Core 3.x ahora están disponibles en Visual Studio 2019 y en adelante


¿Alguna idea de si esto crea o no una copia de la matriz?
Tim Pohlmann


22

En C # 7.2 , puede usar Span<T>. El beneficio del nuevo System.Memorysistema es que no necesita copiar datos.

El método que necesitas es Slice:

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

Muchos métodos ahora son compatibles Spany IReadOnlySpan, por lo tanto, será muy sencillo utilizar este nuevo tipo.

Tenga en cuenta que al momento de escribir este Span<T>tipo aún no está definido en la versión más reciente de .NET (4.7.1), por lo que para usarlo debe instalar el paquete System.Memory de NuGet.


1
Tenga en cuenta que el Span<T>tipo aún no está definido en la versión más reciente de .Net (4.7.1), por lo que para usarlo debe instalar System.MemoryNuGet (y recuerde marcar "incluir prelanzamiento" cuando lo busque en NuGet)
Matthew Watson el

@MatthewWatson Gracias. Reescribí tu comentario y lo agregué a mi respuesta.
Patrick Hofman el

16

Otra posibilidad que no he visto mencionada aquí: Buffer.BlockCopy () es un poco más rápido que Array.Copy (), y tiene el beneficio adicional de poder convertir sobre la marcha de una serie de primitivas (digamos, short []) a una matriz de bytes, que puede ser útil cuando tienes matrices numéricas que necesitas transmitir a través de Sockets.


2
Buffer.BlockCopyprodujo resultados diferentes a Array.Copy()pesar de que aceptan los mismos parámetros: había muchos elementos vacíos. ¿Por qué?
Jocull

77
@jocull: en realidad no toman los mismos parámetros. Array.Copy () toma sus parámetros de longitud y posición en elementos. Buffer.BlockCopy () toma sus parámetros de longitud y posición en bytes. En otras palabras, si quisieras copiar una matriz de enteros de 10 elementos, usarías Array.Copy(array1, 0, array2, 0, 10), pero Buffer.BlockCopy(array1, 0, array2, 0, 10 * sizeof(int)).
Ken Smith

14

Si quieres IEnumerable<byte>, entonces solo

IEnumerable<byte> data = foo.Take(x);

14

Aquí hay un método de extensión simple que devuelve un segmento como una nueva 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;
}

Entonces puedes usarlo como:

byte[] slice = foo.Slice(0, 40);

8

Si no desea agregar LINQ u otras extensiones, simplemente haga lo siguiente:

float[] subArray = new List<float>(myArray).GetRange(0, 8).ToArray();

Error CS0246: The type or namespace name 'List<>' could not be found (are you missing a using directive or an assembly reference?) La documentación de Microsoft es inútil con cientos de entradas de "Lista" indexadas. ¿Cuál es el correcto aquí?
wallyk

1
System.Collections.Generic.List
Tetralux

7

Podría usar un contenedor alrededor de la matriz original (que es IList), como en este fragmento de código (no probado).

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

}


44
Sugeriría usar EqualityComparer.Default para IndexOf, de esa manera no necesita ninguna carcasa especial.
Jon Skeet el

1
Espero que esté absolutamente bien. Ciertamente iría con el código más simple primero.
Jon Skeet

Algo así es, en mi opinión, la mejor manera de hacerlo. Pero obviamente es más trabajo (la primera vez) que simple Array.Copy, a pesar de que esto puede tener muchas ventajas, como que la Sublista es literalmente una región dentro de la Lista principal, en lugar de una copia de las entradas en la Lista.
Aidiakapi


6

Para las matrices de bytes, System.Buffer.BlockCopy le dará el mejor rendimiento.


1
Lo que realmente importa si está haciendo esto en un bucle miles o millones de veces. En una aplicación de sockets, probablemente esté tomando algo de entrada y dividiéndolo en partes. Si solo lo hace una vez, el mejor rendimiento es lo que el próximo programador entienda más fácilmente.
Michael Blackburn

5

Puede usar el método de extensión Take

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

3

Esta puede ser una solución que:

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

Entonces el resultado es un IEnumerable <IEnumerable <byte >> con un primer IEnumerable <byte> contiene los primeros 40 bytes de foo , y un segundo IEnumerable <byte> contiene el resto.

Escribí una clase de contenedor, toda la iteración es perezosa, espero que pueda ayudar:

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

2

No creo que C # sea compatible con la semántica de Range. Sin embargo, podría escribir un método de extensión, como:

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

Pero, como han dicho otros, si no necesita establecer un índice de inicio, entonces Takees todo lo que necesita.


1

Aquí hay una función de extensión que usa un genérico y se comporta como la función PHP array_slice . El desplazamiento negativo y la longitud están 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;
    }
}

1
Bastante bueno, aunque algunas cosas del mundo .NET. Si startno está entre 0 y arr.Length, probablemente debería arrojar una excepción fuera de límites. Además, end >= start >= 0por lo que no necesita verificar end < 0, no es posible que suceda. Probablemente podría hacerlo aún más sucintamente comprobando eso length >= 0y luego en len = Math.min(length, arr.Length - start)lugar de jugar con él 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();
        }
    }
}
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.