Получение подмассива из существующего массива

335

У меня есть массив X из 10 элементов. Я хотел бы создать новый массив, содержащий все элементы из X, которые начинаются с индекса 3 и заканчиваются индексом 7. Конечно, я могу легко написать цикл, который сделает это для меня, но я хотел бы сохранить мой код как можно более чистым , Есть ли в C # метод, который может сделать это для меня?

Что-то вроде (псевдокод):

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

Array.Copyне соответствует моим потребностям . Мне нужно, чтобы элементы в новом массиве были клонами. Array.copyэто просто memcpyэквивалент C-Style , это не то, что я ищу.

user88637
источник
7
@Kirtan - этот «dup» специально хочет IEnumerable <T> - который отличается и имеет разные оптимальные решения; ИМО
Марк Гравелл
Итак, две строки, которые потребуются для объявления нового массива и вызова .Copy (), не являются «чистым кодом»?
Эд С.
2
@ Эд Свангрен - нет, если вам нужно сделать это в середине цепного выражения, нет ;-p
Марк
2
Ответ ShaggyUk, вероятно, правильный: stackoverflow.com/questions/943635/…
Dykam

Ответы:

469

Вы можете добавить его как метод расширения:

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

Обновление повторного клонирования (что не было очевидно в исходном вопросе). Если вы действительно хотите глубокого клона; что-то вроде:

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

Это требует, чтобы объекты были сериализуемыми ( [Serializable]или ISerializable), хотя. Вы можете легко заменить любой другой сериализатором в соответствующих случаях - XmlSerializer, DataContractSerializer, Protobuf-сети и т.д.

Обратите внимание, что глубокий клон сложен без сериализации; в частности, ICloneableтрудно доверять в большинстве случаев.

Марк Гравелл
источник
1
(очевидно, что использование конечного индекса, а не длины - это простое изменение; я опубликовал «как есть», потому что это более «типичное» использование)
Марк Гравелл
1
Тогда ... жестко; это не делает этого .... вам, вероятно, нужно использовать сериализацию для достижения чего-то похожего
Марк Гравелл
1
см. мой ответ для некоторых альтернатив и ссылки на несколько реализаций. часть о том, как сделать это с подмассивом, довольно тривиальна, вам действительно нужен бит клонирования, и это сложный и несколько открытый вопрос, который полностью зависит от ваших ожиданий относительно того, каким должно быть «правильное» поведение .
ShuggyCoUk
2
Это хорошо. И особенно приятно отметить, что ICloneable ненадежен, потому что, да, это всегда.
Маркус Грип
1
Спасибо за то, что подчеркнули проблемы с глубоким клонированием в C #. Это действительно позор, поскольку глубокое копирование является фундаментальной операцией .
Дмитрий С.
317

Вы можете использовать Array.Copy(...)для копирования в новый массив после его создания, но я не думаю, что есть метод, который создает новый массив и копирует диапазон элементов.

Если вы используете .NET 3.5, вы можете использовать LINQ:

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

но это будет несколько менее эффективно.

Посмотрите этот ответ на аналогичный вопрос для вариантов для более конкретных ситуаций.

Джон Скит
источник
+1 Мне тоже нравится этот вариант. Джон, не могли бы вы рассказать, почему это считается менее эффективным?
Ян Рок
@Jon: Чтобы соответствовать вопросу, это не было бы "Take (5)"? @Ian: подход Array.Copy не требует использования перечислителя и, скорее всего, будет простой запиской ...
Марк Гравелл
@Marc: Да, действительно. Слишком много вопросов, снимающих :)
Джон Скит
11
@Ian: подход LINQ вводит два уровня косвенности (итераторы), должен явно пропустить элементы и не знает, насколько большим будет конечный массив заранее. Подумайте о том, чтобы взять вторую половину массива из двух миллионов элементов: простой подход «создать целевой массив, скопировать» просто скопирует необходимый блок, не касаясь других элементов, и сразу. Подход LINQ будет проходить через массив, пока не достигнет начальной точки, а затем начнет принимать значения, формируя буфер (увеличивая размер буфера и периодически копируя). Гораздо менее эффективно.
Джон Скит
если 5 - EndIndexm, то правильный вопрос - array.Skip (3) .Take (5-3 + 1) .ToArray (); то есть. array.Skip (StartIndex) .Снять (ENDINDEX-StartIndex + 1) .ToArray ();
Klaus78
74

Вы рассматривали возможность использования ArraySegment?

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

Алекс Блэк
источник
1
Он, вероятно, делает то, что вам нужно, но он не поддерживает синтаксис массива по умолчанию и не поддерживает IEnumerable, поэтому он не особенно чист.
Алекс Блэк
5
Это нуждается в большем количестве голосов. В моем собственном опыте копирование ArraySegment также происходит немного быстрее (ведь я использую массивы для материалов, критичных к скорости) ..
nawfal
5
@AlexBlack Похоже на .NET 4.5 , он реализует IEnumerable<T>и множество других полезных интерфейсов.
PSWG
1
Как бы вы использовали, ArraySegmentчтобы ответить на оригинальный вопрос?
Крейг МакКуин
2
@CraigMcQueen - Попробуйте следующий однострочный подход:IList<T> newArray = (IList<T>)new ArraySegment<T>(oldArray, beginIndex, endIndex);
skia.heliou
36

