Plasterki tablic w C #

228

Jak ty to robisz? Biorąc pod uwagę tablicę bajtów:

byte[] foo = new byte[4096];

Jak uzyskać pierwsze x bajtów tablicy jako osobną tablicę? (W szczególności potrzebuję go jako IEnumerable<byte>)

To jest do pracy z Sockets. Myślę, że najłatwiejszym sposobem byłoby dzielenie tablic, podobne do składni Perlsa:

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

Co zwróci pierwsze 41 elementów do @bartablicy. Czy jest coś w C #, którego po prostu brakuje, czy jest coś innego, co powinienem zrobić?

LINQ jest dla mnie opcją (.NET 3.5), jeśli to pomaga.

Matthew Scharley
źródło
3
Wycinanie
Mark
3
W C # 8.0 pojawi się wprowadzenie natywnego dzielenia tablic. Zobacz odpowiedź po więcej szczegółów
Remy,
1
Możesz być zainteresowany ArraySlice <T>, który implementuje krojenie tablic z krokiem jako widok na oryginalne dane: github.com/henon/SliceAndDice
henon

Odpowiedzi:

196

Tablice są policzalne, więc foojuż jesteś IEnumerable<byte>sobą. Po prostu użyj metod sekwencji LINQ, takich jak, Take()aby uzyskać z niej to, czego chcesz (nie zapomnij dołączyć Linqprzestrzeni nazw using System.Linq;):

byte[] foo = new byte[4096];

var bar = foo.Take(41);

Jeśli naprawdę potrzebujesz tablicy z dowolnej IEnumerable<byte>wartości, możesz użyć do tego ToArray()metody. Wydaje się, że tak nie jest.

peSHIr
źródło
5
Jeśli mamy zamiar skopiować do innej tablicy, wystarczy użyć metody statycznej Array.Copy. Jednak myślę, że inne odpowiedzi poprawnie zinterpretowały zamiar, inna tablica nie jest wymagana tylko IEbezliczalny <bajt>, który przez pierwsze 41 bajtów.
AnthonyWJones
2
Należy pamiętać, że tylko tablice jednowymiarowe i poszarpane są policzalne, a tablice wielowymiarowe nie.
Abel
11
Uwaga przy użyciu Array.Copy wykonuje się znacznie szybciej niż przy użyciu metod Take or Skip LINQ.
Michael
4
@Abel To jest bardzo niepoprawne. Wielu tablice wymiarowe przeliczalne ale wyliczyć jak poniżej: [2,3] => [1,1], [1,2], [1,3], [2,1], [2,2], [2,3]. Poszarpane tablice są również wyliczalne, ale zamiast zwracać wartość po wyliczeniu, zwracają swoją wewnętrzną tablicę. W ten sposób:type[][] jaggedArray; foreach (type[] innerArray in jaggedArray) { }
Aidiakapi
3
@Aidiakapi „very incorect”? ;). Ale częściowo masz rację, powinienem napisać „tablice multidim nie implementują IEnumerable<T>”, wtedy moje oświadczenie byłoby jaśniejsze. Zobacz także: stackoverflow.com/questions/721882/...
Abel
211

Możesz użyć ArraySegment<T>. Jest bardzo lekki, ponieważ nie kopiuje tablicy:

string[] a = { "one", "two", "three", "four", "five" };
var segment = new ArraySegment<string>( a, 1, 2 );
Mike Scott
źródło
5
Niestety nie jest to IEnumerable.
rekurencyjny
1
To prawda, ale łatwo byłoby napisać wokół niego opakowanie iteratora, które implementuje IEnumerable.
Mike Scott
22
Czy ktoś wie DLACZEGO nie jest to IENumerable? Ja nie. Wygląda na to, że tak powinno być.
Fantius
39
ArraySegment jest IList i IEnumerable, począwszy od .Net 4.5. Szkoda dla starszych użytkowników wersji.
Todd Li,
6
@Zyo Miałem na myśli, że ArraySegment <T> implementuje IEnumerable <T>, począwszy od .Net 4.5, a nie sama IEnumerable <T> jest nowa.
Todd Li
137

Możesz użyć CopyTo()metody tablic .

Lub z LINQ możesz używać Skip()i Take()...

