Array irisan dalam C #

228

Bagaimana Anda melakukannya? Diberikan array byte:

byte[] foo = new byte[4096];

Bagaimana saya mendapatkan x byte pertama dari array sebagai array terpisah? (Secara khusus, saya membutuhkannya sebagai IEnumerable<byte>)

Ini untuk bekerja dengan Sockets. Saya pikir cara termudah adalah mengiris array, mirip dengan sintaks Perls:

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

Yang akan mengembalikan 41 elemen pertama ke dalam @bararray. Apakah ada sesuatu di C # yang saya lewatkan, atau ada hal lain yang harus saya lakukan?

LINQ adalah opsi untuk saya (.NET 3.5), jika itu membantu.

Matthew Scharley
sumber
3
Mengiris
Mark
3
C # 8.0 akan melihat pengenalan irisan array asli. Lihat jawaban untuk detail lebih lanjut
Remy
1
Anda mungkin tertarik pada ArraySlice <T> yang mengimplementasikan pengirisan array dengan langkah sebagai tampilan atas data asli: github.com/henon/SliceAndDice
henon

Jawaban:

196

Array dapat dihitung, sehingga Anda foosudah merupakan IEnumerable<byte>dirinya sendiri. Cukup gunakan metode urutan LINQ Take()untuk mendapatkan apa yang Anda inginkan (jangan lupa menyertakan Linqnamespace using System.Linq;):

byte[] foo = new byte[4096];

var bar = foo.Take(41);

Jika Anda benar-benar membutuhkan array dari IEnumerable<byte>nilai apa pun , Anda bisa menggunakan ToArray()metode untuk itu. Tampaknya tidak demikian di sini.

PEHIr
sumber
5
Jika kita akan menyalin ke array lain cukup gunakan metode statis Array.Copy. Namun saya pikir jawaban lain telah menafsirkan maksud dengan benar, array lain tidak diperlukan hanya <number> IEnumberable yang selama 41 byte pertama.
AnthonyWJones
2
Perhatikan bahwa hanya array satu dimensi dan bergerigi yang dapat dihitung, array multi dimensi tidak.
Abel
11
Catatan menggunakan Array.Copy melakukan jauh lebih cepat daripada menggunakan metode Take atau Skip LINQ.
Michael
4
@ Bel Itu sebenarnya sangat tidak benar. Array multi dimensi yang enumerable tapi mereka menyebutkan seperti ini: [2,3] => [1,1], [1,2], [1,3], [2,1], [2,2], [2,3]. Array bergerigi juga enumerable tetapi bukannya mengembalikan nilai ketika disebutkan, mereka mengembalikan array batin mereka. Seperti ini:type[][] jaggedArray; foreach (type[] innerArray in jaggedArray) { }
Aidiakapi
3
@Aidiakapi "sangat sempurna"? ;). Tapi Anda sebagian benar, saya harus menulis "array multidim tidak mengimplementasikan IEnumerable<T>", maka pernyataan saya akan menjadi lebih jelas. Lihat juga ini: stackoverflow.com/questions/721882/…
Abel
211

Anda bisa menggunakannya ArraySegment<T>. Ini sangat ringan karena tidak menyalin array:

string[] a = { "one", "two", "three", "four", "five" };
var segment = new ArraySegment<string>( a, 1, 2 );
Mike Scott
sumber
5
Sayangnya itu bukan IEnumerable.
Rekursif
1
Benar, tetapi akan mudah untuk menulis pembungkus iterator di sekitarnya yang mengimplementasikan IEnumerable.
Mike Scott
22
Adakah yang tahu MENGAPA itu bukan IEnumerable? Bukan saya. Sepertinya memang seharusnya begitu.
Fantius
39
ArraySegment adalah IList dan IEnumerable mulai dari .Net 4.5. Sayang sekali bagi pengguna versi yang lebih tua ..
Todd Li
6
@ Zyo saya maksudkan ArraySegment <T> mengimplementasikan IEnumerable <T> mulai dari .Net 4.5, bukan IEnumerable <T> itu sendiri baru.
Todd Li
137

Anda bisa menggunakan array CopyTo() metode .

Atau dengan LINQ yang dapat Anda gunakan Skip()dan Take()...