Я вижу, вы хотите делать клонирование, а не просто копировать ссылки. В этом случае вы можете использовать .Selectдля проецирования элементов массива на их клонов. Например, если ваши элементы реализованы, IClonableвы можете сделать что-то вроде этого:

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

Примечание. Для этого решения требуется .NET Framework 3.5.

zvolkov
источник
Это более элегантно.
smwikipedia
Это именно то, что я искал. Это работает для любого IEnumerable. Я могу получить IEnumerable, IList, IArrayи т.д. ... с минимальной суетой, рядный , если мне нужно. Если мне не нужна глубокая копия, я просто удаляю Select. Падение Skipили Takeпозволяет мне контролировать диапазон. В качестве альтернативы, я могу смешать это с SkipWhileи / или TakeWhile.
Майк
33

Следующий код делает это в одну строку:

// 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();
Volker
источник
Отдельная строка и нет необходимости добавлять Linq. Это мой любимый способ.
Димитрис
Тем не менее, это не клонирует источник ... но в любом случае это хороший подход
IG Pascual
1
Он должен клонировать источник, потому что ToArray: (1) создает новый массив и (2) выполняет Array.Copy. В конце Source и Slice два отдельных объекта. Подход правильный, однако я предпочитаю Array.Copy: referenceource.microsoft.com/#mscorlib/system/collections/…
Краусс
13

В C # 8 они ввели новый Rangeи 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 }
Прасант Луи
источник
12
string[] arr = { "Parrot" , "Snake" ,"Rabbit" , "Dog" , "cat" };

arr = arr.ToList().GetRange(0, arr.Length -1).ToArray();
user3698437
источник
8

Основываясь на ответе Марка, но добавляя желаемое поведение клонирования

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

И если реализация ICloneable слишком похожа на тяжелую работу, рефлексивную, использующую библиотеку Ховарда Страндена с возможностью копирования, чтобы выполнить тяжелую работу.

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

Обратите внимание, что реализация OX.Copyable работает с любым из:

Для того, чтобы автоматизированная копия работала, необходимо, например, одно из следующих утверждений:

  • Его тип должен иметь конструктор без параметров, или
  • Это должен быть Копируемый, или
  • Для его типа должен быть зарегистрирован IInstanceProvider.

Так что это должно охватывать практически любую ситуацию, которая у вас есть. Если вы клонируете объекты, где подграф содержит такие вещи, как соединения БД или дескрипторы файлов / потоков, у вас, очевидно, есть проблемы, но это верно для любой обобщенной глубокой копии.

Если вы хотите использовать какой-то другой подход глубокого копирования, в этой статье перечислено несколько других, поэтому я бы посоветовал не пытаться писать свой собственный.

ShuggyCoUk
источник
Первое, вероятно, желаемое решение, так как он просит клонировать. Обратите внимание, что при использовании метода Copy вам, вероятно, даже не нужно проверять наличие нуля, так как это метод расширения, если сам метод уже делает это. Стоит попробовать.
Dykam
Да, я отметил нулевую проверку, но не хотел путать ОП, если он не читал источник.
ShuggyCoUk
2
Просто примечание: последняя версия Copyable на GitHub не требует, чтобы объекты имели конструктор без параметров. :) См. Github.com/havard/copyable
Håvard S
8

Вы можете сделать это довольно легко;

    object[] foo = new object[10];
    object[] bar = new object[7];   
    Array.Copy(foo, 3, bar, 0, 7);  
RandomNickName42
источник
Нет, бар все равно будет нулевым. Array.Copy волшебным образом не создает новый массив, тем более что bar не передается с ref или out.
Zr40
2
о, да, вы правы, я сделал это в спешке, но, эй, возможно, когда вы пишете критику, вы должны внести исправление, конструктивная критика намного полезнее для всех. поэтому перед этим array.copy вы делаете "bar = new object [7];"
RandomNickName42
4

Я думаю, что код, который вы ищете:

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

Шон
источник
Я думаю, что у меня тут есть хороший друг ... такой же ответ, как и у вас;) и меня много проголосовали !! ха !! Во всяком случае, хорошие времена хорошие времена.
RandomNickName42
3

В качестве альтернативы копированию данных вы можете создать оболочку, которая дает вам доступ к части исходного массива, как если бы она была копией части массива. Преимущество состоит в том, что вы не получаете другую копию данных в памяти, а недостатком является небольшая нагрузка при доступе к данным.

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

}

Использование:

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
Guffa
источник
@ Роберт: Нет, это не так. Попробуйте вместо этого использовать ArraySegment, и вы увидите, что вы не можете ни получить доступ к элементам по индексу, ни перебирать элементы.
Guffa
2

Array.ConstrainedCopy будет работать.

