Excel VBA質問箱 IV

当質問箱は、有志のボランティア精神のおかげで成り立っています。
問題が解決したら、必ずお礼をしましょうね。
本サイトの基本方針をまとめました。こちら をご一読ください。

投稿種別の選択が必要です。ご注意ください。
迷惑投稿防止のため、URLの入力を制限しています。ご了承ください。


1818 / 13645 ツリー ←次へ | 前へ→

【71555】表の整理 ドカ 12/3/16(金) 20:43 質問[未読]
【71557】Re:表の整理 ドカ 12/3/16(金) 20:54 発言[未読]
【71559】Re:表の整理 何か変じゃないですか 12/3/16(金) 22:44 発言[未読]
【71561】Re:表の整理 ドカ 12/3/17(土) 4:42 発言[未読]
【71560】Re:表の整理 UO3 12/3/16(金) 23:09 発言[未読]
【71562】Re:表の整理 ドカ 12/3/17(土) 4:57 お礼[未読]
【71564】Re:表の整理 UO3 12/3/17(土) 9:39 回答[未読]
【71671】Re:表の整理 ドカ 12/3/27(火) 14:10 質問[未読]
【71672】Re:表の整理 UO3 12/3/27(火) 16:43 発言[未読]
【71673】Re:表の整理 UO3 12/3/27(火) 17:20 発言[未読]
【71674】Re:表の整理 ドカ 12/3/27(火) 20:11 発言[未読]
【71675】Re:表の整理 ドカ 12/3/28(水) 9:08 お礼[未読]
【71676】Re:表の整理 UO3 12/3/28(水) 10:34 発言[未読]
【71677】Re:表の整理 ドカ 12/3/28(水) 11:10 発言[未読]
【71678】Re:表の整理 UO3 12/3/28(水) 15:08 発言[未読]
【71679】Re:表の整理 UO3 12/3/28(水) 15:13 発言[未読]
【71683】Re:表の整理 ドカ 12/3/28(水) 20:34 お礼[未読]
【71688】Re:表の整理 ドカ 12/3/30(金) 8:29 質問[未読]
【71689】Re:表の整理 ドカ 12/3/30(金) 9:06 質問[未読]
【71690】Re:表の整理 UO3 12/3/30(金) 10:27 発言[未読]
【71691】Re:表の整理 UO3 12/3/30(金) 11:46 発言[未読]
【71692】Re:表の整理 UO3 12/3/30(金) 21:10 発言[未読]
【71720】Re:表の整理 UO3 12/4/2(月) 14:10 発言[未読]
【71724】Re:表の整理 ドカ 12/4/3(火) 7:59 お礼[未読]
【71728】Re:表の整理 UO3 12/4/3(火) 13:16 発言[未読]
【71731】Re:表の整理 UO3 12/4/3(火) 16:54 発言[未読]
【71725】Re:表の整理 ドカ 12/4/3(火) 8:15 質問[未読]
【71726】Re:表の整理 UO3 12/4/3(火) 12:39 発言[未読]
【71727】Re:表の整理 UO3 12/4/3(火) 12:44 発言[未読]
【71736】Re:表の整理 ドカ 12/4/4(水) 15:59 お礼[未読]

【71555】表の整理
質問  ドカ  - 12/3/16(金) 20:43 -

引用なし
パスワード
   横方向は、氏名、商品、価格というデータの塊が何個かあり決まってません。
縦方向は、何行あるか決まっていません。

氏名  商品  価格   氏名  商品  価格・・・・
山本  本    1    加藤  花    4
佐藤  ノート  5    山本  砂糖   1
加藤  花    4    加藤  みかん  2
佐藤  ペン   3
山本  のり   2

これを次のように、氏名で整理したい。
横方向に山本のデータの並びがあり、山本がなくなったら、
次は加藤の並びとなり、以下続くとなります。

山本  本    1    山本  砂糖   1・・・
山本  のり   2    
−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
加藤  花    4    加藤  花    4
             加藤  みかん  2
−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
佐藤  ノート  5    
佐藤  ペン   3    
−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
名前の並び順は、あいうえお順でも何でもよいです。

【71557】Re:表の整理
発言  ドカ  - 12/3/16(金) 20:54 -

引用なし
パスワード
   数日間返信できない環境になるますので、よろしくお願いします。

【71559】Re:表の整理
発言  何か変じゃないですか  - 12/3/16(金) 22:44 -

引用なし
パスワード
   ▼ドカ さん:
>数日間返信できない環境になるますので、よろしくお願いします。

返信できるようになってから質問するのが普通じゃないですか?
返信できなければ内容の不明な点もあなたに確認できない。
まさか、返信できないけど、その間にコード作っておいてってことじゃないよね。

【71560】Re:表の整理
発言  UO3  - 12/3/16(金) 23:09 -

引用なし
パスワード
   ▼ドカ さん:

前トピでは、列を取り違えていてごめんなさいね。
さて、「何か変じゃないですか」さんのご指摘、その通りだと思います。
思いますが、私も、数日、PC環境のないところにでかけますので
とりあえず書いたものをアップしておきます。
また、「こんなわけのわからんコードはお断り」といわれそうですが。
必要なら、旅から戻った後、前トピのSample3のようなコードも考えてみますが。

