[[20220516124034]] 『VBA: 2つのセル範囲で重なっていない範囲の取得』(VBA勉強中) ページの最後に飛ぶ

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

 

『VBA: 2つのセル範囲で重なっていない範囲の取得』(VBA勉強中)

VBAで2つの範囲に対し、重なっている範囲を取得するにはIntersectを用いればよいかと思いますが、逆に重なっていない範囲を取得するにはどういう方法が考えられるでしょうか?

具体的には以下のような状況を想定しております。

rng1 = Range("A1:C5")
rng2 = Range("C1:D5")
上記のとき、rng1とrng2の重なっていないRange("A1:B5")およびRange("D1:D5")を取得したいです。

よろしくお願いします。

< 使用 Excel:Excel2019、使用 OS:Windows10 >


簡単なメソッドでは求められないように思います。
For Eachで範囲Aの各セルの範囲Bとの重なりをIntersectで判定し、
含まれないものを順次、Unionでつなげていくのでは?
範囲Bの各セルについても同様です。
コード作成にトライしてみてはいかがですか?
(γ) 2022/05/16(月) 13:06

 かぶってますが...Union かこんな意味ですか?

 Dim MyRNG As Range
 Set MyRNG = Range("A1:B5,D1:D5")

 If Intersect(Target, MyRNG) Is Nothing Then Exit Sub
(あみな) 2022/05/16(月) 13:11

こんなのがありました。ご参考。

https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1191798316

(わからん) 2022/05/16(月) 13:19


γ様、あみな様
ご回答いただきありがとうございます!
範囲Aに限定してFor Eachで判定すればだいぶ高速化できそうな気がしますので、早速試してみたいと思います(当初作成したコードではA1セルから最終行・最終列の全セルに対し総当たりで判定しUnionしておりました…)

わからん様
参考になるページを教えていただきありがとうございます!
一通り調べてみたつもりでしたが、お教えいただいたページにはたどり着けておりませんでした。。

引き続き勉強を進めていきます。ありがとうございました。
(VBA勉強中) 2022/05/16(月) 13:39


 一時的に作業用ワークシートを追加したらどうでしょう

    Sub sample()
       Dim rng1 As Range, rng2 As Range
       Set rng1 = Range("A1:C5")
       Set rng2 = Range("C1:D5")
       SymmetricDiff(rng1, rng2).Select
    End Sub

    Function SymmetricDiff(ByVal r1 As Range, ByVal r2 As Range) As Range
       Dim uRng As Range, iRng As Range

       Set uRng = Union(r1, r2)
       Set iRng = Intersect(r1, r2)
       If iRng Is Nothing Then
         Set SymmetricDiff = uRng
         Exit Function
       End If

       Set tmpws = Worksheets.Add
       Set uRng = tmpws.Range(uRng.Address)
       Set iRng = tmpws.Range(iRng.Address)

       uRng.Value = 1
       iRng.ClearContents
       Set SymmetricDiff = uRng.SpecialCells(xlCellTypeConstants)
       Set SymmetricDiff = r1.Parent.Range(SymmetricDiff.Address)
       Application.DisplayAlerts = False
          tmpws.Delete
       Application.DisplayAlerts = True
    End Function
(´・ω・`) 2022/05/16(月) 13:49

(´・ω・`)様
ありがとうございます!
ご丁寧に具体的なコードまで記載していただきとても勉強になります。
作業用シートを使うのもかなり早くなりそうですし、直感的にも理解しやすいです。

勉強のため皆さまにいただいた例をそれぞれ実装して差を比較したいと思います。

