プロが教えるわが家の防犯対策術!

別の方の質問で私のコードについてやり取りがあり、その中で気になった点がありましたので質問を立てました。

元の質問:
https://oshiete.goo.ne.jp/qa/11698221.html

気になった点は私のコードの書き方が『VBAよりVB的』な物とのご意見がありましたので、その点について知りたいと思いました。
この書き方自体は18年程前にとあるサイトでのExcelVBAの質問に対する常連回答者さんの回答を参考にしています。
当時としては丁度VB.NET2002が出たかどうかで、まだVB6.0の頃だったと思います。

VB6.0であればDictionaryは”オブジェクト”であって”クラス”ではなかったと記憶します。
→VB6.0の経験が皆無なので間違いであればごめんなさい。

なので『VB的なコード』とはどの部分を指しているのでしょう?
VBで書くのであればもっと違う書き方を選択しますし。(特に2008以降ならLINQで)

それと東西線さんの要望に合わせてDictionaryで記載してましたので、速度は気にしてませんでした。
速度重視であればどのような方法があったのだろうと興味もあります。
私が東西線さんに対して回答してきたのはDictionaryとADODBの二通りなので、可能なら3つ目を知って自身の勉強に役立てたいと思っております。
なにせExcelは2002から一気に365に最近変更したので2007以降で追加された基本機能ならその名称だけでも構いません。あとは自力でなんとか頑張ります。


PS:東西線さんへ

元リンクで間違っている箇所は、

If Not Dic.Exists(R.Value) Then _
Dic.Add R.Value, Array(R.Range("B1"), 1) '変更した箇所

v = Dic(R.Value)
v(0) = v(0) + R.Offset(, 1)
v(1) = v(1) + 1
Dic(R.Value) = v

の所ですね。
Dictionaryにキーが登録されていなければキーとアイテムを追加しますが、その後ろで無条件にもう一度同じアイテムを加工して追加してます。(余分に増えているのは最初に見つかった数値だと思います)
この場合ならキーの存在の有無で処理を分ける必要があるので、

If Not Dic.Exists(R.Value) Then ' _はなし
Dic.Add R.Value, Array(R.Range("B1"), 1) '変更した箇所
Else '切り分ける
v = Dic(R.Value)
v(0) = v(0) + R.Offset(, 1)
v(1) = v(1) + 1
Dic(R.Value) = v
End If

こんな感じになるのではないかと。

質問者からの補足コメント

  • へこむわー

    やはりVBA(EXCEL)らしい方法って『品物』の列を別シートにコピペし重複を削除して必要箇所に貼り付ける。
    後の値はSUMIF関数やCOUNTIF関数を使う事なのでしょうかね。
    Dictionary自体不要な物と時代は流れたのでしょうか。

      補足日時:2020/06/12 07:58
  • つらい・・・

    上記補足の方法は20年前には行われていたので、この方法で作業を行なってきた人にしてみると1行ずつ調べるDictionaryはとても遅いと感じても仕方ないですよねぇ。
    当時余りにDictionaryが面白くてそっちのやり方だけに拘ったのが失敗なのかな。

      補足日時:2020/06/12 15:38

A 回答 (2件)

#1です



> ある質問者さんが使われてたRemoveDuplicates

それでも「出来る」例なのかと??

例えば、
元データが 100 行、重複要素 10 個 とかであれば、
重複要素 10 個の隣に、SUMIF なり COUNTIF ??
それぞれの式では、必ず元データ 100 行処理するわけで・・・

元データ 10 万行、重複要素 1万 個 なら・・・

データ量によって処理時間は大きく変わってきますね
「出来る」と「使える・使えない」・・・実感するかと

提示した Samp1 Samp2 は、元データをなめるのは 1 巡だけなので・・・

VBA を提示する際には、想定データ量が必要ですね??

でも、少ないデータ量の時から Samp1 等に慣れておくと、
データ量が多くなっても、速さで困ることは無いかと・・・??
    • good
    • 1
この回答へのお礼

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

>RemoveDuplicates

につきましてはその内に検証してみたいかなと思います。

データ量って点では今はそう言った業務も外れた関係もあり、尚且つダミーで準備するのも億劫になってしまって・・・・
何かサンプルデータ郡を探してみますかね・・・
郵政省の郵便番号データはそれなりにありますので、抽出する列に気を付けて適度に重複削除を検証し実行してみます。
他に何かないものですかね。気象庁辺りかな?

No.1のお礼で漏れてしまいましたが。

>R.Range("B1") → R.Range("B1").Value と、「値」にしておくとか

どうも若い方なのかプロパティを記載せず回答しているのを見かけます。
昭和の私には時代の変化なのかと気にもなりますので過去には指摘投稿しましたが削除対象になりました。(これも誹謗中傷なの?)
私が覚え始めた時は『プロパティは必ずつけること』と教わりましたし、何より他言語ではエラーの元にもなりますし。
見ていて『値が欲しいのかオブジェクトを指定しているのか・・・?』と気になってましたが、指摘できないので放置です。
あくまで自分が回答する時にはきちんと付けようにしてます。

