Отримання під-масиву з існуючого масиву


335

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

Щось на кшталт (псевдокод):

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

Array.Copyне відповідає моїм потребам . Мені потрібні елементи в новому масиві, щоб бути клонами. Array.copyце просто memcpyеквівалент С-стилю , це не те, що я шукаю.



7
@Kirtan - той "дуб" спеціально хоче IEnumerable <T> - який відрізняється і має різні оптимальні рішення; IMO
Marc Gravell

Отже, два рядки, необхідні для оголошення нового масиву та виклику .Copy () - це не "чистий код"?
Ед С.

2
@Ed Swangren - ні, якщо вам потрібно це зробити посеред ланцюгового виразу, ні
;-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 є ненадійним, тому що це коли-небудь.
Marcus Griep

1
Дякуємо, що підкреслили проблеми із глибоким клонуванням у C #. Це справді прикро, оскільки глибоке копіювання - це фундаментальна операція .
Димитрій К.

316

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

Якщо ви використовуєте .NET 3.5, ви можете використовувати LINQ:

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

але це буде дещо менш ефективно.

Дивіться цю відповідь на подібне запитання щодо варіантів більш конкретних ситуацій.


+1 Мені також подобається ця варіація. Джон, можете розширити, чому це вважається менш ефективним?
Ян Рок

@Jon: Щоб відповісти на питання, чи не буде це "Take (5)"? @Ian: Підхід Array.Copy не передбачає перелік, і, швидше за все, це буде пряма записка ...
Marc Gravell

@Marc: Так. Занадто багато запитань :)
Джон Скіт

11
@Ian: Підхід LINQ вводить два рівні непрямості (ітератори), повинен явно пропускати елементи і не знає, наскільки великим буде остаточний масив заздалегідь. Розглянемо, як взяти другу половину двомільйонного масиву: простий підхід "створити цільовий масив, скопіювати" просто скопіює необхідний блок, не торкаючись інших елементів, і за один раз. Підхід LINQ буде проходити через масив, поки він не досягне початкової точки, потім почне приймати значення, нарощувати буфер (збільшуючи розмір буфера і періодично копіюючи). Набагато менш ефективні.
Джон Скіт

якщо 5 - EndIndexm, то правильним питанням є масив.Skip (3) .Take (5-3 + 1) .ToArray (); тобто. array.Skip (StartIndex) .Take (EndIndex-StartIndex + 1) .ToArray ();
Klaus78

73

Ви розглядали можливість використання 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.


Це більш елегантно.
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();

Синг-рядок і немає необхідності додавати Linq. Це мій кращий спосіб.
Димитріс

Все-таки воно не клонує джерело ... але це все одно хороший підхід
IG Pascual

1
Він повинен клонувати джерело, оскільки ToArray: (1) створює новий масив і (2) виконує Array.Copy. Зрештою Джерело та Зріз - це два окремих об'єкти. Підхід правильний, однак, я віддаю перевагу Array.Copy: referenceource.microsoft.com/#mscorlib/system/collections/…
Krauss

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 }


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 надто схожа на важку роботу, що відображає, використовуючи копіювану бібліотеку Håvard Stranden, щоб зробити необхідний важкий підйом.

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 для свого типу.

Тож це повинно охоплювати майже будь-яку ситуацію, що у вас є. Якщо ви клонуєте об’єкти, де під графіком містяться такі речі, як db-з'єднання або файли / потоки, у вас, очевидно, є проблеми, але це правда для будь-якої узагальненої глибокої копії.

Якщо ви хочете скористатися іншим підходом до глибокої копії, натомість у цій статті перелічено декілька інших, тому я б радив не намагатися писати свій власний.


Перший - це, мабуть, бажане рішення, оскільки він просить клонування. Зауважте, що за допомогою методу Copy вам, мабуть, навіть не потрібно перевіряти наявність null, оскільки це метод розширення, якщо сам метод вже робить це. Варто спробувати.
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);  

Ні, смужка все одно буде нульовою. Array.Copy не магічно створює новий масив, тим більше, що смужка не передається з посиланням чи виводом.
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

@Robert: Ні, це не так. Спробуйте використовувати натомість ArraySegment, і ви бачите, що ви не можете ні отримати доступ до елементів за індексом, ні повторити їх.
Гуффа

2

Array.ConstrainedCopy буде працювати.

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

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 у всіх класах, для яких вам потрібно використовувати це, але це має робити.


1

Я не впевнений, наскільки це насправді, але:

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

Це трохи накладні витрати, але це може вирішити непотрібний метод.


1

C # 8 надає функцію, яку називають діапазоном, щоб отримати підрядність від початку до кінця індексу. ви можете використовувати його так.

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 }

Ось якийсь божевільний пітонічний s ** t. Я це люблю.
Ч3шир

Так, це дійсно приємна особливість
vivek nuna

0

Що стосується клонування, я не думаю, що серіалізація викликає ваші конструктори. Це може зламати інваріантів класу, якщо ви робите цікаві речі в ctor's.

Здається, що більш безпечною ставкою є віртуальні методи клонування, що викликають конструктори копій.

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

Сподіваюся, що це допомагає!


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();
        }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.