ありがとうございました。
(VBA勉強中) 2022/05/16(月) 14:19


 エヘヘ。遅れ馳せながら...^^;

 リンク先にも感化され、真正面から取り組んだらどうなるかちょっと考えてみたくなりました。
 「Union、Intersectがあって、SQLみたく『Minus』が無いから作ってみよう」ってお題で暇つぶし。

 式
    Function Minus(Arg1 As Range, Arg2 As Range) As Range

 機能
    Arg1にあってArg2に無い範囲を返す => Arg1からArg2を除く範囲を返す

 方針
 UnionやIntersectの様な[Arg3]〜[Arg30]部分はMinus処理には不要だと思われる。
 但し、Arg1、Arg2共に複数Areaから成るRangeにも対応させる。
 各引数内の各Areaを「矩形オブジェクト」と考え、そのコレクションとして扱って計算してみようと思う。

 '[clsRect]モジュール(クラスモジュール)-------------------------------------------------------------
    Option Explicit
    Public Left As Long
    Public Top As Long
    Public Right As Long
    Public Bottom As Long
    Public Property Get ToAddress(Optional ByVal RefStyle As XlReferenceStyle = xlA1) As String
    Rem デバッグ用プロパティ(要らん)
        Dim a As String
        a = "R" & Top & "C" & Left & ":R" & Bottom & "C" & Right
        If RefStyle <> xlA1 Then
            ToAddress = Application.ConvertFormula(a, xlR1C1)
        Else
            ToAddress = Application.ConvertFormula(a, xlR1C1, xlA1, xlRelative)
        End If
    End Property

 '以下、標準モジュールの話--------------------------------------------------------------------------
    Function Minus(Arg1 As Range, Arg2 As Range) As Range
    Rem セル範囲Arg1からセル範囲Arg2を除くセル範囲を返す
        If Arg1.Worksheet Is Arg2.Worksheet Then
            Dim r As Collection
            Set r = MinusRects(Range2Rect(Arg1), Range2Rect(Arg2))
            If r.Count > 0 Then Set Minus = Rect2Range(r, Arg1.Worksheet)
        Else
            Set Minus = Arg1
        End If
    End Function
    Private Function Range2Rect(Arg1 As Range) As Collection
    Rem セル範囲Arg1を矩形集合に変換して返す
        Dim r As clsRect, a As Range, cRects As New Collection
        For Each a In Arg1.Areas
            Set r = New clsRect
            r.Top = a.Row
            r.Left = a.Column
            r.Bottom = r.Top + a.Rows.Count - 1
            r.Right = r.Left + a.Columns.Count - 1
            cRects.Add r
        Next
        Set Range2Rect = cRects
    End Function
    Private Function Rect2Range(Arg1 As Collection, Optional ByVal ParentSheet As Worksheet) As Range
    Rem 矩形集合Arg1をセル範囲に変換して返す
        Dim r As Range, i As Long, e As Long
        If ParentSheet Is Nothing Then Set ParentSheet = ActiveSheet
    Debug.Print "Rect2Range result --------------------"
        For i = 1 To Arg1.Count
            On Error Resume Next
            With ParentSheet
                Set r = Range(.Cells(Arg1.Item(i).Top, Arg1.Item(i).Left), .Cells(Arg1.Item(i).Bottom, Arg1.Item(i).Right))
            End With
            e = Err.Number
            On Error GoTo 0
            If e = 0 Then
                If Rect2Range Is Nothing Then Set Rect2Range = r Else Set Rect2Range = Union(Rect2Range, r)
            End If
    Debug.Print i; Arg1.Item(i).ToAddress; " => ";
    If Not Rect2Range Is Nothing Then Debug.Print Rect2Range.Address(0, 0);
    Debug.Print
        Next
    End Function
    Private Function MinusRects(Rects1 As Collection, Rects2 As Collection) As Collection
    Rem 矩形集合1内の各矩形から矩形集合2内の各矩形を除いた矩形集合を作って返す
        Dim r As Collection, i As Long
        Set r = New Collection
        For i = 1 To Rects1.Count
            r.Add Rects1.Item(i)
        Next
        For i = 1 To Rects2.Count
    Debug.Print "Minus ["; Rects2.Item(i).ToAddress; "] ---------------------"
            Set r = MinusRect_RSmR(r, Rects2.Item(i))
        Next
        Set MinusRects = r
    End Function
    Private Function MinusRect_RSmR(Rects1 As Collection, RectB As clsRect) As Collection
    Rem 中継関数
        Dim cRtn As Collection, i As Long
        Dim iRect As clsRect
        Dim cRects As New Collection, j As Long
        For i = 1 To Rects1.Count
            Set iRect = Rects1.Item(i)
    Debug.Print i, iRect.ToAddress; " - "; RectB.ToAddress
            Set cRtn = MinusRect(iRect, RectB)
            For j = 1 To cRtn.Count
    Debug.Print , j, cRtn.Item(j).ToAddress
                cRects.Add cRtn.Item(j)
            Next
        Next
        Set MinusRect_RSmR = cRects
    End Function
    Private Function MinusRect(RectA As clsRect, RectB As clsRect) As Collection
    Rem 矩形Aから矩形Bを除いた矩形集合(行優先)を作って返す
        Dim v As Collection, h As Collection, i As Long, j As Long
        Dim r As clsRect, cRects As New Collection
        Set v = LineDivider(RectA.Top, RectA.Bottom, RectB.Top, RectB.Bottom)
        Set h = LineDivider(RectA.Left, RectA.Right, RectB.Left, RectB.Right)
        For i = 1 To v.Count
            If v(i)(2) Then
                For j = 1 To h.Count
                    If Not h(j)(2) Then
                        Set r = New clsRect
                        r.Top = v(i)(0)
                        r.Bottom = v(i)(1)
                        r.Left = h(j)(0)
                        r.Right = h(j)(1)
                        cRects.Add r
                    End If
                Next
            Else
                Set r = New clsRect
                r.Top = v(i)(0)
                r.Bottom = v(i)(1)
                r.Left = RectA.Left
                r.Right = RectA.Right
                cRects.Add r
            End If
        Next
        Set MinusRect = cRects
    End Function
    Private Function LineDivider(minA As Long, maxA As Long, minB As Long, maxB As Long) As Collection
    Rem 区間Aを区間Bと重なる部分と重ならない部分に分割(配列(始点Long,終点Long,重なりBool)のコレクション)
        Dim c As New Collection, Ary(0 To 2)
        If maxB < minA Or minB > maxA Then '<---------重ならない
            Ary(0) = minA: Ary(1) = maxA
            Ary(2) = False
            c.Add Ary
        ElseIf minB <= minA And maxB >= maxA Then '<--Aの全区間が重なる
            Ary(0) = minA: Ary(1) = maxA
            Ary(2) = True
            c.Add Ary
        ElseIf minB > minA And maxB < maxA Then '<----Bの全区間が重なる
            Ary(0) = minA: Ary(1) = minB - 1
            Ary(2) = False
            c.Add Ary
            Ary(0) = minB: Ary(1) = maxB
            Ary(2) = True
            c.Add Ary
            Ary(0) = maxB + 1: Ary(1) = maxA
            Ary(2) = False
            c.Add Ary
        ElseIf maxB < maxA Then '<--------------------Aの途中まで重なる
            Ary(0) = minA: Ary(1) = maxB
            Ary(2) = True
            c.Add Ary
            Ary(0) = maxB + 1: Ary(1) = maxA
            Ary(2) = False
            c.Add Ary
        ElseIf minB > minA Then '<--------------------Aの途中から重なる
            Ary(0) = minA: Ary(1) = minB - 1
            Ary(2) = False
            c.Add Ary
            Ary(0) = minB: Ary(1) = maxA
            Ary(2) = True
            c.Add Ary
        End If
        Set LineDivider = c
    End Function

 'Minusメソッド使用例 ------------------------------------------------------------------------------
    Sub 選択範囲の除去()
        If ActiveWorkbook Is Nothing Then Exit Sub
        If TypeName(Selection) <> "Range" Then Exit Sub
        Dim Ra As Range
        On Error Resume Next
        Set Ra = Application.InputBox("現在の選択範囲と交わる範囲を選択して下さい。", Title:="除去する範囲の選択", Type:=8)
        On Error GoTo 0
        If Ra Is Nothing Then Exit Sub
        Set Ra = Minus(ActiveWindow.RangeSelection, Ra)
        If Ra Is Nothing Then
            MsgBox "選択範囲が全滅します。", vbCritical
            Exit Sub
        End If
        Ra.Select
    End Sub
    Sub 動作確認()
        Dim Ra As Range, Rb As Range
        Set Ra = [A4:G6,B1:F9]
        Ra.Select
        Set Rb = [C2:D2,C5:D7]
    Debug.Print "▼["; Ra.Address(0, 0); "] - ["; Rb.Address(0, 0); "]"
        Set Ra = Minus(Ra, Rb)
    Debug.Print "■ => ["; Ra.Address(0, 0); "]"
        Ra.Select
    End Sub
    Sub 重なっていない範囲の取得テスト()
        Dim Ra As Range, Rb As Range, Rc As Range
        Set Ra = [A1:C5]
        Set Rb = [C1:D5]
        Set Rc = Minus(Union(Ra, Rb), Intersect(Ra, Rb))
        Debug.Print Rc.Address(0, 0)
    End Sub

