Tranches de tableau en C #

228

Comment faites-vous? Étant donné un tableau d'octets:

byte[] foo = new byte[4096];

Comment puis-je obtenir les premiers x octets du tableau en tant que tableau séparé? (Plus précisément, j'en ai besoin en tant que IEnumerable<byte>)

C'est pour travailler avec l' Socketart. Je pense que le moyen le plus simple serait le découpage de tableaux, similaire à la syntaxe Perls:

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

Ce qui retournerait les 41 premiers éléments dans le @bartableau. Y a-t-il quelque chose en C # qui me manque, ou y a-t-il autre chose que je devrais faire?

LINQ est une option pour moi (.NET 3.5), si cela aide.

Matthew Scharley
la source
3
Le découpage de tableau est une proposition pour c # 7.2 github.com/dotnet/csharplang/issues/185
Mark
3
C # 8.0 verra l'introduction du découpage natif des tableaux. Voir la réponse pour plus de détails
Remy
1
Vous pourriez être intéressé par ArraySlice <T> qui met en œuvre tranchage de tableaux à l' étape en vue sur les données originales: github.com/henon/SliceAndDice
Henon

Réponses:

196

Les tableaux sont énumérables, donc votre foodéjà est un IEnumerable<byte>soi. Utilisez simplement les méthodes de séquence LINQ comme Take()pour en obtenir ce que vous voulez (n'oubliez pas d'inclure l' Linqespace de noms avec using System.Linq;):

byte[] foo = new byte[4096];

var bar = foo.Take(41);

Si vous avez vraiment besoin d'un tableau de n'importe quelle IEnumerable<byte>valeur, vous pouvez utiliser la ToArray()méthode pour cela. Cela ne semble pas être le cas ici.

peSHIr
la source
5
Si nous allons copier dans un autre tableau, utilisez simplement la méthode statique Array.Copy. Cependant, je pense que les autres réponses ont correctement interprété l'intention, un autre tableau n'est pas requis, juste un IEnumberable <byte> qui sur les 41 premiers octets.
AnthonyWJones
2
Notez que seuls les tableaux unidimensionnels et dentelés sont énumérables, les tableaux multidimensionnels ne le sont pas.
Abel
11
Notez que l'utilisation de Array.Copy est beaucoup plus rapide que celle des méthodes Take ou Skip de LINQ.
Michael
4
@Abel C'est en fait très incorrect. Tableaux multidimensionnels sont dénombrable mais ils énumèrent comme ceci: [2,3] => [1,1], [1,2], [1,3], [2,1], [2,2], [2,3]. Les tableaux irréguliers sont également énumérables, mais au lieu de renvoyer une valeur lorsqu'ils sont énumérés, ils retournent leur tableau interne. Comme ça:type[][] jaggedArray; foreach (type[] innerArray in jaggedArray) { }
Aidiakapi
3
@Aidiakapi "très incorecte"? ;). Mais vous avez partiellement raison, j'aurais dû écrire "les tableaux multidim ne sont pas implémentés IEnumerable<T>", alors ma déclaration aurait été plus claire. Voir aussi ceci: stackoverflow.com/questions/721882/…
Abel
211

Vous pourriez utiliser ArraySegment<T>. Il 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 );
Mike Scott
la source
5
Malheureusement, ce n'est pas IEnumerable.
récursif du
1
Certes, mais il serait facile d'écrire autour de lui un wrapper d'itérateur qui implémente IEnumerable.
Mike Scott
22
Est-ce que quelqu'un sait POURQUOI ce n'est pas IEnumerable? Je ne. Il semble que ce devrait être le cas.
Fantius
39
ArraySegment est IList et IEnumerable à partir de .Net 4.5. Dommage pour les utilisateurs de versions plus anciennes ..
Todd Li
6
@Zyo Je voulais dire qu'ArraySegment <T> implémente IEnumerable <T> à partir de .Net 4.5, et non IEnumerable <T> lui-même est nouveau.
Todd Li
137

Vous pouvez utiliser la CopyTo()méthode des tableaux .

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);
Arjan Einbu
la source
1
+1 pour une bonne idée, mais j'ai besoin d'utiliser le tableau retourné comme entrée pour une autre fonction, ce qui fait que CopyTo nécessite une variable temporaire. J'attendrai encore d'autres réponses.
Matthew Scharley
4
Je ne connais pas encore LINQ, c'est peut-être une preuve supplémentaire que je devrais vraiment l'être.
Matthew Scharley
11
cette approche est au moins 50 fois plus lente que Array.Copy. Ce n'est pas un problème dans de nombreuses situations, mais lorsque vous effectuez un découpage de tableau dans un cycle, la baisse des performances est très évidente.
Valentin Vasilyev
3
Je fais un seul appel, donc les performances ne sont pas un problème pour moi. C'est super pour la lisibilité ... merci.
Rich
2
Merci pour Skip(). Vous Take()n'obtiendrez tout simplement pas une tranche arbitraire. De plus, je cherchais quand même une solution LINQ (tranche IEnumerable, mais je savais que les résultats sur les tableaux seraient plus faciles à trouver).
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
la source
11
Je pense que Buffer.BlockCopy () est plus efficace et obtient les mêmes résultats.
Matt Davis
28

À partir de C # 8.0 / .Net Core 3.0

Le découpage des tableaux sera pris en charge, ainsi que les nouveaux types Indexet en Rangecours d'ajout.

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

Exemple de code ci-dessus tiré du blog C # 8.0 .

notez que le ^préfixe indique le comptage à partir de la fin du tableau. Comme illustré dans l' exemple de documents

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

Rangeet Indexégalement travailler en dehors des tableaux de découpage, par exemple avec des boucles

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