Sub Sample()
  Dim v As Variant
  Dim x As Long
  Dim y As Long
  Dim dic As Object
  Dim dicRow As Object
  Dim w() As String
  Dim z As Variant
  Dim i As Long
  Dim j As Long
  Dim k As Long
  Dim n As Long
  Dim myName As Variant
  Dim rowKey As String
  
  Application.ScreenUpdating = False
  
  Set dic = CreateObject("Scripting.Dictionary")
  Set dicRow = CreateObject("Scripting.Dictionary")
  
  With Sheets("Sheet1")  '元シート
    With .Range("A1").CurrentRegion
      x = .Columns.Count   '表の列数
      y = .Rows.Count     '表の行数
    End With
    
    ReDim w(1 To x)
    
    For i = 2 To y
      For j = 1 To y Step 3
        myName = .Cells(i, j).Value
        If Len(myName) > 0 Then '空白はスキップ
          If Not dic.exists(myName) Then Set dic(myName) = CreateObject("Scripting.Dictionary")
          rowKey = myName & vbTab & j
          dicRow(rowKey) = dicRow(rowKey) + 1
          n = dicRow(rowKey)
          If Not dic(myName).exists(n) Then dic(myName)(n) = w '行スケルトン
          z = dic(myName)(n)
          z(j) = myName
          z(j + 1) = .Cells(i, j + 1).Value
          z(j + 2) = .Cells(i, j + 2).Value
          dic(myName)(n) = z
        End If
      Next
    Next
    
  End With
  
  i = 2
  With Sheets("Sheet2")  '転記シート
    .Cells.ClearContents
    .Range("A1").Resize(, x).Value = Sheets("Sheet1").Range("A1").Resize(, x).Value 'タイトル行コピー
    For Each myName In dic
      .Range("A" & i).Resize(dic(myName).Count, x).Value = _
        WorksheetFunction.Transpose(WorksheetFunction.Transpose(dic(myName).items))
      i = i + dic(myName).Count
    Next
    .Select
  End With
  
  Set dic = Nothing
  Set dicRow = Nothing
  
  Application.ScreenUpdating = True
  MsgBox "転記完了です"
          
End Sub

【71561】Re:表の整理
発言  ドカ  - 12/3/17(土) 4:42 -

引用なし
パスワード
   ▼何か変じゃないですか さんへ
おっしゃる通りですね。

【71562】Re:表の整理
お礼  ドカ  - 12/3/17(土) 4:57 -

引用なし
パスワード
   ▼UO3 さん いつも回答ありがとうございます。

期待通りの動きが出来ました。

それと、以前教えて頂いた、縦に並んでいるデータを横に並べるコードをうまく活用すれば、泥臭いコードかもしれませんが、実現できそうです。

これからパソコンが使えない環境となります。

【71564】Re:表の整理
回答  UO3  - 12/3/17(土) 9:39 -

引用なし
パスワード
   ▼ドカ さん:

出発前に時間が取れたので、シートを見ながら、手作業で切り貼りや並び替えやセルの挿入をする
そんな流れをコードにしてみました。
処理効率、かなり悪くなりますが、操作とコードが一致しているので、理解しやすいかもしれません。
先にアップしたものは、元シートの列の左から右に、名前の出現順の並びでしたが、こんどのものは
名前の昇順になります。

Sub Sample2()
  Dim blocks As Long
  Dim x As Long
  Dim y As Long
  Dim wkCol1 As Long
  Dim wkCol2 As Long
  Dim j As Long
  Dim i As Long
  Dim k As Long
  Dim n As Long
  Dim c As Range
  Dim v() As Long
  Dim z As Long
  
  Application.ScreenUpdating = False
  
  With Sheets("Sheet2")
  '準備作業
    Sheets("Sheet1").Cells.Copy .Range("A1")  'Sheet1をSheet2にコピー
    With .Range("A1").CurrentRegion
      x = .Columns.Count   '表の列数
      y = .Rows.Count     '表の行数
    End With
    blocks = x \ 3
    wkCol1 = x + 2
    wkCol2 = wkCol1 + 2
    '各ブロックの名前列を作業列1にセットするとともに、名前順に並び替え
    i = 1
    For j = 1 To blocks
      k = (j - 1) * 3 + 1             'ブロックの名前列番号
      n = .Cells(.Rows.Count, k).End(xlUp).Row  'ブロックの名前列の最終行番号
      .Cells(i, wkCol1).Resize(n).Value = .Cells(1, k).Resize(n).Value
      .Columns(k).Resize(, 3).Sort Key1:=.Columns(k), Order1:=xlAscending, Header:=xlYes
      i = i + n
    Next
    'この名前から重複を排除し作業列2に抽出
    .Cells(1, wkCol1).CurrentRegion.AdvancedFilter Action:=xlFilterCopy, _
                    CopyToRange:=.Cells(1, wkCol2), Unique:=True
    '作業列2を名前順に並び替え
    .Columns(wkCol2).Sort Key1:=.Columns(wkCol2), Order1:=xlAscending, Header:=xlYes
  '作業列2から名前を取り出して処理開始
    i = 2    '表のデータ開始行
    For Each c In .Cells(1, wkCol2).CurrentRegion
      If c.Value <> .Range("A1").Value Then    '名前タイトル文字ならスキップ
        ReDim v(1 To blocks)
        z = 0
        For j = 1 To blocks
          k = (j - 1) * 3 + 1             'ブロックの名前列番号
          n = .Cells(.Rows.Count, k).End(xlUp).Row  'ブロックの名前列の最終行番号
          v(j) = WorksheetFunction.CountIf(.Columns(k), c.Value) 'この列のこの名前の個数
          If v(j) > z Then z = v(j)                '全体のこの名前の個数の最大値
        Next
        For j = 1 To blocks
          k = (j - 1) * 3 + 1             'ブロックの名前列番号
          n = 0
          If .Cells(i, k).Value <> c.Value Then
            n = z
          Else
            n = z - v(j)
          End If
          If n > 0 Then
            .Cells(i + v(j), k).Resize(n, 3).Insert Shift:=xlDown
          End If
        Next
        i = i + z
      End If
    Next
    .Cells(1, wkCol1).CurrentRegion.Clear  '作業列1のクリア
    .Cells(1, wkCol2).CurrentRegion.Clear  '作業列2のクリア
    .Select
  End With
  
  
  Application.ScreenUpdating = True
  MsgBox "転記完了です"
  
