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 2009年

2
1次元配列とギザギザの配列のみが列挙可能であり、多次元配列は列挙できないことに注意してください。
Abel、

11
Array.Copyを使用すると、LINQのTakeまたはSkipメソッドを使用するよりもはるかに高速に実行されます。
マイケル

4
@アベルそれは実際には非常に間違っています。多次元配列列挙可能ですが、次のように列挙されます[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/...
アベル

211

使用できますArraySegment<T>。配列をコピーしないため、非常に軽量です。

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

5
残念ながらIEnumerableではありません。
2010

1
正しいですが、IEnumerableを実装するイテレータラッパーを簡単に作成できます。
マイクスコット

22
IEnumerableでない理由を誰かが知っていますか?私はしません。あるべきようです。
Fantius

39
ArraySegmentは、.Net 4.5以降、IListおよびIEnumerableです。旧バージョンのユーザーのために残念...
トッド・リー

6
@Zyoつまり、ArraySegment <T>が.Net 4.5以降のIEnumerable <T>を実装することを意味し、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、2009年

4
私はまだLINQに精通していませんが、これはおそらく私が本当にあるべきさらなる証拠です。
Matthew Scharley、2009年

11
このアプローチは、Array.Copyよりも少なくとも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以降

配列のスライスは、新しいタイプIndexRange追加されるとともにサポートされます。

Range Struct docs
Index 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以降で利用可能になりました。


これが配列のコピーを作成するかどうかについて何か考えはありますか?
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>タイプは純まだ(4.7.1)ので、あなたがインストールする必要があり、それを使用するには、最新バージョンで定義されていないSystem.MemoryNuGetから(NuGetでそれを検索するときにカチカチすると覚えている「プレリリースを含めます」)
マシューワトソン

@MatthewWatsonありがとう。私はあなたのコメントを書き直して私の答えに追加しました。
Patrick Hofman、

16

ここで言及していない別の可能性:Buffer.BlockCopy()はArray.Copy()よりもわずかに高速であり、プリミティブの配列からオンザフライで変換できるという追加の利点があります(たとえば、短い[])をバイトの配列に変換します。これは、ソケットを介して送信する必要がある数値配列がある場合に便利です。


2
Buffer.BlockCopyArray.Copy()同じパラメーターを受け入れる場合とは異なる結果が生成されました-多くの空の要素がありました。どうして?
jocull 2012

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 2018年

1
System.Collections.Generic.List
Tetralux、2018年

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、2010

このようなものは私の意見では行くための最良の方法です。しかし、明らかにArray.Copy、これは単純なよりも(初回)より多くの作業です。ただし、SubListが文字通り親リスト内の領域であり、リスト内のエントリのコピーではないなど、多くの利点があります。
アイディアカピ


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>にはfooの最初の40バイトが含まれ、2番目の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の世界からのいくつかのことです。startが0との間にない場合はarr.Length、おそらく範囲外の例外をスローするはずです。また、end >= start >= 0を確認する必要がないため、end < 0発生する可能性はありません。おそらく、そのチェックすることで、より簡潔にそれを行うことができlength >= 0、その後len = Math.min(length, arr.Length - start)ではなく、fuddlingをend
マシューシャーリー2014年

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.