public static void ConstrainedCopy (
    Array sourceArray,
    int sourceIndex,
    Array destinationArray,
    int destinationIndex,
    int length
)
crauscher
источник
2
Это просто копирует данные; это не создаст новый массив и т.д .; и если массив новый, мы могли бы использовать Array.Copy, который более эффективен (нет необходимости в дополнительных проверках / откатах).
Марк Гравелл
Это правильно, но создание нового массива - это всего одна строка кода, и новый метод не требуется. Я согласен, что Array.Copy также будет работать.
crauscher
1

Там нет единого метода, который будет делать то, что вы хотите. Вам нужно будет сделать метод клона доступным для класса в вашем массиве. Затем, если LINQ является опцией:

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

class Foo
{
    public Foo Clone()
    {
        return (Foo)MemberwiseClone();
    }
}
Торарин
источник
1

Как насчет использования Array.ConstrainedCopy :

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

Ниже мой оригинальный пост. Он не будет работать

Вы можете использовать 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

Как насчет этого:

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;

}

Затем вам нужно реализовать интерфейс ICloneable на всех классах, на которых вы хотите это использовать, но это должно быть сделано.

RCIX
источник
1

Я не уверен, насколько глубоко это на самом деле, но:

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

Это немного накладные расходы, но это может исключить ненужный метод.

SCNerd
источник
1

C # 8 предоставляет функцию, называемую Range, чтобы получить подарри от начального до конечного индекса. Вы можете использовать это так.

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 };  
var slice = a[i1..i2]; // { 3, 4, 5 }
Вивек Нуна
источник
Это какой-то сумасшедший питон Я люблю это.
Ch3shire
Да, это действительно хорошая функция
Вивек Нуна
0

Что касается клонирования, я не думаю, что сериализация вызывает ваши конструкторы. Это может нарушить инварианты класса, если вы делаете интересные вещи в ctor'е.

Кажется, безопаснее ставить виртуальные методы-клоны, вызывающие конструкторы копирования.

protected MyDerivedClass(MyDerivedClass myClass) 
{
  ...
}

public override MyBaseClass Clone()
{
  return new MyDerivedClass(this);
}
Ханс Малербе
источник
Независимо от того, вызывает ли сериализация ваши конструкторы, зависит от конкретного сериализатора. Некоторые делают, некоторые нет. Но те, которые обычно не предлагают поддержку обратного вызова, чтобы позволить вам делать любые необходимые исправления.
Марк Гравелл
Это подчеркивает еще одну сложность сериализации: вы должны предоставить конструкторы по умолчанию.
Ганс Малербе
0

Клонирование элементов в массиве - это не то, что можно сделать универсальным способом. Вы хотите глубокое клонирование или простую копию всех участников?

Давайте перейдем к подходу «наилучшее усилие»: клонирование объектов с использованием интерфейса ICloneable или двоичная сериализация:

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

Это не идеальное решение, потому что просто нет ни одного, который будет работать для любого типа объекта.

Филипп Лейберт
источник
Разве это не должно быть что-то вроде результата [i-index] = (T) ...?
Дональд Берд
да :) И не только это. Граница петли неверна. Я исправлю это. Спасибо!
Филипп Лейберт
0

Вы можете взять уроки, сделанные 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;
    }
}

а потом

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

Я нашел оптимальный способ сделать это:

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

Надеюсь, поможет!

OscarMas
источник
0

использовать метод расширения:

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

и вы можете использовать его

var NewArray = OldArray.Slice(3,7);
Эрвин Драконис
источник
0

Код из System.Private.CoreLib.dll:

public static T[] GetSubArray<T>(T[] array, Range range)
{
    if (array == null)
    {
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
    }
    (int Offset, int Length) offsetAndLength = range.GetOffsetAndLength(array.Length);
    int item = offsetAndLength.Offset;
    int item2 = offsetAndLength.Length;
    if (default(T) != null || typeof(T[]) == array.GetType())
    {
        if (item2 == 0)
        {
            return Array.Empty<T>();
        }
        T[] array2 = new T[item2];
        Buffer.Memmove(ref Unsafe.As<byte, T>(ref array2.GetRawSzArrayData()), ref Unsafe.Add(ref Unsafe.As<byte, T>(ref array.GetRawSzArrayData()), item), (uint)item2);
        return array2;
    }
    T[] array3 = (T[])Array.CreateInstance(array.GetType().GetElementType(), item2);
    Array.Copy(array, item, array3, 0, item2);
    return array3;
}


Иезекииль Майгуа
источник
0

Он не соответствует вашему требованию клонирования, но кажется более простым, чем многие ответы:

Array NewArray = new ArraySegment(oldArray,BeginIndex , int Count).ToArray();
Мистер Бой
источник
-1
public   static   T[]   SubArray<T>(T[] data, int index, int length)
        {
            List<T> retVal = new List<T>();
            if (data == null || data.Length == 0)
                return retVal.ToArray();
            bool startRead = false;
            int count = 0;
            for (int i = 0; i < data.Length; i++)
            {
                if (i == index && !startRead)
                    startRead = true;
                if (startRead)
                {

                    retVal.Add(data[i]);
                    count++;

                    if (count == length)
                        break;
                }
            }
            return retVal.ToArray();
        }
Бинай Рана
источник