[リストへもどる]   [VBレスキュー(花ちゃん)]
一括表示

投稿時間:2005/07/05(Tue) 07:57
投稿者名:はこ
Eメール:
URL :
タイトル:
動的二次元配列
CSV形式のファイルからデータを読込み、Excelに帳票出力させようとしています。

こちらの掲示板の過去ログを参考にさせていただいたところ(記事No.3670)、
Excelに出力するデータを二次元配列に格納して、一気に出力すると高速に
処理できる、とのことでしたので、CSVファイルから読込んだデータを一度、
二次元配列に格納しようとしています。

CSVファイルの行数が何行あるかわからないため、一行読込むたびに二次元配列を
再定義したらよいのではないかと思い、

Dim fnum            As Integer
Dim buf             As String
Dim csv_data()      As String
Dim output_data()   As Variant
Dim cnt             As Long
Dim i               As Integer

Do While Not EOF(fnum)
    Line Input #fnum, buf
    csv_data = Split("")
    csv_data = Split(buf, ",")
    If UBound(csv_data) <> -1 Then
        ReDim Preserve output_data(cnt, UBound(csv_data)) As Variant
        For i = 0 To UBound(csv_data)
            output_data(cnt, i) = csv_data(i)
        Next
        cnt = cnt + 1
    End If
Loop

このようにしてみたところ、1回目のReDimは通るのですが、2回目にReDimしようとすると、
「インデックスが有効範囲にありません。」とエラーになってしまいます。

二次元配列の動的定義の仕方からして間違っているような気がするのですが、
よい解決方法がありましたら、ご教授いただけないでしょうか?

よろしくお願いします。

投稿時間:2005/07/05(Tue) 08:19
投稿者名:ガッ
Eメール:
URL :
タイトル:
Re: 動的二次元配列
> CSV形式のファイルからデータを読込み、Excelに帳票出力させようとしています。
>
> こちらの掲示板の過去ログを参考にさせていただいたところ(記事No.3670)、
> Excelに出力するデータを二次元配列に格納して、一気に出力すると高速に
> 処理できる、とのことでしたので、CSVファイルから読込んだデータを一度、
> 二次元配列に格納しようとしています。
>
> CSVファイルの行数が何行あるかわからないため、一行読込むたびに二次元配列を
> 再定義したらよいのではないかと思い、
>
> Dim fnum            As Integer
> Dim buf             As String
> Dim csv_data()      As String
> Dim output_data()   As Variant
> Dim cnt             As Long
> Dim i               As Integer
>
> Do While Not EOF(fnum)
>     Line Input #fnum, buf
>     csv_data = Split("")
>     csv_data = Split(buf, ",")
>     If UBound(csv_data) <> -1 Then
>         ReDim Preserve output_data(cnt, UBound(csv_data)) As Variant
>         For i = 0 To UBound(csv_data)
>             output_data(cnt, i) = csv_data(i)
>         Next
>         cnt = cnt + 1
>     End If
> Loop
>
> このようにしてみたところ、1回目のReDimは通るのですが、2回目にReDimしようとすると、
> 「インデックスが有効範囲にありません。」とエラーになってしまいます。
>
> 二次元配列の動的定義の仕方からして間違っているような気がするのですが、
> よい解決方法がありましたら、ご教授いただけないでしょうか?
>
> よろしくお願いします。
MSDNで、"Redim ステートメント"について見ると訳が分かります。
で、このような場合、
・「配列を要素とするユーザ定義型、の配列」を用いる。
・「配列の配列」:ジャグ配列を用いる。
 →例えば、[Variant]()の各々の元に[String]()を格納すると、
  [Variant](Index)[String](SubIndex)のように取り出せます…きっと。
・そもそも二次元的な配列にしない。
など、色々方法がありえます。

投稿時間:2005/07/05(Tue) 09:28
投稿者名:はこ
Eメール:
URL :
タイトル:
Re^2: 動的二次元配列
ガッさん。

おはようございます。
ご回答どうもありがとうございます。

MSDNもチェックせずに質問してすみませんでした。

> ・「配列を要素とするユーザ定義型、の配列」を用いる。

ユーザ定義型を標準モジュールでこのように定義して、

Public Type CSV_INFO
    csv_buf()               As String
End Type
Public CSVInfo()            As CSV_INFO

先ほどのソースを、

Dim fnum As Integer
Dim buf As String
Dim csv_data() As String
Dim cnt As Long
Dim i As Integer

Do While Not EOF(fnum)
    Line Input #fnum, buf
    csv_data = Split("")
    csv_data = Split(buf, ",")
    If UBound(csv_data) <> -1 Then
        ReDim Preserve CSVInfo(cnt) As CSV_INFO
        ReDim Preserve CSVInfo(cnt).csv_buf(UBound(csv_data)) As String
        CSVInfo(cnt).csv_buf = csv_data
        cnt = cnt + 1
    End If
