Các lát mảng trong C #


228

Bạn làm nó như thế nào? Cho một mảng byte:

byte[] foo = new byte[4096];

Làm thế nào tôi có thể nhận được x byte đầu tiên của mảng dưới dạng một mảng riêng biệt? (Cụ thể, tôi cần nó như một IEnumerable<byte>)

Đây là để làm việc với Sockets. Tôi nghĩ cách dễ nhất sẽ là cắt mảng, tương tự như cú pháp Perls:

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

Mà sẽ trả lại 41 phần tử đầu tiên vào @barmảng. Có điều gì đó trong C # mà tôi chỉ thiếu, hoặc có một số điều khác tôi nên làm không?

LINQ là một tùy chọn cho tôi (.NET 3.5), nếu điều đó có ích.


3
Cắt mảng là một đề xuất cho c # 7.2 github.com/dotnet/csharplang/issues/185
Đánh dấu

3
C # 8.0 sẽ thấy sự ra đời của mảng cắt gốc. Xem câu trả lời để biết thêm chi tiết
Rémy

1
Bạn có thể quan tâm đến ArraySlice <T> thực hiện việc cắt các mảng với từng bước dưới dạng xem dữ liệu gốc: github.com/henon/SliceAndDice
henon

Câu trả lời:


196

Mảng là vô số, vì vậy bạn foođã là một IEnumerable<byte>chính nó. Chỉ cần sử dụng các phương thức chuỗi LINQ như Take()để có được những gì bạn muốn từ nó (đừng quên bao gồm Linqkhông gian tên với using System.Linq;):

byte[] foo = new byte[4096];

var bar = foo.Take(41);

Nếu bạn thực sự cần một mảng từ bất kỳ IEnumerable<byte>giá trị nào , bạn có thể sử dụng ToArray()phương thức cho điều đó. Điều đó dường như không phải là trường hợp ở đây.


5
Nếu chúng ta sẽ sao chép sang một mảng khác, chỉ cần sử dụng phương thức tĩnh Array.Copy. Tuy nhiên, tôi nghĩ rằng các câu trả lời khác đã diễn giải ý định một cách chính xác, một mảng khác không bắt buộc chỉ là một <byte> IEnumberable trong 41 byte đầu tiên.
AnthonyWJones

2
Lưu ý rằng chỉ có các mảng một chiều và lởm chởm là vô số, các mảng đa chiều thì không.
Abel

11
Lưu ý khi sử dụng Array.Copy thực hiện nhanh hơn rất nhiều so với sử dụng các phương thức Take hoặc Skip của LINQ.
Michael

4
@Abel Điều đó thực sự rất không chính xác. Mảng đa chiều là vô số nhưng chúng liệt kê như thế này : [2,3] => [1,1], [1,2], [1,3], [2,1], [2,2], [2,3]. Các mảng lởm chởm cũng có thể đếm được nhưng thay vì trả về một giá trị khi liệt kê, chúng trả về mảng bên trong của chúng. Như thế này:type[][] jaggedArray; foreach (type[] innerArray in jaggedArray) { }
Aidiakapi

3
@Aidiakapi "rất hợp"? ;). Nhưng bạn đã đúng một phần, tôi nên viết "nhiều mảng không thực hiện IEnumerable<T>", thì tuyên bố của tôi sẽ rõ ràng hơn. Xem thêm điều này: stackoverflow.com/questions/721882/ Kẻ
Abel

211

Bạn có thể sử dụng ArraySegment<T>. Nó rất nhẹ vì nó không sao chép mảng:

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

5
Thật không may, nó không phải là số lượng lớn.
đệ quy

1
Đúng, nhưng sẽ dễ dàng để viết một trình bao bọc lặp xung quanh nó thực hiện IEnumerable.
Mike Scott

22
Có ai biết TẠI SAO nó không phải là IEn Countable không? Tôi không. Có vẻ như nó nên như vậy.
Fantius

39
ArraySegment là IList và IEnumerable bắt đầu từ .Net 4.5. Quá tệ cho người dùng phiên bản cũ ..
Todd Li

6
@Zyo Ý tôi là ArraySegment <T> thực hiện IEnumerable <T> bắt đầu từ .Net 4.5, chứ không phải bản thân IEnumerable <T> là mới.
Todd Li

137

Bạn có thể sử dụng CopyTo()phương thức mảng .

