Масив фрагментів в C #


228

Як ти це робиш? Дано байтовий масив:

byte[] foo = new byte[4096];

Як я можу отримати перші x байти масиву як окремий масив? ( В зокрема, мені це потрібно як IEnumerable<byte>)

Це для роботи з Sockets. Думаю, найпростішим способом було б нарізання масиву, подібне до синтаксису Perls:

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

Який би повернув перші 41 елементи в @barмасив. Чи є щось на C #, що мені просто не вистачає, чи є якась інша річ, яку я повинен робити?

LINQ - це варіант для мене (.NET 3.5), якщо це допомагає.



3
C # 8.0 побачить введення нарізки нарізного масиву. Дивіться відповідь для більш детальної інформації
Ремі

1
Можливо, вас зацікавить ArraySlice <T>, який реалізує нарізання масивів із кроком як перегляд вихідних даних: github.com/henon/SliceAndDice
henon

Відповіді:


196

Масивів безліч, тож ваш fooуже є IEnumerable<byte>самим собою. Просто використовуйте методи послідовності LINQ, як, Take()щоб отримати з нього те, що ви хочете (не забудьте включити Linqпростір імен using System.Linq;):

byte[] foo = new byte[4096];

var bar = foo.Take(41);

Якщо вам дійсно потрібен масив з будь-якого IEnumerable<byte>значення, ви можете використовувати ToArray()метод для цього. Це, мабуть, не так.


5
Якщо ми збираємось скопіювати в інший масив, просто скористайтеся статичним методом Array.Copy. Однак я думаю, що інші відповіді інтерпретували наміри правильно, інший масив не потрібен лише IEnumberable <байт>, який перевищує перші 41 байт.
AnthonyWJones

2
Зауважимо, що перелічуються лише одновимірні та зубчасті масиви, багатовимірні масиви - не.
Авель

11
Зауважте, що використання Array.Copy працює набагато швидше, ніж використання методів LINQ Take or Skip.
Майкл

4
@Abel Це насправді дуже неправильно. Багатовимірні масиви є перелічуваних але перерахувати так: [2,3] => [1,1], [1,2], [1,3], [2,1], [2,2], [2,3]. Нерівні масиви також перелічені, але замість повернення значення при перерахунку вони повертають свій внутрішній масив. type[][] jaggedArray; foreach (type[] innerArray in jaggedArray) { }
Ось так

3
@Aidiakapi "дуже неправильно"? ;). Але ви частково праві, я мав би записати "багатоскладні масиви не реалізують IEnumerable<T>", тоді моє твердження було б зрозумілішим. Дивіться також це: stackoverflow.com/questions/721882/…
Abel

211

Ви можете використовувати ArraySegment<T>. Це дуже невелика вага, оскільки він не копіює масив:

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

5
На жаль, це не безліч.
рекурсивна

1
Щоправда, але було б легко написати обгортку ітератора навколо неї, яка реалізує IEnumerable.
Майк Скотт

22
Хтось знає, Чому це не мало? Я не. Здається, так і має бути.
Фантюс

39
ArraySegment - це IList та IEsumerable, починаючи з .Net 4.5. Шкода для користувачів старшої версії ..
Todd Li

6
@Zyo Я мав на увазі ArraySegment <T> реалізує IEnumerable <T>, починаючи з .Net 4.5, а не IEnumerable <T>, сам по собі новий.
Тодд Лі

137

Ви можете використовувати CopyTo()метод масивів .

Або з LINQ ви можете використовувати Skip()і Take()...

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

1
+1 для гарної ідеї, але мені потрібно використовувати повернутий масив як вхід для іншої функції, завдяки чому CopyTo потребує тимчасової змінної. Я ще чекатиму інших відповідей.
Метью Шарлі

4
Я ще не знайомий з LINQ, можливо, це ще одне підтвердження того, що я насправді повинен бути.
Метью Шарлі

11
такий підхід принаймні на 50 разів повільніше, ніж Array.Copy. Це не є проблемою у багатьох ситуаціях, але при виконанні нарізки масиву в циклі падіння продуктивності дуже очевидно.
Валентин Васильєв

3
Я здійснюю єдиний дзвінок, тому продуктивність не є проблемою для мене. Це чудово для читабельності ... дякую.
Багатий

2
Дякуємо за Skip(). Просто Take()ви не отримаєте довільний фрагмент. Крім того, я шукав рішення LINQ у будь-якому випадку (фрагмент IEnumerable, але я знав, що результати про масив буде легше знайти).
Томаш Гандор

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
Я думаю, що Buffer.BlockCopy () є більш ефективним і досягає тих же результатів.
Метт Девіс

28

Починаючи з C # 8.0 / .Net Core 3.0

Буде підтримуватися нарізка масиву, а також нові типи Indexта Rangeдодаються.

Документи діапазону Структура Документи
Індекс Структура

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 }

Наведений вище зразок коду, взятий із блогу C # 8.0 .