頂いたサンプルコードにつきましてはダミーで検証の上次回回答時には使えるようになっていたいものです。
しかし雑誌で『PythonによるExcel操作』云々の記事を最近見かけた際、Pythonの方が速いのかな?と気にもなるこの頃です。

お礼日時:2020/06/13 10:45

> 『VB的なコード』とはどの部分を指しているのでしょう?



これについてはわかりません

> Dictionaryが面白くてそっちのやり方だけに拘ったのが失敗なのかな。
> Dictionary自体不要な物と時代は流れたのでしょうか。

そんなこと、無いと思いますよ
遅い VLOOKUP より使い道あると思いますけど・・・
クロス集計もやり易くなったりしますね・・・


ただ、Dictionary の使い方も色々あって
1) 情報を Dictionary にため込む
2) 位置だけを覚えさせる
等々

提示されていたのは 1) の方法ですね
2) の方法にしてみると
対象データを読み込んだ変数内で詰め直すことを・・・
どこに詰め直したかを Dictionary に覚えておくだけにしてみると
例えば


Public Sub Samp1()
  Dim dic As Object
  Dim vA As Variant
  Dim i As Long, k As Long, n As Long

  Set dic = CreateObject("Scripting.Dictionary")

  With Range("A2", Cells(Rows.Count, "A").End(xlUp))
    vA = .Resize(, 3).Value
  End With

  n = 0
  For i = 1 To UBound(vA)
    k = dic(vA(i, 1))
    If (k = 0) Then
      n = n + 1
      dic(vA(i, 1)) = n
      vA(n, 1) = vA(i, 1)
      vA(n, 2) = vA(i, 2)
      vA(n, 3) = 1
    Else
      vA(k, 2) = vA(k, 2) + vA(i, 2)
      vA(k, 3) = vA(k, 3) + 1
    End If
  Next

  If (n > 0) Then
    Range("F2").Resize(n, 3).Value = vA
  End If

  Set dic = Nothing
End Sub


この方法にしてみると、
データの入手・書き出し、各1回で高速化が見込めたり・・・

なお、Array(R.Range("B1"), 1)
ここで注意すべきは、配列添え字と配列の中身
Option Base 1 があっても無くても動くように記述しておくとか、
R.Range("B1") → R.Range("B1").Value と、「値」にしておくとか

ちなみに、1) の方法で書き出し回数をそれなりに減らしてみると
以下の様に記述できたり・・・

Public Sub Samp2()
  Dim dic As Object
  Dim vA As Variant, v As Variant, vT(1 To 2) As Variant
  Dim i As Long

  Set dic = CreateObject("Scripting.Dictionary")

  With Range("A2", Cells(Rows.Count, "A").End(xlUp))
    vA = .Resize(, 2).Value
  End With

  For i = 1 To UBound(vA)
    v = dic(vA(i, 1))
    If (IsArray(v)) Then
      v(1) = v(1) + vA(i, 2)
      v(2) = v(2) + 1
    Else
      v = vT
      v(1) = vA(i, 2)
      v(2) = 1
    End If
    dic(vA(i, 1)) = v
  Next

  i = dic.Count
  If (i > 0) Then
    With Range("F2").Resize(i)
      .Value = WorksheetFunction.Transpose(dic.Keys)
      With .Columns(2).Resize(, 2)
        .Value = WorksheetFunction.Transpose( _
          WorksheetFunction.Transpose(dic.Items))
      End With
    End With
  End If

  Set dic = Nothing
End Sub


※ バージョンによっては
Transpose で扱える配列の大きさに制限があったり・・・
    • good
    • 1
この回答へのお礼

ご回答ありがとうございます。
正直無理かなぁと諦めてました。

確かに提示されているコードはこちらでは見かける機会が多いです。
セル範囲への書き出しを二次元配列変数に任せると言う点をVBA的と考えると、確かに私の方法はVB的に見えなくもないですかね。
VBAを数年中断している間にVB・VC#を弄ってましたので、そこの影響もあって書き方が固定化されたのかもです。

先にも書きましたが化石化してたXPが仮死状態になったのを機にOfficeを365に切り替え、最初に驚いたのはある質問者さんが使われてたRemoveDuplicatesメソッドですね。
Dictionaryによって重複削除処理を行ってたのが、使ってない別シートに重複のない値を取り出したい列をコピペし重複削除を行い処理後に元のシート等に戻す事でワークシート関数で処理
出来ちゃうのですから。
漬物石のように固まってた脳ミソがほぐれた感じでした。

お礼日時:2020/06/13 09:00

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!

このQ&Aを見た人はこんなQ&Aも見ています


このQ&Aを見た人がよく見るQ&A

人気Q&Aランキング