Hoặc với LINQ bạn có thể sử dụng Skip()Take()...

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

1
+1 cho một ý tưởng tốt, nhưng tôi cần sử dụng mảng được trả về làm đầu vào cho một chức năng khác, điều này khiến CopyTo yêu cầu một biến tạm thời. Tôi sẽ chờ câu trả lời khác.
Matthew Scharley

4
Tôi chưa quen với LINQ, có lẽ đây là bằng chứng nữa cho thấy tôi thực sự nên như vậy.
Matthew Scharley

11
Cách tiếp cận này chậm hơn ít nhất 50 lần so với Array.Copy. Đây không phải là một vấn đề trong nhiều tình huống nhưng khi thực hiện cắt mảng trong một chu kỳ, hiệu suất giảm là rất rõ ràng.
Valentin Vasilyev

3
Tôi đang thực hiện một cuộc gọi, vì vậy hiệu suất không phải là vấn đề đối với tôi. Điều này là tuyệt vời cho khả năng đọc ... cảm ơn.
Giàu

2
Cảm ơn vì Skip(). Chỉ cần Take()bạn không có được một lát tùy ý. Bên cạnh đó, dù sao tôi cũng đang tìm kiếm một giải pháp LINQ (lát IEnumerable, nhưng tôi biết kết quả về mảng sẽ dễ tìm hơn).
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);

11
Tôi nghĩ Buffer.BlockCopy () hiệu quả hơn và đạt được kết quả tương tự.
Matt Davis

28

Bắt đầu từ C # 8.0 / .Net Core 3.0

Cắt lát sẽ được hỗ trợ, cùng với các loại mới IndexRangeđược thêm vào.

Phạm vi cấu trúc tài liệu
Chỉ mục cấu trúc tài liệu

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 }

Mẫu mã trên được lấy từ blog C # 8.0 .

lưu ý rằng ^tiền tố chỉ ra việc đếm từ cuối mảng. Như thể hiện trong ví dụ về tài liệu

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

RangeIndexcũng hoạt động bên ngoài các mảng cắt, ví dụ với các vòng lặp

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

Sẽ lặp qua các mục từ 1 đến 4


lưu ý rằng tại thời điểm viết câu trả lời này, C # 8.0 vẫn chưa được phát hành chính thức
C # 8.x và .Net Core 3.x hiện có sẵn trong Visual Studio 2019 trở đi


bất kỳ ý tưởng cho dù điều này có tạo ra một bản sao của mảng không?
Tim Pohlmann

2
có vẻ như đó là một bản sao: codejTHER.net/2019/02/csharp-8-slicing-indexes-ranges
Tim Pohlmann

22

Trong C # 7.2 , bạn có thể sử dụng Span<T>. Lợi ích của cái mớiSystem.Memory hệ thống là nó không cần sao chép xung quanh dữ liệu.

Phương pháp bạn cần là Slice:

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

Rất nhiều phương pháp hiện nay hỗ trợ SpanIReadOnlySpan, vì vậy sẽ rất đơn giản để sử dụng loại mới này.

Lưu ý rằng tại thời điểm viết Span<T>loại chưa được xác định trong phiên bản .NET mới nhất (4.7.1), vì vậy, để sử dụng nó, bạn cần cài đặt gói System.Memory từ NuGet.


1
Lưu ý rằng Span<T>loại chưa được xác định trong phiên bản mới nhất của .Net (4.7.1) vì vậy để sử dụng loại này, bạn cần cài đặt System.Memorytừ NuGet (và nhớ đánh dấu "bao gồm cả phát hành trước" khi tìm kiếm nó trong NuGet)
Matthew Watson

@MatthewWatson Cảm ơn. Tôi viết lại bình luận của bạn và thêm nó vào câu trả lời của tôi.
Patrick Hofman

16

Một khả năng khác tôi chưa từng thấy được đề cập ở đây: Buffer.BlockCopy () nhanh hơn một chút so với Array.Copy () và nó có thêm lợi ích là có thể chuyển đổi nhanh chóng từ một loạt các nguyên thủy (giả sử, ngắn []) đến một mảng byte, có thể hữu ích khi bạn có các mảng số mà bạn cần truyền qua Sockets.


2
Buffer.BlockCopytạo ra các kết quả khác nhau hơn Array.Copy()mặc dù chúng chấp nhận cùng một tham số - có rất nhiều phần tử trống. Tại sao?
vui vẻ

