目次

キーワード

概要

「複数の数値を入力してその和を求める」とかいうように、複数のデータを一まとめにして扱いたい場合があります。 C# などのプログラミング言語には、 複数のデータを一まとめにするための「配列」というものがあります。

配列
配列

ポイント
  • 配列: 複数のデータをひとまとめに

  • x[n] で、x の n 番目の要素にアクセス

配列がなかったら

まずは、もし複数のデータを一まとめにせずにばらばらに扱おうとするとどうなるか考えてみましょう。 例として、5個の整数を入力して、それらの二乗和を求めることを考えます。 プログラムは以下のようになるでしょう。

int a, b, c, d, e; // 変数を入力したいデータの数だけ用意。

// 値の入力
a = int.Parse(Console.ReadLine());
b = int.Parse(Console.ReadLine());
c = int.Parse(Console.ReadLine());
d = int.Parse(Console.ReadLine());
e = int.Parse(Console.ReadLine());

// 値の計算
int square_sum = a*a + b*b + c*c + d*d + e*e;

// 値の出力
Console.Write("二乗和は {0} です", square_sum);

似たような文が何度も繰り返し出てきています。 これは書くのにも手間がかかりますし、修正が必要になった場合、何箇所も修正する必要が出てきます。 それに、入力するデータの数を5個から10個とかいうように変更したい場合にも、修正が大変になります。

配列を使う

C# には複数のデータを一まとめにするために配列というものが用意されています。 先ほどの例では、5個のデータをa, b, c, d, eという5つの変数に格納していましたが、 配列を使うことでa[0], a[1], a[2], a[3], a[4]というように、番号を振って管理出来ます。

配列は以下のようにして宣言します。 すなわち、 型名に [] を付けることで配列型を作ることができます

型名[] 変数名;

配列は宣言しただけでは利用できず、まずは配列の実体を作成する必要があります。 実体の作成は new というキーワードを用いて以下のようにします。

配列型変数 = new 型名[配列の長さ];

詳しくは「クラス」で説明しますが、 配列型の変数というのは配列を格納するためのただの入れ物で、 配列の実体を作成して変数に格納してやる必要があります。 new はこの実体を作成するための演算子です。

先ほどの例を配列を使って書き直してみましょう。

int[] a = new int[5]; // 長さが5の整数型配列を用意。

// 値の入力
for(int i=0; i<a.Length; ++i) // a.Length は配列 a の長さ。これの例では5。
{
  a[i] = int.Parse(Console.ReadLine());
}

// 値の計算
int square_sum = 0;
for(int i=0; i<a.Length; ++i)
{
  square_sum += a[i]*a[i];
}

// 値の出力
Console.Write("二乗和は {0} です", square_sum);

配列を使うことで、手動で何度も繰り返し書いていた文が1つの for 文にまとまりました。 書く手間は1度ですみますし、修正も1箇所で済みます。 また、入力したいデータの数を変更したい場合にも、最初の1行を修正するだけで済みます。

サンプル
using System;

class ArraySample
{
  static void Main()
  {
    // フィボナッチ数列の20項目までを求める
    int[] sequence = new int[20];

    // 最初の2項を入力
    Console.Write("a1 = ");
    sequence[0] = int.Parse(Console.ReadLine());
    Console.Write("a2 = ");
    sequence[1] = int.Parse(Console.ReadLine());

    // 漸化式を使って20項目までを計算
    for(int i=2; i<sequence.Length; ++i)
    {
      sequence[i] = sequence[i-1] + sequence[i-2];
    }

    // 結果の出力
    Console.Write("{");
    for(int i=0; i<sequence.Length-1; ++i)
    {
      Console.Write(sequence[i] + ", ");
    }
    Console.Write(sequence[sequence.Length-1] + "}");
  }
}
a1 = 2
a2 = 1
{2, 1, 3, 4, 7, 11, 18, 29, 47, 76, 123, 199, 322, 521, 843, 1364, 2207,
 3571, 5778, 9349}

また、配列は以下のようにして宣言時に初期化することも出来ます。

型名[] 変数名 = new 型名[] {値1, 値2, .....};

例えば、1, 3, 5, 7, 9 という初期値を持った int 型配列を作成するには以下のようにします。

int[] a = new int[] {1, 3, 5, 7, 9};

変数宣言と同時に限り、以下のような書き方も可能です。 (new[] を省略できる。)

int[] a = {1, 3, 5, 7, 9};

ちなみに、こういう{}で初期値を与える書き方のことを「初期化子」(initializer)と呼びます。

