[[20220317165423]] 『帳票の日付が同じもの同士で重複をチェックしたい』(北のカエル) ページの最後に飛ぶ

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

 

『帳票の日付が同じもの同士で重複をチェックしたいです。』(北のカエル)

Excelの表でB列に日付があり、同じ日付の範囲でF列、G列、J列、K列のそれぞれの列ごとで重複があればセルに色を付けて確認できるようにしたいです。上記の列のセルには改行された複数の文字列で記入してあり、1つでも重複したらセルに色を付けたいです。dictionaryオブジェクトを使用せず、できる方法があればお教えいただきたいです。よろしくお願いします。

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


質問内容があいまいなので、

1)サンプルデータの提示
2)dictionaryオブジェクトを使用したコードの提示

をお願いします。

(マナ) 2022/03/17(木) 18:31


(マナ)さん

1)Excelのサンプルは以下のとおりとなります。
Sheet1とSheet2で同じ書式として、違うシートの同じ日付、指定列で重複を確認したいです。

【Sheet1】
B列・・・F列・・・G列 J列 K列
4/1   A⇐色付
―――――――――――――――――――
4/1   A⇐色付き
     B⇐色付き(Aが含まれるセルのため)
―――――――――――――――――――
4/1   C⇐色付き(Sheet2の4/1の範囲でCが含まれていると仮定してSheet2のCが含まれるセルも色付)
―――――――――――――――――――
4/5   A⇐色なし(日付が違うため色なし)


・ 以下 最終行まで

2)dictionaryオブジェクトを使用したコードを記載したことがなく、形になるようなコードができて
おりません。大変恐縮ですが、ヒントだけでもいただければ幸いです。
(北のカエル) 2022/03/17(木) 19:02


なぜ、dictionaryを使わない方法に限定するのでしょうか

(マナ) 2022/03/17(木) 20:54


普通のループが組めない相手にdictionaryを使う方がおかしいと思うけど。
回答者の優越感かな?
(劣等感) 2022/03/17(木) 22:18

質問者は少なくてもDictionaryオブジェクトという言葉を知ってる

小学生が連立方程式を使わない方法でつるかめ算の解き方を教えてくださいって言ったら
ほんとに小学生か?って思うよね
(素朴な疑問) 2022/03/18(金) 06:35


(マナ) さん
(劣等感)さん
(素朴な疑問)さん

仰る通りかもしれません。VBAに関して独学の初心者でして、作成したものを配布するとなったときにDictionaryオブジェクトを用いると個々の環境に依存して使用できないのではと考えたからでした。
ループで実現するとなると多重ループとなり、挙動が分からなくなってしまったため、ご質問した次第でした。

(北のカエル) 2022/03/18(金) 08:23


「WorksheetFunction.CountIfs」を使うといいかもしれません。

(わからん) 2022/03/18(金) 09:45


 >多重ループとなり
 別プロシジャに追い出しちゃいましょう

 Sub sample()
  Dim sRow As Long, numRow As Long
  Dim rng As Range
  sRow = 2
  Do While IsDate(Cells(sRow, "B").Value)
     numRow = 1
     Do While Cells(sRow, "B").Value = Cells(sRow + numRow, "B").Value
        numRow = numRow + 1
     Loop
     With Rows(sRow).Resize(numRow)
        For Each strCol In Array("F", "G", "J", "K")
           IndicateDupRow .Columns(strCol)
        Next
     End With
     sRow = sRow + numRow
  Loop
 End Sub

 Sub IndicateDupRow(rng As Range)
    For i = 1 To rng.Rows.Count
       For j = i + 1 To rng.Rows.Count
           If checkContainSameValue(rng.Cells(i), rng.Cells(j).Value) Then
              rng.Cells(i).Interior.ColorIndex = 6
              rng.Cells(j).Interior.ColorIndex = 6
           End If
       Next
    Next
 End Sub

 Function checkContainSameValue(a, b) As Boolean
     checkContainSameValue = True
 End Function
 checkContainSameValueの中身はまたあとで。
(´・ω・`) 2022/03/18(金) 09:48

(´・ω・`)さん

ご回答ありがとうございます。別のプロシージャに分けるという発想がなかなか思いつかず、どうしてもベタ打ちしてしまいますので、大変勉強になります。

引き続き、checkContainSameValueの方もお教えいただければ幸いです。よろしくお願いします。

(北のカエル) 2022/03/18(金) 10:26


こんにちわ。

