
サンプル・プログラム
2次元配列
番号 | 国語 | 算数 | 理科 | 社会 |
---|---|---|---|---|
1 | 92 | 98 | 74 | 85 |
2 | 85 | 68 | 62 | 65 |
3 | 69 | 87 | 53 | 87 |
4 | 94 | 45 | 57 | 83 |
5 | 81 | 59 | 83 | 88 |
6 | 76 | 42 | 62 | 66 |
7 | 96 | 75 | 81 | 55 |
8 | 100 | 98 | 73 | 47 |
9 | 74 | 75 | 86 | 80 |
10 | 79 | 94 | 71 | 76 |
11 | 76 | 44 | 100 | 67 |
12 | 92 | 83 | 53 | 69 |
$a[1][0] = 92;添字が2つあることから、このような配列を2次元配列と呼ぶ。
$a[1][1] = 98;
$a[1][2] = 74;
$a[1][3] = 85;
Excelとの対比でいえば、1次元目は行、2次元目は列に対応する。

一般論として、添字の組み合わせ(次元数)は自在に増やすことができ、このような配列を多次元配列と呼ぶ。
PHPによる連想配列
PHPは、添字に文字列を使うことができる連想配列という仕組みをもっている。そこで、連想配列を使った2次元配列のプログラム "dt03-12-01.php" を書いてみる。
10: //多次元配列+連想配列
11: $a[1] = array('国語'=>92, '算数'=>98, '理科'=>74, '社会'=>85);
12: $a[2] = array('国語'=>85, '算数'=>68, '理科'=>62, '社会'=>65);
13: $a[3] = array('国語'=>69, '算数'=>87, '理科'=>53, '社会'=>87);
14: $a[4] = array('国語'=>94, '算数'=>45, '理科'=>57, '社会'=>83);
15: $a[5] = array('国語'=>81, '算数'=>59, '理科'=>83, '社会'=>88);
16: $a[6] = array('国語'=>76, '算数'=>42, '理科'=>62, '社会'=>66);
17: $a[7] = array('国語'=>96, '算数'=>75, '理科'=>81, '社会'=>61);
18: $a[8] = array('国語'=>100, '算数'=>98, '理科'=>73, '社会'=>47);
19: $a[9] = array('国語'=>74, '算数'=>75, '理科'=>86, '社会'=>80);
20: $a[10] = array('国語'=>79, '算数'=>94, '理科'=>71, '社会'=>76);
21: $a[11] = array('国語'=>76, '算数'=>44, '理科'=>100, '社会'=>67);
22: $a[12] = array('国語'=>92, '算数'=>83, '理科'=>53, '社会'=>69);
23:
24: //合計
25: $sums['国語'] = array_sum(array_column($a, '国語'));
26: $sums['算数'] = array_sum(array_column($a, '算数'));
27: $sums['理科'] = array_sum(array_column($a, '理科'));
28: $sums['社会'] = array_sum(array_column($a, '社会'));
29:
30: //平均
31: foreach ($sums as $key=>$val) {
32: $avgs[$key] = $sums[$key] / count($a);
33: }
34:
35: //出力
36: printf("\n 番号 国語 算数 理科 社会\n");
37: printf("----------------------------------\n");
38: foreach ($a as $key=>$b) {
39: printf(" %2d", $key); //出席番号
40: foreach ($b as $val) {
41: printf(" %4d", $val); //点数
42: }
43: printf("\n");
44: }
45: printf("----------------------------------\n");
46: printf(" 合計");
47: foreach ($sums as $val) {
48: printf(" %4d", $val); //合計
49: }
50: printf("\n 平均");
51: foreach ($avgs as $val) {
52: printf(" %4.1f", $val); //平均
53: }
54: printf("\n");

