Fette di matrice in C #


228

Come si fa? Dato un array di byte:

byte[] foo = new byte[4096];

Come otterrei i primi x byte dell'array come array separato? (In particolare, ne ho bisogno come IEnumerable<byte>)

Questo è per lavorare con Sockets. Immagino che il modo più semplice sarebbe la suddivisione in array, simile alla sintassi di Perls:

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

Il che restituirebbe i primi 41 elementi @barnell'array. C'è qualcosa in C # che mi manca o c'è qualche altra cosa che dovrei fare?

LINQ è un'opzione per me (.NET 3.5), se questo aiuta.


3
L'array slicing è una proposta per c # 7.2 github.com/dotnet/csharplang/issues/185
— Mark

3
C # 8.0 vedrà l'introduzione della suddivisione in array nativa. Vedi la risposta per maggiori dettagli
— Remy

1
Potresti essere interessato a ArraySlice <T> che implementa lo slicing di array con step come vista sui dati originali: github.com/henon/SliceAndDice
— henon

Risposte:


196

Le matrici sono enumerabili, quindi il tuo fooè già un IEnumerable<byte>se stesso. Usa semplicemente metodi di sequenza LINQ come Take()ottenere ciò che vuoi da esso (non dimenticare di includere lo Linqspazio dei nomi con using System.Linq;):

byte[] foo = new byte[4096];

var bar = foo.Take(41);

Se hai davvero bisogno di un array da qualsiasi IEnumerable<byte>valore, puoi usare il ToArray()metodo per quello. Questo non sembra essere il caso qui.


5
Se stiamo per copiare su un altro array, usa semplicemente il metodo statico Array.Copy. Tuttavia, penso che le altre risposte abbiano interpretato correttamente l'intento, non è necessario un altro array, ma solo un IEnumberable <byte> che nei primi 41 byte.
— AnthonyWJones,

2
Si noti che solo gli array monodimensionali e frastagliati sono enumerabili, gli array multidimensionali no.
— Abel

11
Nota l'utilizzo di Array.Copy si comporta molto più velocemente rispetto all'utilizzo dei metodi Take o Skip di LINQ.
— Michael,

4
@Abel Questo è in realtà molto errato. Array multi dimensionale sono enumerabile ma elencano in questo modo: [2,3] => [1,1], [1,2], [1,3], [2,1], [2,2], [2,3]. Le matrici frastagliate sono anche enumerabili ma invece di restituire un valore quando vengono enumerate, restituiscono la loro matrice interna. In questo modo:type[][] jaggedArray; foreach (type[] innerArray in jaggedArray) { }
— Aidiakapi,

3
@Aidiakapi "very incorect"? ;). Ma hai in parte ragione, avrei dovuto scrivere "le matrici multimim non implementano IEnumerable<T>", quindi la mia affermazione sarebbe stata più chiara. Vedi anche questo: stackoverflow.com/questions/721882/…
— Abele

211

Puoi usare ArraySegment<T>. È molto leggero in quanto non copia l'array:

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

5
Sfortunatamente non è IEnumerable.
— ricorsivo

1
È vero, ma sarebbe facile scrivere un wrapper iteratore che implementa IEnumerable.
— Mike Scott,

22
Qualcuno sa PERCHÉ non è IEnumerable? Io non. Sembra che dovrebbe essere.
— Fantius,

39
ArraySegment è IList e IEnumerable a partire da .Net 4.5. Peccato per gli utenti della versione precedente.
— Todd Li

6
@Zyo intendevo che ArraySegment <T> implementa IEnumerable <T> a partire da .Net 4.5, non IEnumerable <T> è nuovo.
— Todd Li,

137

È possibile utilizzare il CopyTo()metodo array .

O con LINQ puoi usare Skip()e Take()...

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

1
+1 per una buona idea, ma ho bisogno di usare l'array restituito come input per un'altra funzione, il che rende CopyTo richiedere una variabile temporanea. Aspetterò altre risposte ancora.
— Matthew Scharley,

4
Non ho ancora familiarità con LINQ, forse questa è un'ulteriore prova che dovrei davvero essere.
— Matthew Scharley,

11
questo approccio è almeno 50 volte più lento di Array.Copy. Questo non è un problema in molte situazioni ma quando si esegue lo slicing dell'array in un ciclo, il calo delle prestazioni è molto evidente.
— Valentin Vasilyev il

3
Sto effettuando una singola chiamata, quindi le prestazioni non sono un problema per me. Questo è ottimo per la leggibilità ... grazie.
— Ricco

2
Grazie per Skip(). Solo Take()non ti darà una fetta arbitraria. Inoltre, cercavo comunque una soluzione LINQ (slice IEnumerable, ma sapevo che i risultati sull'array sarebbero stati più facili da trovare).
— 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
Penso che Buffer.BlockCopy () sia più efficiente e ottenga gli stessi risultati.
— Matt Davis,

28

A partire da C # 8.0 / .Net Core 3.0

Verrà supportata la suddivisione in array, insieme ai nuovi tipi Indexe in Rangefase di aggiunta.

Range Documenti
Struct Indice Documenti 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 }

Esempio di codice sopra riportato dal blog C # 8.0 .

si noti che il ^prefisso indica il conteggio dalla fine dell'array. Come mostrato nell'esempio di documenti

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

Range e Index lavorare anche al di fuori di array di sezioni, ad esempio con loop

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