悪いけどSheet1,Sheet2の部分は省略してる。
適当に改造してくれ。

Sub Macro()

    Dim i As Long, j As Long
    Dim s As String
    Dim colArr(4) As Long
    Dim m As Long, n As Long

    colArr(0) = 6
    colArr(1) = 7
    colArr(2) = 10
    colArr(3) = 11

    For i = 1 To 5 - 1
        For j = i + 1 To 5
            If Cells(i, "B") = Cells(j, "B") Then

                For m = 0 To 3
                    For n = 0 To 3
                        If InStr(CStr(Cells(i, colArr(n))), CStr(Cells(j, colArr(m)))) > 0 Then
                            Cells(i, "A").Interior.ColorIndex = 6
                            Cells(j, "A").Interior.ColorIndex = 6
                        ElseIf InStr(CStr(Cells(j, colArr(m))), CStr(Cells(i, colArr(n)))) > 0 Then
                            Cells(i, "A").Interior.ColorIndex = 6
                            Cells(j, "A").Interior.ColorIndex = 6
                        End If
                    Next n
                Next m

            End If
        Next j
    Next i
End Sub

(てらてら) 2022/03/18(金) 10:34


おっと、A列塗るんじゃないのか。
失礼、以下に書き換えてください。

                        If InStr(CStr(Cells(i, colArr(n))), CStr(Cells(j, colArr(m)))) > 0 Then
                            Cells(i, colArr(n)).Interior.ColorIndex = 6
                            Cells(j, colArr(m)).Interior.ColorIndex = 6
                        ElseIf InStr(CStr(Cells(j, colArr(m))), CStr(Cells(i, colArr(n)))) > 0 Then
                            Cells(i, colArr(n)).Interior.ColorIndex = 6
                            Cells(j, colArr(m)).Interior.ColorIndex = 6
                        End If

(てらてら) 2022/03/18(金) 10:46


例示がさっぱり理解できないのですが行列を踏まえて説明できないでしょうか?

また、↓のように「セルには改行された複数の文字列」という条件が無くせるのであれば、既に提案されているようにCOUNTIFS関数をつかって【条件付き書式】で対応出来るでしょう。

    __B__ ... __F__   __G__ ... __J__   __K__
 1  日付      項目1   項目2     項目3   項目4
 2   4/1     【あ】    か        さ    【た】
 3   4/1     【あ】  【き】      し    【た】
 4   4/1       い    【き】      す    【た】
 5   4/2       あ      か        さ      た
 6   4/2       い      き        し      ち
 7   4/3       あ      か        さ      た
 8   4/4       あ      か        さ      た

データとしての活用もしやすくなると思うので、レイアウト修正が出来る段階なら検討してみては如何でしょうか?(日付の欄で同じ日付が並ぶのが嫌とかなら、2つめ以降の文字色を白にするだけで回避出来ます)

(もこな2 ) 2022/03/18(金) 11:32


 私のは別シートのことを失念してましたので取り下げます。
 ちょっと保留..
(´・ω・`) 2022/03/18(金) 12:32

(てらてら)さん
ありがとうございます。
テンプレートとして活用させていただきます。

(もこな2 ) さん
他の帳票からどうしても改行したデータが含まれるかたちで出力されてしまいます。
他のシートとも同時比較という点も難しいと感じています…

(´・ω・`)さん
もしよろしければ引き続き、アドバイスいただければ幸いです。
よろしくお願いします。
(北のカエル) 2022/03/18(金) 12:42


例示が中途半端なので、理解できていません。

>同じ日付の範囲でF列、G列、J列、K列のそれぞれの列ごとで重複があれば
>違うシートの同じ日付、指定列で重複を確認
>Sheet2の4/1の範囲でCが含まれていると仮定してSheet2のCが含まれるセルも色付

「列ごとで」というのが、他の条件と一致していないように思えます。

(わからん) 2022/03/18(金) 13:18


 コードの提示も無いですし、何が分からない、苦労してるのか、
 私には読み解けませんでした。
 全部分からないっていう、ただの作成依頼でしょうか。

 そうでない事を願って私見を。

 セル内で改行しているなら、改行文字はLFなので、
 Split関数で、その改行文字で分割すれば、文字列が取り出せます。
 1次元配列またはCollectionを利用して、ユニークの文字列を抽出できるでしょう。
 あとはベタにループすれば、重複確認は可能かと。

 まず、ループが深くなっても、冗長的でもいいから、動くコードを書いてから、
 別プロシージャにするなりして、スリムにしていけばいいんじゃないですか。
 基礎的なロジックを構築できることが最優先ですよ。

