C#中的数组切片
你怎么做呢? 给定一个字节数组:
byte[] foo = new byte[4096];
我怎么会得到数组的前x个字节作为一个单独的数组? (具体来说,我需要它作为一个IEnumerable<byte>
)
这是为了与Socket
s一起工作。 我认为最简单的方法是数组切片,类似于Perls语法:
@bar = @foo[0..40];
这会将前41个元素返回到@bar
数组中。 C#中是否存在一些我只是缺less的东西,或者是我应该做的其他事情吗?
LINQ是我的select(.NET 3.5),如果有帮助的话。
数组是可枚举的,所以你的foo
已经是一个IEnumerable<byte>
本身。 简单地使用像Take()
这样的LINQ序列方法来获得你想要的东西(不要忘记using System.Linq;
来包含Linq
名字空间):
byte[] foo = new byte[4096]; var bar = foo.Take(41);
如果您确实需要任何IEnumerable<byte>
值的数组,则可以使用ToArray()
方法。 这似乎并不是这种情况。
你可以使用ArraySegment<T>
。 这是非常轻的,因为它不复制arrays:
string[] a = { "one", "two", "three", "four", "five" }; var segment = new ArraySegment<string>( a, 1, 2 );
你可以使用数组CopyTo()
方法。
或者用LINQ,你可以使用Skip()
和Take()
…
byte[] arr = {1, 2, 3, 4, 5, 6, 7, 8}; var subset = arr.Skip(2).Take(2);
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);
另一种可能性我没有在这里提到:Buffer.BlockCopy()比Array.Copy()稍微快一点,它还具有能够从原始数组即时转换的额外好处[])转换为一个字节数组,当你有需要通过套接字传输的数值数组时,这可以很方便。
下面是一个简单的扩展方法,它将一个slice作为一个新的数组返回:
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);
如果你想IEnumerable<byte>
,那么只是
IEnumerable<byte> data = foo.Take(x);
你可以在原始数组(也就是IList)中使用一个包装,就像在这个(未经testing的)代码片段中一样。
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
}
byte[] foo = new byte[4096]; byte[] bar = foo.Take(40).ToArray();
如果你不想添加LINQ或其他扩展只是做:
float[] subArray = new List<float>(myArray).GetRange(0, 8).ToArray();
您可以使用Take扩展方法
var array = new byte[] {1, 2, 3, 4}; var firstTwoItems = array.Take(2);
这可能是一个解决scheme:
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; }
对于字节数组, System.Buffer.BlockCopy会给你最好的性能。
这是一个扩展函数,它使用generics,像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; } }
我不认为C#支持Range语义。 你可以编写一个扩展方法,如:
public static IEnumerator<Byte> Range(this byte[] array, int start, int end);
但是和其他人一样,如果你不需要设置一个开始索引, Take
就是你所需要的。