End Sub

【71671】Re:表の整理
質問  ドカ  - 12/3/27(火) 14:10 -

引用なし
パスワード
   ▼UO3 さん 回答ありがとうございます。

こちらの方が分かりやすいのかなと思い、こちらのコードで勉強していました。
最終的にデータを下に下げていき、データをきれいに並べ替えていく作業ですが、どのように考えて、データを揃えているのでしょうか?

コードの理解というより、整列の考え方を教えて頂きたいです。
むづかしくて中々理解できずにいます。

また、こちらのコードは実際のデータで試したところ、正しく動いていないようです。データがきれいに並びません。もう一方のほうは、ちゃんと動くようです。

【71672】Re:表の整理
発言  UO3  - 12/3/27(火) 16:43 -

引用なし
パスワード
   ▼ドカ さん:

こんにちは

検証、ありがとうございます。

まず、【うまくいっている】という最初のコード Sample ですが、隠れているバグがありました。
データの状況によっては発生しないのですが、あきらかにバグです。
With Sheets("Sheet1")  '元シート このブロックに
For j = 1 To y Step 3 がありますが、ケアレスミスでした。
For j = 1 To x Step 3 にしてください。

で、【データがきれいに並ばない】2番目のコード Sample2 ですが、確認作業の過程で
作業列を含めた各列の氏名の並び替えが、必ずしも、同じ並びにならないことを発見しました。
たとえば、山田、田中があったとき、ある列は山田が上、ある列は田中が上になる現象です。

2ヶ所あるソートのパラメータに、以下のように SortMethod パラメータを追加してください。

      .Columns(k).Resize(, 3).Sort Key1:=.Columns(k), Order1:=xlAscending, _
        SortMethod:=xlStroke, Header:=xlYes

    '作業列2を名前順に並び替え
    .Columns(wkCol2).Sort Key1:=.Columns(wkCol2), Order1:=xlAscending, _
        SortMethod:=xlStroke, Header:=xlYes

じれで、おそらく【きれいに並ぶ】のではと期待しています。

さて、ご質問の件、少し整理してみます。
少しお待ちください。

【71673】Re:表の整理
発言  UO3  - 12/3/27(火) 17:20 -

引用なし
パスワード
   ▼ドカ さん:

こんにちは

Sample2の行の整列アルゴリズム(というほど、たいそうなものじゃありませんが)

・まず、各列の氏名を作業列にコピーします。で、これを、重複を排除して
 一意の名前のリストとして別の作業列におきます。
 (これをユニークな名前リストとよびましょう)
・で、次に、3列ごとの各ブロックを、氏名順に並び替えます。
 また、ユニークな名前リストも氏名順に並び替えます。
・これで、各ブロックも、ユニークな名前リストも、氏名の出現順は同じになりますね。
・データ内容としては、あるブロックには田中はあるけど、あるブロックにはない。
 で、ユニークな氏名リストには田中が必ず1つ存在するという状況になります。

 ここから処理です。処理はユニークな名前リストの上から順番に行います。

1.ユニークな名前リストの先頭が安部だったとします。この安部を処理します。
2.処理すべきユニークな名前リストから取り出した氏名とデータの突合せですが、まず最初は
 2行目(データの最初)を見ます。
3.各ブロックの安部の数をブロックごとに把握しておきます。また、これらのなかでの
 最大値も把握します。最大値が4件だったとします。
4.見ている行が安部かもしれません。安部ではないかもしれません。
 安部ではなかった場合、安部の最大値の4行の空白セル3列分をこのブロックに挿入します。
 安部だった場合、このブロックの安部の件数が4件だった場合(つまりブロックの中で安部の最大値)
 挿入は行いません。
 このブロックの安部の件数が4未満の場合、4から、その件数を引いた行数分の3列の空白セルを挿入します。
5.この結果、元のデータは、最初の行(2行目から5行目)が安部用、他のデータは、6行目以降に下げられて
 います。
6.で、次に、ユニークな名前リストから次の名前を取り出し、データの6行目をつき合わせます。
7.上記、3.〜5.を実行します。
8.このように、ユニークな名前リストから全て名前を取り出すまで、ループ処理をします。

こんな程度でご理解いただけましたでしょうか?

【71674】Re:表の整理
発言  ドカ  - 12/3/27(火) 20:11 -

引用なし
パスワード
   ▼UO3 さん 何度も回答ありがとうございます。

アルゴリズムについては、理解できました。

一方、SortMethod パラメータを追加については、またまた綺麗には並びませんでした。

一度しか試していないので、何らかの手違いがあったかもしれないので、また、明日になりますが確認します。
(エラーなく処理が終わったので、記述ミスなどの間違いはないと思うのですが)

【71675】Re:表の整理
お礼  ドカ  - 12/3/28(水) 9:08 -

引用なし
パスワード
   ▼UO3 さん いつもお世話になっております。

正しく作動しない件ですが、
重複を排除して一意の名前のリストを作る
ことが正しく出来ていないようです。

これは手動でやっても駄目でした。
エクセルのバグなのでしょうか?

【71676】Re:表の整理
発言  UO3  - 12/3/28(水) 10:34 -

引用なし
パスワード
   ▼ドカ さん:

>重複を排除して一意の名前のリストを作る
>ことが正しく出来ていないようです。
>これは手動でやっても駄目でした。