(白茶) 2022/05/20(金) 20:15


 あ。 もうひとつ使い道思い付いたのでメモ ^^;

 Minus(Cells, Minus(Cells, 範囲))
 で、
 順番や重複がぐちゃぐちゃした範囲をキレイに整えられますね。

 例
 [B5:F5,D5:D19,B9:B19,F9:F19,B13:F14,B5:F6]
          ↓
 [B5:F6,B9:B12,D7:D12,F9:F12,B13:F14,B15:B19,D15:D19,F15:F19]

(白茶) 2022/05/21(土) 17:59


 自信ありませんが

 Function RangeN(領域 As Range) As Dictionary
     Dim dic As New Dictionary
     Dim WA As Range
     Dim i As Long
     Dim W1 As Variant
     Dim wK As Variant

     For Each WA In 領域.Areas
     For Each W1 In WA.Cells
         Select Case dic.Exists(W1.Address)
             Case True: dic(W1.Address) = Empty
             Case Else: dic.Add W1.Address, W1
         End Select
     Next
     Next
     For Each wK In dic.Keys
         If IsEmpty(dic(wK)) Then dic.Remove wK
     Next
     Set RangeN = dic
 End Function
 Sub test()
     Dim x As Variant
     Dim W9 As Range
     Dim 範囲 As Range
 '    Set 範囲 = Range("A1:C5,C1:D5,A2:E3")
     Set 範囲 = Range("A1:C5,C1:D5")

     '(1) --各要素へのアクセスだけなら ↓ な感じで
     For Each x In RangeN(範囲).Items
         Debug.Print x.Address; x.Value
     Next
     '(2)敢えてRangeがほしいなら
     For Each x In RangeN(範囲).Items
         If W9 Is Nothing Then Set W9 = x Else Set W9 = Union(W9, x)
     Next
     Debug.Print W9.Address
 End Sub

