Array-Slices in C #

228

Wie machst du das? Gegeben ein Byte-Array:

byte[] foo = new byte[4096];

Wie würde ich die ersten x Bytes des Arrays als separates Array erhalten? (Speziell brauche ich es als IEnumerable<byte>)

Dies ist für die Arbeit mit Sockets. Ich denke, der einfachste Weg wäre das Schneiden von Arrays, ähnlich der Perls-Syntax:

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

Welches würde die ersten 41 Elemente in das @barArray zurückgeben. Gibt es etwas in C #, das mir gerade fehlt, oder gibt es noch etwas, was ich tun sollte?

LINQ ist eine Option für mich (.NET 3.5), falls dies hilfreich ist.

Matthew Scharley
quelle
3
Array Slicing ist ein Vorschlag für c # 7.2 github.com/dotnet/csharplang/issues/185
Mark
3
In C # 8.0 wird das native Array-Slicing eingeführt. Siehe Antwort für weitere Details
Remy
1
Sie könnten an ArraySlice <T> interessiert sein, das das Schneiden von Arrays mit Schritt als Ansicht über die Originaldaten implementiert: github.com/henon/SliceAndDice
Henon

Antworten:

196

Arrays sind aufzählbar, so dass Sie foobereits ein Selbst sind IEnumerable<byte>. Verwenden Sie einfach LINQ-Sequenzmethoden Take(), um das zu erreichen, was Sie wollen (vergessen Sie nicht, den LinqNamespace mit einzuschließen using System.Linq;):

byte[] foo = new byte[4096];

var bar = foo.Take(41);

Wenn Sie wirklich ein Array von einem beliebigen IEnumerable<byte>Wert benötigen , können Sie die ToArray()Methode dafür verwenden. Dies scheint hier nicht der Fall zu sein.

peSHIr
quelle
5
Wenn Sie in ein anderes Array kopieren möchten, verwenden Sie einfach die statische Methode Array.Copy. Ich denke jedoch, dass die anderen Antworten die Absicht richtig interpretiert haben. Für ein anderes Array ist nicht nur ein IE-nummerierbares <Byte> erforderlich, das sich über die ersten 41 Bytes erstreckt.
AnthonyWJones
2
Beachten Sie, dass nur eindimensionale und gezackte Arrays aufzählbar sind, mehrdimensionale Arrays jedoch nicht.
Abel
11
Beachten Sie, dass die Verwendung von Array.Copy viel schneller ist als die Verwendung der Take- oder Skip-Methoden von LINQ.
Michael
4
@Abel Das ist eigentlich sehr falsch. Mehrdimensionale Arrays sind aufzählbar, werden jedoch wie folgt aufgelistet : [2,3] => [1,1], [1,2], [1,3], [2,1], [2,2], [2,3]. Gezackte Arrays sind ebenfalls aufzählbar, aber anstatt bei der Aufzählung einen Wert zurückzugeben, geben sie ihr inneres Array zurück. So:type[][] jaggedArray; foreach (type[] innerArray in jaggedArray) { }
Aidiakapi
3
@Aidiakapi "sehr inkorrekt"? ;). Aber Sie haben teilweise Recht, ich hätte schreiben sollen "Multidim-Arrays nicht implementieren IEnumerable<T>", dann wäre meine Aussage klarer gewesen. Siehe auch dies: stackoverflow.com/questions/721882/…
Abel
211

Sie könnten verwenden ArraySegment<T>. Es ist sehr leicht, da es das Array nicht kopiert:

string[] a = { "one", "two", "three", "four", "five" };
var segment = new ArraySegment<string>( a, 1, 2 );
Mike Scott
quelle
5
Leider ist es nicht IEnumerable.
rekursiv
1
Stimmt, aber es wäre einfach, einen Iterator-Wrapper darum zu schreiben, der IEnumerable implementiert.
Mike Scott
22
Weiß jemand, warum es nicht IEnumerable ist? Ich nicht. Es scheint so, als ob es sein sollte.
Fantius
39
ArraySegment ist IList und IEnumerable ab .Net 4.5. Schade für ältere Version Benutzer ..
Todd Li
6
@Zyo Ich meinte, ArraySegment <T> implementiert IEnumerable <T> ab .Net 4.5, nicht IEnumerable <T> selbst ist neu.
Todd Li
137

Sie können die Arrays- CopyTo()Methode verwenden.

Oder mit LINQ können Sie verwenden Skip()und Take()...