(tkit) 2022/03/18(金) 13:37


 説明が不得要領なので、確認します。
 ●同一日付で複数行にわたることがあるんですか?それとも1日1行ですか?
   セル内改行してまとめているということは、1日1行ではないんですか?

 ●(1)仮に1日1行だとして、
   Sheet1のF1セルのセル内改行で分離した各文字列と比較すべきは、どのセルの文字列なんですか?
   ・Sheet2のF1だけとも読めるし、(「列ごとで重複があれば」という日本語からはこう解釈される)
   ・Sheet1のG1,J1,K1 およびSheet2のF1,G1,J1,K1 とも読める。
   (2)1日1行と限らなければ、B1と同一日付のものに広げて考えればよいのか。
   肝心なことを明確に説明して欲しい。
(γ) 2022/03/19(土) 06:14

 大作になってしまった...
 解説はありません。
 遅いです。
 エラー処理が諸処必要と思うところありますが入れてません(くじけました)
 質問者さんの意図とちがっていたら捨ててください。

 Sub sample()
   Dim target1  As Range, target2 As Range
   Dim sRow As Long, d As Date
   Dim tmpws As Worksheet, tmprng1 As Range, tmprng2 As Range
   Application.ScreenUpdating = False
   With ThisWorkbook
      Set tmpws = .Worksheets.Add(after:=.Worksheets(.Worksheets.Count))
   End With
   sRow = 2
   Do While IsDate(Worksheets(1).Cells(sRow, "B").Value)
      d = Worksheets(1).Cells(sRow, "B").Value
      Set target1 = GetRangeByDate(Worksheets("Sheet1").Columns("B"), d)
      Set target2 = GetRangeByDate(Worksheets("Sheet2").Columns("B"), d)
      With CopyTarget(tmpws, target1, target2)
         For Each strCol In Array("F", "G", "J", "K")
            IndicateDupRow .Columns(strCol)
         Next
      End With
      CopyFormat tmpws, target1, target2
      sRow = sRow + target1.Rows.Count
   Loop
   Application.DisplayAlerts = False
      tmpws.Delete
   Application.DisplayAlerts = True
   Application.ScreenUpdating = True
   MsgBox "終了"
 End Sub

 Private Function GetRangeByDate(rng As Range, d As Date) As Range
   Dim sRow As Long, nRow As Long
   On Error Resume Next
      sRow = WorksheetFunction.Match(CLng(d), rng.Columns(1), 0)
      If Err Then Exit Function
   On Error GoTo 0
   Do While d = rng.Columns(1).Cells(sRow + nRow)
      nRow = nRow + 1
   Loop
   Set GetRangeByDate = rng.Rows(sRow).Resize(nRow)
 End Function

 Private Function CopyTarget(ws As Worksheet, ParamArray target()) As Range
   Dim i As Long, r As Long, c As Range
   r = 1
   ws.Cells.Delete
   For i = LBound(target) To UBound(target)
       ws.Rows(r).Resize(target(i).Rows.Count).EntireRow.Value = target(i).EntireRow.Value
       target(i).EntireRow.Copy
       ws.Rows(r).Resize(target(i).Rows.Count).EntireRow.PasteSpecial xlPasteFormats
       r = r + target(i).Rows.Count
   Next
   Set CopyTarget = ws.Rows(1).Resize(r - 1)
 End Function

 Private Sub CopyFormat(ws As Worksheet, ParamArray target())
   Dim i As Long, r As Long, c As Range
   r = 1
   For i = LBound(target) To UBound(target)
       ws.Rows(r).Resize(target(i).Rows.Count).EntireRow.Copy
       target(i).EntireRow.PasteSpecial xlPasteFormats
       r = r + target(i).Rows.Count
   Next
 End Sub

 Private Sub IndicateDupRow(rng As Range)
   For i = 1 To rng.Rows.Count
      For j = i + 1 To rng.Rows.Count
        If checkContainSameValue(rng.Cells(i), rng.Cells(j).Value) Then
           rng.Cells(i).Interior.ColorIndex = 6
           rng.Cells(j).Interior.ColorIndex = 6
        End If
      Next
   Next
 End Sub

 Private Function checkContainSameValue(ByVal A As String, ByVal B As String) As Boolean
   If A = "" Or B = "" Then Exit Function
   If (UBound(Split(A, vbLf)) > UBound(Split(B, vbLf))) Then
       tmp = A: A = B: B = tmp
   End If
   aryb = Split(B, vbLf)
   For Each v In Split(A, vbLf)
      On Error Resume Next
         ret = 0
         ret = WorksheetFunction.Match(v, aryb, 0)
      On Error GoTo 0
      If ret > 0 Then
         checkContainSameValue = True
         Exit Function
      End If
   Next
 End Function
