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.