byte[] arr = {1, 2, 3, 4, 5, 6, 7, 8};
var subset = arr.Skip(2).Take(2);
Arjan Einbu
źródło
1
+1 za dobry pomysł, ale muszę użyć zwróconej tablicy jako danych wejściowych dla innej funkcji, co powoduje, że CopyTo wymaga zmiennej tymczasowej. Będę jeszcze czekać na inne odpowiedzi.
Matthew Scharley,
4
Nie znam jeszcze LINQ, być może jest to kolejny dowód, że naprawdę powinienem być.
Matthew Scharley
11
takie podejście jest co najmniej 50 razy wolniejsze niż Array.Copy. W wielu sytuacjach nie stanowi to problemu, ale podczas wycinania tablic w cyklu spadek wydajności jest bardzo oczywisty.
Valentin Vasilyev
3
Wykonuję pojedyncze połączenie, więc wydajność nie stanowi dla mnie problemu. To jest świetne dla czytelności ... dzięki.
Rich
2
Dzięki za Skip(). Po prostu Take()nie dostanę cię w dowolny kawałek. Poza tym i tak szukałem rozwiązania LINQ (kawałek IEnumerable, ale wiedziałem, że wyniki dotyczące tablicy będą łatwiejsze do znalezienia).
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
źródło
11
Myślę, że Buffer.BlockCopy () jest bardziej wydajny i osiąga te same wyniki.
Matt Davis,
28

Począwszy od C # 8.0 / .Net Core 3.0

Krojenie tablic będzie obsługiwane, wraz z nowymi typami Indexi Rangedodawanymi.

Zakres Dokumenty Struct
Indeks Dokumenty 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 }

Powyższy przykładowy kod pochodzi z bloga C # 8.0 .

zwróć uwagę, że ^przedrostek wskazuje zliczanie od końca tablicy. Jak pokazano w przykładzie dokumentacji

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

Rangea Indextakże pracować poza tablicami krojenia, na przykład z pętlami

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

Przechodzi przez wpisy od 1 do 4


zwróć uwagę, że w chwili pisania tej odpowiedzi wersja C # 8.0 nie została jeszcze oficjalnie wydana w wersji
C # 8.x, a program .Net Core 3.x jest już dostępny w programie Visual Studio 2019 i nowszych wersjach

Remy
źródło
jakiś pomysł, czy to tworzy kopię tablicy?
Tim Pohlmann
2
wygląda na to, że to kopia: codejourney.net/2019/02/csharp-8-slicing-indexes-ranges
Tim Pohlmann
22

W C # 7.2 możesz użyć Span<T>. Zaletą nowego System.Memorysystemu jest to, że nie wymaga kopiowania wokół danych.

Potrzebna metoda to Slice:

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

Wiele metod obsługują teraz Spani IReadOnlySpantak będzie to bardzo proste do korzystania z tego nowego typu.

Zauważ, że w chwili pisania tego Span<T>typu typ nie jest jeszcze zdefiniowany w najnowszej wersji .NET (4.7.1), więc aby go użyć, musisz zainstalować pakiet System.Memory z NuGet.

Patrick Hofman
źródło
1
Zauważ, że ten Span<T>typ nie jest jeszcze zdefiniowany w najnowszej wersji .Net (4.7.1), więc aby go użyć, musisz zainstalować go System.Memoryz NuGet (i pamiętaj, aby zaznaczyć „włącz prerelease” podczas wyszukiwania go w NuGet)
Matthew Watson,
@MatthewWatson Thanks. Przepisałem twój komentarz i dodałem go do mojej odpowiedzi.
Patrick Hofman,
16

Inna możliwość, o której nie wspomniałem tutaj: Buffer.BlockCopy () jest nieco szybszy niż Array.Copy () i ma tę dodatkową zaletę, że jest w stanie konwertować w locie z szeregu prymitywów (np. []) do tablicy bajtów, co może być przydatne, gdy masz tablice numeryczne, które musisz przesyłać przez gniazda.

