Массив ломтиков в C #

228

Как ты это делаешь? Дан байтовый массив:

byte[] foo = new byte[4096];

Как бы я получил первые х байтов массива в виде отдельного массива? (Конкретно мне это нужно как IEnumerable<byte>)

Это для работы с Socketс. Я полагаю, что самым простым способом было бы нарезать массив, аналогично синтаксису Perls:

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

Который вернул бы первые 41 элемент в @barмассив. Есть ли в C # что-то, чего мне просто не хватает, или есть что-то еще, что я должен делать?

LINQ - вариант для меня (.NET 3.5), если это поможет.

Мэтью Шарли
источник
3
Разрезание массива - это предложение для c # 7.2 github.com/dotnet/csharplang/issues/185
Марк
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()метод для этого. Это не похоже на случай здесь.

peSHIr
источник
5
Если мы собираемся скопировать в другой массив, просто используйте статический метод Array.Copy. Однако я думаю, что другие ответы правильно интерпретировали намерение, другой массив не требуется, просто IEnumberable <byte>, который занимает первые 41 байт.
AnthonyWJones
2
Обратите внимание, что только одномерные и зубчатые массивы являются перечисляемыми, а многомерные - нет.
Авель
11
Обратите внимание, что использование Array.Copy выполняется намного быстрее, чем использование методов LINQ Take или Skip.
Майкл
4
@Abel Это на самом деле очень неправильно. Многомерные массивы являются перечислимы но перечислить так: [2,3] => [1,1], [1,2], [1,3], [2,1], [2,2], [2,3]. Зубчатые массивы также перечислимы, но вместо того, чтобы возвращать значение при перечислении, они возвращают свой внутренний массив. Как это:type[][] jaggedArray; foreach (type[] innerArray in jaggedArray) { }
Aidiakapi
3
@Aidiakapi "очень плохо"? ;). Но вы частично правы, я должен был написать «мультидимные массивы не реализуются IEnumerable<T>», тогда мое заявление было бы более ясным. Смотрите также это: stackoverflow.com/questions/721882/…
Абель
211

Вы могли бы использовать ArraySegment<T>. Он очень легкий, так как не копирует массив:

string[] a = { "one", "two", "three", "four", "five" };
var segment = new ArraySegment<string>( a, 1, 2 );
Майк Скотт
источник
5
К сожалению это не IEnumerable.
рекурсивный
1
Правда, но было бы легко написать обертку итератора, которая реализует IEnumerable.
Майк Скотт
22
Кто-нибудь знает, ПОЧЕМУ это не IEnumerable? Я не. Кажется, так и должно быть.
Фантиус
39
ArraySegment является IList и IEnumerable начиная с .Net 4.5. Слишком плохо для пользователей старшей версии ..
Тодд Ли
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);
WOPR
источник
11
Я думаю, что Buffer.BlockCopy () более эффективен и достигает тех же результатов.
Мэтт Дэвис
28

Начиная с C # 8.0 / .Net Core 3.0

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

Range Struct docs
Индекс структурных документов

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 и далее

Remy
источник
Любая идея, создает ли это копию массива?
Тим
2
похоже, что это копия: codejourney.net/2019/02/csharp-8-slicing-indexes-ranges
Тим
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 (), и имеет дополнительное преимущество, заключающееся в возможности преобразования на лету из массива примитивов (скажем, short []) к массиву байтов, который может быть полезен, когда у вас есть числовые массивы, которые нужно передавать через сокеты.

Кен Смит
источник
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

Если хочешь IEnumerable<byte>, то просто

IEnumerable<byte> data = foo.Take(x);
Марк Гравелл
источник
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
Тетралюкс
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

}

Rauhotz
источник
4
Я бы предложил использовать EqualityComparer.Default для IndexOf - таким образом, вам не нужно никакого специального кожуха.
Джон Скит
1
Я ожидаю, что это будет абсолютно нормально. Я бы, конечно, сначала пошел с более простым кодом.
Джон Скит
Как-то так, на мой взгляд, лучший путь. Но очевидно, что это скорее работа (в первый раз), чем простая Array.Copy, хотя это может иметь много преимуществ, таких как, например, SubList, являющийся регионом в родительском Списке, вместо копии записей в Списке.
Aidiakapi
7
byte[] foo = new byte[4096]; 

byte[] bar = foo.Take(40).ToArray();
Greyline
источник
6

Для байтовых массивов System.Buffer.BlockCopy обеспечит вам наилучшую производительность.

Саймон Джайлс
источник
1
Что действительно имеет значение, если вы делаете это в цикле тысячи или миллионы раз. В приложении с сокетами вы, вероятно, берете некоторые данные и разбиваете их на части. Если вы делаете это только один раз, самая лучшая производительность - это то, что следующему программисту будет легче понять.
Майкл Блэкберн
5

Вы можете использовать метод расширения Take

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 <byte> содержит первые 40 байтов foo , а второй IEnumerable <byte> содержит остальные.

Я написал класс-обертку, вся итерация ленива, надеюсь, это поможет:

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 # поддерживает семантику Range. Вы можете написать метод расширения, например:

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

Но, как говорили другие, если вам не нужно устанавливать начальный индекс, тогда Takeэто все, что вам нужно.

bleevo
источник
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();
        }
    }
}
Ахмад АльСалум
источник