C # 'deki dizi dilimleri


228

Bunu nasıl yapıyorsun? Bir bayt dizisi verildiğinde:

byte[] foo = new byte[4096];

Dizinin ilk x baytını ayrı bir dizi olarak nasıl alabilirim? (Özellikle, bir olarak ihtiyacım var IEnumerable<byte>)

Bu Sockets ile çalışmak içindir . Ben en kolay yolu Perls sözdizimine benzer, dizi dilimleme olacağını anlıyorum:

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

Bu, ilk 41 öğeyi @bardiziye döndürür . C # 'da sadece eksik olduğum bir şey var mı, yoksa yapmam gereken başka bir şey var mı?

LINQ benim için bir seçenektir (.NET 3.5), eğer yardımcı olursa.


3
Dizi dilimleme c # 7.2 için bir tekliftir github.com/dotnet/csharplang/issues/185
Mark

3
C # 8.0 yerel dizi dilimlemenin girişini görecektir. Daha fazla bilgi için cevaba bakınız
Remy

1
Orijinal veriler üzerinden adım olarak dizilerin dilimlenmesini gerçekleştiren ArraySlice <T> ile ilgilenebilirsiniz: github.com/henon/SliceAndDice
henon

Yanıtlar:


196

Diziler numaralandırılabilir, bu yüzden foozaten bir kendiniz IEnumerable<byte>. Take()İstediğinizi almak için LINQ sekans yöntemlerini kullanmanız yeterlidir ( Linqad alanını dahil etmeyi unutmayın using System.Linq;):

byte[] foo = new byte[4096];

var bar = foo.Take(41);

Gerçekten herhangi bir IEnumerable<byte>değerden bir dizi gerekiyorsa , bunun için ToArray()yöntemi kullanabilirsiniz . Burada böyle görünmüyor.


5
Başka bir diziye kopyalanacaksak, Array.Copy statik yöntemini kullanmanız yeterlidir. Ancak diğer cevaplar niyet doğru yorumladı düşünüyorum, başka bir dizi sadece ilk 41 bayt üzerinde bir IEnumberable <byte> gerekli değildir.
AnthonyWJones

2
Sadece tek boyutlu ve tırtıklı dizilerin numaralandırılabileceğini, çok boyutlu dizilerin olmadığını unutmayın.
Abel

11
Array.Copy kullanarak not, LINQ'nun Take veya Skip yöntemlerini kullanmaktan çok daha hızlı performans gösterir.
Michael

4
@Abel Bu aslında çok yanlış. Çok boyutlu diziler vardır enumerable ama böyle numaralandırmak: [2,3] => [1,1], [1,2], [1,3], [2,1], [2,2], [2,3]. Pürüzlü diziler de numaralandırılabilir, ancak numaralandırıldığında bir değer döndürmek yerine iç dizilerini döndürürler. type[][] jaggedArray; foreach (type[] innerArray in jaggedArray) { }
Şunun

3
@Aidiakapi "çok doğru"? ). Ama kısmen haklısın, "multidim dizileri uygulamıyor" yazmalıydım, o zaman ifadem IEnumerable<T>daha net olurdu. Ayrıca bakınız: stackoverflow.com/questions/721882/…
Abel

212

Kullanabilirsin ArraySegment<T>. Diziyi kopyalamadığı için çok hafif:

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

5
Ne yazık ki IEnumerable değil.
özyinelemeli

1
Doğru, ama etrafına IEnumerable uygulayan bir yineleyici sarmalayıcı yazmak kolay olurdu.
Mike Scott

22
NEDEN IEnumerable olmadığını bilen var mı? Yapmıyorum. Olması gerektiği gibi görünüyor.
Fantius

39
ArraySegment .Net 4.5'ten başlayarak IList ve IEnumerable'dır. Eski sürüm kullanıcıları için çok kötü ..
Todd Li

