2次元配列の宣言

解決


marllbolo  2012-06-04 19:20:12  No: 143327  IP: 192.*.*.*

VB2010を使っております。

2次元配列で困っております。

たとえば

Dim strArray()() As String



Dim strArray(,) As String

の違いはあるのでしょうか?

実は、某サイトから

    Private Function Test(ByVal fileName As String)

        ' StreamReader の新しいインスタンスを生成する
        Dim cReader As New System.IO.StreamReader(fileName, System.Text.Encoding.Default)
        Dim textLines As New List(Of String())

        ' 読み込んだ結果をすべて格納するための変数を宣言する
        Dim stResult As String = String.Empty

        ' 読み込みできる文字がなくなるまで繰り返す
        While (cReader.Peek() >= 0)

            ' ファイルを 1 行ずつ読み込む
            Dim stBuffer As String = cReader.ReadLine()

            ' 読み込んだものを追加で格納する
            Dim cols() As String
            cols = Split(stBuffer, ",")
            textLines.Add(cols)

        End While

        cReader.Close()

        Dim strArr()() As String
        strArr = textLines.ToArray
        Return strArr

    End Function

というソースを引っ張ってきたのですが、
Dim strArray()() As String
には格納できて、
Dim strArray(,) As String
には格納できません。

何か勘違いしているところがあるのでしょうか。

編集 削除
魔界の仮面弁士  2012-06-04 22:46:16  No: 143328  IP: 192.*.*.*

> Dim strArray()() As String
> と
> Dim strArray(,) As String
> の違いはあるのでしょうか?

違うものであることは、すでに気づいておられるのですよね。実際、
> には格納できません。
という状況になっているわけですし。


まず、後者は二次元配列ですが、前者はそうではありません。
専門用語では「ジャグ配列」と呼ばれる種類のものです。


後者は二次元配列であり、その個々の要素は「String」という型ですが、
前者は一次元配列であり、その個々の要素は「String の一次元配列」です。

配列の配列、それがジャグ配列(jagged arrays)です。
http://msdn.microsoft.com/ja-jp/library/hkhhsz9t.aspx


たとえば、こんなコードがあるとします。a はジャグ配列、b は二次元配列です。

Dim a()() As Integer = {
    New Integer() {1, 2, 3},
    New Integer() {4, 5, 6, 7},
    New Integer() {8, 9}
}
MsgBox(TypeName(a))       'Integer()()
MsgBox(TypeName(a(0)))    'Integer()
MsgBox(TypeName(a(1)(2))) 'Integer

Dim b(,) As Integer = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
}
MsgBox(TypeName(b))       'Integer(,)
MsgBox(TypeName(b(0, 0))) 'Integer


a の方は、a(0) と a(1) で要素数が異なっていますよね。
一方 b は、b は 3x3 の二次元配列です。仮に b を

Dim b(,) As Integer = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9, 10}
}
とした場合、四角形な二次元配列にならず、エラーとなります。
3x4 となるよう
Dim b(,) As Integer = {
    {1, 2, 3, 0 },
    {4, 5, 6, 0},
    {7, 8, 9, 10}
}
のようにする分には OK ですけれどね。これが両者の違いです。



そしてジャグ配列は、単に「配列の配列」というだけですので、
下記のようなパターンもアリです。

Dim a()() As Integer      '1次元配列を格納した1次元配列
Dim b(,)() As Integer     '1次元配列を格納した2次元配列
Dim c()(,) As Integer     '2次元配列を格納した1次元配列
Dim d(,)(,) As Integer    '2次元配列を格納した2次元配列

編集 削除
marllbolo  2012-06-05 00:53:16  No: 143329  IP: 192.*.*.*

そうなんですね。

Dim strArray(,) As String

に格納できる、Function関数など何かございますでしょうか。CSVファイルから読み込むものなんですけど。

編集 削除
魔界の仮面弁士  2012-06-05 11:11:54  No: 143330  IP: 192.*.*.*

ジャグ配列のままだと、何か都合が悪いのでしょうか?

二次元配列だと、行数や列数を変化させる場合に手間がかかりそうですし、
特に理由がなければ、今のコードのままでも問題ないように思います。

格納済みの要素にアクセスするなら、
  「boxDimArray(rowIndex, colIndex)」が
  「jaggedArray(rowIndex)(colIndex)」