情報、ありがとうございます。
そうですか。こちらで、適当な名前のテストデータでやる限り、重複は排除されますが
ものが漢字ですので、なとえば通常の並び替えを行っても、不安定な結果になったように
なにか、エクセルとの相性の問題があるのかもしれませんね。

ちなみに、そちらで実行された時に、重複の排除がされなかった氏名をいくつか
教えていただけませんか。

【71677】Re:表の整理
発言  ドカ  - 12/3/28(水) 11:10 -

引用なし
パスワード
   ▼UO3 さん:

実際のデータは氏名ではなく、項目名です。
また、重複が排除されないのではなく、残るべき項目が消えてしまうという現象が起きています。

消えてしまった項目名
プロジェクターリース料
ポスト・イット
大学ノート
などなど多岐に渡ります。

【71678】Re:表の整理
発言  UO3  - 12/3/28(水) 15:08 -

引用なし
パスワード
   ▼ドカ さん:

こんにちは

まず、ピックアップいただいた言葉も含めてテストデータをつく入り、手作業でフィルターオプションで
重複を排除してみましたが、こちらでは正常に1つずつ残ります。(エクセルは2003で確認)

各ブロックの中のものがもれなく作業列にコピーされているか、以下で確認いただけませんか。

・まず、先頭のApplication.ScreenUpdating = False をコメントアウト
・以下のソートコードにブレークポイントを設定
'作業列2を名前順に並び替え
.Columns(wkCol2).Sort Key1:=.Columns(wkCol2), Order1:=xlAscending, Header:=xlYes

こうして止まった時点でSheet2をみてください。
リストの右の方に作業列が2列あります。
最初の列に消えてしまったコードがあるかどうか確認してください。
また、その右の作業列に消えてしまったコードがあるかどうかも確認してください。

【71679】Re:表の整理
発言  UO3  - 12/3/28(水) 15:13 -

引用なし
パスワード
   ▼ドカ さん:

ふと思いついたんですが・・・
3列ごとのブロックの中に氏名が空白のものはありませんか?
もしあれば、フィルター処理のところで、その空白の下のデータは無視されます。
このような状態があるなら、それはそれで、コードを変更して対処することもできると思いますが。

【71683】Re:表の整理
お礼  ドカ  - 12/3/28(水) 20:34 -

引用なし
パスワード
   ▼UO3 さん: 回答ありがとうございます。

ダミーのデータを混ぜたりしていたので、氏名に空白が1個ありました。
これを正すことで、うまくいきました。

>もしあれば、フィルター処理のところで、その空白の下のデータは無視されます。
>このような状態があるなら、それはそれで、コードを変更して対処することもできると思いますが。

このような状態は、正規のデータではないので、コード変更の対処は不要です。
色々とありがとうございました。

【71688】Re:表の整理
質問  ドカ  - 12/3/30(金) 8:29 -

引用なし
パスワード
   ▼UO3 さん すみませんが、また、教えてください。

CreateObject("Scripting.Dictionary")を使ったほうのコードですが、アルゴリズムの考え方を教えてください。

CreateObject("Scripting.Dictionary")のコードは超高速で、こちらのコードの方が実用的ですね。

【71689】Re:表の整理
質問  ドカ  - 12/3/30(金) 9:06 -

引用なし
パスワード
   ▼UO3 さん 追加の質問になりますが、


Debug.Print dicRow(rowKey)
これはちゃんと中身を表示してくれます。

Debug.Print dic(myName)(n)
これはエラーが出て、中身が見えません。
どうしてなのでしょうか?
どうすれば、見えるのでしょうか?

【71690】Re:表の整理
発言  UO3  - 12/3/30(金) 10:27 -

引用なし
パスワード
   ▼ドカ さん:

こんにちは

アルゴリズムの説明と追加質問の回答の前に、Dictionaryというものを
さわりだけ、さらっとご説明します。(既にご存知であればスルーしてください)
(以下の説明の中で、Dictionaryの変数を、とりあえず Dic とします)

本来なら、参考書やネットで、わかりやすいものを見つけて読んでいただくほうが
私のつたないメモよりは、ずっと役立つとは思いますが。

Dictionary とは、文字通り、「辞書」です。
たとえば英和辞書があって、Summer という項目を引けば、その内容を見ることができますね。
で、この中身は、以下に説明するような配列です。
ですので、Dictionaryのテクニカルタームとしての日本語訳は、一応「連想配列」ということに
なっています。
この「連想」が、人工知能的な響きがあって、だから難しいものなんだろうなと敬遠される
要因にもなっているかもしれませんが、実は、すごく簡単で便利なものです。

Dictionary の構造は キー(辞書の索引)とデータです。
で、このキーとデータが、それぞれ「1次元配列」におさめられています。
(プロパティでいうと Dic.Keys がキーの配列で、Dic.Itemsがデータの配列です)
1次元配列というのは、シートでいいますと「行」にあたります。
Sheet1の1行目にキーが記載されていて、それとは別に、Sheet2の1行目にデータが記載されている
そんなイメージです。

通常の配列と異なる点としては、もちろん、キーで直接データを呼び出せるということが
ありますが、もう1つ、通常の配列ですと、あらかじめ配列の大きさを宣言する必要がありますね。
たとえば Dim V(1 to 10) これは1次元配列で要素が10個のもの。
ここに11個目の要素を追加することは(普通のやり方では)不可能です。
必要であれば ReDim PreServe V(1 to 11) といったように追加の都度、配列サイズを変更します。
でも、Dictionaryの場合は、おかまいなしに、どんどん登録していくことができます。
で、このデータはVariant型で用意されていますので、どんな値でもいれておくことができますし
たとえば、何も登録されていないキーに対して、Dic(新しいキー)=dic(新しいキー)+100 という演算も
できます。これは、たとえばシート上で空白のA1があったとして別のセルで =A1+100 とやっても
エラーにならずに計算結果がでるのと同じと考えてください。