Loop

というように修正したら、ユーザ定義型配列CSVInfo()にはCSVファイルのデータが
ちゃんと格納されるのですが、それをいざExcelファイルに出力しようと、

xlsSheet.Range("$A$2") _
    .Resize(UBound(CSVInfo) + 1, UBound(CSVInfo(0).csv_buf) + 1) _
    .Value = CSVInfo

としたところ、コンパイルエラー「パブリックオブジェクトモジュールで定義された
ユーザー定義型に限り、変数に割り当てることができ、実行時バインディングの
関数に渡すことができます。」が発生してしまいました。

そこで、

> ・「配列の配列」:ジャグ配列を用いる。
>  →例えば、[Variant]()の各々の元に[String]()を格納すると、
>   [Variant](Index)[String](SubIndex)のように取り出せます…きっと。

これを実験してみようと、

Dim fnum As Integer
Dim buf As String
Dim csv_data() As String
Dim cnt As Long
Dim i As Integer
Dim tbl()       As Variant
Dim csv_buff()  As String

Do While Not EOF(fnum)
    Line Input #fnum, buf
    csv_data = Split("")
    csv_data = Split(buf, ",")
    If UBound(csv_data) <> -1 Then
        ReDim Preserve tbl(cnt) As Variant
        ReDim Preserve csv_buff(UBound(csv_data)) As String
        csv_buff = csv_data
        tbl(cnt) = csv_buff
        cnt = cnt + 1
    End If
Loop

このようにソースを修正したところ、ジャグ配列tbl()()にはCSVファイルのデータが
ちゃんと格納されたのですが、いざExcelファイルに出力しようと、

xlsSheet.Range("$A$2").Resize(UBound(tbl) + 1, UBound(tbl(0)) + 1).Value = tbl

としたところ、Excelファイルには何も出力されませんでした。
(エラーは何も発生しなかったのですが。)

> ・そもそも二次元的な配列にしない。

CSVファイルのデータが数千行以上存在しても、それなりに高速に
動かしたいので、できたら二次元配列を使用したいと考えています。

せっかく教えていただいたのに、わがまま言ってすみません。

投稿時間:2005/07/05(Tue) 09:30
投稿者名:るしぇ
Eメール:
URL :
タイトル:
Re^2: 動的二次元配列
はい。理由はガッさんのおっしゃる通りヘルプに書いてあります。
>このようにしてみたところ、1回目のReDimは通るのですが、2回目にReDimしようとすると、
裏を返せば最初の1回は可能という事です。…で、エクセルに書き込むのに
最終的な形は Variant 型の2次元配列にする必要がありますので、ワンクッション
置いてガッさんの上げられたような変数に一時格納しておき、縦横の最大行数(配列の要素数)
を最初に調べ、1回目のReDimで要素数を決定してしまう方法が1つ。

もう一つ、どうせエクセル使うなら、CSVファイルをエクセルで開いてしまい、
使用しているセル全ての Value を Variant 型の2次元配列で読み込んでしまう
…という手もあります。ただし、この場合、配列のインデックスが1始まりに
なってると思います。注意して下さい。

投稿時間:2005/07/05(Tue) 10:45
投稿者名:はこ
Eメール:
URL :
タイトル:
Re: 動的二次元配列
るしぇさん

ご回答どうもありがとうございます。

最終的にお二人のご意見を参考にさせていただいて、CSVファイルから読込んだデータを
一旦すべてジャグ配列に格納してから、Variant型の二次元配列の再定義を行い、
Excelに一括出力することにしました。

Dim fnum            As Integer
Dim buf             As String
Dim csv_data()      As String
Dim cnt             As Long
Dim i               As Long
Dim tbl()           As Variant
Dim csv_buff()      As String
Dim output_data()   As Variant

'1行ずつ読込んで、ジャグ配列に一時格納
Do While Not EOF(fnum)
    Line Input #fnum, buf
    csv_data = Split("")
    csv_data = Split(buf, ",")
    If UBound(csv_data) <> -1 Then
        ReDim Preserve tbl(cnt) As Variant
        ReDim csv_buff(UBound(csv_data)) As String
        csv_buff = csv_data
        tbl(cnt) = csv_buff
        cnt = cnt + 1
    End If
Loop

'Variant型の二次元配列にコピーして、Excel出力
ReDim output_data(UBound(tbl), UBound(tbl(0))) As Variant
For i = 0 To UBound(tbl)
    For j = 0 To UBound(tbl(0))
        output_data(i, j) = tbl(i)(j)
    Next
Next
xlsSheet.Range("$A$2") _
    .Resize(UBound(output_data, 1) + 1, UBound(output_data, 2) + 1) _
    .Value = output_data

Variant型の配列にデータをコピーする際、二重ループを使って
コツコツやっているのがちょっと気になるのですが、これはもう
どうしようもないですよね?