🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
VBA

VBAはオブジェクト指向プログラミング言語のひとつで、マクロを作成によりExcelなどのOffice業務を自動化することができます。

配列

配列は、各データの要素(値または変数)が連続的に並べられたデータ構造です。各配列は添え字(INDEX)で識別されています。

Q&A

解決済

3回答

1873閲覧

【VBA】指定の列に含まれる文字列を行ごとに結合したい

nana_mag

総合スコア4

VBA

VBAはオブジェクト指向プログラミング言語のひとつで、マクロを作成によりExcelなどのOffice業務を自動化することができます。

配列

配列は、各データの要素(値または変数)が連続的に並べられたデータ構造です。各配列は添え字(INDEX)で識別されています。

0グッド

2クリップ

投稿2021/02/19 12:55

編集2021/02/19 14:18

前提・実現したいこと

初めて質問致します。

1行目に番号が振られていて、同じ番号の列の内容を行ごとに結合したいと考えています。
実際のものはデータが多い為、なるべく速く完了する方法はありませんでしょうか。
処理前

処理後のイメージは次のようにコロン等の記号を挟み、余分な列は削除された形です。
重たくなるようなら記号を挟まず結合でも構いません。元のデータでA2,B2セルやE2,F2のように値が同じ場合は重複せず出力したいです。
処理後

試したこと・発生している問題

行と列がそれぞれ数千ずつあり、次のような方法で試してみたのですが一向に処理が終わりません。中断後にシートを見たところ処理自体は進んでいるようでした。

  1. 値が重複しているセルの入力値を削除

 ・例えば1行目が"1"の列について
最初の列Aと2番目の列Bの値を各行繰り返し処理で比較。同じ値のセルがあればB列の該当セルをUnionメソッドで連結する。3番目C列以降の列も同様にA列と比較し連結。
・Unionメソッドに入れたセルの入力値をまとめてClear
_
0. 1行目の値が同じセルの値を結合
・TextJoin 関数で"1"の列(A:C)の文字列を各行ごとに結合
・最終行まで繰り返し
_
0. 1行目が"1"の列2番目以降の列は必要無いのでDelete
_
0. 1行目が"2"以降も、同じ番号の列が複数あれば繰り返す

最初、列をそれぞれ配列に格納し、要素が同じもの同士の値を連結できるような方法があればと思い調べましたが、配列内の全要素の連結しか見つからず… 初学者なので知識が足りていないかもしれませんが、どなたかお答え頂ければ幸いです。

※追記:1行目の番号は1からの連番です。出力先は別シートでも構いません。
"行と列がそれぞれ数千ずつ"と書きましたが、列は別の処理により減っているので、正しくは列が100、行が8000程度になります。
※追記2:セル内のデータですが、空白の場合もあります。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

tatsu99

2021/02/19 13:46

1行目の番号は必ず1からの連番になってますか。番号が飛ぶことはありますか。 出力先は別のシートの方が良いかと思いますが、同じシートに結合した結果を出力するのでしょうか。
nana_mag

2021/02/19 13:51

1からの連番です。同一番号がいくつあるかは番号により異なりますが。 同一シートで考えておりましたが、別シートでも構いません。 また"行と列がそれぞれ数千ずつ"と書いてしまいましたが、列は別の処理により百程度まで減った状態でした。
tatsu99

2021/02/20 00:57 編集

1行目に3 3 2 2 1 1 2行目にA B C D E F とあった場合、 2行目をまとめた結果は E:F C:D A:B ですか(番号順出力)、それとも A:B C:D E:F ですか(元の並びの順に出力)
nana_mag

2021/02/20 04:54

データは1から昇順ですが、もしその場合は1行目が3 2 1、2行目がA:B C:D E:Fの想定です。
guest

回答3

0

以下のようにしてください。
こちらの環境で、10000行、150列のデータで約2分30秒かかりました。(corei5 メモリ12G)
Sheet1の内容を読み込み、Sheet2へ出力します。

VBA