大きさを考えないで、どんどん追加できる。
何でも登録できる。
直接呼び出すことができるので、データの並び順に悩まず、何回か前に登録したデータに対して
簡単に加算減算、上書きができる。
で、書いた内容を1つずつ取り出すこともできますし、上で説明したように、
中身は1次元配列そのものですから、配列として取り出したものを処理することもできる。

このようなものですので、むしろ、初心者向けんのツールだということもできます。

実は、大量データを相手に複雑なマッチング処理を行おうとすると、
処理にふさわしい並び替え => その処理に必要な複雑なマッチングロジック
これが最も処理時間が早くなるほうほうなんですが、初心者のみならず、このアルゴリズムを
正確に実装するのは、大変に骨が折れます。

Dictionaryは、深く考えずにコードを書けるということも、さることながら、その処理効率も
「そんなに悪くない」というレベルになっています。

是非、今後のコード作成で使ってみてください。

【71691】Re:表の整理
発言  UO3  - 12/3/30(金) 11:46 -

引用なし
パスワード
   ▼ドカ さん:

▼ドカ さん:

こんにちは

最初に、追加質問のほうを。
これは、基本的には Debug.Print dic(myName)(n) でもエラーにならず参照できると思いますが
このコードが書かれている時点で、myName の値や n の値が正しいということが前提です。
エラーになった時、黄色く光りますが、マウスを myName や n に当てた時にポップアップされる値は
正しいものでしょうか?

次に、前の説明の中で、Dictionaryには「何でも格納できる」といいました。
たとえばオブジェクトも (Set をかけることで)Dictionaryのデータとして格納できます。

これは、いささか「マニアック」な使い方になりますけど、
Set Dic("AAA") = CreateObject("Scripting.Dictionary")
Set Dic("BBB") = CreateObject("Scripting.Dictionary")
このようにしますと、Dic の中の"AAA" というキーのデータが、"AAA"用の、独立したDictionary。
Dic の中の"BBB" というキーのデータが、"BBB"用の、独立したDictionaryになります。

ということは Dic("AAA") そのものが Dictionary ですから
Dic("AAA")("XYZ") と書きますと、Dic("AAA") というDictionaryの中に登録されている
"XYZ"というキーを持つデータということになります。

また、「配列」も格納することができます。

たとえば
Dic("AAA") = Array("X","Y","Z") と書きますと
"AAA" というキーで登録されているのが1次元配列ということになります。

で、ご質問の dicRow(rowKey) 。
dicRow は キーが 名前とその名前のあった列番号(ブロックの最初の列番号)を組み合わせたもの。
データは、そのキーでの項目の数、つまり、そのブロックに、その名前が何件あったかという数です。
なので、dicRow(rowKey) で得られる内容は、単純な数字ですから、中身の参照ができます。
仮に、dicRowに rowKey に当たるデータがなくとも、エラーにはならず空白値を返してくれます。
(これも、Dictionaryが使いやすい一面です)

一方、dic(myName)(n)。
こちらのほうは、Dic(myName) で得られるデータは myName用の「子Dictionary」です。
Dic("田中") とした場合、"田中"が存在すれば、"田中"用の子Dictionaryのオブジェクトが
1次元配列がかえってきます。
n は dicRow(rowKey) ですから Dic("田中")(4) とすると、"田中"用のDictionaryのキーが4
つまり、F列から始まるブロックのデータということになります。
で、このデータは、どんなものかといいますと、「1次元配列」です。
これがエラーになるとすると、考えられることは以下の場合のみです。

Dic に "田中"が未登録。 この場合、未登録でもエラーにはなりません。
なりませんが、配列でもオブジェクトでもない空白値が返ります。
配列であればその4番目の要素、Dictionaryであれば 4 というキーのデータということになるのですが
空白値(4) ということですので、当然エラーになります。

【71692】Re:表の整理
発言  UO3  - 12/3/30(金) 21:10 -

引用なし
パスワード
   ▼ドカ さん:

アルゴリズムは整理中です。
もう少しお待ちください。

↑のレスの下の方の

Dic("田中") とした場合、"田中"が存在すれば、"田中"用の子Dictionaryのオブジェクトが
1次元配列がかえってきます。

ちょっとメモを整理していて、おかしくなりました。正しくは

Dic("田中") とした場合、"田中"が存在すれば、"田中"用の子Dictionaryのオブジェクトがかえってきます。

【71720】Re:表の整理
発言  UO3  - 12/4/2(月) 14:10 -

引用なし
パスワード
   ▼ドカ さん:

こんにちは

遅くなりました。
アルゴリズムですが、部分、部分のコードの使い方などは既にご説明していますので
アップされた実例(3列1組が2組ある例)から転記イメージが、どのように作られるかを、
簡単にお話します。

まず、登場するDictionaryは 3 つあります。

Dic のキーは名前です。データとして、その名前の転記イメージをセットする「子Dictionary」
(のオブジェクト)を格納してあります。
この子Dictionaryが、どのように作られるかの説明になります。
説明の中では、「親Dic」、「子Dic」とよびます。
また、3列1組のブロックの最初の列番号、1,4,7,・・・・「ブロック」とよびます。
子Dic は キーが 当該の名前の当該ブロック内での行番号になります。
また、データは、3要素 X 2 ブロック = 6 個の要素を持つ1次元配列です。

もう1つ、dicRow があります。
これは、当該の名前の当該ブロック内での行番号取得用のカウンターで、
キーが 名前/ブロック、データが 行番号です。
さて、処理は、各行、左から右に、各ブロックを処理していきます。
で、名前ごとに、それぞれ処理がされるのですが、それらの処理はセットする子Dicが
異なるだけで、どの名前でも同じですので、ここでは "加藤" を例に説明します。

