【C#】多次元配列とジャグ配列(2次元配列)のサイズ(長さ)、相互変換など 
2016/12/04 Sun [edit]
Java では2次元配列はジャグ配列になるのだが、C#には「多次元配列」と「ジャグ配列」の選択肢がある。使う時は微妙に概念やプロパティなどが違うので、簡単な図解で理解し、相互変換などをやってみよう。
■概念・サイズなどのプロパティ
「多次元配列とジャグ配列の違いは?」を超簡単に説明すると「多次元配列はデータを格子状に並べた配列」で「ジャグ配列は個々の配列を更に並べた配列(入れ子の配列)」でいいと思う。そんな感じで理解してしまえば、「Length」や「GetLength()」の使い方がよくわかる。以下にそれぞれの違いを図解してみよう。
■多次元配列とは
とりあえず「データを格子状に並べた配列」と考えれば良い。図解にすると以下のようになる。

●多次元配列
using System;
using System.Collections.Generic;
using System.Text;
public static void Main(){
//多次元配列
int[,] grid = {
{ 0, 1, 2, 3, 4},
{ 5, 6, 7, 8, 9},
{10, 11, 12, 13, 14},
};
Console.WriteLine(ToString(grid)); //デバッグ用関数(※下記を参照)
Console.WriteLine("Length = " + grid.Length);
Console.WriteLine("GetLength(0) = " + grid.GetLength(0));
Console.WriteLine("GetLength(1) = " + grid.GetLength(1));
Console.WriteLine("grid[2, 3] = " + grid[2, 3]);
}
[5, 6, 7, 8, 9],
[10, 11, 12, 13, 14]]
Length = 15
GetLength(0) = 3
GetLength(1) = 5
grid[2, 3] = 13
(※) ToString(T[,]) は下記を参照
各次元のサイズは、
GetLength(次元数)
になる。例では2次元配列になっているが、3次元配列なら GetLength(0~2) のようになる。
そして「Lengthは全体の要素数」になるので注意しよう。ひとつづきのデータが格子状に並んでいるだけと考えればわかりやすいだろう。3次元以上になったときも同じように考えれば良い。ちなみに「foreach」で列挙すれば図のような並びで出てくる。
foreach (int i in grid) Console.Write(i + " ");
また、実際にメモリ上でもひとつづきに領域が確保されており、ポインタで連続処理するのに利用することもできる。そういった処理の場合はジャグ配列より多次元配列の方が良いだろう。以下に参考URLを載せておこう。
(参考)
・多次元配列の速度比較
・C#のジャグ配列・多次元配列をpin_ptrでC++に渡せるか?
■ジャグ配列とは
とりあえず「個々の配列を更に並べた配列(入れ子の配列)」と考えれば良い。図解にすると以下のようになる。

