可変長の2次元を配列をRedimなしで使うには

解決


うい  2008-08-07 07:39:07  No: 140189

開発環境はVisualStudio2005 WindowsXPです。

Excelに値を出力するときに 
dim Str(,) as String の2次元配列に格納してから
出力するようにしています。
そのときに、配列の長さはあらかじめわからないので
Redim Preserveで何度もサイズを変更しながら値を加えています。
ArrayListは1次元の配列に出力する機能はついてるようですが、
今回は2次元の配列なのでうまくいきませんでした。
.NETでも2次元配列を操作するときはRedimで繰り返しサイズを変更する
方法が普通なのでしょうか。
考える方向性がわからないので、ヒントでもいただければ幸いです。
よろしくお願いします。


魔界の仮面弁士  2008-08-07 18:36:02  No: 140190

> dim Str(,) as String の2次元配列に格納してから
「Str 関数」と競合するような名前は、避けておいた方が無難かと。
# Str は予約語では無いので言語的には問題無いのですが、混乱を招くので。

> Redim Preserveで何度もサイズを変更しながら値を加えています。
見た目上は、Preserve はサイズの拡張/縮小を行っているように見えますが、
実際には新たな配列を作りなおして、そこにデータをコピーする命令です。

そのため頻繁なサイズ変更は、データ件数増加につれて、効率が著しく悪くなります。
旧VB にしても VB.NET にしても。

> ArrayListは1次元の配列に出力する機能はついてるようですが、
今回の件とは関係ありませんが、VB2005 をお使いであるならば、
System.Collections.ArrayList クラスよりも、
System.Collections.Generic.List(Of T) クラスの方が便利ですよ。

> .NETでも2次元配列を操作するときはRedimで繰り返しサイズを変更する
> 方法が普通なのでしょうか。
何が普通という事もありませんが、配列の再生成回数を減らすために、
最初は、大きめの配列や別のコレクションなどに入れておき、最後に
二次元配列へと加工するという手法があります。

たとえば、データを System.Data.DataTable の形で保持するようにすれば、
行数は .Rows.Count、列数は .Columns.Count で取得できますので、
後からループで、二次元配列へと変換できますね。
(もちろん、DataTable 以外の物で管理しても良いでしょう)


うい  2008-08-08 08:31:09  No: 140191

># Str は予約語では無いので言語的には問題無いのですが、混乱を招くので。
知らなかったです。次からは他の変数名にします。

>System.Collections.Generic.List(Of T) クラスの方が便利ですよ。
ありがとうございます、List(Of T)を使わせていただきます。

現在の状況の記述が少なすぎたのでもう少し詳しく書きたいと思います。

現在、System.Data.DataTableもしくは配列に値をもっていて
DataTableの方は
1列目でソートしたDataTableをデータベースから取得したときのままです。

DataTableの1列目の値が変わったらシートを替えてExcelに表示するよう
いわれています。
値が変わるのは何行目かわからないので可変長になりそうです。

DataTableをそのまま出力するときは
.Rows.Countを使わせていただきました。


魔界の仮面弁士  2008-08-08 20:00:05  No: 140192

> 値が変わるのは何行目かわからないので可変長になりそうです。
その「変わった結果」を、いきなり配列に入れるのでは無く、
DataView で処理したり、あるいは一時的に「別の DataTable 等に入れなおす」ことで、
二次元配列化しやすくなるかと。

たとえば、こんなデータがあったとします。

