Array slices ใน C #


228

คุณจะทำอย่างไรมันได้หรือไม่? รับอาร์เรย์ไบต์:

byte[] foo = new byte[4096];

ฉันจะรับ x ไบต์แรกของอาร์เรย์เป็นอาร์เรย์แยกได้อย่างไร (โดยเฉพาะฉันต้องการมันเป็นIEnumerable<byte> )

นี่คือการทำงานกับSockets ฉันคิดว่าวิธีที่ง่ายที่สุดคือการแบ่งอาร์เรย์คล้ายกับไวยากรณ์ของ Perls:

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

ซึ่งจะคืนองค์ประกอบ 41 รายการแรกลงใน@barอาร์เรย์ มีบางอย่างใน C # ที่ฉันเพิ่งหายไปหรือมีบางสิ่งที่ฉันควรทำ?

LINQ เป็นตัวเลือกสำหรับฉัน (. NET 3.5) ถ้ามันช่วยได้


3
Array slicing เป็นข้อเสนอสำหรับ c # 7.2 github.com/dotnet/csharplang/issues/185
Mark

3
C # 8.0 จะเห็นการแนะนำการแบ่งส่วนข้อมูลแบบเนทีฟ ดูคำตอบสำหรับรายละเอียดเพิ่มเติม
Remy

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 แบบคงที่ อย่างไรก็ตามฉันคิดว่าคำตอบอื่น ๆ ได้ตีความเจตนาอย่างถูกต้องแล้วไม่จำเป็นต้องมีอีกอาร์เรย์หนึ่งที่เป็นเพียง <number> ของ IE ที่สามารถทำได้มากกว่า 41 ไบต์แรก
AnthonyWJones

2
โปรดทราบว่ามีอาร์เรย์แบบมิติเดียวและแบบขรุขระเท่านั้นที่นับได้อาร์เรย์แบบหลายมิตินั้นไม่ใช่
Abel

11
หมายเหตุการใช้ Array.Copy ทำงานได้เร็วกว่าการใช้วิธี Take หรือข้ามของ LINQ
Michael

4
@bel นั่นจริงๆแล้วไม่ถูกต้องมาก อาร์เรย์หลายมิติมีนับ [2,3] => [1,1], [1,2], [1,3], [2,1], [2,2], [2,3]แต่พวกเขาระบุเช่นนี้ อาร์เรย์แบบ Jagged นั้นนับได้เช่นกัน แต่แทนที่จะส่งกลับค่าเมื่อแจกแจงพวกมันจะคืนค่าอาร์เรย์ภายใน เช่นนี้:type[][] jaggedArray; foreach (type[] innerArray in jaggedArray) { }
Aidiakapi

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
น่าเสียดายที่มันไม่ใช่ IEnumerable
เรียกซ้ำ

1
จริง แต่มันจะง่ายในการเขียน iterator wrapper รอบ ๆ ซึ่งใช้ IEnumerable
Mike Scott

22
ไม่มีใครรู้ว่าทำไมมันไม่นับ IEn? ฉันไม่. ดูเหมือนว่าควรจะเป็น
Fantius

39
ArraySegment คือ IList และ IEnumerable เริ่มต้นจาก. 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 ต้องใช้ตัวแปรชั่วคราว ฉันจะรอคำตอบอื่น ๆ
Matthew Scharley

4
ฉันยังไม่คุ้นเคยกับ LINQ บางทีนี่อาจเป็นหลักฐานเพิ่มเติมที่ฉันควรจะเป็น
Matthew Scharley

11
วิธีนี้ช้ากว่าอาเรย์อย่างน้อย 50 เท่า นี่ไม่ใช่ปัญหาในหลาย ๆ สถานการณ์ แต่เมื่อทำการแบ่งอาร์เรย์ในรอบการวางประสิทธิภาพจะชัดเจนมาก
Valentin Vasilyev

