Tranches de tableau en C#
Comment faites-vous? Donné un tableau d'octets:
byte[] foo = new byte[4096];
Comment pourrais-je obtenir les premiers octets x du tableau comme un tableau séparé? (Spécifiquement, j'en ai besoin comme un IEnumerable<byte>
)
Ceci est pour travailler avec Socket
s. Je pense que la manière la plus facile serait de découper des tableaux, similaire à la syntaxe Perls:
@bar = @foo[0..40];
qui renverrait les 41 premiers éléments dans le tableau @bar
. Y a-t-il quelque chose en C que je suis manque juste, ou est-il autre chose que je devrais faire?
LINQ est une option pour moi (.NET 3.5), si cela peut aider.
16 réponses
sont dénombrables, donc votre foo
est déjà un IEnumerable<byte>
lui-même.
Utilisez simplement les méthodes de la séquence LINQ comme Take()
pour obtenir ce que vous voulez (n'oubliez pas d'inclure l'espace de noms Linq
avec using System.Linq;
):
byte[] foo = new byte[4096];
var bar = foo.Take(41);
si vous avez vraiment besoin d'un tableau à partir de n'importe quelle valeur IEnumerable<byte>
, vous pouvez utiliser la méthode ToArray()
pour cela. Ce qui ne semble pas être le cas ici.
vous pourriez utiliser ArraySegment<T>
. C'est très léger car il ne copie pas le tableau:
string[] a = { "one", "two", "three", "four", "five" };
var segment = new ArraySegment<string>( a, 1, 2 );
vous pouvez utiliser la méthode des tableaux CopyTo()
.
ou avec LINQ vous pouvez utiliser Skip()
et 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);
une autre possibilité que je n'ai pas vu mentionnée ici: tampon.BlockCopy () est légèrement plus rapide que Array.Copier(), et il a l'avantage supplémentaire d'être en mesure de convertir à la volée à partir d'un tableau de primitives (par exemple, la court[]) pour un tableau d'octets, ce qui peut être pratique quand vous avez des tableaux numériques que vous avez besoin de transmettre sur les Sockets.
Voici une méthode d'extension simple qui retourne une tranche comme un nouveau tableau:
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;
}
, Alors vous pouvez l'utiliser comme:
byte[] slice = foo.Slice(0, 40);
si vous voulez IEnumerable<byte>
, alors juste
IEnumerable<byte> data = foo.Take(x);
vous pouvez utiliser un wrapper autour du tableau original (qui est IList), comme dans ce morceau de code (non testé).
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();
vous pouvez utiliser la méthode Take extension
var array = new byte[] {1, 2, 3, 4};
var firstTwoItems = array.Take(2);
il peut s'agir d'une solution qui:
var result = foo.Slice(40, int.MaxValue);
, Puis le résultat est un IEnumerable< IEnumerable< byte>> avec un premier IEnumerable< byte> contient les 40 premiers octets de foo , et un second IEnumerable< byte> détient le reste.
j'ai écrit une classe d'Emballage, toute l'itération est paresseuse, j'espère qu'elle pourrait aider:
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;
}
pour les tableaux d'octets système.Tampon.BlockCopy vous donnera la meilleure performance.
dans C# 7.2 , vous pouvez utiliser Span<T>
. L'avantage du nouveau système System.Memory
est qu'il n'a pas besoin de copier autour des données.
la méthode dont vous avez besoin est Slice
:
Span<byte> slice = foo.Slice(0, 40);
beaucoup de méthodes supportent maintenant Span
et IReadOnlySpan
, il sera donc très simple d'utiliser ce nouveau type.
noter qu'à l'époque de l'écriture du type Span<T>
n'est pas encore défini dans la version la plus récente de .NET (4.7.1) donc pour l'utiliser vous devez installer le système .Paquet mémoire de NuGet.
Voici une fonction d'extension qui utilise un générique et se comporte comme la fonction PHP array_slice . Un décalage et une longueur négatifs sont autorisés.
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;
}
}
Je ne pense pas que C# supporte la sémantique de gamme. Vous pouvez cependant écrire une méthode d'extension, comme:
public static IEnumerator<Byte> Range(this byte[] array, int start, int end);
mais comme d'autres ont dit si vous n'avez pas besoin de définir un index de départ alors Take
est tout ce dont vous avez besoin.