(チオチモリン) 2022/05/21(土) 23:22


 >rng1 = Range("A1:C5")
 >rng2 = Range("C1:D5")
 >上記のとき、rng1とrng2の重なっていないRange("A1:B5")およびRange("D1:D5")を取得したいです。

なら ↓ のようでしょうか

 Sub test2()
     Dim rng1 As Range
     Dim rng2 As Range
     Dim W9 As Range
     Set rng1 = Range("A1:C5")
     Set rng2 = Range("C1:D5")

     MsgBox RangeN2(rng1, rng2).Address
     MsgBox RangeN2(rng1, rng2, Range("A2:E3")).Address
 End Sub

 Function RangeN2(ParamArray 領域() As Variant) As Range
     Dim dic As New Dictionary
     Dim i As Long
     Dim W1 As Variant
     Dim wK As Variant
     Dim W9 As Range

     For i = 0 To UBound(領域)
     For Each W1 In 領域(i).Cells
         Select Case dic.Exists(W1.Address)
             Case True: dic(W1.Address) = Empty
             Case Else: dic.Add W1.Address, W1
         End Select
     Next
     Next
     For Each wK In dic.Keys
         If IsEmpty(dic(wK)) = False Then
             If W9 Is Nothing Then Set W9 = dic(wK) Else Set W9 = Union(W9, dic(wK))
         End If
     Next
     Set RangeN2 = W9
 End Function