'-------------
Private Function CreateSampleData() As DataSet
  Dim ds As New DataSet()
  Dim t As DataTable = ds.Tables.Add("Table1")
  t.Columns.Add("日付", GetType(Date))
  t.Columns.Add("地域")
  With t.Rows
    .Add(#8/1/2008#, "青森")
    .Add(#8/1/2008#, "埼玉")
    .Add(#8/1/2008#, "東京")
    .Add(#8/1/2008#, "京都")
    .Add(#8/1/2008#, "大阪")

    .Add(#8/2/2008#, "秋田")
    .Add(#8/2/2008#, "石川")
    .Add(#8/2/2008#, "東京")

    .Add(#8/3/2008#, "宮崎")
    .Add(#8/3/2008#, "那覇")
  End With

  ds.AcceptChanges()
  Return ds
End Function
'-------------

そしてそこから、1列目の日付でグループ化して、
    総件数:3
    2008/08/01=5件
    2008/08/02=3件
    2008/08/03=2件
の結果を得たいとします。

これが VB2008 であれば、LINQ 一発なのですが…。

'-------------
Dim query = _
  From row In table.AsEnumerable() _
  Group By dt = row.Field(Of Date)("日付") Into Group, cnt = Count() _
  Order By dt Ascending  

Console.WriteLine("総件数:{0}件", query.Count)
For Each q In query
  Console.WriteLine("{0:yyyy/MM/dd}={1,-5:#,0}", q.dt, q.cnt)
Next
'-------------

残念ながら今回は 2005 なので、上記のように簡単には書けず、自分で集計せねば
なりませんが、たとえば、グループ化を管理するために、DataSet の「式列」や
リレーション機能(DataRow.GetChildRows など)を使ってみては如何でしょう。

'-------------
Private Shared Function CreateSampleData() As DataSet
  '(省略:先の内容と同じ)
End Function

Private Sub Form1_Load(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Load
  Dim ds As DataSet = CreateSampleData()

  '日付でグループ化されたテーブルを作成
  Dim dsGroup As DataSet = CreateGroupingDataSet(ds)

  '中身確認
  Dim grid As New DataGrid()
  grid.Dock = DockStyle.Fill
  Controls.Add(grid)
  grid.DataSource = dsGroup.Tables("Group1")

  '件数表示
  Dim fmtGroup As String = "{0:yyyy/MM/dd} = {1:#,0}件"
  Dim fmt As String = "    {0:yyyy/MM/dd}, {1}"
  Dim msg As New System.Text.StringBuilder()
  For Each rowGroup As DataRow In dsGroup.Tables("Group1").Rows
    Dim rows() As DataRow = rowGroup.GetChildRows("GroupBy日付")
    'msg.AppendLine(String.Format(fmtGroup, rowGroup("日付"), rows.Length))
    msg.AppendLine(String.Format(fmtGroup, rowGroup("日付"), rowGroup("件数")))
    For Each row As DataRow In rows
      msg.AppendLine(String.Format(fmt, row("日付"), row("地域")))
    Next
  Next
  MessageBox.Show(msg.ToString())
End Sub

Private Function CreateGroupingDataSet(ByVal dsSource As DataSet) As DataSet
  Dim ds As DataSet = dsSource.Copy()
  ds.EnforceConstraints = False

  '日付列でグループ化
  Dim tblSrc As DataTable = ds.Tables("Table1")
  Dim tblGrp As DataTable = ds.Tables.Add("Group1")
  tblGrp.Columns.Add("日付", GetType(Date))
  ds.Relations.Add("GroupBy日付", tblGrp.Columns("日付"), tblSrc.Columns("日付"))
  tblGrp.Columns.Add("件数", GetType(Decimal), "COUNT(Child.日付)")

  '集計作業は自分で書く必要がある…
  Dim view As New DataView(ds.Tables("Table1"))
  view.Sort = "日付 ASC"
  Dim preDate As Date = Date.MaxValue
  For Each row As DataRowView In view
    Dim curDate As Date = CDate(row.Row("日付"))
    If preDate <> curDate Then
      preDate = curDate
      tblGrp.Rows.Add(curDate)
    End If
  Next
  ds.EnforceConstraints = True
  tblGrp.AcceptChanges()
  Return ds
End Function


魔界の仮面弁士  2008-08-08 20:18:37  No: 140193

こちらは DataView 版。やっている事は同じ…。

Private Sub Form1_Load(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Load
  Dim ds As DataSet = CreateSampleData()

  Dim table1 As DataTable = ds.Tables("Table1")
  Dim view1 As New DataView(table1)
  view1.Sort = "日付 ASC"
  Dim preDate As Date = Date.MaxValue

  Dim msg As New System.Text.StringBuilder()
  Dim count As Integer = 0
  For Each row As DataRowView In view1
    Dim curDate As Date = CDate(row.Row("日付"))
    If preDate <> curDate Then
      preDate = curDate
      Dim view2 As New DataView(table1)
      view2.RowFilter = String.Format("日付=#{0:MM/dd/yyyy}#", curDate)
      msg.AppendLine(String.Format("   {0:yyyy/MM/dd}={1:#,0}件", curDate, view2.Count))
      count += 1
    End If
  Next
  msg.Insert(0, String.Format("グループ化総件数:{0}件", count) & vbNewLine, 1)
  MessageBox.Show(msg.ToString())
End Sub


うい  2008-08-14 07:39:21  No: 140194

>その「変わった結果」を、いきなり配列に入れるのでは無く、
>DataView で処理したり、あるいは一時的に「別の DataTable 等に入れなお>す」ことで、
>二次元配列化しやすくなるかと。

返信が遅くなってすいません。
何とか思った動きのものができました。
ソースまで載せていただきありがとうございます。
おっしゃる通り一度DataTableに入れることで可変長のデータが
無駄なく扱えるようになりました。

今まで「2次元配列をExcel出力する関数」しかなかったのでそれにこだわりすぎてました。
DataTableを2次元配列に変換してを「2次元配列をExcel出力する関数」に渡す関数を作って
その関数に渡すためのDataTableを各画面で作るようにしました。
ありがとうございます。

>これが VB2008 であれば、LINQ 一発なのですが…。
これは知らなかったので目からうろこです。
VB2008を使うのが楽しみになりました。

少し話がずれてしまうのですが
上記のソースを参考にさせていただいたて、
ループ処理でFor Eachというのが使われていますが
使える限りドンドン使ったほうがいいのでしょうか。
ただのFor文でも同じような処理ができて、
ループに使う変数をDataTableの行数を表すのに使っています。


うい  2008-08-14 07:41:29  No: 140195

For Eachに関して他に質問スレッドを立てたほうがいいようでしたら
立てますので、よろしくお願いします。


魔界の仮面弁士  2008-08-14 08:33:24  No: 140196

> VB2008を使うのが楽しみになりました。
ちなみに。

先のコードは、Visual Studio を使ってはおらず、メモ帳に直接記述して、
  C:\WINDOWS\Microsoft.NET\Framework\v3.5\VBC.EXE
を使って、手動コンパイルして確認していたりします。(^^;
(いつも使っている PC には、VS2008 を入れていなかったので…)

> ループ処理でFor Eachというのが使われていますが
> 使える限りドンドン使ったほうがいいのでしょうか。
For Each と For では、その利用目的が微妙に異なりますので、
その時々で使い分ける事になると思います。

どちらでも実装可能な場合は、お好きな方を選べば良いでしょう。

For Each が必須となるのは、「順序」を持たない一覧を列挙する場合などが相当します。
たとえば、以下のようなコードの場合などです。
  Dim hash As New Hashtable()
  hash.Add("Key1", "Value1")
  hash.Add("Key2", "Value2")

  Dim dict As New Dictionary(Of String, String)
  dict.Add("Key3", "Value3")
  dict.Add("Key4", "Value4")

  '内容を列挙
  For Each item As DictionaryEntry In hash
    Console.WriteLine("{0}='{1}'", item.Key, item.Value)
  Next
  For Each item As KeyValuePair(Of String, String) In dict
    Console.WriteLine("{0}='{1}'", item.Key, item.Value)
  Next

このような場合は、For では列挙できませんね。(GetEnumerator すれば別ですが)


うい  2008-08-15 07:42:49  No: 140197

>C:\WINDOWS\Microsoft.NET\Framework\v3.5\VBC.EXE
>を使って、手動コンパイルして確認していたりします。(^^;
すごすぎです。  メモ帳に直接記述ってのがかっこよすぎです。
Visual Studioに頼りっぱなしの自分は
体験版を入れては期限切れになるたび、家にある他の端末に入れてます。
家族のPCすべてにVisualStudioが(汗

>For Each が必須となるのは、「順序」を持たない一覧を列挙する場合などが相当します。
Hashtableなど文字列で値を決めるタイプのものを想像してませんでした。

ものすごく勉強になりました。
ありがとうございました。


魔界の仮面弁士  2008-08-15 18:33:37  No: 140198

> 体験版を入れては期限切れになるたび、
2008 では無いのですよね。現在お使いの製品は、
Visual Studio .NET 2003 Professional 60日評価版 でしょうか。
Visual Studio 2005 Professional Edition 90 日間評価版 でしょうか。
Visual Studio 2005 Team Suite 180 日間評価版 でしょうか。

で、それらの代わりに、2005/2008 の Express Edition を使ってみては
いかがでしょう。これなら、試用期限はありませんので。

http://www.microsoft.com/japan/msdn/vstudio/Express/
http://www.microsoft.com/japan/msdn/vstudio/express/past/2005/


うい  2008-08-20 08:05:36  No: 140199

Visual Studio 2005 Professional Edition 90 日間評価版 を使っています。

さっそくExpress Edition使わせていただきました。
ありがとうございます。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加