Quantcast
Channel: t-hom’s diary
Viewing all articles
Browse latest Browse all 493

ExcelでネストしたIf関数をVBAでインデントして分析しやすくする

$
0
0

ExcelでIF関数を使うと条件によっていろいろと処理を変えることができる。
複雑な条件はIF関数を組み合わせることで実現できるが、やりすぎるとすごく見づらい。

たとえば、以下のような式を作ってみた。

=IF(条件,IF(条件,IF(条件,TRUE,FALSE),IF(条件,IF(条件,FALSE,TRUE),FALSE)),FALSE)

ネストしすぎて解析が困難になっている。

これを解析用にインデントしてみると、このような構造になっていることがわかる。
f:id:t-hom:20170820115954p:plain

VBAと違ってElse文はないけれど、このようにインデントすれば真と偽の対応がわかりやすい。
【注意】解析用のインデントなので、最終的にはタブと改行を削除して元に戻さないと式として使えない。

今回はこの改行とインデントを自動で行うマクロを作ってみた。
技術的にはコンパイラの字句解析を応用したものだ。
これは説明するとすごく長くなるので割愛し、コードだけ貼り付けておく。
※末尾に参考文献を貼り付けておくので興味がある方はどうぞお買い求めください。

コード

準備するモジュールは4つある。

標準モジュール1つ

  • Module1

クラスモジュール3つ

  • Expression
  • Token
  • Stack

Module1のコード

プログラムのエントリーポイント(開始場所)はMainプロシージャである。

PublicEnum TokenType
    Target 'If
    BeginParen '(
    EndParen   ')
    Comma  ',
    Other
EndEnumPublicEnum CharType
    Alphabet
    Number
    BeginParen
    EndParen
    Comma
    DoubleQuote
    Other