(チオチモリン) 2022/05/23(月) 09:33


 (独り言です。スルー希望 ^^; )

 なんか神髄さんで似たような話題を見つけてしまった...

Rangeオブジェクトの論理演算(差集合と排他的論理和)|VBA技術解説
https://excel-ubara.com/excelvba4/EXCEL_VBA_425.html

 Union         和集合
 Intersect     積集合
 Except        差集合 (今回の「Minus」)
 Complement    補集合 (「Not(範囲)」的な...)

 Except([A], [B]) = Intersect([A], Complement([B]))
 なので
 「Complementを作ってみよう」で話を進めても良かったかも知れない。
 (引数範囲の上下左右に存在する矩形集合を返す。という)

 でも
 Complement([B]) = Except(Cells, [B])
 だから
 Complementを作る作業って結局Exceptを作る作業とほとんど概念が同じなのね。

 やっぱ「Exceptを作ってみよう」で良かったんだろう。(としておこう)

  * * *

 しかしComplementも案外使えるかもなぁ...
 単独で使用する機能ではないだろうけど、
 PhotoShop等の画像処理系では「選択範囲の反転」機能は必須で、
 例えば[反転]→[調整]→[反転]という流れは常套手段だし。

 「○○なセル範囲」を取得する機能を持っていれば、
 「○○以外なセル範囲」を取得する機能も間接的に実装出来ちゃう訳で。
 (例:空白セル以外とか、数式セル以外、この色以外とか...)
 ちょっと試験配備ですな

(白茶) 2022/05/26(木) 10:57


例えば、新規シートに複数のセル範囲に何か書きこむ。
重なった所を消す。
文字が残っているセルを選択。
これじゃダメ?
(甲樹) 2022/05/26(木) 16:52

あれ、掲載されてない。

重なった所の文字消す。
です。

(甲樹) 2022/05/26(木) 17:03


 >これじゃダメ?
 もちろんアリだと思います。
 (´・ω・`)さんの 2022/05/16(月) 13:49 の様な手法ですよね。

 別に否定してる訳じゃなくって、
 何ていうか、もう少しオタクっぽい話なんすね。

 範囲選択の話なんだから「何かの最中」に必要となるであろう機能なのにも関わらず、
 ただ範囲選択するだけでUnDoリスト破棄されたり裏で無駄なイベントが投げ込まれたりされちゃう手法を
 自ら選ぶ気には到底なれない。とか
 ましてそんな機能を自分が使うアドインに組み込むなんで我慢ならない。とか...

 これに関しては汎用化を想定するにあたって正攻法を採らないと気が済まなかったんですね。
 (実際使う時にはホントどうでもいいことなんですけどね!!)
 ま、めんどくせー奴なんすよ^^;

 # 解決後にウダウダとしつこく書かせて頂いた身として責任を感じたのでスルー出来ませんでした。
 # 「別にあんたに訊いてないよ」って場合はご容赦下さい。

(白茶) 2022/05/26(木) 19:06


コメント返信:

[ 一覧(最新更新順) ]


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