byte[] arr = {1, 2, 3, 4, 5, 6, 7, 8};
var subset = arr.Skip(2).Take(2);
Arjan Einbu
sumber
1
+1 untuk ide yang bagus, tetapi saya perlu menggunakan array yang dikembalikan sebagai input untuk fungsi lain, yang membuat CopyTo memerlukan variabel sementara. Saya akan menunggu jawaban lain.
Matthew Scharley
4
Saya belum terbiasa dengan LINQ, mungkin ini adalah bukti lebih lanjut bahwa saya seharusnya.
Matthew Scharley
11
pendekatan ini setidaknya 50x lebih lambat dari Array.Copy. Ini bukan masalah dalam banyak situasi tetapi ketika melakukan pemotongan array dalam siklus, penurunan kinerja sangat jelas.
Valentin Vasilyev
3
Saya membuat satu panggilan, jadi kinerja bukan masalah bagi saya. Ini bagus untuk keterbacaan ... terima kasih.
Kaya
2
Terima kasih untuk Skip(). Hanya Take()tidak akan membuat Anda mendapatkan potongan acak. Selain itu, saya masih mencari solusi LINQ (iris IEnumerable, tapi saya tahu hasil tentang array akan lebih mudah ditemukan).
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);
WOPR
sumber
11
Saya pikir Buffer.BlockCopy () lebih efisien dan mencapai hasil yang sama.
Matt Davis
28

Mulai dari C # 8.0 / .Net Core 3.0

Mengiris array akan didukung, bersama dengan tipe baru Indexdan Rangeditambahkan.

Range Struct docs
Indeks 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 }

Sampel kode di atas diambil dari blog C # 8.0 .

perhatikan bahwa ^awalan menunjukkan penghitungan dari akhir array. Seperti yang ditunjukkan pada contoh dokumen

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

Rangedan Indexjuga bekerja di luar array slicing, misalnya dengan loop

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

Akan mengulangi entri 1 hingga 4


perhatikan bahwa pada saat penulisan jawaban ini, C # 8.0 belum secara resmi dirilis
C # 8.x dan .Net Core 3.x sekarang tersedia di Visual Studio 2019 dan seterusnya

Remy
sumber
ada ide apakah ini membuat salinan array?
Tim Pohlmann
2
sepertinya itu salinan: codejourney.net/2019/02/csharp-8-slicing-indexes-ranges
Tim Pohlmann
22

Di C # 7.2 , Anda dapat menggunakan Span<T>. Manfaat dari System.Memorysistem baru ini adalah tidak perlu menyalin data.

Metode yang Anda butuhkan adalah Slice:

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

Banyak metode sekarang mendukung Spandan IReadOnlySpan, jadi akan sangat mudah untuk menggunakan tipe baru ini.

Perhatikan bahwa pada saat penulisan, Span<T>tipe ini belum didefinisikan dalam versi .NET terbaru (4.7.1) sehingga untuk menggunakannya Anda perlu menginstal paket System.Memory dari NuGet.

Patrick Hofman
sumber
1
Perhatikan bahwa Span<T>jenis ini belum didefinisikan dalam versi .Net terbaru (4.7.1) sehingga untuk menggunakannya Anda harus menginstalSystem.Memory dari NuGet (dan ingat untuk mencentang "sertakan prerelease" saat mencarinya di NuGet)
Matthew Watson
@ MatthewWatson Terima kasih. Saya menulis ulang komentar Anda dan menambahkannya ke jawaban saya.
Patrick Hofman
16

Kemungkinan lain yang belum saya lihat disebutkan di sini: Buffer.BlockCopy () sedikit lebih cepat daripada Array.Copy (), dan memiliki manfaat tambahan untuk dapat mengkonversi on-the-fly dari berbagai primitif (katakanlah, pendek []) ke array byte, yang bisa berguna ketika Anda memiliki array numerik yang perlu Anda transmisikan melalui Soket.

Ken Smith
sumber
2
Buffer.BlockCopymenghasilkan hasil yang berbeda daripada Array.Copy()meskipun mereka menerima parameter yang sama - ada banyak elemen kosong. Mengapa?
jocull
7
@ jocull - Mereka sebenarnya tidak cukup mengambil parameter yang sama. Array.Copy () mengambil parameter panjang dan posisi dalam elemen. Buffer.BlockCopy () mengambil parameter panjang dan posisi dalam byte. Dengan kata lain, jika Anda ingin menyalin array 10 elemen bilangan bulat, Anda akan menggunakannya Array.Copy(array1, 0, array2, 0, 10), tetapi Buffer.BlockCopy(array1, 0, array2, 0, 10 * sizeof(int)).
Ken Smith
14