EndEnumFunction GetCharType(c)As CharType
    Dim ret As CharType
    SelectCaseAsc(c)CaseAsc("a")ToAsc("z"),Asc("A")ToAsc("Z")
        ret = CharType.Alphabet
    CaseAsc("0")ToAsc("9")
        ret = CharType.Number
    CaseElseSelectCaseTrueCase c ="(": ret = CharType.BeginParen
            Case c =")": ret = CharType.EndParen
            Case c =",": ret = CharType.Comma
            Case c ="""": ret = CharType.DoubleQuote
            CaseElse
                ret = CharType.Other
        EndSelectEndSelect
    GetCharType = ret
EndFunctionFunction IsIn(target_,ParamArray check())AsBooleanDim i AsLong, ret AsBoolean: ret =FalseFor i =LBound(check)ToUBound(check)
        ret = ret Or check(i)= target_
    Next
    IsIn = ret
EndFunctionSub Main()Dim targetExpression As Expression: Set targetExpression =New Expression
    targetExpression.ExpressionString =InputBox("数式を入力してください。")Dim tokens As Collection
    Set tokens = GetTokens(targetExpression)Dim t As Token
    
    Dim st As Stack: Set st =New Stack
    Dim tabCount AsLongFor i =1To tokens.Count
        Debug.Print tokens(i).tString;
        SelectCase tokens(i).tType
            Case TokenType.BeginParen
                If tokens(i -1).tType = TokenType.Target Then
                    st.Push True
                    tabCount = tabCount +1Else
                    st.Push FalseEndIfCase TokenType.EndParen
                If st.Pop Then tabCount = tabCount -1Case TokenType.Comma
                If st.Top Then
                    Debug.Print
                    Debug.PrintString(tabCount,vbTab);
                EndIfCaseElseEndSelectNextEndSubFunction GetTokens(targetExpression As Expression)As Collection
    Dim ret As Collection: Set ret =New Collection
    
    Dim t As Token
    DoWhile targetExpression.hasNext
        Set t =New Token
        t.tString = targetExpression.getNext
        SelectCase GetCharType(t.tString)Case CharType.Alphabet
                DoWhile IsIn(GetCharType(targetExpression.checkNext), CharType.Alphabet, CharType.Number)
                    t.AddChar targetExpression.getNext
                LoopCase CharType.Number
                DoWhile GetCharType(targetExpression.checkNext)= CharType.Number
                    t.AddChar targetExpression.getNext
                LoopCase CharType.DoubleQuote
                DoWhile GetCharType(targetExpression.checkNext)<> CharType.DoubleQuote
                    t.AddChar targetExpression.getNext
                Loop
                t.AddChar targetExpression.getNext
            Case CharType.BeginParen
            Case CharType.EndParen
            Case CharType.Comma
            Case CharType.Other
        EndSelect
        ret.Add t
    LoopSet GetTokens = ret
EndFunction

Expressionクラスのコード

Public ExpressionString
Private cursor
PrivateSub Class_Initialize()
    cursor =1EndSubFunction hasNext()AsBoolean
    hasNext =Len(ExpressionString)> cursor -1EndFunctionFunction getNext()AsString
    getNext =Mid(ExpressionString, cursor,1)
    cursor = cursor +1EndFunctionFunction checkNext()AsStringIf hasNext Then
        checkNext =Mid(ExpressionString, cursor,1)ElseMsgBox"error"EndIfEndFunctionSubReset()
    cursor =1EndSub

Tokenクラスのコード

Public tString
Sub AddChar(c)
    tString = tString & c
EndSubPropertyGet tType()As TokenType
    Dim ret As TokenType
    SelectCaseUCase(tString)Case"IF": ret = TokenType.Target
        Case"(": ret = TokenType.BeginParen
        Case")": ret = TokenType.EndParen
        Case",": ret = TokenType.Comma
        CaseElse: ret = TokenType.Other
    EndSelect
    tType = ret
EndProperty

Stackクラスのコード

Private Items()AsVariantPropertyGetCount()AsIntegerCount=UBound(Items)EndPropertyPropertyGet Top()AsVariant
    Top = Items(UBound(Items))EndPropertyPublicFunction Pop()AsVariantIfUBound(Items)>0Then
        Pop = Items(UBound(Items))ReDimPreserve Items(UBound(Items)-1)Else
        Pop =EmptyEndIfEndFunctionPublicSub Push(ByRef x AsVariant)ReDimPreserve Items(UBound(Items)+1)
    Items(UBound(Items))= x
EndSubPrivateSub Class_Initialize()ReDim Items(0)EndSub

実行してみる。

Module1のMainプロシージャを実行すると、インプットボックスが表示されるので適当なネストしたIF関数を入れる。
f:id:t-hom:20170820121126p:plain

OKをクリックすると、イミディエイトウインドウ内にインデントされた式が出てくる。
f:id:t-hom:20170820121303p:plain

ネストしたIFを考えるのが面倒なので、以下のサイトからサンプルをいくつかいただいてきた。
Excel(エクセル)関数の技:IF関数のネスト(入れ子)の方法

例1)

=IF(C3="","",IF(C3>60,"○",IF(C3>30,"△","×"))

=IF(C3="",
    "",
    IF(C3>60,
        "○",
        IF(C3>30,
            "△",
            "×"))

例2)

=IF(AND(C3>30,C3<=60),"△",IF(AND(C3>=0,C3<=30),"×",IF(AND(C3>60,C3<=100),"○","")))

=IF(AND(C3>30,C3<=60),
    "△",
    IF(AND(C3>=0,C3<=30),
        "×",
        IF(AND(C3>60,C3<=100),
            "○",
            "")))

例3)
日本語まじりの抽象式でもこのとおり。

=IF(論理式1, [真の場合1], IF(論理式2, [真の場合2], IF(論理式3, [真の場合3], [偽の場合3])))

=IF(論理式1,
     [真の場合1],
     IF(論理式2,
         [真の場合2],
         IF(論理式3,
             [真の場合3],
             [偽の場合3])))

参考文献

初版48ページ 字句解析プログラム

【注意】VBAの書籍ではありません。C言語で書かれてます。

2017/08/26 修正

GetCharType関数でNumberの判定が1~9になってたのを0~9に修正しました。
※今回のIFの判定に影響はありません。


Viewing all articles
Browse latest Browse all 493

Trending Articles