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 # 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 정적 메서드를 사용하십시오. 그러나 다른 답변이 의도를 올바르게 해석했다고 생각합니다. 다른 배열은 처음 41 바이트 이상의 IEnumberable <byte> 만 필요하지 않습니다.
AnthonyWJones

2
단 차원 배열과 들쭉날쭉 한 배열 만 열거 할 수 있고 다차원 배열은 열거 할 수 없습니다.
Abel

11
Array.Copy를 사용하면 LINQ의 Take 또는 Skip 메서드를 사용하는 것보다 훨씬 빠르게 수행됩니다.
Michael

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 "아주 잘못"? ;). 그러나 당신은 부분적으로 옳습니다. "multidim arrays does not implement 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
사실이지만 IEnumerable을 구현하는 반복자 래퍼를 작성하는 것은 쉽습니다.
Mike Scott

22
IEnumerable이 아닌 이유를 아는 사람이 있습니까? 난 아니야 그래야 할 것 같습니다.
Fantius

39
ArraySegment는 .Net 4.5부터 IList 및 IEnumerable입니다. 이전 버전 사용자에게는 너무 나쁩니다.
Todd Li

6
@Zyo ArraySegment <T>는 IEnumerable <T> 자체가 새로운 것이 아니라 .Net 4.5부터 IEnumerable <T>을 구현한다는 것을 의미했습니다.
Todd Li

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
이 방법은 Array.Copy보다 50 배 이상 느립니다. 이것은 많은 상황에서 문제가되지 않지만 사이클에서 어레이 슬라이싱을 수행 할 때는 성능 저하가 매우 분명합니다.
Valentin Vasilyev

3
단일 통화를하고 있으므로 성능이 문제가되지 않습니다. 이것은 가독성에 좋습니다 ... 감사합니다.
Rich

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 ()가 더 효율적이며 동일한 결과를 얻습니다.
매트 데이비스

28

C # 8.0 / .Net Core 3.0부터

새로운 유형 IndexRange추가 되는 배열 슬라이싱이 지원 됩니다.

범위 구조 문서
색인 구조 문서

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.2 , 당신은 사용할 수 있습니다 Span<T>. 새로운 System.Memory시스템 의 장점은 데이터를 복사 할 필요가 없다는 것입니다.

필요한 방법은 Slice다음과 같습니다.

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

방법의 많은 지금 지원 Span하고 IReadOnlySpan는이 새로운 유형을 사용하는 것은 매우 간단합니다, 그래서.

작성 당시 Span<T>유형은 최신 버전의 .NET (4.7.1)에 아직 정의되어 있지 않으므로이를 사용하려면 NuGet 에서 System.Memory 패키지 를 설치해야합니다 .


1
Span<T>유형은 최신 버전의 .Net (4.7.1)에 아직 정의되어 있지 않으므로이를 사용하려면 System.MemoryNuGet에서 설치해야합니다 (NuGet에서 검색 할 때 "시험판 포함"을 선택해야 함)
Matthew Watson

@MatthewWatson 감사합니다. 귀하의 의견을 다시 작성하여 답변에 추가했습니다.
Patrick Hofman

16

내가 여기에 언급하지 않은 또 다른 가능성 : Buffer.BlockCopy ()는 Array.Copy ()보다 약간 빠르며 프리미티브 배열에서 즉석에서 변환 할 수 있다는 추가 이점이 있습니다 (예 : 짧음 [])를 바이트 배열로 변환하면 소켓을 통해 전송해야하는 숫자 형 배열이있을 때 편리합니다.


2
Buffer.BlockCopyArray.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

이 (비평가) 코드와 같이 원래 배열 (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
IndexOf에 EqualityComparer.Default를 사용하는 것이 좋습니다. 따라서 특별한 케이싱이 필요하지 않습니다.
Jon Skeet

1
나는 그것이 절대적으로 좋을 것으로 기대합니다. 더 간단한 코드를 먼저 사용하겠습니다.
Jon Skeet

이 같은 것이 내 의견으로는 갈 수있는 가장 좋은 방법입니다. 그러나 Array.CopySubList가 문자 그대로 List의 항목 복사본 대신 부모 List 내의 영역과 같은 많은 이점을 가질 수는 있지만 분명히 단순한 것보다 더 많은 작업 (처음으로) 입니다.
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 <byte>foo 의 첫 40 바이트를 포함하고 두 번째 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가 없다면 필요한 것입니다.


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 세계의 몇 가지 사항이 있지만 꽤 좋습니다. 경우 start0이 아닌 사이입니다 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.