Jika Anda mau IEnumerable<byte>, maka adil

IEnumerable<byte> data = foo.Take(x);
Marc Gravell
sumber
14

Berikut adalah metode ekstensi sederhana yang mengembalikan sepotong sebagai array baru:

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

Maka Anda dapat menggunakannya sebagai:

byte[] slice = foo.Slice(0, 40);
Vladimir Mitrovic
sumber
8

Jika Anda tidak ingin menambahkan LINQ atau ekstensi lain, cukup lakukan:

float[] subArray = new List<float>(myArray).GetRange(0, 8).ToArray();
Dimitris
sumber
Error CS0246: The type or namespace name 'List<>' could not be found (are you missing a using directive or an assembly reference?) Dokumentasi Microsoft tidak ada harapan dengan ratusan entri "Daftar" diindeks. Apa yang benar di sini?
wallyk
1
System.Collections.Generic.List
Tetralux
7

Anda bisa menggunakan pembungkus di sekitar array asli (yang adalah IList), seperti dalam potongan kode (belum teruji) ini.

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

}

Rauhotz
sumber
4
Saya sarankan menggunakan EqualityComparer.Default untuk IndexOf - dengan begitu Anda tidak perlu casing khusus.
Jon Skeet
1
Saya berharap itu baik-baik saja. Saya pasti akan pergi dengan kode yang lebih sederhana terlebih dahulu.
Jon Skeet
Menurut saya ini adalah cara terbaik untuk pergi. Tapi jelas itu lebih banyak bekerja (pertama kali) daripada yang sederhana Array.Copy, meskipun ini dapat memiliki banyak keuntungan, seperti SubList yang secara harfiah menjadi wilayah di dalam Daftar induk, daripada salinan entri dalam Daftar.
Aidiakapi
7
byte[] foo = new byte[4096]; 

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

Untuk byte array System.Buffer.BlockCopy akan memberikan Anda kinerja terbaik.

Simon Giles
sumber
1
Yang hanya penting jika Anda melakukan ini dalam satu lingkaran ribuan atau jutaan kali. Dalam aplikasi soket, Anda mungkin mengambil beberapa input dan memecahnya menjadi beberapa bagian. Jika Anda hanya melakukannya sekali, kinerja terbaik adalah apa pun yang paling mudah dipahami oleh programmer berikutnya.
Michael Blackburn
5

Anda dapat menggunakan Ambil metode ekstensi

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

Ini mungkin solusi yang:

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

Maka hasilnya adalah IEnumerable <IEnumerable <byte >> dengan IEnumerable <byte> pertama berisi 40 byte pertama dari foo , dan IEnumerable kedua <byte> memegang sisanya.

Saya menulis kelas pembungkus, seluruh iterasi malas, berharap bisa membantu:

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;
}
Li Zhen
sumber
2

Saya tidak berpikir C # mendukung semantik Range. Anda dapat menulis metode ekstensi, seperti:

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

Tetapi seperti yang dikatakan orang lain jika Anda tidak perlu menetapkan indeks awal, itulah Takeyang Anda butuhkan.

Bleevo
sumber
1

Berikut ini adalah fungsi ekstensi yang menggunakan generik dan berperilaku seperti fungsi PHP array_slice . Offset dan panjang negatif diizinkan.

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;
    }
}
Brendan Taylor
sumber
1
Cukup bagus, meskipun beberapa hal dari dunia .NET. Jika starttidak antara 0 dan arr.Length, mungkin harus membuang pengecualian di luar batas. Juga, end >= start >= 0jadi Anda tidak perlu memeriksa end < 0, itu tidak mungkin terjadi. Anda mungkin bisa melakukannya dengan lebih ringkas dengan memeriksa itu length >= 0dan kemudian len = Math.min(length, arr.Length - start)alih-alih berkutat dengan 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();
        }
    }
}
Ahmad AlSaloum
sumber