1.まず、2行目の2ブロック目(4列目)の加藤が最初に処理されますね。
2.で、親Dic("加藤") には、まだ何も登録されていませんので、加藤用の子Dicを生成して
  ここにおさめます。
3.dicRow の 加藤/4 に 1を加算します。この時点では 加藤/4 の登録が無いので
  結果は 1 になります。
4.加藤用の子Dicのキーの 1 が、まだ生成されていないので、以下のイメージで
  初期化データをセットします。

+------+------+-----+------+------+-----+
|   |   |   |   |   |   |
+------+------+-----+------+------+-----+

5.で、ここに、今処理している2行目の加藤のデータ、加藤、花、4 をセットします。
 結果は以下のようになります。

+------+------+-----+------+------+-----+
|   |   |   |加藤 |花  |4  |
+------+------+-----+------+------+-----+

6.次に、加藤でいえば、4行目の1番目のブロック(列番号は1)のデータを処理します。
 既に親Dicには火等が登録されているので2.は行いません。
7.dicRow の 加藤/1 に 1を加算します。この時点では 加藤/1 の登録が無いので
  結果は 1 になります。
8.加藤用の子Dicの 1 は、4.で既に作成されていますので、4.は行いません。
9.すでにある子Dicのデータに4行目の1番目のブロックの加藤、花、4 をいれます。
 結果は以下のようになります。

+------+------+-----+------+------+-----+
|加藤 |花  |4  |加藤 |花  |4  |
+------+------+-----+------+------+-----+

10.4行目の2番目のブロック(4列目)の加藤、みかん、2 を処理します。
11.既に、親Dicはできているので、2.は行いません。
12.DicRowの加藤/4 に 1を加算します。この時のdicRowの加藤/4 は 3.で、
   既に 1 になっていますので、結果は 2 になります。
13.加藤用の子Dicのキーの 2 がまだ生成されていないので、以下のイメージで
   初期化データを追加します。

+------+------+-----+------+------+-----+
|   |   |   |   |   |   |
+------+------+-----+------+------+-----+

14.ここに、加藤、みかん、2 をセットします。
 結果は以下のようになります。

+------+------+-----+------+------+-----+
|   |   |   |加藤 |みかん|2  |
+------+------+-----+------+------+-----+

15.これまでの結果、加藤用の子Dic は以下の姿になっています。

+------+------+-----+------+------+-----+
|加藤 |花  |4  |加藤 |花  |4  |
+------+------+-----+------+------+-----+
|   |   |   |加藤 |みかん|2  |
+------+------+-----+------+------+-----+

16.他の名前についても、それどれ同様の処理がなされ、各名前の子Dicが
 できあがります。
17.親Dicから子Dicを順番に取り出し、できあがっている子Dicの配列イメージを
 順番にSheet2に落とし込みます。

【71724】Re:表の整理
お礼  ドカ  - 12/4/3(火) 7:59 -

引用なし
パスワード
   回答ありがとうございました。

アルゴリズムとコードの意味は、色々教えて頂きながら、自分でも考え続け、理解することが出来ました。
一般的なコードは、手作業をマクロに置き換えるというものが多いと思いますが、今回のコードはちょっと変わっていて、中々すごいと感じました。(私だけかもしれませんが)
辞書の中身をZ配列に入れたり、Z配列の値を辞書に戻したり、するところが、何となくしっくりこなかったのですが、何をやっているかようやく分かりすっきりしました。

【71725】Re:表の整理
質問  ドカ  - 12/4/3(火) 8:15 -

引用なし
パスワード
   ▼UO3 さん:
ちょっと教えていただきたいことがありまして、差し支えなければ、よろしくお願いいたします。

<質問1>
「子Dictionary」というものが作れるということを今回初めて知りました。
ネット上を調べても、「子Dictionary」という説明を見つけることが出来ません。

UO3さんはどうやって、「子Dictionary」なるものが作れると知ることが出来たのでしょうか?
専門書を買えば、普通に乗っていることなのでしょうか?

<質問2>
「Dictionary」に関しては、自分では使いこなせないのですが、以前から知っていました。
特に、超高速処理に向いているという認識です。
5分掛かるデータ処理も「Dictionary」を使えば1秒以下で出来てしまいます。

一方で、UO3さんの回答にある

>実は、大量データを相手に複雑なマッチング処理を行おうとすると、
>処理にふさわしい並び替え => その処理に必要な複雑なマッチングロジック
>これが最も処理時間が早くなるほうほうなんですが、初心者のみならず、このアルゴリズムを
>正確に実装するのは、大変に骨が折れます。

この記述ですが、本当なのでしょうか?
(失礼な聞き方ですみません)

このサイトでも、色々お願いして、高速処理にコードを変えてもらったことがありますが、「Dictionary」より速いコードを見たことがありません。
てっきりエクセルに備わっている機能を最初から使うほうが、自作コードより絶対的に速いと思っていたのですが、違うのでしょうか?

【71726】Re:表の整理
発言  UO3  - 12/4/3(火) 12:39 -

引用なし
パスワード
   ▼ドカ さん:

こんにちは
ご理解いただけたようでなによりです。

>UO3さんはどうやって、「子Dictionary」なるものが作れると知ることが出来たのでしょうか?