ヒアドキュメント
そこで、Excelの表をCSV形式(1行目:ラベル,列:タブ区切り,行:改行区切り)として、そのままヒアドキュメントとしてプログラムに取り込み、配列処理するプログラム "dt03-12-02.php" を書いてみる
11: //データ(1行目:ラベル,列:タブ区切り,行:改行区切り)
12: $data =<<< EOT
13: 番号 国語 算数 理科 社会
14: 1 92 98 74 85
15: 2 85 68 62 65
16: 3 69 87 53 87
17: 4 94 45 57 83
18: 5 81 59 83 88
19: 6 76 42 62 66
20: 7 96 75 81 61
21: 8 100 98 73 47
22: 9 74 75 86 80
23: 10 79 94 71 76
24: 11 76 44 100 67
25: 12 92 83 53 69
26:
27: EOT;
28:
29: /**
30: * CSV形式テキストを読み込んで、多次元連想配列に展開する
31: * @param string $data CSV形式テキスト(列:タブ,行:改行)
32: * @param array $labels ラベルを格納(1行目のテキスト)
33: * @param array $items 要素を格納(多次元連想配列)
34: * @return int 要素数
35: */
36: function parseArray($data, &$labels, &$items) {
37: $rec = preg_split("/\n/ui", $data); //行に分解
38: $cnt = 0;
39: foreach ($rec as $str) {
40: $str = trim($str);
41: if (mb_strlen($str) == 0) continue;
42: $col = preg_split("/\t/ui", $str); //列に分解
43: if ($cnt == 0) { //ラベルを格納
44: foreach ($col as $key=>$val) {
45: $labels[$key] = trim($val);
46: }
47: } else { //要素を格納
48: foreach ($col as $key=>$val) {
49: $items[$cnt][$labels[$key]] = trim($val);
50: }
51: }
52: $cnt++;
53: }
54: return $cnt;
55: }
56:
57: // メイン・プログラム ======================================================
58: //配列へ読み込み
59: $cnt = parseArray($data, $labels, $items);
60:
61: //合計・平均
62: for ($i = 1; $i < count($labels); $i++) {
63: $key = $labels[$i];
64: $sum[$key] = array_sum(array_column($items, $key));
65: $avg[$key] = $sum[$key] / count($items);
66: }
67:
68: //出力
69: printf("\n ");
70: foreach ($labels as $key) {
71: printf("%s ", $key);
72: }
73: printf("\n----------------------------------\n");
74: foreach ($items as $key=>$b) {
75: foreach ($b as $val) {
76: printf(" %4d", $val); //番号・点数
77: }
78: printf("\n");
79: }
80: printf("----------------------------------\n");
81: printf(" 合計");
82: foreach ($sum as $val) {
83: printf(" %4d", $val); //合計
84: }
85: printf("\n 平均");
86: foreach ($avg as $val) {
87: printf(" %4.1f", $val); //平均
88: }
89: printf("\n");
これを多次元連想配列に展開するユーザー関数 parseArray を用意したためプログラムは長くなってしまったが、この関数は再利用が利く。また、ヒアドキュメントではなくても、同じ書式であれば、CSVデータを外部ファイルにもたせて読み込むことも可能である。
実際、Excelで下書きをした点数表を、そのままコピー&ペースとしているだけなので、作業時間へ減らせるし、転記ミスも無くなる。大量のデータを扱うシステムでは、いかにしてオリジナルのデータを手作業で加工することなくコンピュータへ渡せるかという点も重要になってくる。
最初のプログラムのように、連想配列の添字として教科名が入っていることが確認できる。
Array(
[1]=>Array([番号]=>1,[国語]=>92,[算数]=>98,[理科]=>74,[社会]=>85)
[2]=>Array([番号]=>2,[国語]=>85,[算数]=>68,[理科]=>62,[社会]=>65)
[3]=>Array([番号]=>3,[国語]=>69,[算数]=>87,[理科]=>53,[社会]=>87)
[4]=>Array([番号]=>4,[国語]=>94,[算数]=>45,[理科]=>57,[社会]=>83)
[5]=>Array([番号]=>5,[国語]=>81,[算数]=>59,[理科]=>83,[社会]=>88)
[6]=>Array([番号]=>6,[国語]=>76,[算数]=>42,[理科]=>62,[社会]=>66)
[7]=>Array([番号]=>7,[国語]=>96,[算数]=>75,[理科]=>81,[社会]=>61)
[8]=>Array([番号]=>8,[国語]=>100,[算数]=>98,[理科]=>73,[社会]=>47)
[9]=>Array([番号]=>9,[国語]=>74,[算数]=>75,[理科]=>86,[社会]=>80)
[10]=>Array([番号]=>10,[国語]=>79,[算数]=>94,[理科]=>71,[社会]=>76)
[11]=>Array([番号]=>11,[国語]=>76,[算数]=>44,[理科]=>100,[社会]=>67)
[12]=>Array([番号]=>12,[国語]=>92,[算数]=>83,[理科]=>53,[社会]=>69)
)
Pythonによる連想配列
import sys
from statistics import mean
# データ(1行目:ラベル,列:タブ区切り,行:改行区切り)
data = """
番号 国語 算数 理科 社会
1 92 98 74 85
2 85 68 62 65
3 69 87 53 87
4 94 45 57 83
5 81 59 83 88
6 76 42 62 66
7 96 75 81 61
8 100 98 73 47
9 74 75 86 80
10 79 94 71 76
11 76 44 100 67
12 92 83 53 69
""".strip()
"""
* CSV形式テキストを読み込んで、リストに展開する
* @param string data CSV形式テキスト(列:タブ,行:改行)
* @param list labels ラベルを格納(1行目のテキスト)
* @param list items 要素を格納するリスト
* @return int 要素数
"""
def parseList(data, labels, items):
rec = data.split("\n") #行に分解
cnt = 0
for str in rec:
str = str.strip()
col = str.split("\t") #列に分解
if cnt == 0: #ラベルを格納
for key, val in enumerate(col):
labels.append(val.strip())
else: #要素を格納
item = dict() #辞書を用意
for key, val in enumerate(col):
try: #データ属性を判定
item[labels[key]] = int(val)
except ValueError:
try:
item[labels[key]] = float(val)
except ValueError:
item[labels[key]] = val
items.append(item) #リストに加える
cnt = cnt + 1
return(cnt)
# メイン・プログラム ======================================================
# 配列へ読み込み
labels = list() #リストを用意
items = list()
cnt = parseList(data, labels, items)
# 合計・平均
sums = dict() #辞書を用意
avgs = dict()
for key in labels[1:]:
sums[key] = sum([item.get(key) for item in items])
avgs[key] = mean([item.get(key) for item in items])
# 出力
sys.stdout.write("\n ")
for key in labels:
sys.stdout.write("{} ".format(key))
sys.stdout.write("\n----------------------------------\n")
for b in items:
for val in b.values():
sys.stdout.write(" {0:4d}".format(val)) #番号・点数
sys.stdout.write("\n")
sys.stdout.write("----------------------------------\n")
sys.stdout.write(" 合計")
for val in sums.values():
sys.stdout.write(" {0:4d}".format(val)) #合計
sys.stdout.write("\n 平均")
for val in avgs.values():
sys.stdout.write(" {0:4.1f}".format(val)) #平均
sys.stdout.write("\n")
一方で、行方向はキーが不要(行番号があるだけ)なので、リストというデータ構造を用いることにする。詳しいことは「リスト」をご覧いただきたい。
このようにPythonでは、1次元目はリスト、2次元目は辞書という、複合型の多次元配列を構築することが可能である。