(´・ω・`) 2022/03/19(土) 08:20

「列ごとで」というのがはっきりしませんが、とりあえず
「WorksheetFunction.CountIfs」を使ったやつを提示しておきます。

 Sub Test()
    Set ws1 = Worksheets("Sheet1")
    Set ws2 = Worksheets("Sheet2")

    For Each ws In Array(ws1, ws2)
        ws.Cells.Interior.Color = xlNone
        For r = 1 To ws.Cells(Rows.Count, "B").End(xlUp).Row
            If ws.Cells(r, "B") <> "" Then
                For Each c In Array("F", "G", "J", "K")
                    buf = Split(ws.Cells(r, c), vbLf)
                    For i = LBound(buf) To UBound(buf)
                        n = 0
                        'For Each cc In Array("F", "G", "J", "K")
                            n = n + WorksheetFunction.CountIfs(ws1.Columns("B"), ws.Cells(r, "B"), ws1.Columns(cc), "*" & buf(i) & "*")
                            n = n + WorksheetFunction.CountIfs(ws2.Columns("B"), ws.Cells(r, "B"), ws2.Columns(cc), "*" & buf(i) & "*")
                        'Next
                        If n > 1 Then
                            ws.Cells(r, c).Interior.Color = vbRed
                        End If
                    Next
                Next
            End If
        Next
    Next
 End Sub

(わからん) 2022/03/19(土) 13:29


 "*" & buf(i) & "*" と COUNTIFS関数だと、部分一致になってしまうので注意が必要です。
 (例:"aa改行bb"に"a" がマッチ判定してしまうケース)
  
 私もとあるところで InStrで判断するコードをそのまま追従してしまいましたが、これも間違いでした。
 直さないといけないと思っていたところ、こちらの質問が出され修正する機会を逸していたところ、
 (´・ω・`)さんからMatchを使う修正案が提示されました。
 なお、私は、
 tmp = Split(Cells(y, jj).Value, vbLf)
 If Not IsError(Application.Match(buf, tmp, 0)) Then
 のような On Error Resume Next を使わないコードにしていましたが。

(γ) 2022/03/19(土) 14:23


なるほど。それは気づきませんでした。
↓の4パターンでチェックすればいいですかね。

 "*" & vbLf & buf(i) & vbLf & "*"
 buf(i) & vbLf & "*"
 "*" & vbLf & buf(i)
 buf(i)

(わからん) 2022/03/19(土) 14:40


元の文字列の前後にvbLfを追加したうえで、
"*" & vbLf & buf(i) & vbLf & "*"
でチェックすると1回のチェックで済むかもしれません。
(γ) 2022/03/19(土) 14:56

あ、それは今の使い方では無理でしたか。失礼しました。取り消します。

(γ) 2022/03/19(土) 15:02