byte[] arr = {1, 2, 3, 4, 5, 6, 7, 8};
var subset = arr.Skip(2).Take(2);
Arjan Einbu
quelle
1
+1 für eine gute Idee, aber ich muss das zurückgegebene Array als Eingabe für eine andere Funktion verwenden, wodurch CopyTo eine temporäre Variable benötigt. Ich werde noch auf andere Antworten warten.
Matthew Scharley
4
Ich bin noch nicht mit LINQ vertraut, vielleicht ist dies ein weiterer Beweis dafür, dass ich es wirklich sein sollte.
Matthew Scharley
11
Dieser Ansatz ist mindestens 50x langsamer als Array.Copy. Dies ist in vielen Situationen kein Problem, aber wenn Sie das Array in einem Zyklus aufteilen, ist der Leistungsabfall sehr offensichtlich.
Valentin Vasilyev
3
Ich telefoniere nur einmal, daher ist die Leistung für mich kein Problem. Dies ist gut für die Lesbarkeit ... danke.
Rich
2
Danke für Skip(). Sie bekommen einfach Take()keine willkürliche Scheibe. Außerdem suchte ich sowieso nach einer LINQ-Lösung (Slice IEnumerable, aber ich wusste, dass Ergebnisse über Arrays leichter zu finden sind).
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
quelle
11
Ich denke, Buffer.BlockCopy () ist effizienter und erzielt die gleichen Ergebnisse.
Matt Davis
28

Ab C # 8.0 / .Net Core 3.0

Array-Slicing wird zusammen mit den neuen Typen unterstützt Indexund Rangehinzugefügt.

Range Struct-Dokumente
Index Struct-Dokumente

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 }

Das obige Codebeispiel stammt aus dem C # 8.0- Blog .

Beachten Sie, dass das ^Präfix das Zählen ab dem Ende des Arrays anzeigt . Wie im Dokumentbeispiel gezeigt

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

Rangeund Indexarbeiten auch außerhalb von Slicing-Arrays, beispielsweise mit Schleifen

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

Durchläuft die Einträge 1 bis 4


Beachten Sie, dass C # 8.0 zum Zeitpunkt des Schreibens dieser Antwort noch nicht offiziell freigegeben ist.
C # 8.x und .Net Core 3.x sind jetzt in Visual Studio 2019 und höher verfügbar

Remy
quelle
Irgendeine Idee, ob dadurch eine Kopie des Arrays erstellt wird oder nicht?
Tim Pohlmann
2
scheint eine Kopie zu sein: codejourney.net/2019/02/csharp-8-slicing-indexes-ranges
Tim Pohlmann
21

In C # 7.2 können Sie verwenden Span<T>. Der Vorteil des neuen System.MemorySystems besteht darin, dass keine Daten kopiert werden müssen.

Die Methode, die Sie benötigen, ist Slice:

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

Viele Methoden unterstützen jetzt Spanund IReadOnlySpandaher wird es sehr einfach sein, diesen neuen Typ zu verwenden.

Beachten Sie, dass der Span<T>Typ zum Zeitpunkt des Schreibens noch nicht in der neuesten Version von .NET (4.7.1) definiert ist. Um ihn zu verwenden, müssen Sie das System.Memory-Paket von NuGet installieren .

Patrick Hofman
quelle
1
Beachten Sie, dass die Span<T>Art nicht in der der neuesten Version von .Net noch (4.7.1) , so dass es zu verwenden , definiert ist müssen Sie die Installation System.Memoryvon NuGet (und denken Sie daran, tick „schließen Prerelease“ , wenn es in NuGet Suche)
Matthew Watson
@ MatthewWatson Danke. Ich habe Ihren Kommentar umgeschrieben und meiner Antwort hinzugefügt.
Patrick Hofman
16

Eine andere Möglichkeit, die ich hier nicht gesehen habe: Buffer.BlockCopy () ist etwas schneller als Array.Copy () und hat den zusätzlichen Vorteil, dass es in der Lage ist, on-the-fly aus einem Array von Grundelementen (z. B. kurz) zu konvertieren []) zu einem Array von Bytes, was nützlich sein kann, wenn Sie numerische Arrays haben, die Sie über Sockets übertragen müssen.

