既存の配列からサブ配列を取得する


335

10個の要素の配列Xがあります。インデックス3で始まり、インデックス7で終わるXのすべての要素を含む新しい配列を作成したいと思います。確かに、私のためにそれを実行するループを簡単に作成できますが、コードをできるだけ簡潔にしたいと思います。私のためにそれを行うことができるC#のメソッドはありますか?

(疑似コード)のようなもの:

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

Array.Copy私のニーズに合いません。新しいアレイのアイテムをクローンにする必要があります。Array.copyCスタイルのmemcpy同等物ですが、私が探しているものではありません。



7
@Kirtan-その「dup」は特にIEnumerable <T>を必要としています-これは異なり、最適なソリューションも異なります。IMO
マークグラベル

それで、新しい配列を宣言して.Copy()を呼び出すのにかかる2行は「クリーンなコード」ではありませんか?
Ed S.

2
@Ed Swangren-連鎖式の途中で行う必要がある場合は不可、-p
Marc Gravell

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)にする必要があります。-あなたは簡単に、必要に応じて他のシリアライザのために置き換えることができXmlSerializerDataContractSerializerなど、いるProtobufネット、

ディープクローンはシリアル化しないと扱いにくいことに注意してください。特に、ICloneableほとんどの場合、信頼するのは難しいです。


1
(明らかに、長さではなく終了インデックスを使用するのは簡単な変更です。「より典型的な」使用法であるため、「
現状のまま

1
それから...厳しいです。それはそれを行いません....おそらく、シリアライゼーションを使用して同様の何かを実現する必要があります
Marc Gravell

1
いくつかの代替案といくつかの実装へのリンクについては、私の回答を参照してください。サブ配列に対してそれを行うことに関する部分は本当にかなり些細なことです、あなたが本当に欲しいのはクローニングビットです、そしてそれは完全に「正しい」振る舞いがどうあるべきかについてのあなたの期待に完全に依存する複雑でいくぶん未解決の質問です。
ShuggyCoUk 2009

2
これはいいね。そして、ICloneableは信頼性がないことを指摘するのは特に良いことです。
Marcus Griep、

1
C#のディープクローニングの問題に下線を付けてくれてありがとう。深いコピーは基本的な操作なので、本当に残念です。
Dimitri C.

316

あなたは使用することができArray.Copy(...)、あなたがそれを作成した後に新しい配列にコピーすることが、私は新しい配列を作成する方法がないと思うコピーの要素の範囲は。

.NET 3.5を使用している場合、LINQを使用できます。

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

しかし、それはやや効率が悪くなります。

より具体的な状況のオプションについては、同様の質問に対するこの回答を参照してください。


+1私もこのバリエーションが好きです。ジョン、なぜこれが効率が悪いと思われるのか、詳しく説明していただけますか?
Ian Roke

@ジョン:質問に一致させるには、「Take(5)」ではないでしょうか?@Ian:Array.Copyアプローチは列挙子を含まないため、ほとんどの場合、単純なmemcopyになります...
Marc Gravell

@マーク:はい、確かに。質問が多すぎます:)
Jon Skeet

11
@Ian:LINQアプローチでは、2レベルの間接参照(イテレータ)が導入され、アイテムを明示的にスキップする必要があり、最終的な配列の大きさが事前にわからない。200万要素の配列の後半を取ることを検討してください。単純な「ターゲット配列の作成、コピー」アプローチは、他の要素に触れることなく、必要なブロックを一度にコピーするだけです。LINQアプローチは、配列が開始点に達するまで配列をウォークスルーし、次に値の取得を開始し、バッファーを構築します(バッファーサイズを増やして定期的にコピーします)。はるかに効率が悪い。
Jon Skeet、

5がEndIndexmの場合、正しい質問はarray.Skip(3).Take(5-3 + 1).ToArray();です。すなわち。array.Skip(StartIndex).Take(EndIndex-StartIndex + 1).ToArray();
Klaus78 14

73

使用を検討しましたArraySegmentか?

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


1
それはおそらくあなたが望むことをしますが、それはデフォルトの配列構文をサポートしませんし、IEnumerableもサポートしないので、特にきれいではありません。
アレックスブラック

5
これにはもっと賛成票が必要です。私自身の経験値では、ArraySegmentコピーが若干速く過ぎる(スピードクリティカル詰め込むためのすべてのIの使用アレイの後に)..です
nawfal

