配列の一部だけをコピーするには?[C#/VB]:.NET TIPS
ArrayクラスのCopyメソッド、ArraySegmentクラスなどを使い、配列の一部の要素だけをコピーする方法を説明。また、2つの配列をマージする方法も紹介する。
配列の一部だけを別の配列にコピーしたいことがある。もちろんforループやforeachループを記述してもよいのだが、Arrayクラス(System名前空間)などの機能を使えば簡単に書ける。
さらに本稿では、その応用として、複数の配列を1つにマージする方法も紹介する。
なお、配列は.NET Frameworkの最初からあるものだが、本稿はそれ以降の内容も含んでいる。サンプルコードをそのまま試すには、Visual Studio 2015(またはそれ以降)が必要である。
配列を一部だけをコピーするには?
ArrayクラスのCopyメソッドを使うと、1行でコピーできる(次のコード)。また、ArrayクラスのCopyメソッドは、同じ配列内でコピーしたり、多次元配列間でコピーしたりもできる(後述)。
// コピーする個数
int n = ……省略……;
// コピー元の配列
string[] a = { ……省略…… };
int i = ……省略……; // 配列a内での読み取り開始インデックス
// コピー先の配列
int j = ……省略……; // 配列b内での書き込み開始インデックス
string[] b = new string[n+j];
// Array.Copyメソッドでコピー
Array.Copy(a, i, b, j, n);
// 配列aの(i+1)番目の要素を配列bの(j+1)番目の位置へと、順にn個をコピーする。
' コピーする個数
Dim n As Integer = ……省略……
' コピー元の配列
Dim a() As String = { ……省略…… }
Dim i As Integer = ……省略…… ' 配列a内での読み取り開始インデックス
' コピー先の配列
Dim j As Integer = ……省略…… ' 配列b内での書き込み開始インデックス
Dim b(n + j - 1) As String
' Array.Copyメソッドでコピー
Array.Copy(a, i, b, j, n)
' 配列aの(i+1)番目の要素を配列bの(j+1)番目の位置へと、順にn個をコピーする。
なお、元の配列の指定範囲の要素だけを含む配列を新しく作りたいとき(=上のコードでjが0の場合)、.NET Framework 4.5(またはそれ以降)であれば、ArraySegment構造体(System名前空間)も使える(次のコード)。
// コピー元の配列
string[] a = { ……省略…… };
int i = ……省略……; // 配列a内での読み取り開始インデックス
int n = ……省略……; // コピーする個数
string[] c = (new ArraySegment<string>(a, i, n)).ToArray();
// 配列aの(i+1)番目からのn個を参照するArraySegment構造体を作り、
// それを配列に変換する。
' コピー元の配列
Dim a() As String = { ……省略…… }
Dim i As Integer = ……省略…… ' 配列a内での読み取り開始インデックス
Dim n As Integer = ……省略…… 'コピーする個数
Dim c() As String = (New ArraySegment(Of String)(a, i, n)).ToArray()
' 配列aの(i+1)番目からのn個を参照するArraySegment構造体を作り、
' それを配列に変換する。
このようにしてJavaScriptのsliceメソッドと同様な結果を得ることができる。
ただし、ArraySegment構造体は.NET Framework 2.0からあるが、ここで使っているToArray拡張メソッドが利用できるのは.NET Framework 4.5からである。また、ArraySegment構造体が扱える配列は1次元のものだけだ。
実際の例
上記2つの方法を実際のサンプルコードで示すと、例えば次のコンソールアプリのようになる。
using System;
using System.Linq;
using static System.Console;
class Program
{
static void Main(string[] args)
{
// コピー元の配列
string[] a = { "a", "b", "c", "d", "e", "f", };
// 配列の2番目から3個を、別の配列にコピーする例
// Array.Copyメソッドを使う
string[] b = new string[3];
Array.Copy(a, 1, b, 0, 3);
WriteLine($"b[] = {string.Join(", ", b)}");
// 出力:b[] = b, c, d
// ArraySegment構造体を使う(.NET Framework 4.5以上/1次元配列のみ)
string[] c = (new ArraySegment<string>(a, 1, 3)).ToArray();
WriteLine($"c[] = {string.Join(", ", c)}");
// 出力:c[] = b, c, d
#if DEBUG
// コンソールが閉じてしまうのを防ぐ
ReadKey();
#endif
}
}
Imports System.Console
Module Module1
Sub Main()
' コピー元の配列
Dim a() As String = {"a", "b", "c", "d", "e", "f"}
' 配列の2番目から3個を、別の配列にコピーする例
' Array.Copyメソッドを使う
Dim b(2) As String
Array.Copy(a, 1, b, 0, 3)
WriteLine($"b[] = {String.Join(", ", b)}")
' 出力:b[] = b, c, d
' ArraySegment構造体を使う(.NET Framework 4.5以上/1次元配列のみ)
Dim c() As String = (New ArraySegment(Of String)(a, 1, 3)).ToArray()
WriteLine($"c[] = {String.Join(", ", c)}")
' 出力:c[] = b, c, d
#If DEBUG Then
' コンソールが閉じてしまうのを防ぐ
ReadKey()
#End If
End Sub
End Module
同じ配列内にコピーするとどうなるか?
ArrayクラスのCopyメソッドは、コピー先としてコピー元と同じ配列も指定できる。そのとき、コピー元の範囲とコピー先の範囲が重なっていたらどうなるだろうか? 実際に試してみれば分かるが(次のコード)、コピー元の範囲の全ての要素がいったんバッファーにコピーされてからそれがコピー先の範囲へコピーし直されるような動作になる。
// コピー元の配列
string[] a = { "a", "b", "c", "d", "e", "f", };
// 配列の2番目から3個を、同じ配列の4番目以降にコピーする
Array.Copy(a, 1, a, 3, 3);
WriteLine($"a[] = {string.Join(", ", a)}");
// 出力:a[] = a, b, c, b, c, d
' コピー元の配列
Dim a() As String = {"a", "b", "c", "d", "e", "f"}
' 配列の2番目から3個を、同じ配列の4番目以降にコピーする
Array.Copy(a, 1, a, 3, 3)
WriteLine($"a[] = {String.Join(", ", a)}")
' 出力:a[] = a, b, c, b, c, d
配列の2番目から3個(={"b", "c", "d"})が取り出され、それが4番目(="d"のところ)からの3個に上書きされるような動作になる。
次のような動作ではない。
(1) 配列の2番目から"b"を取り出して4番目(="d"のところ)に書き込む
(2) 配列の3番目から"c"を取り出して5番目(="e"のところ)に書き込む
(3) 配列の4番目((1)で"b"に変わっている)から"b"を取り出して6番目(="f"のところ)に書き込む
もしもこの(1)〜(3)のような動作だったならば、結果が「a[] = a, b, c, b, c, b」となってしまうところだ。
多次元配列をコピーする
ArrayクラスのCopyメソッドは、多次元配列でもコピーできる。ただし、コピー元とコピー先の次元は同じでなければならない。このとき、コピー先の配列のどの要素から書き込み(コピー)が開始されるかは、書き込み開始インデックスとして指定した値、配列の次元数、各次元の要素数により決まる。書き込みを行っている次元の配列から溢れた要素は次に書き込みを行う次元の配列の先頭から順に書き込まれる。次の例では、配列は2次元で、各次元の配列の要素数は3、書き込み開始インデックスは「0」、コピーする要素数は4個となっている。そのため、コピー先となる配列では、1次元目の配列の先頭から順に要素が書き込まれ(3個)、そこから溢れた1個の要素が2次元目の配列の先頭に書き込まれる(出力結果の最後が「, ,」となっているのは2次元目の配列における2個目と3個目の要素にはコピーされないから)。
2次元配列の一部をコピーする例を次のコードに示す。
// コピー元の配列
string[,] d = { { "a", "b", "c", }, { "1", "2", "3", } };
// 2次元配列の2番目から4個を、別の配列にコピーする
// コピー先の配列
//string[] e = new string[d.Length]; // コピー元と次元が違うと、例外になる
string[,] e = new string[d.GetLength(0), d.GetLength(1)];
Array.Copy(d, 1, e, 0, 4);
WriteLine($"e[] = {string.Join(", ", e.Cast<string>())}");
// 出力:e[] = b, c, 1, 2, ,
' コピー元の配列
Dim d(,) As String = {{"a", "b", "c"}, {"1", "2", "3"}}
' 2次元配列の2番目から4個を、別の配列にコピーする
' コピー先の配列
'Dim e(d.Length - 1) As String ' コピー元と次元が違うと、例外になる
Dim e(d.GetLength(0) - 1, d.GetLength(1) - 1) As String
Array.Copy(d, 1, e, 0, 4)
WriteLine($"e[] = {String.Join(", ", e.Cast(Of String)())}")
' 出力:e[] = b, c, 1, 2, ,
応用:複数の配列をマージした配列を作る
ArrayクラスのCopyメソッドを使って、複数の配列を結合した配列を作れる。次のコードは、2つの配列を結合させた1つの配列を作る例だ。1次元配列の場合には、配列のCopyToメソッドやLINQ拡張を使ってもできる。
// 元の配列
string[] a = { "a", "b", "c", };
string[] b = { "1", "2", "3", };
// 結合先の配列
string[] c = new string[a.Length + b.Length];
// 配列aを配列cの先頭にコピーする
Array.Copy(a, c, a.Length);
// その後ろに配列bをコピーする
Array.Copy(b, 0, c, a.Length, b.Length);
WriteLine($"c[] = {string.Join(", ", c)}");
// 出力:c[] = a, b, c, 1, 2, 3
// 1次元配列なら配列のCopyToメソッドを使ってもよい
// (引数が少ないので、分かりやすい)
string[] d = new string[a.Length + b.Length];
a.CopyTo(d, 0);
b.CopyTo(d, a.Length);
WriteLine($"d[] = {string.Join(", ", d)}");
// 出力:d[] = a, b, c, 1, 2, 3
// また、1次元配列ではLINQのConcat拡張メソッドとToArray拡張メソッドでも可能
string[] e = a.Concat(b).ToArray();
WriteLine($"e[] = {string.Join(", ", e)}");
// 出力:e[] = a, b, c, 1, 2, 3
' 元の配列
Dim a() As String = {"a", "b", "c"}
Dim b() As String = {"1", "2", "3"}
' 結合先の配列
Dim c(a.Length + b.Length - 1) As String
' 配列aを配列cの先頭にコピーする
Array.Copy(a, c, a.Length)
' その後ろに配列bをコピーする
Array.Copy(b, 0, c, a.Length, b.Length)
WriteLine($"c[] = {String.Join(", ", c)}")
' 出力:c[] = a, b, c, 1, 2, 3
' 1次元配列なら配列のCopyToメソッドを使ってもよい
' (引数が少ないので、分かりやすい)
Dim d(a.Length + b.Length - 1) As String
a.CopyTo(d, 0)
b.CopyTo(d, a.Length)
WriteLine($"d[] = {String.Join(", ", d)}")
' 出力:d[] = a, b, c, 1, 2, 3
' また、1次元配列ではLINQのConcat拡張メソッドとToArray拡張メソッドでも可能
Dim e() As String = a.Concat(b).ToArray()
WriteLine($"e[] = {String.Join(", ", e)}")
' 出力:e[] = a, b, c, 1, 2, 3
まとめ
配列の一部をコピーするには、ArrayクラスのCopyメソッドを使う。1次元配列の場合には、ArraySegment構造体(.NET 4.5以降)やLINQ拡張も活用できる。
利用可能バージョン:.NET Framework 1.0以降(サンプルコードにはそれ以降の機能/構文も含む)
カテゴリ:クラス・ライブラリ 処理対象:配列
使用ライブラリ:Arrayクラス(System名前空間)
使用ライブラリ:ArraySegment構造体(System名前空間)
使用ライブラリ:Enumerableクラス(System.Linq名前空間)
関連TIPS:配列のコピーを1行でするには?[C#/VB]
関連TIPS:C#で配列を宣言するには?
関連TIPS:VB.NETで配列を宣言するには?
関連TIPS:配列のサイズを変更するには?(Resize編)[2.0のみ、C#、VB]
関連TIPS:配列を独自の順序でソート(並べ替え)するには?
関連TIPS:自作クラスによる配列をソート(並べ替え)するには?(LINQ版)[3.5、C#、VB]
関連TIPS:文字列配列内の文字列を連結するには?
関連TIPS:構文:クラス名を書かずに静的メソッドを呼び出すには?[C# 6.0]
関連TIPS:VB.NETでクラス名を省略してメソッドや定数を利用するには?
関連TIPS:数値を右詰めや0埋めで文字列化するには?[C#、VB]
関連TIPS:Visual Studioでコンソール・アプリケーションのデバッグ実行時にコマンド・プロンプトを閉じないようにするには?
■この記事と関連性の高い別の.NET TIPS
Copyright© Digital Advantage Corp. All Rights Reserved.