Ken Smith
quelle
2
Buffer.BlockCopyführten zu anderen Ergebnissen als Array.Copy()obwohl sie die gleichen Parameter akzeptieren - es gab viele leere Elemente. Warum?
Jocull
7
@jocull - Sie nehmen eigentlich nicht ganz die gleichen Parameter an. Array.Copy () verwendet seine Längen- und Positionsparameter in Elementen. Buffer.BlockCopy () nimmt seine Längen- und Positionsparameter in Bytes. Mit anderen Worten, wenn Sie ein 10-Element-Array von Ganzzahlen kopieren möchten Array.Copy(array1, 0, array2, 0, 10), würden Sie aber verwenden Buffer.BlockCopy(array1, 0, array2, 0, 10 * sizeof(int)).
Ken Smith
14

Wenn du willst IEnumerable<byte>, dann einfach

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

Hier ist eine einfache Erweiterungsmethode, die ein Slice als neues Array zurückgibt:

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

Dann können Sie es verwenden als:

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

Wenn Sie LINQ oder andere Erweiterungen nicht hinzufügen möchten, gehen Sie einfach wie folgt vor :

float[] subArray = new List<float>(myArray).GetRange(0, 8).ToArray();
Dimitris
quelle
Error CS0246: The type or namespace name 'List<>' could not be found (are you missing a using directive or an assembly reference?) Die Microsoft-Dokumentation ist hoffnungslos, da Hunderte von "Listen" -Einträgen indiziert sind. Was ist hier das Richtige?
Wallyk
1
System.Collections.Generic.List
Tetralux
7

Sie können einen Wrapper um das ursprüngliche Array (IList) verwenden, wie in diesem (nicht getesteten) Code.

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
quelle
4
Ich würde vorschlagen, EqualityComparer.Default für IndexOf zu verwenden - auf diese Weise benötigen Sie kein spezielles Gehäuse.
Jon Skeet
1
Ich würde erwarten, dass es absolut in Ordnung ist. Ich würde sicherlich zuerst mit dem einfacheren Code gehen.
Jon Skeet
So etwas ist meiner Meinung nach der beste Weg. Aber offensichtlich ist es mehr Arbeit (das erste Mal) als eine einfache Array.Copy, obwohl dies viele Vorteile haben kann, z. B. dass die Unterliste buchstäblich eine Region innerhalb der übergeordneten Liste ist, anstatt eine Kopie der Einträge in der Liste.
Aidiakapi
7
byte[] foo = new byte[4096]; 

byte[] bar = foo.Take(40).ToArray();
graue Linie
quelle
6

Für Byte-Arrays bietet System.Buffer.BlockCopy die beste Leistung.

Simon Giles
quelle
1
Was nur dann wirklich wichtig ist, wenn Sie dies tausend- oder millionenfach in einer Schleife tun. In einer Sockets-Anwendung nehmen Sie wahrscheinlich einige Eingaben und teilen sie in Teile auf. Wenn Sie es nur einmal tun, ist die beste Leistung das, was der nächste Programmierer am leichtesten verstehen wird.
Michael Blackburn
5

Sie können die Take-Erweiterungsmethode verwenden

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

Dies kann eine Lösung sein, die:

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

Das Ergebnis ist dann, dass ein IEnumerable <IEnumerable <Byte >> mit einem ersten IEnumerable <Byte> die ersten 40 Bytes von foo enthält und ein zweites IEnumerable <Byte> den Rest enthält.

Ich habe eine Wrapper-Klasse geschrieben, die ganze Iteration ist faul, hoffe es könnte helfen:

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
quelle
2

Ich glaube nicht, dass C # die Range-Semantik unterstützt. Sie könnten jedoch eine Erweiterungsmethode schreiben, wie:

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

Aber wie andere gesagt haben, wenn Sie keinen Startindex festlegen müssen, Takeist alles, was Sie brauchen.

bleevo
quelle
1

Hier ist eine Erweiterungsfunktion, die eine generische Funktion verwendet und sich wie die PHP-Funktion array_slice verhält . Negativer Versatz und Länge sind zulässig.

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
quelle
1
Ziemlich gut, obwohl ein paar Dinge aus der .NET-Welt. Wenn startes nicht zwischen 0 und liegt arr.Length, sollte es wahrscheinlich eine Ausnahme außerhalb der Grenzen auslösen. Auch end >= start >= 0, so dass Sie nicht brauchen zu überprüfen end < 0, ist es nicht möglich , dass es passieren wird. Sie könnten es wahrscheinlich noch prägnanter machen, indem Sie das überprüfen length >= 0und dann, len = Math.min(length, arr.Length - start)anstatt damit herumzuspielen 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
quelle