Ken Smith
źródło
2
Buffer.BlockCopydało inne wyniki niż Array.Copy()te, które akceptują te same parametry - było wiele pustych elementów. Czemu?
jocull
7
@jocull - W rzeczywistości nie przyjmują tych samych parametrów. Array.Copy () przyjmuje parametry długości i położenia w elementach. Buffer.BlockCopy () pobiera parametry długości i położenia w bajtach. Innymi słowy, jeśli chcesz skopiować 10-elementową tablicę liczb całkowitych, użyłbyś Array.Copy(array1, 0, array2, 0, 10), ale Buffer.BlockCopy(array1, 0, array2, 0, 10 * sizeof(int)).
Ken Smith
14

Jeśli chcesz IEnumerable<byte>, to tylko

IEnumerable<byte> data = foo.Take(x);
Marc Gravell
źródło
14

Oto prosta metoda rozszerzenia, która zwraca wycinek jako nową tablicę:

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

Następnie możesz użyć go jako:

byte[] slice = foo.Slice(0, 40);
Vladimir Mitrovic
źródło
8

Jeśli nie chcesz dodawać LINQ lub innych rozszerzeń, po prostu wykonaj:

float[] subArray = new List<float>(myArray).GetRange(0, 8).ToArray();
Dimitris
źródło
Error CS0246: The type or namespace name 'List<>' could not be found (are you missing a using directive or an assembly reference?) Dokumentacja Microsoft jest beznadziejna, z indeksowanymi setkami pozycji „List”. Który tutaj jest właściwy?
wallyk
1
System.Collections.Generic.List
Tetralux,
7

Możesz użyć opakowania wokół oryginalnej tablicy (która jest IList), tak jak w tym (nieprzetestowanym) fragmencie kodu.

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
źródło
4
Sugerowałbym użycie EqualityComparer.Default dla IndexOf - w ten sposób nie potrzebujesz żadnej specjalnej obudowy.
Jon Skeet
1
Spodziewałbym się, że będzie w porządku. Na pewno wybrałbym najpierw prostszy kod.
Jon Skeet,
Coś takiego jest moim zdaniem najlepszym sposobem. Ale oczywiście jest to więcej pracy (za pierwszym razem) niż proste Array.Copy, chociaż może to mieć wiele zalet, takich jak SubList dosłownie będący regionem na liście nadrzędnej, zamiast kopii wpisów na liście.
Aidiakapi
7
byte[] foo = new byte[4096]; 

byte[] bar = foo.Take(40).ToArray();
greyline
źródło
6

W przypadku tablic bajtowych System.Buffer.BlockCopy zapewnia najlepszą wydajność.

Simon Giles
źródło
1
Co naprawdę ma znaczenie, jeśli robisz to w pętli tysiące lub milionów razy. W aplikacji gniazdowej prawdopodobnie bierzesz trochę danych i dzielisz je na części. Jeśli robisz to tylko raz, najlepszą wydajnością jest wszystko, co następny programista najłatwiej zrozumie.
Michael Blackburn
5

Możesz użyć opcji Weź rozszerzenie

var array = new byte[] {1, 2, 3, 4};
var firstTwoItems = array.Take(2);
aku
źródło
3

Może to być rozwiązanie, które:

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

Następnie wynikiem jest IEnumerable <IEnumerable <bajt >> z pierwszym IEnumerable <bajt> zawiera pierwsze 40 bajtów foo , a drugi IEnumerable <bajt> zawiera resztę.

Napisałem klasę otoki, cała iteracja jest leniwa, mam nadzieję, że może pomóc:

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
źródło
2

Nie sądzę, aby C # obsługuje semantykę zakresu. Możesz jednak napisać metodę rozszerzenia, na przykład:

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

Ale jak powiedzieli inni, jeśli nie trzeba ustawiać indeksu początkowego Take, wystarczy.

bleevo
źródło
1

Oto funkcja rozszerzenia, która używa funkcji ogólnej i zachowuje się jak funkcja PHP array_slice . Dozwolone jest przesunięcie ujemne i długość.

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
źródło
1
Całkiem niezłe, choć kilka rzeczy ze świata .NET. Jeśli startnie mieści się w przedziale od 0 do arr.Length, prawdopodobnie powinien wyrzucić wyjątek poza granice. Ponadto, end >= start >= 0więc nie trzeba sprawdzać end < 0, nie jest możliwe, aby tak się stało. Prawdopodobnie możesz to zrobić jeszcze bardziej zwięźle, sprawdzając to, length >= 0a następnie len = Math.min(length, arr.Length - start)zamiast marudzić 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
źródło