2010年5月30日日曜日

【Excel】サブフォルダ一覧【VBA】

 対象フォルダのサブフォルダ一覧をワークシートに出力するコード。
Sub MakeFolderList()
    Set sf = CreateObject("Scripting.FileSystemObject").GetFolder("C:\").SubFolders
    Dim x: ReDim x(sf.Count, 1)
    For Each f In sf
        x(i, 0) = f.Name
        i = i + 1
    Next
    Range("A1:A" & sf.Count) = x
End Sub
 対象フォルダを変えたければ"C:\"の所を書き換えればOK。
 もしA1セル以下ではなく、C3セル以下にリストを出力したい場合は、最後の行を"Range("C3:C" & sf.Count + 2) = x"のように書き換えればOK。

 このコードを考えるにあたって、サンプルとして一万個のダミーフォルダを作成・削除したり、処理時間を測定したのが以下。
Public StartTime As Double
Public EndTime As Double
Const TargetFolder = "C:\test"

Sub TimerStart()
    StartTime = Timer
End Sub

Sub TimerStop()
    EndTime = Timer
    MsgBox (EndTime - StartTime & " 秒")
End Sub

Sub MakeDummyFolder()
    Call TimerStart         '---- 測定開始
    Set fs = CreateObject("Scripting.FileSystemObject")
    For i = 0 To 9999
        fs.CreateFolder (TargetFolder & _
                         "\_dummy_" & _
                         Format(i, "0000"))
    Next i
    Set fs = Nothing
    Call TimerStop          '---- 約3.5~10秒
End Sub

Sub MakeFolderListA()
    [A:A].Clear
    Call TimerStart         '---- 測定開始
    Set fs = CreateObject("Scripting.FileSystemObject")
    Set sf = fs.GetFolder(TargetFolder).SubFolders
    i = 1
    For Each f In sf
        Cells(i, 1) = f.Name
        i = i + 1
    Next
    Set sf = Nothing
    Set fs = Nothing
    Call TimerStop          '---- 約0~8秒???
                            'フォルダがなければ当然0秒
End Sub

Sub MakeFolderListB()
    [B:B].Clear
    Call TimerStart         '---- 測定開始
    Set fs = CreateObject("Scripting.FileSystemObject")
    Set sf = fs.GetFolder(TargetFolder).SubFolders
    Dim x As Variant
    ReDim x(sf.Count, 1)  '<-- 配列のリサイズ
    For Each f In sf
        x(i, 0) = f.Name
        i = i + 1
    Next
    Range("B1:B" & sf.Count) = x
    Set sf = Nothing
    Set fs = Nothing
    Call TimerStop          '---- 約0~6秒???
                            'Aより少しだけ速い
End Sub

Sub DeleteFolders()
    Call TimerStart         '---- 測定開始
    Set fs = CreateObject("Scripting.FileSystemObject")
    Set sf = fs.GetFolder(TargetFolder).SubFolders
    For Each f In sf
        If f.Name Like "*_dummy_*" Then
            f.Delete      '<-- ※注意!フォルダ全削除
        End If
    Next
    Set sf = Nothing
    Set fs = Nothing
    Call TimerStop          '---- 約4~10秒
End Sub

2010年5月29日土曜日

月配列R_20100506

 ゴールデンウィーク中にぐちゃぐちゃにいじって、ゴールデンウィークが明けたら割と普通に戻りました。
 普通と言っても、何と比べての普通だよ!って話ですけどw。


 それなりに不満なく使えています。

2010年5月26日水曜日

【速度対決】10万セルに値を書き出す【VBA】

 A1:CV1000(1000行×100列=10万セル)に値を書き出してみます。

Public StartTime As Double
Public EndTime As Double

Sub TimerStart()
    StartTime = Timer
End Sub

Sub TimerStop()
    EndTime = Timer
    MsgBox (EndTime - StartTime & " 秒")
End Sub

Sub Sample01()
    [a1:cv1000].Clear
    Call TimerStart         '---- 測定開始
    For r = 1 To 1000
        For c = 1 To 100
            '-- セルを一つずつ呼び出し
            Cells(r, c) = r
        Next c
    Next r
    Call TimerStop          '---- 約33.8秒
End Sub

Sub Sample02()
    [a1:cv1000].Clear
    Call TimerStart         '---- 測定開始
    '-- 画面の更新を停止
    Application.ScreenUpdating = False
    For r = 1 To 1000
        For c = 1 To 100
            '-- セルを一つずつ呼び出し
            Cells(r, c) = r
        Next c
    Next r
    Application.ScreenUpdating = True
    Call TimerStop          '---- 約21.4秒
End Sub

Sub Sample03()
    [a1:cv1000].Clear
    Call TimerStart         '---- 測定開始
    Dim x(1 To 1000, 1 To 100)
    For r = 1 To 1000
        For c = 1 To 100
            '-- あらかじめ配列に代入
            x(r, c) = r
        Next c
    Next r
    '-- 配列の内容をセルへ一気に書き出し
    [a1:cv1000] = x
    Call TimerStop          '---- 約0.13秒
End Sub

 Sample01ではCells()で各セルに直接アクセス。
 Sample02はSample01と同じコードで画面の更新を停止。
 Sample03ではあらかじめ配列へ代入した後にセルへ一気に書き出しています。
 一番遅いSample01でも、1万セルまでであれば3秒程度で終了しますので、1万セルを境にコードの最適化を考えれば十分かも知れません。

【速度対決】1を100万回足してみる【VBA】

 1を100万回足すコードを少しずつ書き換えて速度比較をしてみました。
 セルのA1:A10000に入力された数字を全て合計する処理を100回繰り返しています。

Public StartTime As Double
Public EndTime As Double

Sub TimerStart()
    StartTime = Timer
End Sub

Sub TimerStop()
    EndTime = Timer
    MsgBox (EndTime - StartTime & " 秒")
End Sub

Sub Syokika()
    [a1:a10000].Clear
    [a1:a10000] = 1
End Sub

Sub Sample01()
    Call Syokika
    Call TimerStart         '---- 測定開始
    For i = 1 To 100
        For Each a In [a1:a10000] '<-- この書き方がNG
            ans = ans + a
        Next
    Next i
    Call TimerStop          '---- 約7秒
    MsgBox Format(ans, "#,###")
End Sub

Sub Sample02()
    Call Syokika
    Call TimerStart         '---- 測定開始
    For i = 1 To 100
        '-- 代入を使うだけで速度が15倍
        x = [a1:a10000]
        For Each a In x
            ans = ans + a
        Next
    Next i
    Call TimerStop          '---- 約0.45秒
    MsgBox Format(ans, "#,###")
End Sub

Sub Sample03()
    Call Syokika
    Call TimerStart         '---- 測定開始
    '-- 代入を繰り返し文の外に出すとさらに速くなる
    x = [a1:a10000]
    For i = 1 To 100
        For Each a In x
            ans = ans + a
        Next
    Next i
    Call TimerStop          '---- 約0.24秒
    MsgBox Format(ans, "#,###")
End Sub

Sub Sample04()
    Call Syokika
    Call TimerStart         '---- 測定開始
    Set wf = Application.WorksheetFunction
    For i = 1 To 100
        '-- さらにワークシート関数を使ってみる
        ans = ans + wf.Sum([a1:a10000])
    Next i
    Call TimerStop          '---- 約0.045秒!!!
    MsgBox Format(ans, "#,###")
End Sub

Sub Sample05()
    Call Syokika
    Call TimerStart         '---- 測定開始
    Set wf = Application.WorksheetFunction
    '-- さらに配列に代入してみる
    x = [a1:a10000]
    For i = 1 To 100
        ans = ans + wf.Sum(x)
    Next i
    Call TimerStop          '---- 約0.3秒
                            '遅なっとるやんけ_| ̄|○
    MsgBox Format(ans, "#,###")
End Sub

Sub Sample06()
    Call Syokika
    Call TimerStart         '---- 測定開始
    Set wf = Application.WorksheetFunction
    '-- オブジェクトを代入
    Set x = [a1:a10000]
    For i = 1 To 100
        ans = ans + wf.Sum(x)
    Next i
    Call TimerStop          '---- 約0.045秒!!!
    MsgBox Format(ans, "#,###")
End Sub

Sub Sample07()
    Call Syokika
    Call TimerStart         '---- 測定開始
    Set wf = Application.WorksheetFunction
    '-- あるいはアドレスを文字列で代入
    s = "a1:a10000"
    For i = 1 To 100
        ans = ans + wf.Sum(Range(s))
    Next i
    Call TimerStop          '---- 約0.045秒!!!
    MsgBox Format(ans, "#,###")
End Sub

 Sample02とSample03の違いはコードを書いている段階ですぐに気付きそうですが、Sample01のようなコードは僕なんかはついつい気付かずに書いてしまいそうです。
 Sample04はご存知Excel VBA最強のHuck。ワークシート関数の速さは圧倒的です。
 じゃあ配列に代入したSample05ならどうよ?と考えたのですが、逆に遅くなりました。「セルの内容を配列に格納して処理」と、「ワークシート関数」を組み合わせれば最強コンビになるかと思えば然に非ず。(あと、Sample05はExcel2002だと動きましたが、Excel2000だとエラーが出ました。)
 以上のことから、ワークシート関数を活用しつつ範囲を動的に変えたい場合はSample06 or 07のように書けば良い事になります。

2010年5月25日火曜日

【速度対決】Date vs 固定値【VBA】

 Date関数と固定値("2010/05/25")の比較。

Public StartTime As Double
Public EndTime As Double

Sub TimerStart()
    StartTime = Timer
End Sub

Sub TimerStop()
    EndTime = Timer
    MsgBox (EndTime - StartTime & " 秒")
End Sub

Sub Sample01()
    Call TimerStart         '---- 測定開始
    For i = 1 To 1000000
        buf = Date
    Next i
    Call TimerStop          '---- 約0.9秒
End Sub

Sub Sample02()
    Call TimerStart         '---- 測定開始
    For i = 1 To 1000000
        buf = "2010/05/25"
    Next i
    Call TimerStop          '---- 約0.17秒
End Sub

Sub Sample03()
    Call TimerStart         '---- 測定開始
    d = Format(Date, "yyyy/mm/dd")
    For i = 1 To 1000000
        buf = d
    Next i
    Call TimerStop          '---- 約0.17秒
End Sub

 100万回実行させて、
・Date -> 約0.9秒
・固定値 -> 約0.17秒
で固定値の勝ち。これは当たり前かw。
 つまり実際に使用する際は、Sample03のようにループ文の外で一回だけ代入すれば良いと言う事になります。