web-dev-qa-db-fra.com

Obtenir un sous-tableau à partir d'un tableau existant

J'ai un tableau X de 10 éléments. Je voudrais créer un nouveau tableau contenant tous les éléments de X commençant par l'index 3 et se terminant par l'index 7. Bien sûr, je peux facilement écrire une boucle qui le fera pour moi, mais j'aimerais garder mon code aussi propre que possible. . Existe-t-il une méthode en C # qui peut le faire pour moi?

Quelque chose comme (pseudo code):

Array NewArray = oldArray.createNewArrayFromRange(int BeginIndex , int EndIndex)

Array.Copy ne répond pas à mes besoins. J'ai besoin que les éléments du nouveau tableau soient des clones. Array.copy n'est qu'un C-Style memcpy équivalent, ce n'est pas ce que je recherche.

302
user88637

Vous pouvez l'ajouter en tant que méthode d'extension:

public static T[] SubArray<T>(this T[] data, int index, int length)
{
    T[] result = new T[length];
    Array.Copy(data, index, result, 0, length);
    return result;
}
static void Main()
{
    int[] data = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    int[] sub = data.SubArray(3, 4); // contains {3,4,5,6}
}

Mise à jour sur le clonage (ce qui n'était pas évident dans la question initiale). Si vous vraiment voulez un clone profond; quelque chose comme:

public static T[] SubArrayDeepClone<T>(this T[] data, int index, int length)
{
    T[] arrCopy = new T[length];
    Array.Copy(data, index, arrCopy, 0, length);
    using (MemoryStream ms = new MemoryStream())
    {
        var bf = new BinaryFormatter();
        bf.Serialize(ms, arrCopy);
        ms.Position = 0;
        return (T[])bf.Deserialize(ms);
    }
}

Cela nécessite cependant que les objets soient sérialisables ([Serializable] ou ISerializable). Vous pouvez facilement remplacer tout autre sérialiseur, le cas échéant - XmlSerializer, DataContractSerializer, protobuf-net, etc.

Notez que le clonage en profondeur est délicat sans la sérialisation. en particulier, ICloneable est difficile à faire confiance dans la plupart des cas.

433
Marc Gravell

Vous pouvez utiliser Array.Copy(...) pour copier dans le nouveau tableau après l'avoir créé, mais je ne pense pas qu'il existe une méthode qui crée le nouveau tableau et copie une série d'éléments.

Si vous utilisez .NET 3.5, vous pouvez utiliser LINQ:

var newArray = array.Skip(3).Take(5).ToArray();

mais ce sera un peu moins efficace.

Voir cette réponse à une question similaire pour des options pour des situations plus spécifiques.

293
Jon Skeet

Avez-vous envisagé d'utiliser ArraySegment?

http://msdn.Microsoft.com/en-us/library/1hsbd92d.aspx

67
Alex Black

Le code suivant le fait en une seule ligne:

// Source array
string[] Source = new string[] { "A", "B", "C", "D" };
// Extracting a slice into another array
string[] Slice = new List<string>(Source).GetRange(2, 2).ToArray();
31
Volker

Je vois que vous voulez faire du clonage, pas simplement copier des références. Dans ce cas, vous pouvez utiliser .Select pour projeter les membres du groupe sur leurs clones. Par exemple, si vos éléments implémentés IClonable, vous pouvez faire quelque chose comme ceci:

var newArray = array.Skip(3).Take(5).Select(eachElement => eachElement.Clone()).ToArray();

Remarque: Cette solution nécessite .NET Framework 3.5.

31
zvolkov
string[] arr = { "Parrot" , "Snake" ,"Rabbit" , "Dog" , "cat" };

arr = arr.ToList().GetRange(0, arr.Length -1).ToArray();
12
user3698437

S'appuyant sur la réponse de Marc mais en ajoutant le comportement de clonage souhaité

public static T[] CloneSubArray<T>(this T[] data, int index, int length)
    where T : ICloneable
{
    T[] result = new T[length];
    for (int i = 0; i < length; i++)
    { 
        var original = data[index + i];
        if (original != null)
            result[i] = (T)original.Clone();            
    return result;
}

Et si mettre en œuvre ICloneable ressemble trop à un travail difficile, celui-ci est réflexif et utilise bibliothèque de Håvard Stranden Copyable pour faire le gros du travail requis.

using OX.Copyable;

public static T[] DeepCopySubArray<T>(
    this T[] data, int index, int length)
{
    T[] result = new T[length];
    for (int i = 0; i < length; i++)
    { 
        var original = data[index + i];
        if (original != null)
            result[i] = (T)original.Copy();            
    return result;
}

Notez que l'implémentation OX.Copyable fonctionne avec l'un des:

Cependant, pour que la copie automatisée fonctionne, l'une des déclarations suivantes doit par exemple être vraie:

  • Son type doit avoir un constructeur sans paramètre, ou
  • Ce doit être une copie, ou
  • Il doit avoir un IInstanceProvider enregistré pour son type.

Donc, cela devrait couvrir presque toutes les situations que vous avez. Si vous clonez des objets pour lesquels le sous-graphe contient des éléments tels que des connexions à la base de données ou des descripteurs de fichier/flux, vous avez évidemment des problèmes, mais cela vaut pour toute copie profonde généralisée.

Si vous souhaitez utiliser une autre approche de copie en profondeur à la place de cette l'article en énumère plusieurs autres , je vous suggère donc de ne pas essayer d'écrire la vôtre.

8
ShuggyCoUk

Vous pouvez le faire assez facilement.

    object[] foo = new object[10];
    object[] bar = new object[7];   
    Array.Copy(foo, 3, bar, 0, 7);  
8
RandomNickName42

Je pense que le code que vous recherchez est:

Array.Copy(oldArray, 0, newArray, BeginIndex, EndIndex - BeginIndex)

4
Sean

En C # 8, ils ont introduit un nouveau type Range et Index

int[] a = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
Index i1 = 3;  // number 3 from beginning
Index i2 = ^4; // number 4 from end
var slice = a[i1..i2]; // { 3, 4, 5 }
3
Prasanth Louis

Au lieu de copier les données, vous pouvez créer un wrapper qui vous donne accès à une partie du tableau d'origine comme s'il s'agissait d'une copie de la partie du tableau. L'avantage est que vous n'obtenez pas une autre copie des données en mémoire et que l'inconvénient est une légère surcharge lors de l'accès aux données.

public class SubArray<T> : IEnumerable<T> {

   private T[] _original;
   private int _start;

   public SubArray(T[] original, int start, int len) {
      _original = original;
      _start = start;
      Length = len;
   }

   public T this[int index] {
      get {
         if (index < 0 || index >= Length) throw new IndexOutOfRangeException();
         return _original[_start + index];
      }
   }

   public int Length { get; private set; }

   public IEnumerator<T> GetEnumerator() {
      for (int i = 0; i < Length; i++) {
        yield return _original[_start + i];
      }
   }

   IEnumerator IEnumerable.GetEnumerator() {
      return GetEnumerator();
   }

}

Usage:

int[] original = { 1, 2, 3, 4, 5 };
SubArray<int> copy = new SubArray<int>(original, 2, 2);

Console.WriteLine(copy.Length); // shows: 2
Console.WriteLine(copy[0]); // shows: 3
foreach (int i in copy) Console.WriteLine(i); // shows 3 and 4
3
Guffa

Array.ConstrainedCopy fonctionnera.

public static void ConstrainedCopy (
    Array sourceArray,
    int sourceIndex,
    Array destinationArray,
    int destinationIndex,
    int length
)
2
crauscher

Il n'y a pas de méthode unique qui fera ce que vous voulez. Vous devrez créer une méthode de clonage pour la classe dans votre tableau. Ensuite, si LINQ est une option:

Foo[] newArray = oldArray.Skip(3).Take(5).Select(item => item.Clone()).ToArray();

class Foo
{
    public Foo Clone()
    {
        return (Foo)MemberwiseClone();
    }
}
1
Thorarin

Je ne suis pas sûr de sa profondeur, mais:

MyArray.ToList<TSource>().GetRange(beginningIndex, endIndex).ToArray()

C'est un peu de frais généraux, mais cela pourrait éliminer une méthode inutile.

1
SCNerd

Que dis-tu de ça:

public T[] CloneCopy(T[] array, int startIndex, int endIndex) where T : ICloneable
{
    T[] retArray = new T[endIndex - startIndex];
    for (int i = startIndex; i < endIndex; i++)
    {
        array[i - startIndex] = array[i].Clone();
    }
    return retArray;

}

Vous devez ensuite implémenter l'interface ICloneable sur toutes les classes sur lesquelles vous avez besoin de l'utiliser, mais cela devrait le faire.

1
RCIX

Que diriez-vous d'utiliser Array.Constrained Copy :

int[] ArrayOne = new int[8] {1,2,3,4,5,6,7,8};
int[] ArrayTwo = new int[5];
Array.ConstrainedCopy(ArrayOne, 3, ArrayTwo, 0, 7-3);

Ci-dessous est mon post original. Ça ne marchera pas

Vous pouvez utiliser Array.CopyTo :

int[] ArrayOne = new int[8] {1,2,3,4,5,6,7,8}; int[] ArrayTwo = new int[5]; ArrayOne.CopyTo(ArrayTwo,3); //starts copy at index=3 until it reaches end of //either array

1
Mike

C'est la manière optimale, j'ai trouvé, de faire ceci:

private void GetSubArrayThroughArraySegment() {
  int[] array = { 10, 20, 30 };
  ArraySegment<int> segment = new ArraySegment<int>(array,  1, 2);
  Console.WriteLine("-- Array --");
  int[] original = segment.Array;
  foreach (int value in original)
  {
    Console.WriteLine(value);
  }
  Console.WriteLine("-- Offset --");
  Console.WriteLine(segment.Offset);
  Console.WriteLine("-- Count --");
  Console.WriteLine(segment.Count);

  Console.WriteLine("-- Range --");
  for (int i = segment.Offset; i <= segment.Count; i++)
  {
    Console.WriteLine(segment.Array[i]);
  }
}

J'espère que ça aide!

0
OscarMas

Vous pouvez prendre des cours faits par Microsoft:

internal class Set<TElement>
{
    private int[] _buckets;
    private Slot[] _slots;
    private int _count;
    private int _freeList;
    private readonly IEqualityComparer<TElement> _comparer;

    public Set()
        : this(null)
    {
    }

    public Set(IEqualityComparer<TElement> comparer)
    {
        if (comparer == null)
            comparer = EqualityComparer<TElement>.Default;
        _comparer = comparer;
        _buckets = new int[7];
        _slots = new Slot[7];
        _freeList = -1;
    }

    public bool Add(TElement value)
    {
        return !Find(value, true);
    }

    public bool Contains(TElement value)
    {
        return Find(value, false);
    }

    public bool Remove(TElement value)
    {
        var hashCode = InternalGetHashCode(value);
        var index1 = hashCode % _buckets.Length;
        var index2 = -1;
        for (var index3 = _buckets[index1] - 1; index3 >= 0; index3 = _slots[index3].Next)
        {
            if (_slots[index3].HashCode == hashCode && _comparer.Equals(_slots[index3].Value, value))
            {
                if (index2 < 0)
                    _buckets[index1] = _slots[index3].Next + 1;
                else
                    _slots[index2].Next = _slots[index3].Next;
                _slots[index3].HashCode = -1;
                _slots[index3].Value = default(TElement);
                _slots[index3].Next = _freeList;
                _freeList = index3;
                return true;
            }
            index2 = index3;
        }
        return false;
    }

    private bool Find(TElement value, bool add)
    {
        var hashCode = InternalGetHashCode(value);
        for (var index = _buckets[hashCode % _buckets.Length] - 1; index >= 0; index = _slots[index].Next)
        {
            if (_slots[index].HashCode == hashCode && _comparer.Equals(_slots[index].Value, value))
                return true;
        }
        if (add)
        {
            int index1;
            if (_freeList >= 0)
            {
                index1 = _freeList;
                _freeList = _slots[index1].Next;
            }
            else
            {
                if (_count == _slots.Length)
                    Resize();
                index1 = _count;
                ++_count;
            }
            int index2 = hashCode % _buckets.Length;
            _slots[index1].HashCode = hashCode;
            _slots[index1].Value = value;
            _slots[index1].Next = _buckets[index2] - 1;
            _buckets[index2] = index1 + 1;
        }
        return false;
    }

    private void Resize()
    {
        var length = checked(_count * 2 + 1);
        var numArray = new int[length];
        var slotArray = new Slot[length];
        Array.Copy(_slots, 0, slotArray, 0, _count);
        for (var index1 = 0; index1 < _count; ++index1)
        {
            int index2 = slotArray[index1].HashCode % length;
            slotArray[index1].Next = numArray[index2] - 1;
            numArray[index2] = index1 + 1;
        }
        _buckets = numArray;
        _slots = slotArray;
    }

    internal int InternalGetHashCode(TElement value)
    {
        if (value != null)
            return _comparer.GetHashCode(value) & int.MaxValue;
        return 0;
    }

    internal struct Slot
    {
        internal int HashCode;
        internal TElement Value;
        internal int Next;
    }
}

puis

public static T[] GetSub<T>(this T[] first, T[] second)
    {
        var items = IntersectIteratorWithIndex(first, second);
        if (!items.Any()) return new T[] { };


        var index = items.First().Item2;
        var length = first.Count() - index;
        var subArray = new T[length];
        Array.Copy(first, index, subArray, 0, length);
        return subArray;
    }

    private static IEnumerable<Tuple<T, Int32>> IntersectIteratorWithIndex<T>(IEnumerable<T> first, IEnumerable<T> second)
    {
        var firstList = first.ToList();
        var set = new Set<T>();
        foreach (var i in second)
            set.Add(i);
        foreach (var i in firstList)
        {
            if (set.Remove(i))
                yield return new Tuple<T, Int32>(i, firstList.IndexOf(i));
        }
    }
0
Smagin Alexey

En ce qui concerne le clonage, je ne pense pas que la sérialisation appelle vos constructeurs. Cela peut briser les invariants de classe si vous faites des choses intéressantes dans le ctor.

Il semble que le pari le plus sûr est d'utiliser des méthodes de clonage virtuel appelant des constructeurs de copie.

protected MyDerivedClass(MyDerivedClass myClass) 
{
  ...
}

public override MyBaseClass Clone()
{
  return new MyDerivedClass(this);
}
0
Hans Malherbe

Le clonage d'éléments dans un tableau ne peut pas être réalisé de manière universelle. Voulez-vous un clonage profond ou une simple copie de tous les membres?

Passons à l'approche du "meilleur effort": cloner des objets à l'aide de l'interface ICloneable ou de la sérialisation binaire:

public static class ArrayExtensions
{
  public static T[] SubArray<T>(this T[] array, int index, int length)
  {
    T[] result = new T[length];

    for (int i=index;i<length+index && i<array.Length;i++)
    {
       if (array[i] is ICloneable)
          result[i-index] = (T) ((ICloneable)array[i]).Clone();
       else
          result[i-index] = (T) CloneObject(array[i]);
    }

    return result;
  }

  private static object CloneObject(object obj)
  {
    BinaryFormatter formatter = new BinaryFormatter();

    using (MemoryStream stream = new MemoryStream())
    {
      formatter.Serialize(stream, obj);

      stream.Seek(0,SeekOrigin.Begin);

      return formatter.Deserialize(stream);
    }
  }
}

Ce n'est pas une solution parfaite, car aucune solution ne fonctionnera pour n'importe quel type d'objet.

0
Philippe Leybaert

utiliser la méthode d'extention:

public static T[] Slice<T>(this T[] source, int start, int end)
    {
        // Handles negative ends.
        if (end < 0)
        {
            end = source.Length + end;
        }
        int len = end - start;

        // Return new array.
        T[] res = new T[len];
        for (int i = 0; i < len; i++)
        {
            res[i] = source[i + start];
        }
        return res;
    }

et vous pouvez l'utiliser

var NewArray = OldArray.Slice(3,7);
0
Erwin Draconis