зауважте, що ^префікс вказує на підрахунок від кінця масиву. Як показано в прикладі документів

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а Indexтакож працювати поза нарізки масивів, наприклад, з петлями

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

Проведеться цикл через записи 1 по 4


зауважте, що на момент написання цієї відповіді C # 8.0 ще не випущено офіційно
C # 8.x та .Net Core 3.x тепер доступні у Visual Studio 2019 та далі


будь-яка ідея, чи створює це копія масиву?
Тім

2

22

В C # 7.2 ви можете використовувати Span<T>. Перевага нової System.Memoryсистеми полягає в тому, що їй не потрібно копіювати дані.

Вам потрібен метод Slice:

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

Багато методів тепер підтримують Spanі IReadOnlySpan, таким чином , це буде дуже просто використовувати цей новий тип.

Зверніть увагу, що на момент написання Span<T>тип не визначений в останній версії .NET ще (4.7.1), тому для його використання вам потрібно встановити пакет System.Memory від NuGet.


1
Зауважте, що Span<T>тип не визначений в останній версії .Net поки (4.7.1), тому для його використання вам потрібно встановити System.Memoryз NuGet (і не забудьте позначити "включити попередній випуск" під час пошуку в NuGet)
Меттью Уотсон

@MatthewWatson Дякую Я переписав ваш коментар і додав його до своєї відповіді.
Патрік Хофман

16

Інша можливість, яку я не бачив тут, я не бачив: Buffer.BlockCopy () трохи швидше, ніж Array.Copy (), і це має додаткову перевагу в змозі перетворити на ходу з масиву примітивів (скажімо, короткий []) до масиву байтів, який може бути зручним, коли у вас є числові масиви, які вам потрібно передати через Sockets.


2
Buffer.BlockCopyдали інші результати, ніж Array.Copy()навіть якщо вони приймають однакові параметри - порожніх елементів було багато. Чому?
Jocull

7
@jocull - Вони насправді не приймають однакових параметрів. Array.Copy () приймає параметри довжини та положення в елементах. Buffer.BlockCopy () приймає параметри довжини та позиції в байтах. Іншими словами, якби ви хотіли скопіювати 10-елементний масив цілих чисел, ви б використали Array.Copy(array1, 0, array2, 0, 10), але Buffer.BlockCopy(array1, 0, array2, 0, 10 * sizeof(int)).
Кен Сміт


14

Ось простий метод розширення, який повертає фрагмент як новий масив:

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

Тоді ви можете використовувати його як:

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

8

Якщо ви не хочете додавати LINQ або інші розширення, просто виконайте такі дії:

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?) Документація Microsoft є безперспективною із індексованими сотнями записів "Список". Який тут правильний?
wallyk

1
System.Collections.Generic.List
Tetralux

7

Ви можете використовувати обгортку навколо оригінального масиву (який є IList), як у цьому (неперевіреному) фрагменті коду.

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
Я б запропонував використовувати EqualityComparer.Default для IndexOf - для цього вам не потрібен спеціальний кожух.
Джон Скіт

1
Я б очікував, що це буде абсолютно добре. Я, звичайно, спершу з простим кодом.
Джон Скіт

Щось таке, на мій погляд, найкращий шлях. Але очевидно, що це більше роботи (вперше), ніж простого Array.Copy, хоча це може мати багато переваг, наприклад, що SubList буквально є регіоном у батьківському списку, а не копія записів у Списку.
Айдіакапі


6

Для байтових масивів System.Buffer.BlockCopy дасть вам найкращу продуктивність.


1
Що важливо лише, якщо ви робите це в циклі тисячі чи мільйони разів. У програмі sockets ви, ймовірно, берете деякий внесок і розбиваєте його на частини. Якщо ви робите це лише один раз, найкраща продуктивність - це те, що наступний програміст найлегше зрозуміє.
Майкл Блекберн

5

Можна скористатися методом Take Extension

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

3

Це може бути рішення, яке:

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

Тоді результат - IENumerable <IEnumerable <byte >>; перший IENumerable <байт> містить перші 40 байтів foo та другий IEnumerable <байт> тримає інше.

Я написав обгортковий клас, вся ітерація лінива, сподіваюся, що це може допомогти:

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

Я не думаю, що C # підтримує семантику діапазону. Ви можете написати метод розширення, хоча:

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

Але як і інші говорили, якщо вам не потрібно встановлювати індекс запуску, то Takeце все, що вам потрібно.


1

Ось функція розширення, яка використовує загальну функцію і поводиться як функція PHP array_slice . Негативні зміщення та довжина дозволені.

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
Дуже добре, хоча кілька речей зі світу .NET. Якщо startнемає між 0 і arr.Length, це, ймовірно, повинно викинути виключення з меж. Крім того, end >= start >= 0тому вам не потрібно перевіряти end < 0, не можливо, що це станеться. Ви, напевно, могли зробити це ще більш лаконічно, перевіривши це, length >= 0а потім len = Math.min(length, arr.Length - start)замість того, щоб лупцювати end.
Меттью Шарлі

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();
        }
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.