また、初期化子内の最後には、コンマを付けてもつけなくても構いません。 以下の2行は同じ意味になります。

int[] a = new int[] { 1, 3, 5, 7, 9 };
int[] b = new int[] { 1, 3, 5, 7, 9, };

ソースコード生成など機械的な処理で値を並べる場合には「最後だけ , を消さないといけない」みたいな処理の方が難しいので、末尾コンマを認めています。

Ver. 12

C# 12 からは配列の初期化を以下のように書くことができるようになりました。 これをコレクション式といいます。

int[] a = [1, 3, 5, 7, 9];

{} を使った初期化子との差や、コレクション式のメリットなどは「コレクション式」で説明します。

暗黙的型付け配列

Ver. 3.0

C# 3.0 では、 配列の初期化時の、「new 型名[]」の型名を省略することが可能に成りました。

var a = new[] {1, 3, 5, 7, 9};

配列の型は、{} の中身から推論されます。 この例の場合、{} の中身が int なので、aint[] になります。

範囲アクセス

Ver. 8.0

C# 8.0 で、a[i..j] という書き方で「i番目からj番目の要素を取り出す」というような操作ができるようになりました。

using System;
 
class Program
{
    static void Main()
    {
        var a = new[] { 1, 2, 3, 4, 5 };
 
        // 前後1要素ずつ削ったもの
        var middle = a[1..^1];
 
        // 2, 3, 4 が表示される
        foreach (var x in middle)
        {
            Console.WriteLine(x);
        }
    }
}

詳しくは「インデックス/範囲処理」で説明します。

多次元配列

今までは1次元的に並んだデータを格納するための1次元配列について説明してきました。 しかし、画像の画素などのように、データが多次元的に並んでいる場合もあります。 C# ではそのような多次元データを格納するため、多次元配列が用意されています。

四角い多次元配列

C# の多次元配列は以下のようにして宣言します。 (次節で説明する「配列の配列」と区別するために、「四角い多次元配列」と呼んだりする場合もあります。 単に多次元配列という場合、こちらの四角い多次元配列を指します。)

型名[,] 変数名; // 2次元配列
型名[,,] 変数名; // 3次元配列

1次元配列のときと同じく、new キーワードを用いて配列を作成する必要があります。

変数名 = new 型名[長さ1, 長さ2]; // 2次元配列の場合
変数名 = new 型名[長さ1, 長さ2, 長さ3]; // 3次元配列の場合

また、宣言時に値を初期化する場合には以下のようにします。

型名[,] 変数名 = new 型名[,] {
  {値1-1, 値1-2, .....},
  {値2-1, 値2-2, .....},
  .....
};

例えば、2次元配列を行列に見立てて行列の掛け算を行うプログラムは以下のようになります。

double[,] a = new double[,]{{1, 2}, {2, 1}, {0, 1}}; // 3行2列の行列
double[,] b = new double[,]{{1, 2, 0}, {0, 1, 2}};   // 2行3列の行列
double[,] c = new double[3, 3];                      // 3行3列の行列

for(int i=0; i<a.GetLength(0); ++i) // a.GetLength(0) は a の行数を表す。
{
  for(int j=0; j<b.GetLength(1); ++j) // b.GetLength(1) は b の列数を表す。
  {
    c[i, j] = 0;
    for(int k=0; k<a.GetLength(1); ++k) // a.GetLength(1) は a の列数を表す。
    {
      c[i, j] += a[i, k] * b[k, j];
    }
  }
}

配列の配列

多次元のデータを扱うためには array[x, y] という構文で使用する多次元配列の他に、 「配列の配列」を使う方法もあります。 「配列の配列」とはその名の通り、配列(型名[])をさらに配列にしたもの(型名[][])です。

例として、多次元配列のところで挙げた行列の掛け算を配列の配列を使って書き直すと以下のようになります。

double[][] a = new double[][]{  // 3行2列の行列
  new double[]{1, 2},
  new double[]{2, 1},
  new double[]{0, 1}
};
double[][] b = new double[][]{  // 2行3列の行列
  new double[]{1, 2, 0},
  new double[]{0, 1, 2}
};
double[][] c = new double[3][]; // 3行3列の行列
for(int i=0; i<c.Length; ++i)
  c[i] = new double[3];

for(int i=0; i<a.Length; ++i) // a.Length は a の行数を表す。
{
  for(int j=0; j<b[0].Length; ++j) // b[0].Length は b の列数を表す。
  {
    c[i][j] = 0;
    for(int k=0; k<a[0].Length; ++k) // a[0].Length は a の列数を表す。
    {
      c[i][j] += a[i][k] * b[k][j];
    }
  }
}