items のデータ実体は下記の通りである。
[...] は辞書を、 {...} はリストを意味する。
[
{'番号': 1, '国語': 92, '算数': 98, '理科': 74, '社会': 85},
{'番号': 2, '国語': 85, '算数': 68, '理科': 62, '社会': 65},
{'番号': 3, '国語': 69, '算数': 87, '理科': 53, '社会': 87},
{'番号': 4, '国語': 94, '算数': 45, '理科': 57, '社会': 83},
{'番号': 5, '国語': 81, '算数': 59, '理科': 83, '社会': 88},
{'番号': 6, '国語': 76, '算数': 42, '理科': 62, '社会': 66},
{'番号': 7, '国語': 96, '算数': 75, '理科': 81, '社会': 61},
{'番号': 8, '国語': 100, '算数': 98, '理科': 73, '社会': 47},
{'番号': 9, '国語': 74, '算数': 75, '理科': 86, '社会': 80},
{'番号': 10, '国語': 79, '算数': 94, '理科': 71, '社会': 76},
{'番号': 11, '国語': 76, '算数': 44, '理科': 100, '社会': 67},
{'番号': 12, '国語': 92, '算数': 83, '理科': 53, '社会': 69}
]
C++による連想配列
PHPプログラム "dt03-12-02.php" を移植するにあたり、1次元目(行方向)は動的配列クラス vector を、2次元目(列方向)は連想配列クラス map を組み合わせた多次元配列をデータ構造にしたプログラムが "dt03-12-04.cpp" である。
20: //データ(1行目:ラベル,列:タブ区切り,行:改行区切り)
21: char data[] = R"(
22: 番号 国語 算数 理科 社会
23: 1 92 98 74 85
24: 2 85 68 62 65
25: 3 69 87 53 87
26: 4 94 45 57 83
27: 5 81 59 83 88
28: 6 76 42 62 66
29: 7 96 75 81 61
30: 8 100 98 73 47
31: 9 74 75 86 80
32: 10 79 94 71 76
33: 11 76 44 100 67
34: 12 92 83 53 69
35: )";
37: /**
38: * 文字列をデリミタによって分解する
39: * @param string input 文字列
40: * @param char delimiter デリミタ
41: * @return vector<string> 分解後の部分文字列
42: **/
43: vector<string> split(const string &input, char delimiter) {
44: istringstream stream(input);
45: string field;
46: vector<string> result;
47: while (getline(stream, field, delimiter)) {
48: result.push_back(field);
49: }
50: return result;
51: }
52:
53: /**
54: * CSV形式テキストを読み込んで、2次元連想配列に展開する
55: * @param char *data CSV形式テキスト(列:タブ,行:改行)
56: * @param vector labels ラベルを格納(1行目のテキスト)
57: * @param vector<map> items 要素を格納する配列
58: * @return int 要素数
59: **/
60: int parseList(char *data, vector<string> &labels, vector<map<string,int>> &items) {
61: map<string,int> *item;
62: int cnt = 0;
63: for (string &rec : split(data, '\n')) { //行に分解
64: if (rec.size() > 0) {
65: if (cnt == 0) { //ラベルを格納
66: for (string &col : split(rec, '\t')) { //列に分解
67: labels.push_back(col);
68: }
69: } else { //要素を格納
70: item = new map<string,int>;
71: int i = 0;
72: for (string &col : split(rec, '\t')) { //列に分解
73: item->insert(make_pair(labels[i], stoi(col)));
74: i++;
75: }
76: items.push_back(*item); //リストに加える
77: }
78: cnt = cnt + 1;
79: }
80: }
81: return(cnt - 1); //ラベル行を除く
82: }