[[20220317095518]] 『一行ごとに同じ値がないかチェックしてセルに色を』(0108)
 と同じかたですよね。
 セル内改行で分離した文字列が同一なら色をつけるなどという特殊な話が、
 同時期に別人から質問されるはずがありません。

 少し待っていたのですが、反応が無いようなので(残念)、
 書いておいてメモを投稿して私の区切りとします。

 質問だけして、確認のためのコメントがあっても音沙汰なし
 というのはいかがなものですか?
 しかも質問の当初では二つのシートなどと一言も言及がないのに、途中でころっと変わっています。
 架空のケースで質問しているとさえ思えてしまいます。

 (1)
 dictionaryを使わない理由が環境要因とのことですが、
 scrrun.dllが無い mac環境なら分かりますが、WindowsならOSのバージョンに依らず普通に使えるはずです。
 一般企業で、macとWindowsを混在して使用しているところは極めて稀だと思います。
 また、実行時バインディングの書き方なので、参照設定も不要です。

 (2)
 そもそもですが、各セルの文字列に対して、探索範囲で一致するかどうかを何度も何度も探索するのは無駄です。
 特にこの場合、セル内改行で分離する処理が間に入るので、無駄感は半端ない。

 分離後の各文字列がどのセルで登場したかを、
 それらの文字列ごとにグルーピングすることは自然な発想です。

 全体を一回調べるだけで済みます。
 それができてしまえば、複数のセルに登場していたら、そのセルに色を付ければいいだけの話。
 そして、そのことを簡単に実現できる「データ構造」がdictionaryということになります。
 (文字列を指定するだけで、既存の情報があるかどうか、その値の保存場所等を検索するのは、
   極めて高速です。Hash Tableという仕組みを内部に持っているからだそうです。)

 (3)
 Dictionaryは、連想配列とかHashとかmapとかobjectとかHashmapとか色々な呼び方がありますが、
 たいていのプログラム言語には必ず備わっている、配列と並ぶ代表的なデータ構造です。
 要件にあった適切なデータ構造を使うのはあたりまえで、
 これを使わないほうが不自然とさえいえるほどのものです。
 優越感がどうこうといったろくでもない話じゃないのです。

 既に前回のスレッドでdictionaryを使ったコードを提示しています。
 それはさほど複雑ではないので若干の慣れで普通に理解できると思われます。

 (4)
 仕様は確定的ではないが、参考までに今回の件をコードで書くとこんな風に書けます。(末尾参照)
 実行時間は、
 (´・ω・`)さんが速度について触れられています。制約下の工夫でしょう。
 dictionaryを使うと格段に速くなりますね。
 テストしたところ、dictionaryを利用すると、それの 2%くらいの時間で済んでしまいます。
 (30行くらいのテストデータで2秒に対して、0.04秒くらいです。
  もちろん2秒でも十分早いという見方もあります。何を急いでいるんだと。)

 入れ子にした階層構造にしている点は少し理解しにくいかもしれません。
 しかし、こういう設定の課題には、こうした考え方も十分ありうると考えます。
 参考コードは以下です。仕様が異なれば捨ててください。

 Option Explicit 
 Dim ws1 As Worksheet, ws2 As Worksheet
 Dim dic As Object
 Sub main()
     Dim k As Long
     Dim vB, col
     Dim vcol As String
     Dim ary, a, ws

     Set ws1 = Worksheets("Sheet1")
     Set ws2 = Worksheets("Sheet2")
     Set dic = CreateObject("Scripting.Dictionary")
     Application.ScreenUpdating = False
     ws1.Range("F1,G1,J1,K1").EntireColumn.Interior.ColorIndex = xlNone
     ws2.Range("F1,G1,J1,K1").EntireColumn.Interior.ColorIndex = xlNone

     'key:セル内改行で分離した文字列 → item:そのアドレス の辞書を
     '日付別、列別(ただしシート横断)に作成する。(辞書の入れ子にしてある。)
     For Each ws In Array(ws1, ws2)
         For k = 2 To ws.Cells(Rows.Count, "B").End(xlUp).Row
             vB = ws.Cells(k, "B").Value     '日付
             If Not dic.exists(vB) Then Set dic(vB) = CreateObject("Scripting.Dictionary")

             For Each col In Array("F", "G", "J", "K")
                 vcol = ws.Cells(k, col)
                 If vcol <> "" Then
                     If Not dic(vB).exists(col) Then Set dic(vB)(col) = CreateObject("Scripting.Dictionary")
                     ary = Split(vcol, vbLf)
                     For Each a In ary
                         dic(vB)(col)(a) = dic(vB)(col)(a) _
                            & ws.Name & "!" & ws.Cells(k, col).Address(False, False) & ","
                         'Debug.Print vB, col, a, dic(vB)(col)(a)
                     Next
                 End If
             Next
         Next
     Next
     '文字重複を判定して、重複していた場合はその文字列のあるセルに色をつける
     Dim e1, e2, e3
     Dim adrses
     For Each e1 In dic.keys
         For Each e2 In dic(e1).keys
             For Each e3 In dic(e1)(e2).keys
                 adrses = Split(dic(e1)(e2)(e3), ",")
                 If UBound(adrses) > 1 Then
                     For k = 0 To UBound(adrses) - 1
                         Range(adrses(k)).Interior.ColorIndex = 6
                     Next
                 End If
             Next
         Next
     Next
     Application.ScreenUpdating = True
 End Sub

(γ) 2022/03/23(水) 11:25


コメント返信:

[ 一覧(最新更新順) ]


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