という表記に変わるだけですし、行/列番号を指定して扱う分には大差はないかと。


> Function関数など何かございますでしょうか。
COM Interop を伴うのでお奨めはしませんが(個人的には非推奨)、一応、
ADODB.Recordset の GetRows メソッドが二次元配列を返すようになっています。



> CSVファイルから読み込むものなんですけど。
二次元配列に格納するには、csvの行数/列数をあらかじめ調べねばならないため、
いったん DataTable やジャグ配列に蓄えて、あとで変換する実装が多いかと思います。
(列数は事前に分かることもあるが、行数は可変にせざるを得ない)

列数があらかじめ分かっている場合は、行と列の順序を Dim box(列数, 行数) As String の
順にしておき、行数に応じて ReDim Preserve ステートメントで調整する実装もありますが、
それなら、ジャグ配列から変換した方が楽でしょうね。



> Dim strArray(,) As String
どうしても二次元配列が必要ということであれば…。

既に完成しているジャグ配列から、インデックスの最大値(あるいは要素数)を調べる方法は分かりますか?
それができれば、そこから二次元配列に並び替えるのは容易ですよね。

インデックスの最大値は、UBound 関数または GetUpperBound メソッドで調べられます。
要素数なら、Length プロパティです。

'行番号/列番号の最大値を調べる。
'CSV の列数が事前に分かっているなら、colMaxIndex の方は固定値でも OK。
Dim rowMaxIndex As Integer = jaggedArray.GetUpperBound(0)
Dim colMaxIndex As Integer = Aggregate row In jaggedArray Into Max(row.GetUpperBound(0))

'二次元配列として、必要な行数/列数を確保する
Dim boxDimArray(rowMaxIndex, colMaxIndex) As String

'ジャグ配列から二次元配列に転記する
For rowIndex As Integer = 0 To rowMaxIndex
    For colIndex As Integer = 0 To colMaxIndex
        boxDimArray(rowIndex, colIndex) = jaggedArray(rowIndex)(colIndex)
    Next
Next
'=========================================================


あるいは、ジャグ配列にしてから変換するのではなく、
Split や TextFieldParser で切り出すたびに、
二次元配列に格納していっても良いかと。

'csvファイルを、行ごとに区切った1次元配列にする
Dim rows() As String = System.IO.File.ReadAllLines(
    "C:\temp\test.csv",
    System.Text.Encoding.GetEncoding("Shift_JIS"))

'空行を読み捨てる。
rows = rows.Where(Function(row) Not String.IsNullOrWhiteSpace(row)).ToArray()

'行数を調べる
Dim rowMaxIndex As Integer = rows.GetUpperBound(0)

'先頭行の「,」の数から、列数を調べる
Dim cols() As String = rows.First().Split(",")
Dim colMaxIndex As Integer = cols.GetUpperBound(0)

'行数/列数分の二次元配列を確保する
Dim strArray(rowMaxIndex, colMaxIndex) As String

'確保した二次元配列に、csvデータを格納していく
For rowIndex As Integer = 0 To rowMaxIndex
    cols = rows(rowIndex).Split(",")
    For colIndex As Integer = 0 To colMaxIndex
        strArray(rowIndex, colIndex) = cols(colIndex)
    Next
Next

編集 削除
marllbolo  2012-06-05 13:02:53  No: 143331  IP: 192.*.*.*

魔界の仮面弁士さま

2つのケース、まさにどちらも使いたかったケースです。

'ジャグ配列から二次元配列に転記する
For rowIndex As Integer = 0 To rowMaxIndex
    For colIndex As Integer = 0 To colMaxIndex
        boxDimArray(rowIndex, colIndex) = jaggedArray(rowIndex)(colIndex)
    Next
Next

'確保した二次元配列に、csvデータを格納していく
For rowIndex As Integer = 0 To rowMaxIndex
    cols = rows(rowIndex).Split(",")
    For colIndex As Integer = 0 To colMaxIndex
        strArray(rowIndex, colIndex) = cols(colIndex)
    Next

上記のループして配列に格納、まさにこのイメージがほしかったのです。なんかいいすぎかもしれませんが、体から悶々としたものがスッキリしました。

編集 削除