1Option Explicit 2Public Sub 列統合() 3 Dim dicT As Object '番号対応の列を記憶 4 Dim dicV As Object '重複した値の削除用 5 Dim rg As Range 6 Dim maxrow As Long '最大行 7 Dim maxcol As Long '最大列 8 Dim wrow As Long '行 作業用 9 Dim wcol As Long '列 作業用 10 Dim vcol As Variant '列 作業用 11 Dim key As Variant 'キー 12 Dim keys As Variant 'キーの一覧 13 Dim val As Variant '値 14 Dim vals As Variant '値の一覧 15 Dim sh1 As Worksheet 'データのシート 16 Dim sh2 As Worksheet '出力先シート 17 Dim arr As Object '列のArrayList 18 Dim stime As Variant '開始時刻 19 Dim etime As Variant '終了時刻 20 Application.ScreenUpdating = False 21 stime = Time 22 Set dicT = CreateObject("Scripting.Dictionary") ' 連想配列の定義 23 Set dicV = CreateObject("Scripting.Dictionary") ' 連想配列の定義 24 Set sh1 = Worksheets("Sheet1") 25 Set sh2 = Worksheets("Sheet2") 26 Set rg = sh1.Range("A1").CurrentRegion 27 maxrow = rg.Rows.Count 28 maxcol = rg.Columns.Count 29 '番号を取得し、番号対応の列を記憶する 30 For wcol = 1 To maxcol 31 key = sh1.Cells(1, wcol).Value 32 If key = "" Then 33 MsgBox ("番号が空です") 34 Exit Sub 35 End If 36 If dicT.Exists(key) = False Then 37 '新規番号なら、空のArrayListを作成する 38 Set arr = CreateObject("System.Collections.ArrayList") '.NET Frameworkへの参照 39 dicT.Add key, arr 40 End If 41 '番号対応の列を追加する 42 dicT(key).Add wcol 43 Next 44 'Sheet2クリア&1行目作成 45 sh2.Cells.ClearContents 46 keys = dicT.keys 47 wcol = 1 48 For Each key In keys 49 sh2.Cells(1, wcol).Value = key 50 wcol = wcol + 1 51 Next 52 'Sheet1を2行から最終行まで繰り返す 53 For wrow = 2 To maxrow 54 '番号=1~最大番号迄繰り返す 55 keys = dicT.keys 56 wcol = 1 57 For Each key In keys 58 dicV.RemoveAll 59 '番号に対応する列の一覧を取得 60 Set arr = dicT(key) 61 '各列の重複削除及び空でない要素の登録 62 For Each vcol In arr 63 val = sh1.Cells(wrow, vcol).Value 64 If val <> "" Then 65 dicV(val) = True 66 End If 67 Next 68 '空でないなら、:で区切りSheet2へ出力 69 If dicV.Count > 0 Then 70 vals = dicV.keys 71 sh2.Cells(wrow, wcol).Value = Join(vals, ":") 72 End If 73 wcol = wcol + 1 74 Next 75 Next 76 etime = Time 77 Application.ScreenUpdating = True 78 MsgBox ("実行時間(時:分:秒)=" & Hour(etime - stime) & ":" & Minute(etime - stime) & ":" & Second(etime - stime)) 79End Sub 80

投稿2021/02/20 05:10

tatsu99

総合スコア5493

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

nana_mag

2021/02/20 07:10

ありがとうございます!回してみます。
nana_mag

2021/02/22 04:02

動きました、ありがとうございます。空白セルがそれなりにあった為か1分も掛かりませんでした。 丁寧にコメントを付けて頂いていて非常に分かりやすかったです。また何かありましたら宜しくお願い致します。
guest

0

**「実際のものはデータが多い為、なるべく速く完了する方法」**ということなので、
表データを配列に格納して配列内で処理して、一気に出力する方法で考えてみました。
重複排除は、Dictionaryオブジェクトを利用しました。
そのため、参照設定で「Microsoft Scripting Runtime」にチェックを入れておく必要があります。

vba

1Option Explicit 2Const sep = ":" '区切り文字 3 4Public Sub Main() 5 Dim inTbl 6 inTbl = Worksheets(1).Cells(1, 1).CurrentRegion.Value 7 8 Dim outTbl 9 ReDim outTbl(1 To UBound(inTbl, 1), 1 To UBound(inTbl, 2)) 10 11 Dim c As Long, c1 As Long, c3 As Long 12 13 c1 = 1: c3 = 1 14 For c = 1 To UBound(inTbl, 2) - 1 15 If inTbl(1, c) <> inTbl(1, c + 1) Then 16 columnJoin inTbl, outTbl, c1, c, c3 17 c1 = c + 1: c3 = c3 + 1 18 End If 19 Next 20 columnJoin inTbl, outTbl, c1, c, c3 21 22 Worksheets(2).Cells(1, 1).Resize(UBound(outTbl), UBound(outTbl, 2)).Value = outTbl 23End Sub 24 25Sub columnJoin(inTbl, outTbl, c1 As Long, c2 As Long, c3 As Long) 26 Dim r As Long 27 outTbl(1, c3) = inTbl(1, c1) 28 For r = 2 To UBound(inTbl) 29 outTbl(r, c3) = RemoveDuplicatesBlank(inTbl, r, c1, c2) 30 Next 31End Sub 32 33Function RemoveDuplicatesBlank(Tbl, r As Long, c1 As Long, c2 As Long) As String 34 Dim dic As New Dictionary 35 Dim c As Long 36 For c = c1 To c2 37 If Tbl(r, c) <> "" And Not dic.Exists(Tbl(r, c)) Then dic(Tbl(r, c)) = c 38 Next 39 RemoveDuplicatesBlank = Join(dic.Keys, sep) 40End Function

一番目のシートに表データがあり、2番目のシートに出力するコードになってます。
同じシートに上書きする場合は、Mainの最後の行のWorksheets(2)Worksheets(1)に変更するだけです。

投稿2021/02/20 01:17

編集2021/02/20 15:23
hatena19

総合スコア34073

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

nana_mag

2021/02/20 04:58

回答ありがとうございます。 Dictionaryオブジェクトは使ったことがないので勉強しますね。 上手く動きましたらまたコメント致します!
nana_mag

