以下の内容はhttps://www.limecode.jp/entry/fungo/056-remove-self-reference-from-formulaより取得しました。


56本目:数式内の自身のシート名を消す

Excel&VBA解説サイト「エクセルの神髄」様出題の問題集、
VBA100本ノック」に対する私の回答と解説のページです。

100本ノックの出題リストはこちらから
excel-ubara.com

出題:数式内の自身のシート名を消す

#VBA100本ノック 56本目
数式に自身のシート名が入っていると数式が長く、並べ替えが上手く出来ない等々何かと邪魔です。
そこで全シートの全数式内での自身のシート参照を消してください。
=自身のシート!C2… → =C2…
※シート名に記号が使われている場合を考慮。
※串刺し計算は置換しません。

自シートを参照している数式

◇ 出題ページはこちら

ソースコード

Option Explicit

' 100本ノック056:数式内の自身のシート名を消す
Sub 全シートの自シート参照式を消去する()

    Dim シート As Worksheet
    For Each シート In ActiveWorkbook.Worksheets
        Call 自シート参照式を消去する(シート)
    Next

End Sub

Sub 自シート参照式を消去する(ws対象シート As Worksheet)

    ' Evaluate関数を正しく動かすためにActiveにしてから実行
    ws対象シート.Activate

    ' シート名!または'シート名'!を検索
    Dim 自シート参照式 As String: 自シート参照式 = ws対象シート.Name & "!"
    Dim 自シート参照式_SQ有 As String: 自シート参照式_SQ有 = "'" & ws対象シート.Name & "'!"

    ' 数式セルをループ
    Dim 数式セル As Range
    For Each 数式セル In ws対象シート.UsedRange
        If 数式セル.HasFormula Then

            ' まずシングルクォートありを検索(これは発見した時点で置換)
            If InStr(数式セル.Formula, 自シート参照式_SQ有) > 0 Then
                数式セル.Formula = Replace(数式セル.Formula, 自シート参照式_SQ有, "")

            ' シングルクォートなしを検索(これは発見してもシート名が後方一致しただけの可能性あり)
            ElseIf InStr(数式セル.Formula, 自シート参照式) > 0 Then

                ' シート名!を'シート名'!に変えて数式をEvaluate関数で実行してみる。
                ' 本当に第2シートを参照していれば問題なく動いて同じ値を返す
                ' データ2025! というシートが2025!で引っかかってしまっているだけの場合は、
                ' データ'2025'!という参照式になってしまいエラーとなる
                ' この判定で串刺し計算式も置換対象外となる

                ' 元からエラーが出ている場合は、Evaluateの結果が同じエラーになったら置換
                If IsError(数式セル.Value) Then
                    If 数式セル.Value = Evaluate(Replace(数式セル.Formula, 自シート参照式, 自シート参照式_SQ有)) Then
                        数式セル.Formula = Replace(数式セル.Formula, 自シート参照式, "")
                    End If

                ' 元のセルがエラーでない場合は、Evaluateの結果がエラーになっていなければ置換
                Else
                    If IsError(Evaluate(Replace(数式セル.Formula, 自シート参照式, 自シート参照式_SQ有))) = False Then
                        数式セル.Formula = Replace(数式セル.Formula, 自シート参照式, "")
                    End If

                End If
            End If

        End If
    Next

End Sub

解説

自シート名!A1をA1に置換する問題です。

これはブログ内でも便利マクロとして紹介した処理ですね。

アクティブシートにこの処理を実行するマクロを持っておくと便利ですので、
本問を解くついでにリボンやツールバーに登録してみてください。


肝心のコードについて、基本はReplace関数で置換すればよいですが、

  • シート名に()などの記号があるとシングルクォートが使われる
  • 「テストシート!」と「シート!」があるときは単にシート!で置換できない

この2点に気を付ける必要があります。

「シート名を記号付きに変えてから置換する」という方法もありますが、
シート名を変えて予期せぬエラーが出ると厄介なのでしっかり分岐しました。


シート名の後方一致問題については、Evaluateメソッドを使用して判定しました。

丁寧にコメントを書いてありますので読み解いてみてください。

Dictionaryを使用した高速コード

問題の解答としては上記のコードで十分と思いますが、
実用する場合は速度にも気を使いたいところです。

そもそも1セルずつの変更ではセル数が多いとだいぶ遅くなりますし、
数式の再計算が置換ごとに走ってしまうためさらに遅くなります。


この解決策として、同じ数式を持つセル範囲をひとつのRangeオブジェクトにまとめ、
Formulaの代入を一括で行うコードも作成しました。

数式をKeyとし、その数式を持つ範囲をItemに入れたDictionaryを使用しています。