Passerà attraverso le voci da 1 a 4


si noti che al momento della stesura di questa risposta, C # 8.0 non è ancora ufficialmente rilasciato
C # 8.xe .Net Core 3.x sono ora disponibili in Visual Studio 2019 e versioni successive


qualche idea se questo crea o meno una copia dell'array?
— Tim Pohlmann,


22

In C # 7.2 , puoi usare Span<T>. Il vantaggio del nuovoSystem.Memory sistema è che non è necessario copiare i dati.

Il metodo che ti serve è Slice:

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

Molti metodi ora supportano SpaneIReadOnlySpan , quindi sarà molto semplice usare questo nuovo tipo.

Si noti che al momento della scrittura il Span<T>tipo non è ancora definito nella versione più recente di .NET (4.7.1), quindi per usarlo è necessario installare il pacchetto System.Memory da NuGet.


1
Si noti che il Span<T>tipo non è ancora definito nella versione più recente di .Net (4.7.1), quindi per usarlo è necessario installare System.Memoryda NuGet (e ricordarsi di spuntare "include pre-release" quando si cerca in NuGet)
— Matthew Watson,

@MatthewWatson Grazie. Ho riscritto il tuo commento e l'ho aggiunto alla mia risposta.
— Patrick Hofman,

16

Un'altra possibilità che non ho mai menzionato qui: Buffer.BlockCopy () è leggermente più veloce di Array.Copy () e ha l'ulteriore vantaggio di poter convertire al volo da una matrice di primitivi (diciamo, breve []) a una matrice di byte, che può essere utile quando si hanno array numerici che è necessario trasmettere su Socket.


2
Buffer.BlockCopyha prodotto risultati diversi rispetto a quelli Array.Copy()che accettano gli stessi parametri: c'erano molti elementi vuoti. Perché?
— Giovedì

7
@jocull - In realtà non accettano esattamente gli stessi parametri. Array.Copy () accetta i parametri di lunghezza e posizione negli elementi. Buffer.BlockCopy () accetta i suoi parametri di lunghezza e posizione in byte. In altre parole, se si desidera copiare un array di numeri interi a 10 elementi, è necessario utilizzare Array.Copy(array1, 0, array2, 0, 10), ma Buffer.BlockCopy(array1, 0, array2, 0, 10 * sizeof(int)).
— Ken Smith,

14

Se vuoi IEnumerable<byte>, allora basta

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

14

Ecco un semplice metodo di estensione che restituisce una sezione come un nuovo array:

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

Quindi puoi usarlo come:

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

8

Se non vuoi aggiungere LINQ o altre estensioni, fai semplicemente:

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 documentazione di Microsoft è senza speranza con centinaia di voci "Elenco" indicizzate. Qual è quello giusto qui?
— Wallyk,

1
System.Collections.Generic.List
— Tetralux,

7

È possibile utilizzare un wrapper attorno all'array originale (che è IList), come in questo pezzo di codice (non testato).

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

}


4
Suggerirei di utilizzare EqualityComparer.Default per IndexOf - in questo modo non è necessario alcun involucro speciale.
— Jon Skeet,

1
Mi aspetto che vada assolutamente bene. Sicuramente andrei prima con il codice più semplice.
— Jon Skeet,

Qualcosa del genere è secondo me il modo migliore di procedere. Ma ovviamente è più un lavoro (la prima volta) che un sempliceArray.Copy , anche se questo può avere molti vantaggi, come il fatto che la lista secondaria sia letteralmente una regione all'interno della lista padre, invece di una copia delle voci nella lista.
— Aidiakapi,

7
byte[] foo = new byte[4096]; 

byte[] bar = foo.Take(40).ToArray();

6

Per gli array di byte System.Buffer.BlockCopy offre le prestazioni migliori.


1
Il che conta davvero solo se lo fai in un ciclo migliaia o milioni di volte. In un'applicazione socket, probabilmente stai prendendo un po 'di input e spezzandolo in parti. Se lo fai solo una volta, le prestazioni migliori sono quelle che il prossimo programmatore capirà più facilmente.
— Michael Blackburn,

5

È possibile utilizzare il metodo di estensione Take

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

3

Questa potrebbe essere una soluzione che:

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

Quindi il risultato è un IEnumerable <IEnumerable <byte >> con un primo IEnumerable <byte> contiene i primi 40 byte di pippo e un secondo IEnumerable <byte> contiene il resto.

Ho scritto una classe wrapper, l'intera iterazione è pigra, spero che possa aiutare:

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

Non credo che C # supporti la semantica di Range. È possibile scrivere un metodo di estensione, ad esempio:

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

Ma come altri hanno detto se non è necessario impostare un indice iniziale, Takeè tutto ciò che serve.


1

Ecco una funzione di estensione che utilizza un generico e si comporta come la funzione PHP array_slice . Sono consentiti offset e lunghezza negativi.

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
Abbastanza buono, anche se alcune cose dal mondo .NET. Se startnon è compreso tra 0 e arr.Length, probabilmente dovrebbe generare un'eccezione fuori limite. Inoltre, end >= start >= 0quindi non è necessario verificare end < 0, non è possibile che accada. Probabilmente potresti farlo in modo ancora più succinto controllandolo length >= 0e poi len = Math.min(length, arr.Length - start)invece di confonderlo 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();
        }
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.