3
ฉันกำลังโทรเพียงครั้งเดียวดังนั้นประสิทธิภาพจึงไม่เป็นปัญหาสำหรับฉัน นี่มันยอดเยี่ยมสำหรับการอ่าน ... ขอบคุณ
รวย

2
Skip()ขอบคุณสำหรับ เพียงแค่Take()ไม่ให้คุณได้รับส่วนแบ่งโดยพลการ นอกจากนี้ฉันกำลังมองหาโซลูชัน LINQ อยู่แล้ว (แบ่ง IEnumerable แต่ฉันรู้ว่าผลลัพธ์เกี่ยวกับอาร์เรย์จะหาได้ง่ายขึ้น)
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
ฉันคิดว่า Buffer.BlockCopy () มีประสิทธิภาพมากกว่าและได้ผลลัพธ์เดียวกัน
Matt Davis

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 และต่อไปแล้ว


ความคิดใด ๆ หรือไม่นี้สร้างสำเนาของอาร์เรย์หรือไม่
Tim Pohlmann

2
ดูเหมือนว่าจะเป็นการคัดลอก: codejourney.net/2019/02/csharp-8-slicing-indexes-ranges
Tim Pohlmann

22

ในC # 7.2Span<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)
Matthew Watson

@ MatthewWatson ขอบคุณ ฉันเขียนความคิดเห็นของคุณใหม่และเพิ่มเข้าไปในคำตอบของฉัน
Patrick Hofman

16

ความเป็นไปได้อีกอย่างที่ฉันไม่ได้เห็นได้กล่าวถึงที่นี่: Buffer.BlockCopy () เร็วกว่า Array.Copy () เล็กน้อยและมันมีประโยชน์เพิ่มเติมจากความสามารถในการแปลง on-the-fly จาก array of primitives (พูดสั้น ๆ []) ไปยังอาร์เรย์ของไบต์ซึ่งมีประโยชน์เมื่อคุณมีอาร์เรย์ตัวเลขที่คุณต้องส่งผ่าน 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))
Ken Smith


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

คุณสามารถใช้ wrapper รอบ ๆ อาเรย์ดั้งเดิม (ซึ่งคือ 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 ซึ่งเป็นวิธีที่คุณไม่จำเป็นต้องมีปลอกพิเศษ
Jon Skeet

1
ฉันคาดหวังว่ามันจะดีอย่างแน่นอน ฉันจะไปกับรหัสที่ง่ายกว่าอย่างแน่นอนก่อน
Jon Skeet

อย่างนี้ฉันคิดว่าเป็นวิธีที่ดีที่สุดที่จะไป แต่เห็นได้ชัดว่ามันทำงานได้ดีกว่าครั้งแรก (ง่ายกว่า) Array.Copyถึงแม้ว่าสิ่งนี้จะมีข้อดีหลายประการเช่น SubList อย่างแท้จริงเป็นภูมิภาคในรายการหลักแทนที่จะเป็นสำเนาของรายการในรายการ
Aidiakapi


6

สำหรับไบต์อาร์เรย์System.Buffer.BlockCopyจะให้ประสิทธิภาพที่ดีที่สุดแก่คุณ


1
ซึ่งสำคัญจริงๆถ้าคุณทำสิ่งนี้ในวงวนพันหรือล้านครั้ง ในแอปพลิเคชันซ็อกเก็ตคุณอาจรับอินพุตและแบ่งเป็นส่วน ๆ หากคุณทำเพียงครั้งเดียวประสิทธิภาพที่ดีที่สุดคือสิ่งที่โปรแกรมเมอร์รายต่อไปจะเข้าใจได้ง่ายที่สุด
Michael Blackburn

5

คุณสามารถใช้วิธีการขยาย

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แรก<ไบต์>ประกอบด้วยfoo 40 ไบต์แรกและอีกIEnumerable <ไบต์ที่สองถือที่เหลือ

ฉันเขียนคลาส wrapper การทำซ้ำทั้งหมดเป็นสันหลังยาวหวังว่ามันจะช่วยได้:

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)แทนที่จะ fuddling 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();
        }
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.