「多次元配列」は全ての行の列数が同じになりますが、 「配列の配列」は各列毎に列数が異なっていても構いません。 そのため、「多次元配列」のことを“Rectangular Array”(四角い配列)、 「配列の配列」のことを“Jagged Array” (ぎざぎざ配列)という言うこともあります。

比較

「多次元配列」と「配列の配列」の比較
多次元配列 配列の配列
書き方
int[,] rect =
{
    { 1, 2 },
    { 3, 4 },
};
int[][] jug =
{
    new[] { 1 },
    new[] { 2, 3, 4 },
};
イメージ
利点 * 初期化が楽(new[] が不要) * 無駄にメモリを使わない * 動作が高速 * 行ごとに列数を変えれる

注意: 仕組み上、「多次元配列」の方が「配列の配列」よりも要素の読み書きは速いはずなんですが、 最適化のかかり方次第では速度が逆になるようです。 (.NET Framework の 「IL」 には1次元配列の要素アクセス用の専用命令があって、 下手に多次元配列にするよりも、配列の配列の方がいい命令が出ることがあるようで。)

演習問題

問題1

for 文を使って以下の漸化式の一般項 an を20項目まで求めるプログラムを作成せよ。 ( an を配列で表す。)

an + 2 = 2 an + 1 - 2 an
a0 = 3
a1 = 1

解答例1

using System;

class Exercise
{
  static void Main()
  {
    int[] a = new int[21];
    a[0] = 3;
    a[1] = 1;

    // 数列を求める。
    for (int i = 2; i < a.Length; ++i)
    {
      a[i] = 2 * a[i - 1] - 2 * a[i - 2];
    }

    // 求めた数列を表示。
    for (int i = 0; i < a.Length; ++i)
    {
      Console.Write("{0} ", a[i]);
    }
    Console.Write('\n');
  }
}

問題2

int 型の配列に格納されている値の最大値、最小値および平均値を求めよ。 できれば、配列の長さ n および n 個の整数値をユーザに入力してもらうようにすること。

解答例1

using System;

class Exercise
{
  static void Main()
  {
    // 配列長の入力
    Console.Write("配列の長さ: ");
    int n = int.Parse(Console.ReadLine());

    // 配列の値の入力
    int[] a = new int[n];
    for (int i = 0; i < n; ++i)
    {
      Console.Write("{0}: ", i);
      a[i] = int.Parse(Console.ReadLine());
    }

    // 最大値、最小値、平均値の計算
    int max = int.MinValue;
    int min = int.MaxValue;
    double average = 0;

    for (int i = 0; i < n; ++i)
    {
      if (max < a[i]) max = a[i];
      if (min > a[i]) min = a[i];
      average += a[i];
    }
    average /= n;

    Console.Write(
@"
最大値: {0}
最小値: {1}
平均値: {2}
"
    , max, min, average);
  }
}

問題3

double 型の2次元配列を行列に見立てて、行列の掛け算を行うプログラムを作成せよ。

解答例1

行列の次元は任意だけども、例として2×2行列の場合を示す。

using System;

class Exercise
{
  static void Main()
  {
    double[,] a = new double[,]
    {
      {1, 1},
      {1, 0},
    };
    double[,] b = new double[,]
    {
      {1, 2},
      {3, 4},
    };

    // ここより下は、a, b のサイズが任意の場合でも正しく動作する。
    double[,] c = new double[a.GetLength(0), b.GetLength(1)];

    // a×b を計算
    for (int i = 0; i < a.GetLength(0); ++i)
      for (int j = 0; j < b.GetLength(1); ++j)
        for (int k = 0; k < a.GetLength(1); ++k)
          c[i, j] += a[i, k] * b[k, j];

    // a を表示
    Console.Write("a =\n");
    for (int i = 0; i < a.GetLength(0); ++i)
    {
      for (int j = 0; j < a.GetLength(1); ++j)
        Console.Write("{0, 4} ", a[i, j]);
      Console.Write('\n');
    }

    // b を表示
    Console.Write("b =\n");
    for (int i = 0; i < b.GetLength(0); ++i)
    {
      for (int j = 0; j < b.GetLength(1); ++j)
        Console.Write("{0, 4} ", b[i, j]);
      Console.Write('\n');
    }

    // a×b を表示
    Console.Write("a×b =\n");
    for (int i = 0; i < c.GetLength(0); ++i)
    {
      for (int j = 0; j < c.GetLength(1); ++j)
        Console.Write("{0, 4} ", c[i, j]);
      Console.Write('\n');
    }
  }
}

更新履歴

ブログ