2021/02/20 13:33

2点お聞きしたいのですが、RemoveDuplicatesBlank内のDim dic As New Microは、Dim dic As New Dictionaryとは異なるものなのでしょうか。 また、Dictionaryオブジェクトの値として、対応するキーの列番号と言っていいのか2次元目を取ってきていますが、これは仮に置いているもので別の値でも問題ないのでしょうか。拙い理解ですが、呼び出し元に返す値として必要なのはキーのみになっているように読めたので。
hatena19

2021/02/20 15:22

Dim dic As New Dictionary が正しいです。回答の方も修正しておきます。 DictionaryのItemは重複排除の目的には必要ないので仮のものですので、ご推察の通りなんでもいいです。
nana_mag

2021/02/22 04:03

PCがブラックアウトしてしまい処理出来ませんでしたが、とても綺麗な構成のコードで勉強になりました。ありがとうございます。 確認出来ていませんがメモリ不足でしょうかね...(corei5,16GB)。 かなり勉強になりましたので、またデータの少ない時に応用させて頂きたいと思っております。
guest

0

ベストアンサー

こんな感じでしょうか。

VBA

1Sub Sample() 2 3 Dim ws As Worksheet 4 Set ws = Sheets("Sheet1") 5 6 Dim d As Object 7 Set d = CreateObject("Scripting.Dictionary") 8 9 Dim r As Range, k As String 10 For Each r In ws.UsedRange 11 k = Join(Array(r.Row, r.Offset(1 - r.Row))) 12 If Not d.Exists(k) Then d.Add k, CreateObject("Scripting.Dictionary") 13 d(k)(r.Value) = 0 14 Next 15 16 ws.Cells.ClearContents 17 Dim e, rc, i As Long, j As Long 18 For Each e In d 19 rc = Split(e) 20 i = rc(0) 21 j = rc(1) 22 ws.Cells(i, j).Value = Join(d(e).Keys, ":") 23 Next 24 25End Sub 26 27

<追記>
空白セルもあるとのことなので、読み飛ばす処理を追加しました。
あわせて更なる速度向上のためセル範囲を配列化しました。

VBA

1Sub sample2() 2 3 Dim d As Object 4 Set d = CreateObject("Scripting.Dictionary") 5 6 Dim ws As Worksheet, arr As Variant 7 Set ws = Sheets("Sheet1") 8 arr = ws.UsedRange.Value 9 10 Dim r As Long, c As Long, k As Variant 11 For r = 1 To UBound(arr, 1) 12 For c = 1 To UBound(arr, 2) 13 k = Join(Array(r, arr(1, c))) 14 If Not d.Exists(k) Then d.Add k, CreateObject("Scripting.Dictionary") 15 If arr(r, c) <> "" Then d(k)(arr(r, c)) = 0 16 Next c, r 17 18 ws.Cells.ClearContents 19 For Each k In d 20 r = Split(k)(0) 21 c = Split(k)(1) 22 ws.Cells(r, c).Value = Join(d(k).Keys, ":") 23 Next 24 25End Sub

<再追記>
他の方のコードを拝見し、更に修正を加えました。

VBA

1 2Sub sample3() 3 4 Dim d1, d2 5 Set d1 = CreateObject("Scripting.Dictionary") 6 Set d2 = CreateObject("Scripting.Dictionary") 7 8 Dim ws, maxrow, maxcol, arr 9 Set ws = Sheets(1) 10 With ws.UsedRange 11 maxrow = .Rows.Count 12 maxcol = .Columns.Count 13 arr = .Value 14 End With 15 16 Dim c, k 17 For c = 1 To maxcol 18 k = arr(1, c) 19 If Not d1.Exists(k) Then Set d1(k) = CreateObject("Scripting.Dictionary") 20 d1(k).Add d1(k).Count, c 21 Next 22 ReDim arr2(1 To maxrow, 1 To d1.Count) 23 24 Dim r 25 For r = 1 To maxrow 26 For Each k In d1 27 d2.RemoveAll 28 For Each c In d1(k).Items 29 If arr(r, c) <> "" Then d2(arr(r, c)) = 0 30 Next 31 arr2(r, k) = Join(d2.keys, ":") 32 Next 33 Next 34 35 Set ws = Sheets(2) 36 ws.Cells.ClearContents 37 ws.Cells.Resize(maxrow, d1.Count).Value = arr2 38 39End Sub

投稿2021/02/20 01:10

編集2021/02/20 06:20
jinoji

総合スコア4592

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

nana_mag

2021/02/20 05:01

やはりDictionaryを使うのですね。 コード読ませて頂いて、反映できたらまたコメント致しますね!
jinoji

2021/02/20 14:55

参照設定をして、 Dim d1 As Dictionary, d2 As Dictionary としてあげると、 若干ですが速くなると思います。
nana_mag

2021/02/22 04:03

何度も修正頂きありがとうございます。最後のものは20s程度で処理できました。 迷いましたが、一番要件に合っていた為ベストアンサーにさせて頂きます。ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問