6
@Zyo ArraySegment <T>, .Net 4.5'ten başlayarak IEnumerable <T> uygular, IEnumerable <T> 'nin kendisi yeni değildir.
Todd Li

137

Diziler CopyTo()yöntemini kullanabilirsiniz .

Veya LINQ ile kullanabilirsiniz Skip()ve Take()...

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

1
+1 iyi bir fikir için, ama ben döndürülen dizi CopyTo geçici bir değişken gerektiren yapar başka bir işlev için giriş olarak kullanmanız gerekir. Henüz başka cevaplar bekleyeceğim.
Matthew Scharley

4
Henüz LINQ hakkında bilgi sahibi değilim, belki de bu benim gerçekten olması gereken başka bir kanıt.
Matthew Scharley

11
bu yaklaşım Array.Copy'den en az 50 kat daha yavaştır. Bu çoğu durumda bir sorun değildir, ancak bir döngüde dizi dilimleme yaparken performans düşüşü çok açıktır.
Valentin Vasilyev

3
Tek bir arama yapıyorum, bu yüzden performans benim için bir sorun değil. Bu okunabilirlik için harika ... teşekkürler.
Zengin

2
İçin teşekkürler Skip(). Sadece Take()sana keyfi bir dilim getirmeyecek. Ayrıca, yine de bir LINQ çözümü arıyordum (dilim IEnumerable, ama dizi hakkında sonuçları bulmak daha kolay olacağını biliyordum).
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
Bence Buffer.BlockCopy () daha verimli ve aynı sonuçları elde.
Matt Davis

28

C # 8.0 / .Net Core 3.0'dan itibaren

Dizi dilimleme, yeni türler Indexve Rangeeklenerek desteklenecektir.

Menzil Yapı belgeleri
Dizin Yapı belgeleri

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 }

Yukarıdaki kod örneği C # 8.0 blogundan alınmıştır .

^ön ekin dizinin sonundan saymayı gösterdiğini unutmayın . Dokümanlar örneğinde gösterildiği gibi

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

Rangeve Indexayrıca dilimleme dizilerinin dışında, örneğin döngülerle çalışabilir

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

1'den 4'e kadar olan girişler arasında geçiş yapacak


Bu cevabı yazarken, C # 8.0'ın henüz resmi olarak piyasaya sürülmediğini unutmayın.
C # 8.x ve .Net Core 3.x artık Visual Studio 2019 ve sonrasında mevcuttur.


dizinin bir kopyasını yaratıp yaratmadığı hakkında bir fikriniz var mı?
Tim Pohlmann


22

In C # 7.2 , kullanabilirsiniz Span<T>. Yeni System.Memorysistemin yararı, verilerin etrafında kopyalamaya ihtiyaç duymamasıdır.

İhtiyacınız olan yöntem Slice:

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

Artık birçok yöntem destekliyor Spanve IReadOnlySpanbu nedenle bu yeni türü kullanmak çok kolay olacak.

Yazarken, Span<T>türün henüz .NET'in en son sürümünde (4.7.1) tanımlanmadığını unutmayın, bu nedenle kullanmak için NuGet'ten System.Memory paketini yüklemeniz gerekir .