数万行データの各列に同一の数式が入っている場合は、
前述のコードに比べて千倍以上の速度が出せることもよくあります。

便利マクロとして登録する場合はこちらのコードをご利用ください。

' ノックの解答はこちら
Sub 全シートの自シート参照式を消去する()

    Dim シート As Worksheet
    For Each シート In ActiveWorkbook.Worksheets
        Call 自シート参照式を消去する(シート)
    Next

End Sub

' 便利マクロはこちら
Sub アクティブシートの自シート参照式を消去する()
    Call 自シート参照式を消去する(ActiveSheet)
End Sub


' メインプロシージャ
Sub 自シート参照式を消去する(ws対象シート As Worksheet)
    
    ' Evaluate関数を正しく動かすためにActiveにしてから実行
    ws対象シート.Activate
    
    ' シート名!または'シート名'!を検索
    Dim 自シート参照式 As String: 自シート参照式 = ws対象シート.Name & "!"
    Dim 自シート参照式_SQ有 As String: 自シート参照式_SQ有 = "'" & ws対象シート.Name & "'!"
    
    ' 同一数式をもつ範囲ごとにUsedRangeを分割
    Dim Dic同一数式セル As Dictionary
    Set Dic同一数式セル = 同じ数式を持つセル範囲ごとに1つのItemとするDictionaryを取得する(ws対象シート)
    If Dic同一数式セル.Count = 0 Then Exit Sub
    
    ' 同一数式をもつ範囲ごとにループ
    Dim keyR1C1数式
    For Each keyR1C1数式 In Dic同一数式セル.Keys

        ' まずシングルクォートありを検索(これは発見した時点で置換)
        If InStr(keyR1C1数式, 自シート参照式_SQ有) > 0 Then
            Dic同一数式セル(keyR1C1数式).FormulaR1C1 = Replace(keyR1C1数式, 自シート参照式_SQ有, "")
                        
        ' シングルクォートなしを検索(これは発見してもシート名が後方一致しただけの可能性あり)
        ElseIf InStr(keyR1C1数式, 自シート参照式) > 0 Then
            
            Dim1セル As Range: Set1セル = Dic同一数式セル(keyR1C1数式).Cells(1)
            
            ' シート名!を'シート名'!に変えて数式をEvaluate関数で実行してみる。
            ' 本当に第2シートを参照していれば問題なく動いて同じ値を返す
            ' データ2025! というシートが2025!で引っかかってしまっているだけの場合は、
            ' データ'2025'!という参照式になってしまいエラーとなる
            ' この判定で串刺し計算式も置換対象外となる

            ' 元からエラーが出ている場合は、Evaluateの結果が同じエラーになったら置換
            If IsError(1セル.Value) Then
                If1セル.Value = Evaluate(Replace(1セル.Formula, 自シート参照式, 自シート参照式_SQ有)) Then
                    Dic同一数式セル(keyR1C1数式).FormulaR1C1 = Replace(keyR1C1数式, 自シート参照式, "")
                End If
            
            ' 元のセルがエラーでない場合は、Evaluateの結果がエラーになっていなければ置換
            Else
                If IsError(Evaluate(Replace(1セル.Formula, 自シート参照式, 自シート参照式_SQ有))) = False Then
                    Dic同一数式セル(keyR1C1数式).FormulaR1C1 = Replace(keyR1C1数式, 自シート参照式, "")
                End If
                
            End If
        
        End If
    Next

End Sub

' 同一数式ごとにRangeを分けたDictionary
Function 同じ数式を持つセル範囲ごとに1つのItemとするDictionaryを取得する(ws対象シート As Worksheet) As Dictionary

    Dim Dic結果値 As New Dictionary
    
    ' 数式があるセルをループ
    Dim 数式セル As Range
    For Each 数式セル In ws対象シート.UsedRange
        If 数式セル.HasFormula Then
            
            ' KeyはR1C1数式
            Dim keyR1C1数式 As String: keyR1C1数式 = 数式セル.FormulaR1C1
            
            ' 新出の数式を登録
            If Dic結果値.Exists(keyR1C1数式) = False Then
                Dic結果値.Add keyR1C1数式, 数式セル
            
            ' 既出の数式であればItemに本セルをUnion
            Else
                Set Dic結果値(keyR1C1数式) = Union(Dic結果値(keyR1C1数式), 数式セル)
            
            End If
            
        End If
    
    Next
    
    Set 同じ数式を持つセル範囲ごとに1つのItemとするDictionaryを取得する = Dic結果値
    
End Function



以上の内容はhttps://www.limecode.jp/entry/fungo/056-remove-self-reference-from-formulaより取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14