う〜ん、なんとなくです?
この例は、少なからず、掲示板などの回答としてでていますし、回答者さんの中には
「自分が発見した!皆さんもどうぞ」なんてコメントする人もいます。
その人は、本当に、ご自分で発見されたんでしょうし、同じく、私も発見?
Dictionaryに限らず、私の場合は、できる、できないの前に、
「こんなことができたらいいなぁ」とか「こんなふうにやるとできるべきなのになぁ」なんて考えます。
(VBAだけじゃなく、エクセルの操作でも)
で、やってみると、「おっ!できるじゃん」とか「あぁ、やっぱりだめだよね」
最初は、たとえばユーザーフォーム上のコンロロールを配列的に扱うために、
配列を用意して、そこに「クラス」を登録することからはじめたような記憶があります。
(この部分は、参考書に、よく記載されていますので)
で、思考(?)の順序としては
・クラスもオブジェクトだよね
・じゃぁ、配列にその他のオブジェクトをいれられたら、便利かも。
・で、やってみます。結果は「おっ!できるじゃん」です。
・そのあと、Dictionaryの中身の実態も配列だよね。じゃぁ、その中身にDictionaryというオブジェクトを
 いれることができれば、そのキーごとの「子Dictionary」を保持することができるんじゃないかな?
・で、やってみます。またまた結果は「おっ!できるじゃん」す。
こんな経緯でしたね。

私は手軽に扱えるので、もっぱら、この方法なんですが、本当のエキスパートさんになると
Dictionaryのデータという「窮屈」な場所をつかうのではなく、私が言う、「子Dictionary」を
「データ保持クラス」といった目的の「クラス」を生成して扱ってらっしゃいますね。
このほうが、どんな形のデータももてますし、データ処理のためのコードすら持てます。

>この記述ですが、本当なのでしょうか?

まず、Dictionary は「かなり速い」ツールです。でも、圧倒的に一番かというと、まずますという
ところなんだろうなと思います。「天才」ではなく「秀才」ですね。もちろん、「秀才」ですから
「並の人」より、光り輝いているんです。

たとえば、以下のサンプルは、きわめて単純なレイアウトをきわめて単純なロジックで扱った場合の
比較です。ほとんど差はありませんが、「秒以下」のところで「ほんの少し」差がでます。
これが、もっともっと複雑なレイアウトで複雑なマッチングロジックになると、極端な場合、
Dictionary処理が1.5倍ぐらいかかるケースもありえます。

・まず、DataGen を実行してください。
 これは、結構時間がかかりますけど、がまんしてください。
 Sheet1 のA列,B列に50,000行のランダムな値をセットします。
 Test1,Test2を実行すると、Sheet1 がかわってしまいますので、比較を公平にするために
 1つ実行したら、Sheet2 から セルの内容をSheet1 にコピペしてESCキーでリセットしてから
 もう1つを実行してください。

Sub TestGen()
  Dim i As Long
  Dim x As Long
  Dim z As Long
  
  With Sheets("Sheet1")
    .Cells.Clear
    For i = 1 To 50000
      x = Int((1000) * Rnd + 1)
      z = Int((100) * Rnd + 1)
      .Cells(i, "A").Value = "A" & Format(x, "0000")
      .Cells(i, "B").Value = z
    Next
    .Cells.Copy Sheets("Sheet2").Range("A1")
  End With
  
End Sub

Sub Test1()
  Dim dic As Object
  Dim c As Range
  Dim myTime As Double
  
  myTime = Timer   '計測開始
  
  Application.ScreenUpdating = False
  Set dic = CreateObject("Scripting.Dictionary")
  
  With Sheets("Sheet1")
    With .Range("A1", .Range("A" & .Rows.Count).End(xlUp))
      For Each c In .Cells
        If c.Value > dic(c.Value) Then dic(c.Value) = c.Offset(, 1).Value
      Next
    End With
    .Columns("C:D").Clear
    .Range("C1").Resize(dic.Count).Value = WorksheetFunction.Transpose(dic.keys)
    .Range("D1").Resize(dic.Count).Value = WorksheetFunction.Transpose(dic.items)
  End With
  Application.ScreenUpdating = True
  
  MsgBox Timer - myTime
  
End Sub

Sub Test2()
  Dim dic As Object
  Dim c As Range
  Dim myTime As Double
  Dim v() As Variant
  Dim k As Long
  Dim oldkey As Variant
  Dim maxNum As Long
  
  myTime = Timer   '計測開始
  
  Application.ScreenUpdating = False
  
  With Sheets("Sheet1")
    With .Range("A1", .Range("A" & .Rows.Count).End(xlUp))
      .Resize(, 2).Sort key1:=.Range("A1"), order1:=xlAscending
      ReDim v(1 To .Rows.Count, 1 To 2)
      For Each c In .Cells
        If c.Value <> oldkey Then
          k = k + 1
          maxNum = 0
        End If
        If c.Offset(, 1).Value > maxNum Then maxNum = c.Offset(, 1).Value
        oldkey = c.Value
        v(k, 1) = oldkey
        v(k, 2) = maxNum
      Next
    End With
    .Columns("C:D").Clear
    .Range("C1").Resize(k, 2).Value = v
  End With
  Application.ScreenUpdating = True
  
  MsgBox Timer - myTime

End Sub

【71727】Re:表の整理
発言  UO3  - 12/4/3(火) 12:44 -

引用なし
パスワード
   ▼ドカ さん:

↑ Test1,Test2 で何をやっているのかを書くのを失念していました。
A列のコードごとに、そのコード内のB列の値の最大値を取得して、C,D列に結果を表示します。
そんなの、CountIF とか Max とかつかったらできるんじゃ?
というのは、勘弁してください。
あくまで、Dictionary と Sort/Merge の比較ですので。

【71728】Re:表の整理
発言  UO3  - 12/4/3(火) 13:16 -

引用なし
パスワード
   ▼ドカ さん:

以下は、もっと極端に差がでます。

