Масив фрагментів в 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.