5
@AlexBlack .NET 4.5以降のように見え、実装IEnumerable<T>し、他のさまざまな便利なインターフェースを実装しています。
pswg 2013

1
ArraySegment元の質問にどのように答えますか?
Craig McQueen

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 2014年

これはまさに私が探していたものです。これはどのでも機能しますIEnumerable。私は得ることができIEnumerableIListIArray私はする必要がインラインであれば、最小限の手間で...、など。ディープコピーが必要ない場合は、単に削除しSelectます。ドロップするSkipTake、範囲を制御できます。また、私はそれをミックスすることができSkipWhile、および/またはTakeWhile
マイク

33

次のコードは、1行で実行します。

// 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を追加する必要はありません。それは私の好みの方法です。
Dimitris 2015年

それでもソースのクローンは作成されません...しかし、とにかく良いアプローチです
IG Pascual

1
ToArray:(1)新しい配列を作成し、(2)Array.Copyを実行するため、ソースを複製する必要があります。最後に、SourceとSliceは2つの別々のオブジェクトです。アプローチは正しいです、しかし、私はArray.Copyを好む:referencesource.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();

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åvardStrandenのCopyableライブラリを使用して、必要な重い作業を行うためのリフレクティブライブラリを使用します。

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をチェックする必要もないことに注意してください。nullは拡張メソッドであるため、メソッド自体がすでにその処理を行っている場合に限られます。試すだけの価値があります。
Dykam 2009

はい、私はnullチェックに注意しましたが、ソースを読み取らなかった場合に備えてOPを混乱させたくありませんでした。
ShuggyCoUk 2009

2
余談ですが、GitHubのCopyableの最新バージョンでは、オブジェクトにパラメーターのないコンストラクターは必要ありません。:)を参照してくださいgithub.com/havard/copyable
HåvardS

8

これはかなり簡単に行えます。

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

いいえ、バーはnullのままです。Array.Copyは、特にbarがrefまたはoutで渡されないため、魔法のように新しい配列を作成しません。
Zr40 2009

2
やあ、やあ、そうだね、そう、急いでやったんだけど、もしかすると、もしあなたの執筆批評があなたが訂正をするべきだとしたら、建設的な批評は誰にとってもはるかに有用です。そのため、そのarray.copyの前に、「bar = new object [7];」を実行します
RandomNickName42

4

あなたが探しているコードは次のとおりだと思います:

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


私はここに良い友達を作っていると思います...あなたと同じ答えです;)そして私はたくさん投票されました!! ハァッ!とにかく、良い時は良い時。
RandomNickName42 2009

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

@ロバート:いいえ、違います。代わりにArraySegmentを使用してみてください。インデックスで項目にアクセスすることも、項目を反復処理することもできないことがわかります。
Guffa

2

Array.ConstrainedCopyが機能します。

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

2
データをコピーするだけです。新しい配列などは作成されません。配列が新しい場合は、より効率的なArray.Copyを使用できます(追加のチェック/ロールバックの必要はありません)。
マークグラベル

そのとおりですが、新しい配列の作成は1行のコードであり、新しいメソッドは必要ありません。Array.Copyも機能することに同意します。
クルーシャー09年

1

あなたが望むことをする単一の方法はありません。配列内のクラスで利用できるcloneメソッドを作成する必要があります。次に、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 }

0

クローンに関しては、シリアライゼーションがコンストラクタを呼び出すとは思いません。これは、アクターで興味深いことをしている場合、クラスの不変式を壊す可能性があります。

安全な賭けは、コピーコンストラクターを呼び出す仮想クローンメソッドにあるようです。

protected MyDerivedClass(MyDerivedClass myClass) 
{
  ...
}

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

シリアライゼーションがコンストラクターを呼び出すかどうかは、特定のシリアライザー次第です。する人もいれば、しない人もいます。ただし、通常は必要な修正を行うためのコールバックサポートを提供しないものです。
Marc Gravell

これは、シリアル化のもう1つの摩擦点を強調しています。デフォルトのコンストラクターを提供する必要があります。
Hans Malherbe、

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

これは完全なソリューションではありません。これは、どのタイプのオブジェクトでも機能するものがないためです。


それはresult [i-index] =(T)...のようなものではありませんか?
ドナルドバード

はい:)そしてそれだけではありません。ループ境界が間違っています。直します。ありがとう!
Philippe Leybaert、2009

0

マイクロソフト製のクラスを受講できます。

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();
        }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.