関数 parseList によってデータを分解し、列方向の要素(2次元目)はクラス map に、このmapを行方向(1次元目)に動的配列クラス vector を使って格納してゆく。
map<string,int> は連想配列のキーが文字列(番号,国語,算数‥‥)であることを、値が整数(点数)であることを意味する。
また、vector<map<string,int>> は、2次元目が map<string,int> で、1次元目が vector であることを意味するデータ構造である。
85: int main() {
86: vector<string> labels;
87: vector<map<string,int>> items;
88:
89: //データ読み込み
90: int cnt = parseList(data, labels, items);
91:
92: map<string,int> sums;
93: map<string,float> avgs;
94:
95: //合計計算
96: for (int i = 0; i < cnt; i++) {
97: for (string key : labels) {
98: if (key != "番号") {
99: sums[key] += items[i][key];
100: }
101: }
102: }
103: //平均計算
104: for (string key : labels) {
105: avgs[key] = (float)sums[key] / cnt;
106: }
107:
108: //出力
109: cout << endl << " ";
110: for (string a : labels) {
111: cout << a << " ";
112: }
113: cout << endl << "----------------------------------" << endl;
114: for (int i = 0; i < cnt; i++) {
115: for (string key : labels) {
116: cout << " " << setw(4) << items[i][key]; //番号・点数
117: }
118: cout << endl;
119: }
120: cout << "----------------------------------" << endl;
121: cout << " 合計";
122: for (string key : labels) {
123: if (key != "番号") {
124: cout << " " << setw(4) << sums[key];
125: }
126: }
127: cout << endl << " 平均";
128: for (string key : labels) {
129: if (key != "番号") {
130: cout << " " << fixed << setprecision(1) << avgs[key];
131: }
132: }
133: cout << endl;
134:
135: return (0);
136: }