Parcourra les entrées 1 à 4


notez qu'au moment de la rédaction de cette réponse, C # 8.0 n'est pas encore officiellement publié.
C # 8.x et .Net Core 3.x sont désormais disponibles dans Visual Studio 2019 et versions ultérieures

Rémy
la source
une idée si cela crée ou non une copie du tableau?
Tim Pohlmann
2
semble être une copie: codejourney.net/2019/02/csharp-8-slicing-indexes-ranges
Tim Pohlmann
22

En C # 7.2 , vous pouvez utiliser Span<T>. L'avantage du nouveau System.Memorysystème est qu'il n'a pas besoin de copier autour des données.

La méthode dont vous avez besoin est la suivante Slice:

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

De nombreuses méthodes prennent désormais en charge Spanet IReadOnlySpan, il sera donc très simple d'utiliser ce nouveau type.

Notez qu'au moment de l'écriture, le Span<T>type 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 package System.Memory de NuGet.

Patrick Hofman
la source
1
Notez que le Span<T>type 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 System.Memorydepuis NuGet (et n'oubliez pas de cocher "inclure la pré-version" lorsque vous le recherchez dans NuGet)
Matthew Watson
@MatthewWatson Merci. J'ai réécrit votre commentaire et l'ai ajouté à ma réponse.
Patrick Hofman
16

Une autre possibilité que je n'ai pas vue mentionnée ici: Buffer.BlockCopy () est légèrement plus rapide que Array.Copy (), et il a l'avantage supplémentaire de pouvoir convertir à la volée à partir d'un tableau de primitives (disons, court []) à un tableau d'octets, ce qui peut être pratique lorsque vous avez des tableaux numériques que vous devez transmettre via Sockets.

Ken Smith
la source
2
Buffer.BlockCopyproduit des résultats différents que Array.Copy()même s'ils acceptent les mêmes paramètres - il y avait beaucoup d'éléments vides. Pourquoi?
jocull
7
@jocull - Ils ne prennent pas vraiment les mêmes paramètres. Array.Copy () prend ses paramètres de longueur et de position dans les éléments. Buffer.BlockCopy () prend ses paramètres de longueur et de position en octets. En d'autres termes, si vous vouliez copier un tableau d'entiers à 10 éléments, vous utiliseriez Array.Copy(array1, 0, array2, 0, 10), mais Buffer.BlockCopy(array1, 0, array2, 0, 10 * sizeof(int)).
Ken Smith
14

Si vous voulez IEnumerable<byte>, alors juste

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

Voici une méthode d'extension simple qui renvoie une tranche en tant que 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;
}

Ensuite, vous pouvez l'utiliser comme:

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

Si vous ne voulez pas ajouter LINQ ou d'autres extensions, faites simplement:

float[] subArray = new List<float>(myArray).GetRange(0, 8).ToArray();
Dimitris
la source
Error CS0246: The type or namespace name 'List<>' could not be found (are you missing a using directive or an assembly reference?) La documentation de Microsoft est sans espoir avec des centaines d'entrées "Liste" indexées. Quelle est la bonne ici?
wallyk
1
System.Collections.Generic.List
Tetralux
7

Vous pouvez utiliser un wrapper autour du tableau d'origine (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

}

Rauhotz
la source
4
Je suggère d'utiliser EqualityComparer.Default pour IndexOf - de cette façon, vous n'avez pas besoin de boîtier spécial.
Jon Skeet
1
Je m'attends à ce que ce soit très bien. J'irais certainement avec le code le plus simple en premier.
Jon Skeet
Quelque chose comme ça est à mon avis la meilleure façon de procéder. Mais évidemment, c'est plus de travail (la première fois) qu'un simple Array.Copy, même si cela peut avoir de nombreux avantages, comme la SubList étant littéralement une région de la liste parent, au lieu d'une copie des entrées de la liste.
Aidiakapi
7
byte[] foo = new byte[4096]; 

byte[] bar = foo.Take(40).ToArray();
ligne grise
la source
6

Pour les tableaux d'octets, System.Buffer.BlockCopy vous donnera les meilleures performances.

Simon Giles
la source
1
Ce qui n'a vraiment d'importance que si vous faites cela en boucle des milliers ou des millions de fois. Dans une application de sockets, vous prenez probablement une entrée et la divisez en plusieurs parties. Si vous ne le faites qu'une seule fois, les meilleures performances sont celles que le prochain programmeur comprendra le plus facilement.
Michael Blackburn
5

Vous pouvez utiliser la méthode d'extension Take

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

Cela peut être une solution qui:

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

Le résultat est alors un IEnumerable <IEnumerable <byte >> avec un premier IEnumerable <byte> contient les 40 premiers octets de foo , et un second IEnumerable <byte> contient le reste.

J'ai écrit une classe wrapper, toute l'itération est paresseuse, j'espère que cela 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;
}
Li Zhen
la source
2

Je ne pense pas que C # supporte la sémantique Range. 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 l'ont dit, si vous n'avez pas besoin de définir un index de démarrage, Takec'est tout ce dont vous avez besoin.

bleevo
la source
1

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;
    }
}
Brendan Taylor
la source
1
Très bien, bien que quelques éléments du monde .NET. S'il startn'est pas compris entre 0 et arr.Length, il devrait probablement lever une exception hors limites. De plus, end >= start >= 0pour que vous n'ayez pas besoin de vérifier end < 0, il n'est pas possible que cela se produise. Vous pourriez probablement le faire encore plus succinctement en vérifiant cela length >= 0, puis len = Math.min(length, arr.Length - start)au lieu de vous en occuper 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
la source