●ジャグ配列
using System;
using System.Collections.Generic;
using System.Text;
public static void Main(){
//ジャグ配列
int[][] jag = {
new int[] { 0, 1, 2, 3, 4},
new int[] { 5, 6, 7},
new int[] {10, 11, 12, 13},
};
Console.WriteLine(ToString(jag)); //デバッグ用関数(※下記を参照)
Console.WriteLine("Length = " + jag.Length);
Console.WriteLine("GetLength(0) = " + jag.GetLength(0)); //= jag.Length
Console.WriteLine("jag[0].Length = " + jag[0].Length);
Console.WriteLine("jag[1].Length = " + jag[1].Length);
Console.WriteLine("jag[2].Length = " + jag[2].Length);
Console.WriteLine("jag[2][3] = " + jag[2][3]);
}
[5, 6, 7],
[10, 11, 12, 13]]
Length = 3
GetLength(0) = 3
jag[0].Length = 5
jag[1].Length = 3
jag[2].Length = 4
jag[2][3] = 13
(※) ToString(T[][]) は下記を参照
次にジャグ配列だが、図のように個々の配列が、更にまとめられて1つの配列にされていると考えれば良い。なので多次元配列と違って個々の配列の長さは自由になる(Length はそれぞれのサイズになる)。なので例えば「隣接リスト」などに使えばメモリの節約にもなる(隣接行列ならどちらでも良い)。メモリ上でも配列ごとに違う領域に割り当てられると考えて良いだろう(システムが勝手に割り当てる)。例では1次元配列が入れ子になっているが、親の次元はオブジェクトの参照を要素としている(この例の場合は各配列の頭[0])ので、中身は何でも良い(型が邪魔なら object 配列などにすれば良い→ボックス化のようになる)。
■多次元配列→ジャグ配列 変換
LINQ を使っても良いのかも知れないが、とりあえず今回は概念の図をそのままコーディングするという感じで、ついでにジェネリクスで書いておこう。こういう書き方はダサいかも知れないが(笑)、用途に合わせて改造しやすい利点がある。引数などを追加したバージョンも作りやすいしね。他の言語にも移植しやすい。ちなみに Java ではジェネリクスで new はできないので移植する際には注意しよう。
using System;
using System.Collections.Generic;
using System.Text;
/**
* 多次元配列をジャグ配列に変換
*/
public static T[][] ToJaggedArray<T>(T[,] arr)
{
int row = arr.GetLength(0);
int col = arr.GetLength(1);
T[][] jag = new T[row][];
for (int i = 0; i < row; i++)
{
jag[i] = new T[col];
for (int j = 0; j < col; j++)
{
jag[i][j] = arr[i, j];
}
}
return jag;
}
//メインでは...
int[,] grid = {
{ 0, 1, 2, 3, 4},
{ 5, 6, 7, 8, 9},
{10, 11, 12, 13, 14},
};
int[][] jag = ToJaggedArray(grid);
Console.WriteLine(ToString(jag)); //デバッグ用関数(※下記を参照)
Console.WriteLine("Length = " + jag.Length);
Console.WriteLine("GetLength(0) = " + jag.GetLength(0)); //= jag.Length
Console.WriteLine("jag[0].Length = " + jag[0].Length);
Console.WriteLine("jag[1].Length = " + jag[1].Length);
Console.WriteLine("jag[2].Length = " + jag[2].Length);
[5, 6, 7, 8, 9],
[10, 11, 12, 13, 14]]
Length = 3
GetLength(0) = 3
jag[0].Length = 5
jag[1].Length = 5
jag[2].Length = 5
(※) ToString(T[][]) は下記を参照
■ジャグ配列→多次元配列 変換
using System;
using System.Collections.Generic;
using System.Text;
/**
* ジャグ配列を多次元配列に変換
* col : 列数
*/
public static T[,] ToDimensionalArray<T>(T[][] arr, int col)
{
int row = arr.Length;
T[,] dim = new T[row, col];
for (int i = 0; i < row; i++)
{
for (int j = 0; j < arr[i].Length && j < col; j++)
{
dim[i, j] = arr[i][j];
}
}
return dim;
}
//最大の col(列数)で多次元配列に変換
public static T[,] ToDimensionalArray<T>(T[][] arr)
{
int row = arr.Length;
int col = 0;
//最大を取得
for (int i = 0; i < row; i++)
{
col = Math.Max(col, arr[i].Length);
}
return ToDimensionalArray(arr, col);
}
//メインでは...
int[][] jag = {
new int[] { 0, 1, 2, 3, 4},
new int[] { 5, 6, 7, 8, 9},
new int[] {10, 11, 12, 13, 14},
};
int[,] grid = ToDimensionalArray(jag);
Console.WriteLine(ToString(grid)); //デバッグ用関数(※下記を参照)
Console.WriteLine("Length = " + grid.Length);
Console.WriteLine("GetLength(0) = " + grid.GetLength(0));
Console.WriteLine("GetLength(1) = " + grid.GetLength(1));
[5, 6, 7, 8, 9],
[10, 11, 12, 13, 14]]
Length = 15
GetLength(0) = 3
GetLength(1) = 5
(※) ToString(T[,]) は下記を参照
引数を1つとる関数の方はオーバーロード用で、ジャグ配列は個々の長さが違う可能性があるため、列の最大を取得している。長さが違う場合、列の最大に満たない要素は型の既定値で初期化される。作りたい多次元配列の列数がわかっている場合は、引数を2つとる関数の方の col に具体的な値を入れた方が速い。列の長さが同じとわかってるなら以下のようにしても良い。
int[,] grid = ToDimensionalArray(jag, jag[0].Length);
■文字列化
これはどちらかというとデバッグ用なのだが、よく使うので書いておこう。ただ配列の中身を確認するためのものだ。
using System;
using System.Collections.Generic;
using System.Text;
//文字列変換用
static StringBuilder sb = new StringBuilder(1024);
/**
* 2次元配列の文字列化(多次元配列)
*/
public static string ToString<T>(T[,] arr, string sep = ", ", string rowSep = "\n",
string start_bracket = "[", string end_bracket = "]")
{
sb.Length = 0;
sb.Append(start_bracket);
for (int i = 0; i < arr.GetLength(0); i++) {
if (i > 0) {
sb.Append(sep);
sb.Append(rowSep);
}
sb.Append(start_bracket);
for (int j = 0; j < arr.GetLength(1); j++)
{
if (j > 0)
{
sb.Append(sep);
}
sb.Append(arr[i, j]);
}
sb.Append(end_bracket);
}
sb.Append(end_bracket);
return sb.ToString();
}
/**
* 2次元配列の文字列化(ジャグ配列)
* ※Totring<T>(T[])と曖昧エラーとなるので注意
*/
public static string ToString<T>(T[][] arr, string sep = ", ", string rowSep = "\n",
string start_bracket = "[", string end_bracket = "]")
{
sb.Length = 0;
sb.Append(start_bracket);
for (int i = 0; i < arr.Length; i++) {
if (i > 0) {
sb.Append(sep);
sb.Append(rowSep);
}
sb.Append(start_bracket);
for (int j = 0; j < arr[i].Length; j++)
{
if (j > 0)
{
sb.Append(sep);
}
sb.Append(arr[i][j]);
}
sb.Append(end_bracket);
}
sb.Append(end_bracket);
return sb.ToString();
}
1つだけ注意点は、1次元配列用に「ToString<T>(T[] arr, ~)」を新たに作った場合、ジャグ配列の「ToString<T>(T[][] arr, ~)」と曖昧エラー(「The call is ambiguous between the following methods or properties: ~」)が出るかも知れない。これは T を int として int[][] と解釈、または、T を int[] として int[][] とも解釈できるからだ。その場合は関数をリネームなどして別々に使えば良い。
(関連記事)
【C#】2次元配列(ジャグ配列・多次元配列)のソート
【C#】連想配列(Dictionary)を値 or キーでソート
- 関連記事
-
-
【C#】配列やコレクションの IsNullOrEmpty
-
【C#】最大公約数を求める/分数の約分をする(ユークリッドの互除法)
-
【C#】多次元配列とジャグ配列(2次元配列)のサイズ(長さ)、相互変換など
-
【C#】文字列 → float (浮動小数点) 変換でエラーが出るときは…
-
【C#】最小公倍数を求める(ユークリッドの互除法)
-
トラックバック
トラックバックURL
→http://fantom1x.blog130.fc2.com/tb.php/231-2df80892
この記事にトラックバックする(FC2ブログユーザー)
| h o m e |