1
Span<T>Türün henüz .Net'in en son sürümünde (4.7.1) tanımlanmadığını unutmayın, bu nedenle kullanmak için System.MemoryNuGet'ten yüklemeniz gerekir (ve NuGet'te ararken "yayın öncesi" seçeneğini işaretlemeyi unutmayın)
Matthew Watson

@MatthewWatson Teşekkürler. Yorumunuzu yeniden yazdım ve cevabıma ekledim.
Patrick Hofman

16

Burada bahsetmediğim başka bir olasılık: Buffer.BlockCopy (), Array.Copy () 'den biraz daha hızlıdır ve bir dizi ilkelden (örneğin, kısa []), Yuva üzerinden iletmeniz gereken sayısal dizilere sahip olduğunuzda kullanışlı olabilecek bir bayt dizisine.


2
Buffer.BlockCopyArray.Copy()aynı parametreleri kabul etmelerine rağmen farklı sonuçlar üretti - çok sayıda boş öğe vardı. Neden?
jocull

7
@jocull - Aslında aynı parametreleri almıyorlar. Array.Copy (), uzunluk ve konum parametrelerini öğelerde alır. Buffer.BlockCopy (), uzunluk ve konum parametrelerini bayt cinsinden alır. Başka bir deyişle, 10 elemanlı bir tamsayı dizisini kopyalamak isterseniz, kullanırsınız Array.Copy(array1, 0, array2, 0, 10), ancak Buffer.BlockCopy(array1, 0, array2, 0, 10 * sizeof(int)).
Ken Smith

14

Eğer istersen IEnumerable<byte>, o zaman

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

14

Bir dilimi yeni bir dizi olarak döndüren basit bir uzantı yöntemi:

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

Sonra şu şekilde kullanabilirsiniz:

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

8

LINQ veya diğer uzantıları eklemek istemiyorsanız şunları yapın :

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 belgeleri yüzlerce "List" girdisi ile umutsuzdur. Burada doğru olan nedir?
wallyk

1
System.Collections.Generic.List
Tetralux

7

Bu (denenmemiş) kod parçasında olduğu gibi, orijinal dizi (IList olan) etrafında bir sarıcı kullanabilirsiniz.

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 için EqualityComparer.Default kullanmanızı öneririm - bu şekilde herhangi bir özel kasaya ihtiyacınız yoktur.
Jon Skeet

1
Kesinlikle iyi olmasını beklerdim. Kesinlikle önce daha basit bir kodla giderdim.
Jon Skeet

Böyle bir şey bence en iyi yol. Ancak Array.Copy, bu, Listedeki girişlerin bir kopyası yerine, tam olarak ana Liste içinde bir bölge olması gibi birçok avantaja sahip olsa da, basit olandan daha fazla iş (ilk kez) .
Aidiakapi

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

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

6

Bayt dizileri için System.Buffer.BlockCopy size en iyi performansı verecektir.


1
Bu sadece binlerce veya milyonlarca kez bir döngüde yapıyorsanız gerçekten önemlidir. Bir yuva uygulamasında, muhtemelen bazı girişler alıyorsunuz ve bunları parçalara ayırıyorsunuz. Sadece bir kez yapıyorsanız, en iyi performans bir sonraki programcının en kolay anlayacağı şeydir.
Michael Blackburn

5

Uzantıyı al yöntemini kullanabilirsiniz

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

3

Bu bir çözüm olabilir:

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

Sonra sonuç bir olan IEnumerable <IEnumerable <bayt >> birinci ile IEnumerable <byte> ilk 40 bayt içeren foo ve ikinci bir IEnumerable <bayt> kalanını tutar.

Bir sarıcı sınıf yazdım, tüm yineleme tembel, umarım yardımcı olabilir:

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 semantiği destekler sanmıyorum. Bununla birlikte, bir uzantı yöntemi yazabilirsiniz:

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

Ancak diğerlerinin söylediği gibi, bir başlangıç ​​dizini ayarlamanız gerekmiyorsa, ihtiyacınız olan Taketek şey budur.


1

Genel kullanılan ve PHP işlevi array_slice gibi davranan bir uzantı işlevi . Negatif ofset ve uzunluğa izin verilir.

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 dünyasından birkaç şey olsa da oldukça iyi. start0 ile arasında değilse , arr.Lengthbüyük olasılıkla sınır dışı bir istisna atmalıdır. Ayrıca, end >= start >= 0kontrol etmenize gerek yok end < 0, bunun olması mümkün değil. Muhtemelen bunu kontrol ederek length >= 0ve sonra len = Math.min(length, arr.Length - start)yerine oynamak yerine daha da özlü bir şekilde yapabilirsiniz 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();
        }
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.