7
@jocull - Họ thực sự không có cùng tham số. Array.Copy () lấy tham số độ dài và vị trí của nó trong các phần tử. Buffer.BlockCopy () lấy tham số độ dài và vị trí của nó theo byte. Nói cách khác, nếu bạn muốn sao chép một mảng gồm 10 phần tử, bạn sẽ sử dụng Array.Copy(array1, 0, array2, 0, 10), nhưng Buffer.BlockCopy(array1, 0, array2, 0, 10 * sizeof(int)).
Ken Smith

14

Nếu bạn muốn IEnumerable<byte>, sau đó chỉ

IEnumerable<byte> data = foo.Take(x);

14

Đây là một phương thức mở rộng đơn giản trả về một lát như một mảng mới:

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

Sau đó, bạn có thể sử dụng nó như:

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

8

Nếu bạn không muốn thêm LINQ hoặc các tiện ích mở rộng khác, hãy làm:

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?) Tài liệu của Microsoft là vô vọng với hàng trăm mục "Danh sách" được lập chỉ mục. Cái nào đúng ở đây?
wallyk

1
System.Collections.Generic.List
Tetralux

7

Bạn có thể sử dụng một trình bao bọc xung quanh mảng ban đầu (là IList), giống như trong đoạn mã (chưa được kiểm tra) này.

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
Tôi khuyên bạn nên sử dụng EqualityComparer.Default cho IndexOf - theo cách đó bạn không cần bất kỳ vỏ bọc đặc biệt nào.
Jon Skeet

1
Tôi hy vọng nó sẽ hoàn toàn tốt. Tôi chắc chắn sẽ đi với mã đơn giản hơn đầu tiên.
Jon Skeet

Một cái gì đó như thế này theo ý kiến ​​của tôi là cách tốt nhất để đi. Nhưng rõ ràng nó hoạt động nhiều hơn (lần đầu tiên) hơn là đơn giản Array.Copy, mặc dù điều này có thể có nhiều lợi thế, chẳng hạn như Danh sách con theo nghĩa đen là một khu vực trong Danh sách mẹ, thay vì một bản sao của các mục trong Danh sách.
Aidiakapi

7
byte[] foo = new byte[4096]; 

byte[] bar = foo.Take(40).ToArray();

6

Đối với mảng byte System.Buffer.BlockCopy sẽ cung cấp cho bạn hiệu suất tốt nhất.


1
Điều này chỉ thực sự quan trọng nếu bạn thực hiện việc này trong một vòng lặp hàng nghìn hoặc hàng triệu lần. Trong một ứng dụng ổ cắm, có lẽ bạn đang lấy một số đầu vào và chia nó thành nhiều phần. Nếu bạn chỉ làm điều đó một lần, hiệu suất tốt nhất là bất cứ điều gì lập trình viên tiếp theo sẽ dễ hiểu nhất.
Michael Blackburn

5

Bạn có thể sử dụng phương thức mở rộng

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

3

Đây có thể là một giải pháp:

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

Sau đó, kết quả là một IEnumerable <IEnumerable <byte >> với một IEnumerable <byte> đầu tiên chứa 40 byte đầu tiên của foo và một byte thứ hai <byte> giữ phần còn lại.

Tôi đã viết một lớp bao bọc, toàn bộ lặp đi lặp lại là lười biếng, hy vọng nó có thể giúp:

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

Tôi không nghĩ C # hỗ trợ ngữ nghĩa Range. Bạn có thể viết một phương thức mở rộng, như:

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

Nhưng giống như những người khác đã nói nếu bạn không cần thiết lập một chỉ mục bắt đầu thì đó Takelà tất cả những gì bạn cần.


1

Đây là một hàm mở rộng sử dụng chung và hoạt động giống như hàm PHP Array_slice . Độ lệch âm và chiều dài được phép.

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
Khá tốt, mặc dù một vài điều từ thế giới .NET. Nếu startkhông nằm trong khoảng từ 0 đến arr.Length, có lẽ nó sẽ đưa ra một ngoại lệ giới hạn. Ngoài ra, end >= start >= 0vì vậy bạn không cần kiểm tra end < 0, điều đó không thể xảy ra. Bạn có thể có thể làm điều đó thậm chí ngắn gọn hơn bằng cách kiểm tra điều đó length >= 0và sau đó len = Math.min(length, arr.Length - start)thay vì lộn xộn với 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();
        }
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.