なでしこの配列

C言語などの配列とは大分異なり、なでしこの配列は自由にサイズを拡張することができます。また配列の各要素にも任意の型の値を入れることができます。よく配列の喩えとしてロッカーが用いられますが、ロッカーだとどのボックスも同じサイズです。そこで色んなサイズの段ボール箱がいくつも連なっているイメージをするといいでしょう。

バラバラに何でも入れられるとは言え、大抵の場合配列の中には同じ型の値を入れますね。例えば、要素が数値である配列など。話を進めやすくするために、こういった配列を「〜の配列」と呼びましょう(例:数値の配列)。

さて、何でも入れられるということはもちろん配列も入れられるということです。配列Aの要素が全て配列である(つまり配列の配列)ならば、その配列は少なくとも2次元配列[*1]です。ところがこのことについてきちんと理解できていないと、2次元配列を用いていてイメージではこういって欲しいのに何故かそうならないという現象がよく起こります。

CSV文字列

CSVとは、Comma Separated Valuesの略で、カンマ区切り形式のテキストのことです。なでしこでは、2次元配列CSV文字列を互いに変換することができます。あまり意識していないと、あたかもCSV文字列2次元配列であり、2次元配列CSV文字列であるかのように錯覚してしまいますが、両者は全く異なるものです。

2次元配列CSV文字列が同じものであるかのように錯覚してしまう原因として、暗黙の型変換という仕様があります。命令に型の異なる引数を渡すと、正しい型へと暗黙のうちに変換されて処理されるのです。例えば以下の例を見てください。

A=「a,1
b,0」
Aの変数型確認して表示。#-> 文字列
Aの1を表数値ソート。
Aの変数型確認して表示。#-> 配列
Aを表示。
Aの変数型確認して表示。#-> 配列

このように、最初変数ACSV文字列ですが、破壊的[*2]な命令である表数値ソートを実行したことで、変数A文字列の2次元配列に暗黙のうちに変換されてしまいました。さらに、"Aを表示"などの文字列に対する操作を行うと、再び暗黙のうちに内部で配列から文字列へ変換され、CSV文字列が表示されます。そのため、あたかもCSV文字列がそのままソートされ結果が表示されたように見えてしまうのです。ただし、「表示」命令は破壊的ではないので、変数Aの型は配列のままです。

暗黙の型変換は非常に便利な仕組みですが、間違ったプログラムをしないためにも、明示的に変換することで両者の違いを意識する方がいいでしょう。CSV取得CSV文字列文字列の2次元配列へ変換する命令、表CSV変換配列CSV文字列へ変換する命令です[*3]。いずれも非破壊的命令なので、元々の変数を上書きしません。

次に反復構文を使うときに起きがちなカンチガイです。CSV文字列2次元配列を同一視していると、反復構文を用いた時に大きなミスをする可能性があります。

反復構文は配列の各要素に対して処理をするときに便利な構文ですが、反復1次元配列に対する構文なので、文字列が与えられた場合はその文字列を「改行で区切」って暗黙的に1次元配列に変換します。そのためCSV文字列を与えても、先ほどの例のように2次元配列には変換されず、各「対象」は「1行のCSV文字列」になります。そのため、これを配列だと思って「対象[1]」などと書いても、1列目の値を得ることはできません

2次元配列をきちんと配列の配列として理解出来れば、例えば数値の配列の配列反復したときの「対象」は数値の配列になるということが分かるはずです(反復構文は1次元配列の各要素に対して繰り返す構文だから)。自分でどういう配列の配列を作ったのか把握できていれば、命令を使った後に得られるものが何なのかということも予想できますよね[*4]

数値の配列

そして間違いとまでは言わないものの、恐らく最も多い勘違いが、数値の配列でしょう。少し問題を出してみます。次の変数の型をそれぞれ答えてください。ただし、配列型ならば、何の配列なのか「文字列の配列」のように具体的に答えてください。

  1. 問1=「1{~}2{~}3」
  2. 問2とは配列=「1{~}2{~}3」
  3. 問3=「1,2{~}3,4」
  4. 問4=「1,2{~}3,4」を0で表数値ソート

分かりましたか?上で解説したように、1と3はただの「文字列」です。配列に対する操作を行った時に初めて配列へ暗黙的に変換されるわけです。これは大丈夫ですね。しかし、問2・問4はどちらも「文字列の配列の配列」です。なぜ問2は「整数の配列」ではないのでしょうか?また、なぜ問4は「整数の配列の配列」ではないのでしょうか?

まず2について説明します。変数宣言文初期値代入を行うと、代入される値は強制的に宣言型へ変換されます。実は配列型で宣言して文字列を初期値として代入しようとすると、CSV取得されるのです。つまり、「1{~}2{~}3」は3行1列のCSV文字列から3行1列の2次元配列に変換されるのです。実は上のCSV取得の所でも説明できちんと書いたですが、CSV取得CSV文字列文字列の配列の配列に変換する命令です[*5]。都合良く整数の2次元配列や、文字列の三次元配列へ変換してくれることはありません。

次に4について解説します。表数値ソートは要素を数として扱い破壊的に並べ替える命令ですが、破壊するのはあくまで配列としての構造だけです。確かにソートする際に一時的に数値変換[*6]して比較しますが、元の要素までは壊しません。従って、問2と同様に文字列の2次元配列に暗黙的に変換されてから数値比較によって表の行の順序が入れ替えられ、文字列の2次元配列が結果として返されます。

このように、普通に配列を作るとどうしても文字列の配列になってしまいます。そのため、数値の配列を作るためには、個々の要素に数値リテラルとして代入する(#1)か、明示的に個々の要素を数値変換して自己代入する(#2)しかありません。

#1
Aとは配列
A[0]=1
A[1]=3.14
#2
Bとは配列="1{~}2{~}3"
Bを反復、B[回数-1]=TOINT(対象)

注釈

*1
ある配列の全ての子要素が配列で、さらにその孫要素も全て配列ならば、その配列は少なくとも3次元配列ということになります。
*2
ある命令が破壊的であるというのは、その命令に参照渡しの引数があり、参照渡しした変数が書き換えられる(可能性がある)ことを意味します。なでしこの場合、配列系・表系の命令に破壊的命令が多く、それ以外の命令はほとんど非破壊的命令です。一部の言語では、破壊的命令であることを明示するために名前に"!"をつける習慣もありますが、なでしこでは関数名に"!"を用いることはできません。
*3
表CSV変換は任意次元の配列を文字列に変換することができますが、CSV取得で得られる配列は必ず文字列の2次元配列です。この弊害として例えば、4次元配列を表CSV変換してそれを再びCSV取得すると、CSV文字列の2次元配列になってしまい、厳密には元と異なるデータになってしまいます。
*4
もちろんそのためには、命令の引数と返り値の型も知らないといけませんが、ほとんどの命令は説明文を読めば引数と返り値が何であるか分かるようになっています。
*5
1や3のような文字列を「問1[1]」のように配列として扱う場合も同様にCSV文字列から2次元配列へ暗黙の型変換が起こります。
*6
そもそも、変数を破壊的に数値変換する命令が実はなでしこには用意されていません。そのため変数を数値変換する場合は、数値変換したものを明示的に代入しなければいけません。なお、数値変換の命令には、整数変換(TOINTと同じ)と実数変換(TOFLOATと同じ)の2種類があります。