TestGen2 これは、先ほどのTestGenより、もっと時間がかかりますががまんしてください。
A列,B列にランダムな値をセットします。
で、そのA列からランダムに5つ、値を取り出して、D列におきます。

Test3,Test4 ともに、このD列の値を持つA列の行のB列の値をE列に転記します。

Sub TestGen2()
  Dim i As Long
  Dim x As Long
  
  With Sheets("Sheet1")
    .Cells.Clear
    For i = 1 To 50000
      x = Int((50000) * Rnd + 1)
      .Cells(i, "A").Value = "A" & Format(i, "0000")
      .Cells(i, "B").Value = x
    Next
    For i = 1 To 5
      x = Int((50000) * Rnd + 1)
      .Cells(i, "D").Value = .Cells(x, "A").Value
    Next
    
    .Columns("A:B").Sort key1:=.Range("B1"), order1:=xlAscending, Header:=xlNo
    .Cells.Copy Sheets("Sheet2").Range("A1")
  End With
  
End Sub

Sub Test3()
  Dim dic As Object
  Dim c As Range
  Dim myTime As Double
  
  myTime = Timer   '計測開始
  
  Application.ScreenUpdating = False
  Set dic = CreateObject("Scripting.Dictionary")
  
  With Sheets("Sheet1")
    With .Range("A1", .Range("A" & .Rows.Count).End(xlUp))
      For Each c In .Cells
        dic(c.Value) = c.Offset(, 1).Value
      Next
    End With
    
    For Each c In .Range("D1", .Range("D" & .Rows.Count).End(xlUp))
      c.Offset(, 1).Value = dic(c.Value)
    Next
  End With
  Application.ScreenUpdating = True
  
  MsgBox Timer - myTime
  
End Sub

Sub Test4()
  Dim c As Range
  Dim myTime As Double
  Dim myA As Range
  Dim x As Long
  
  myTime = Timer   '計測開始
  
  Application.ScreenUpdating = False
  
  With Sheets("Sheet1")
    With .Range("A1", .Range("A" & .Rows.Count).End(xlUp))
      .Resize(, 2).Sort key1:=.Range("A1"), order1:=xlAscending
      Set myA = .Columns(1)
    End With
    
    For Each c In .Range("D1", .Range("D" & .Rows.Count).End(xlUp))
      x = WorksheetFunction.Match(c.Value, myA)
      c.Offset(, 1).Value = .Range("B" & x).Value
    Next
  End With
  Application.ScreenUpdating = True
  
  MsgBox Timer - myTime

End Sub

【71731】Re:表の整理
発言  UO3  - 12/4/3(火) 16:54 -

引用なし
パスワード
   ▼ドカ さん:

こんにちは

↑でアップしたTest3とTest4の比較は、見る人が見たらインチキだといわれるかも
しれません。
Dictionaryのほうは、5万件のデータをDictionaryに取り込むところがあるわけで、
それに対して検索は5けんだけなので。

しかし、少なくとも Test1,Test2 の比較においては、差はあまりないといえども
Dictionaryのほうが、「やや遅い」わけで
>「Dictionary」より速いコードを見たことがありません。
似たいすr、1つの答えになっているのではと思います。

で、Test3,Test4の比較ですが、TestGen2 の 後半のループの
For i = 1 To 5 これを For i = 1 To 1000
このように検索実行の数を 5件から1000件にしてみましょう。
それでも、Test4 のほうが、かなり早いことがわかると思います。

で、もう一つ、その早い Test4 と極めてよく似たTest5を。
これは、同じように見えるロジックなのに、めちゃめちゃ遅いです。

なので、Dictionary は、常に、そんなに悪くない結果が得られる。
一方、自作のアルゴリズムは、そのデータに適した処理ロジックか否かで
遅くなったり、早くなったりする。
いわゆる職人の腕の見せ所という側面がでてきます。

Sub Test5()
  Dim c As Range
  Dim myTime As Double
  Dim myA As Range
  Dim x As Long
 
  myTime = Timer   '計測開始
 
  Application.ScreenUpdating = False
 
  With Sheets("Sheet1")
    With .Range("A1", .Range("A" & .Rows.Count).End(xlUp))
      Set myA = .Columns(1)
    End With
  
    For Each c In .Range("D1", .Range("D" & .Rows.Count).End(xlUp))
      x = WorksheetFunction.Match(c.Value, myA, 0)
      c.Offset(, 1).Value = .Range("B" & x).Value
    Next
  End With
  Application.ScreenUpdating = True
 
  MsgBox Timer - myTime

End Sub

【71736】Re:表の整理
お礼  ドカ  - 12/4/4(水) 15:59 -

引用なし
パスワード
   ▼UO3 さん:

回答ありがとうございました。

そうですね。クラスの概念を知っていれば、まさにクラスですね。
そもそもVBAでクラスが使えるなんて知りませんでした。
(もしかしたら、知っていたけど、忘れただけかもしれませんが)

Dictionaryと検索の比較ですが、
提示いただいたデータでは、若干Dictionaryが負けますが、実際の私の手元のデータでは、逆に若干Dictionaryのほうが速いです。
しかし、いずれにしても、若干であり、私にとっての実用性は差がありません。

以前、私が質問した”とにかくこのコードの処理スピードを速くしてほしい”というのは、まさに、今回、貴殿が提示していただいた処理内容でした。
当時は、Dictionaryを使わないコードを、色々な方から”これならどうだぁ!”と提示して頂きましたが、どれも、かなり遅いものでした。
その当時のコードを確認したところ、まさに、test5でした。

1818 / 13645 ツリー ←次へ | 前へ→
ページ:  ┃  記事番号:
2610219
(SS)C-BOARD v3.8 is Free