[[20091113200048]] 『VBAで選択範囲を検索してコピー&ペースト』(テン) ページの最後に飛ぶ

[ 初めての方へ | 一覧(最新更新順) | 全文検索 | 過去ログ ]

 

『VBAで選択範囲を検索してコピー&ペースト』(テン)
 セルA1から下セルへ
  A
  1
  2 
  3
  4
  5
  6
  7
  8
  9
 10
 と数字が並んでいます(必ずしも連続データ状にはなっていません)
 また、AA1セルとそれの隣のAB1セルより下には
  AA  AB
  1   12
  2    3 
  3   47
  5   21
  6    2
  7    1
 10   32 
 とデータが並んでいます

 この時A列の数字に一致するデータをAA列から検索して
 B列C列にAA列AB列のデータを記録したいのです。今回の例では
  A   B    C  
  1   1   12
  2   2    3
  3   3   47 
  4   
  5   5   21 
  6   6    2 
  7   7    1 
  8
  9
 10  10   32

 こういった具合にVBAにより当てはめていきたいのです。
 どうかよろしくお願いいたします。

 数式をVBAで設定しただけですけど・・・・。

 Sub test()
    Dim ra As Range
    Dim frm As String
    Dim raa As Range
    Dim raaab As Range
    Set ra = Range("a1", Cells(Rows.Count, "a").End(xlUp))
    Set raa = Range("aa1", Cells(Rows.Count, "aa").End(xlUp))
    Set raaab = raa.Resize(, 2)
    frm = "=IF(ISERROR(MATCH(" & ra.Address & "," & raa.Address & ",0)),"""",INDEX(" & _
                 raaab.Address & ",MATCH(" & ra.Address & "," & raa.Address & ",0),COLUMN()-1))"
    With ra.Offset(0, 1).Resize(, 2)
       .FormulaArray = frm
       .Value = .Value
    End With

 End Sub

 ichinose


 ichinoseさん、返信ありがとうございます。テンです。
 あれから一晩考えてみたところ、次の記述で自己解決できたのかな?と思いました。
 参考までに載せてみます。
 Sub test()
 i = Selection(1).Row
 j = Selection(1).Column
 k = Selection(Selection.Count).Row

  For m = i To k
   For y = 1 To 30
    If Cells(y, 1) = Cells(m, j) Then

     Cells(y, 2) = Cells(m, j)
     Cells(y, 2 + 1) = Cells(m, j + 1)

    End If
  Next y
  Next m
 End Sub

 すばやい回答をしていただいたichinoseさん有難うございました!

 解決されたようですが、せっかくの題材なので、もうちょっと掘り下げて見ました。
 私の前回の投稿は、取りあえず取り消し・・(動作はするはずですが・・・)。
 検索手法として、掘り下げてみましょう。
 >次の記述で自己解決できたのかな?と思いました。
 はい、細かいことはさておき、逐次検索のロジックは出来ていますね!!
 A列のデータ件数が30件程度で AA列のデータ件数の数10件程度なら、どのような
 アルゴリズムでもそれ程のストレスは感じずに処理はされます。

 が、A列データ件数が5000件で AA列のデータ件数も2000件程ある場合で検証してみます。

 新規ブックの標準モジュール(Module1)に

 '====================================================================
 Sub sample_data()
    With Range("a1:a5000")
       .Formula = "=row()*3-1"
       .Value = .Value
    End With
    With Range("aa1:ab2000")
       .Formula = Array("=row()*2-1", "=int(rand()*10000)+1")
       .Value = .Value
    End With
 End Sub

 適当なシートをアクティブにして、上記sample_dataを実行してみてください。
 A列、AA列及び、AB列にサンプルデータが作成されます。
 このデータで色々なコードで処理速度を検証してみました。
 尚、今回はExcelの関数等は使わないことにしました。これを使えば、結果は違ってくるでしょうが・・。

 まず、テンさんのコード。ちょっとだけ修正して・・・。
 標準モジュールに(Module2)、

 '=====================================================================
 Sub test()
 Dim st As Double
 Dim i As Long
 Dim j As Long
 Dim k As Long
 Dim y As Long
 Dim m As Long
 Dim l As Long
 Range("b:c").ClearContents
 Application.ScreenUpdating = False
 st = [now()]
 l = Range("a" & Rows.Count).End(xlUp).Row
 i = Range("aa1", Cells(Rows.Count, "aa").End(xlUp)).Row
 j = Range("aa1", Cells(Rows.Count, "aa").End(xlUp)).Column
 k = Range("aa1", Cells(Rows.Count, "aa").End(xlUp)).Rows.Count
 st = [now()]
  For m = i To k
   For y = 1 To l
     If Cells(y, 1) = Cells(m, j) Then

     Cells(y, 2) = Cells(m, j)
     Cells(y, 2 + 1) = Cells(m, j + 1)

     End If
   Next y
  Next m
 Application.ScreenUpdating = True
 MsgBox Application.Text([now()] - st, "hh:mm:ss.00")
 End Sub

 上記のtestを実行すると、私の環境で約9分程度かかりました。

 これは、検索ロジックが逐次検索(AA1のデータがA列のどこにあるかを検索するのに
 1行目が順々に照らし合わせていく方法)を使っている事とFor〜Next内でCellsを使っている事が
 時間のかかっている大きな要因になっています。

 これを逐次検索の手法は変えずにデータのやり取りを内部変数を使って行なうと・・・、

 標準モジュール(Module3)に

 '========================================================================
 Sub test2()
    Dim st As Double
    Dim ra As Variant
    Dim raaab As Variant
    Dim aaRW As Long
    Dim aRW As Long
    Dim rbc() As Variant
    Range("b:c").ClearContents
    st = [now()]
    ra = Range("a1", Cells(Rows.Count, "a").End(xlUp)).Value
    ReDim rbc(1 To UBound(ra), 1 To 2)
    raaab = Range("aa1", Cells(Rows.Count, "aa").End(xlUp)).Resize(, 2).Value
    For aaRW = LBound(raaab) To UBound(raaab)
       For aRW = LBound(ra) To UBound(ra)
          If ra(aRW, 1) = raaab(aaRW, 1) Then
             rbc(aRW, 1) = raaab(aaRW, 1)
             rbc(aRW, 2) = raaab(aaRW, 2)
          End If
       Next
    Next
    Range("b1:c" & UBound(rbc)).Value = rbc()
    MsgBox Application.Text([now()] - st, "hh:mm:ss.00")
 End Sub

 とすると、8秒まで処理時間が縮まりました。
 逐次検索の手法でもうまく内部変数を使えば、まあまあの処理時間まで短縮できますね!!

 これを二分探索のアルゴリズムを使って処理すると・・・・、

 標準モジュール(Module4)に

 '===================================================================
 Sub Binary_Search()
    Dim st As Double
    Dim ra As Range
    Dim ans As Long
    Dim raaab As Variant
    Dim aaRW As Long
    Dim aRW As Long
    Dim rbc() As Variant
    Range("b:c").ClearContents
    st = [now()]
    Set ra = Range("a1", Cells(Rows.Count, "a").End(xlUp))
    ReDim rbc(1 To ra.Rows.Count, 1 To 2)
    raaab = Range("aa1", Cells(Rows.Count, "aa").End(xlUp)).Resize(, 2).Value
    Call open_tbl(ra, 1, 0)
    For aaRW = LBound(raaab) To UBound(raaab)
       If gets_tbl(raaab(aaRW, 1), ans) = 0 Then
          rbc(ans, 1) = raaab(aaRW, 1)
          rbc(ans, 2) = raaab(aaRW, 2)
       End If
    Next
    Range("b1:c" & UBound(rbc)).Value = rbc()
    Call close_tbl
    MsgBox Application.Text([now()] - st, "hh:mm:ss.00")
 End Sub

 別の標準モジュール(Module5)に二分探索を行なうプロシジャー群

 '==================================================================================
 Option Explicit
 Private r_array() 'セル範囲をこの配列にセット
 Private r_col As Long '配列の検索キー列番号
 Private r_type As Long '検索列の比較の型
 '  0:数値
 '  1以上:文字列右寄せ比較の場合、数値は、最大文字数
 Private wk1, wk2 'ワーク変数
 '==================================================================================
 Sub open_tbl(rng As Range, key As Long, ktype As Long)
 ' セル範囲を配列にセットし、内部変数を初期化する
 'input : rng --検索対象セル範囲
 '     key---配列内の検索キーとして、列番号
 '         ktype--比較の型と最大文字数
   Call close_tbl
   r_array() = rng.Value
   r_col = key
   r_type = ktype
   If ktype > 0 Then
      wk1 = Space$(ktype)
      wk2 = Space$(ktype)
      End If
 End Sub
 '==================================================================================
 Function gets_tbl(f_val As Variant, ans As Long) As Long
 'r_aaray()配列のr_colで指定された列をf_valの値で検索しその位置を返す
 'input : f_val-----検索値
 'output: gets_tbl----0:検索できた 1:検索値はみつからない
 '     ans-----検索された配列行位置
   Dim r_st As Long
   Dim r_ed As Long
   Dim r_bin As Long
   r_st = LBound(r_array(), 1)
   r_ed = UBound(r_array(), 1)
   gets_tbl = 1
   Do Until r_st > r_ed
      r_bin = Int((r_st + r_ed) / 2)
      If r_type > 0 Then
         RSet wk1 = f_val
         RSet wk2 = r_array(r_bin, r_col)
      Else
         wk1 = f_val
         wk2 = r_array(r_bin, r_col)
         End If
      If wk1 = wk2 Then
         ans = r_bin
         gets_tbl = 0
         Exit Do
      ElseIf wk1 < wk2 Then
         r_ed = r_bin - 1
      Else
         r_st = r_bin + 1
         End If
      Loop
 End Function
 '==================================================================================
 Sub close_tbl()
   Erase r_array()
 End Sub

 これでBinary_Searchを実行してみてください。私の環境で0.05秒でした。
 二分探索については、詳しいアルゴリズムは、検索すれば見つかりますから、調べてみてください。

 Dictionaryというオブジェクトがあります。中のアルゴリズムを知っているわけではないので、
 これをハッシュ検索と呼ぶには、疑問もありますが、ハッシュと呼んでいる人もいるようなので
 Dictionaryを使って、ハッシュ検索で試してみると・・・、

 標準モジュール(Module6)に

 '==============================================================================
 Sub ハッシュ検索()
    Dim dic As Object
    Dim aaRW As Long
    Dim st As Double
    Dim ra As Range
    Dim frm As String
    Dim rarray As Variant
    Dim raaab As Variant
    Range("b:c").ClearContents
    st = [now()]
    Set dic = CreateObject("scripting.dictionary")
    For Each ra In Range("a1", Cells(Rows.Count, "a").End(xlUp))
       dic.Add CStr(ra.Value), Array("", "")
    Next
    raaab = Range("aa1", Cells(Rows.Count, "aa").End(xlUp)).Resize(, 2).Value
    For aaRW = LBound(raaab) To UBound(raaab)
       If dic.Exists(CStr(raaab(aaRW, 1))) Then
          rarray = dic.Item(CStr(raaab(aaRW, 1)))
          rarray(0) = raaab(aaRW, 1)
          rarray(1) = raaab(aaRW, 2)
          dic.Item(CStr(raaab(aaRW, 1))) = rarray
       End If
    Next
    Range("a1", Cells(Rows.Count, "a").End(xlUp)).Offset(0, 1).Resize(, 2).Value = _
            Application.Transpose(Application.Transpose(dic.Items))
    Set dic = Nothing
    Set ra = Nothing
    MsgBox Application.Text([now()] - st, "hh:mm:ss.00")
 End Sub

 上記の ハッシュ検索 を実行すると、私の環境で 0.64秒でした。

 二分探索の場合、A列のデータが整列していなければならないという制約がありますが、
 ハッシュ検索の場合は、その制約がないので、処理時間の差だけでは判断できません。
 尚、ハッシュ検索についてもネットの検索で意味は理解できると思います。

 逐次検索、二分探索、ハッシュ検索(?)と記述しましたが、一長一短はありますから、
 データ量によって、使い分けるのが良いと思います。
 Officeを使っていれば、このような検索も別の方法がいくらでもあるので、
 今回提示したようなコードを記述することは殆どないのですが、
 プログラマー1年目で習いますから、上記の三つの手法ぐらいは、覚えておいても
 損はないと思いますよ!!

 時間があったら試してみてください。

 ichinose


 コメントありがとうございます。
 ichinoseさん、レベルが高過ぎて古文書にしか見えません^^;
 しかし私のロジックで9分かかるところが最速では0.05秒ですか。。。
 素人に産毛が生えたような私ですがかなり興味を惹かれました!
 コメント何度も読み直して、これをきっかけにプログラムの理解を少しでも深めてみます。
 ichinoseさん丁寧な解説コメントどうもありがとうございました!!!

 テン

コメント返信:

[ 一覧(最新更新順) ]


YukiWiki 1.6.7 Copyright (C) 2000,2001 by Hiroshi Yuki. Modified by kazu.