I got 2 tabs in excel and i am kinda new to VBA:
"Operations":
"Details":
You can download the workbook and sampledata from here: Workbook
My CODE actually does this:
VBA SHOULD FIND EVERY TRANSACTION INSIDE "DESCRIPTION" CELL FROM TAB "OPERATIONS". IN THIS CASE THE FIRST ROW CONTAINS ONE TRANSACTION, ROW 2 CONTAINS ONE TRANSACTION AND ROW 3 CONTAINS 2 TRANSACTIONS AND ONLY CONSIDER THE OPERATION CODES WITHIN 11 DIGITS
IT SHOULD COPY THE NUMBER FROM TAB "OPERATIONS" AND PASTE IT INSIDE COLUMN "LINKED" FROM TAB "DESCRIPTION"
FIND ALL THE REPEATED VALUES INSIDE "DESCRIPTION" FIELD, I MEAN THOSE VALUES WITH SAME DESCRIPTION AND TYPE: FAC and N/C AND PERFORM THIS:
IF THE REPEATED VALUES CONTAIN A TYPE "FC" AND "N/C" IT MUST PUT THE VALUE OF THE CELL "NUMBER" FROM TAB "OPERATIONS" OF TYPE "N/C" AND PLACE IT INSIDE "LINKED" COLUMN FROM TAB "DETAILS" AND THEN, WRITE WORD: "DONE" INSIDE "NOTE" FIELD AND FINALLY COPY THE VALUE OF CELL "MONEY" FROM TAB "DETAILS" AND PASTE IT INSIDE CELL "MONEY" FROM TAB "OPERATIONS"
After running my code i got this:
The thing is that after pasting the value inside column MONEY from tab OPERATIONS i want to copy the NUMBER of the N/C and paste it inside column LINKED from tab DETAILS for that specific operation number.
Take a look, this is the expected result:
VBA code (a macro) in excel:
Sub M_snb()
Const VAL_NC As String = "N/C"
Const VAL_FAC As String = "FAC"
'column positions - ops
Const COL_OPS_TYPE As Long = 8
Const COL_OPS_NUMBER As Long = 9
Const COL_OPS_DESCR As Long = 11
Const COL_OPS_MONEY As Long = 12
'column positions - details
Const COL_DET_OPS_NUM As Long = 5
Const COL_DET_MONEY As Long = 6
Const COL_DET_LINKED As Long = 7
Const COL_DET_NOTE As Long = 8
Dim wsOps As Worksheet, wsDets As Worksheet
Dim c As Range, col As Collection, v, m
Dim rngOps As Range, rngDets As Range, rO As Long, rD As Long, rw
Dim dict As Object, colRows As Collection
Dim bFAC As Boolean, bNC As Boolean, amt, typ
Set dict = CreateObject("scripting.dictionary")
Set wsOps = ThisWorkbook.Worksheets("Operations")
Set wsDets = ThisWorkbook.Worksheets("Details")
Set rngOps = wsOps.Range("A1").CurrentRegion
Set rngDets = wsDets.Range("A1").CurrentRegion
'Loop over ops data and find all unique 11-digit numbers,
' and store the rows they're found on in a collection per number
For rO = 2 To rngOps.Rows.Count
Set col = AllNumbers(rngOps.Cells(rO, COL_OPS_DESCR).Value)
For Each v In col
If Not dict.exists(v) Then dict.Add v, New Collection 'new number?
dict(v).Add rO 'store current row number
Next v
Next rO
For Each v In dict.keys 'loop the unique numbers
Set colRows = dict(v) 'all Operations rows which contain this number...
bFAC = False
bNC = False
For Each rw In colRows 'loop rows and check "types"
Select Case rngOps.Cells(rw, COL_OPS_TYPE).Value
Case VAL_NC: bNC = True
Case VAL_FAC: bFAC = True
End Select
If bFAC And bNC Then Exit For 'already found both
Next rw
'loop over Details and see what rows can be matched to this number
' you'll need to figure out the details here...
For rD = 2 To rngDets.Rows.Count
If CStr(rngDets.Cells(rD, COL_DET_OPS_NUM).Value) = v Then
rngDets.Cells(rD, COL_DET_LINKED).Value = rngOps.Cells(colRows(1), COL_OPS_NUMBER).Value
'dataOps(rO, 4) = dataOps(rO, 4) + dataDets(rD, 2) 'fix this
If bNC And bFAC Then 'have both types?
rngDets.Cells(rD, COL_DET_NOTE).Value = "DONE"
End If
'copy the "money" value from Details back to Operations
amt = rngDets.Cells(rD, COL_DET_MONEY).Value
For Each rw In colRows
If rngOps.Cells(rw, COL_OPS_TYPE).Value = VAL_NC Then
rngOps.Cells(rw, COL_OPS_MONEY).Value = amt
End If
Next rw
End If
Next rD
Next v
End Sub
'return all 11-digit strings in v as a Collection
Function AllNumbers(v) As Collection
Const NUM_DIGITS As Long = 11
Dim m As Object, mc As Object, col As New Collection, txt, i As Long, patt, ss
txt = " " & v & " "
patt = String(NUM_DIGITS, "#")
i = 2
For i = 2 To Len(txt) - NUM_DIGITS
ss = Mid(txt, i, 11)
If ss Like patt Then
If Not Mid(txt, i - 1, 1) Like "#" Then
If Not Mid(txt, i + NUM_DIGITS, 1) Like "#" Then
col.Add ss
End If
End If
End If
Next i
Set AllNumbers = col
End Function
I was able to solve this by myself:
Sub M_snb()
Const VAL_NC As String = "N/C"
Const VAL_FAC As String = "FAC"
'column positions - ops
Const COL_OPS_TYPE As Long = 8
Const COL_OPS_NUMBER As Long = 9
Const COL_OPS_DESCR As Long = 11
Const COL_OPS_MONEY As Long = 12
Const COL_OPS_WEB As Long = 15
'column positions - details
Const COL_DET_OPS_NUM As Long = 5
Const COL_DET_MONEY As Long = 6
Const COL_DET_LINKED As Long = 7
Const COL_DET_NOTE As Long = 8
Dim wsOps As Worksheet, wsDets As Worksheet
Dim c As Range, col As Collection, v, m
Dim rngOps As Range, rngDets As Range, rO As Long, rD As Long, rw
Dim dict As Object, colRows As Collection
Dim bFAC As Boolean, bNC As Boolean, amt, typ
Set dict = CreateObject("scripting.dictionary")
Set wsOps = ThisWorkbook.Worksheets("Operations")
Set wsDets = ThisWorkbook.Worksheets("Details")
Set rngOps = wsOps.Range("A1").CurrentRegion
Set rngDets = wsDets.Range("A1").CurrentRegion
'Loop over ops data and find all unique 11-digit numbers,
' and store the rows they're found on in a collection per number
For rO = 2 To rngOps.Rows.Count
Set col = AllNumbers(rngOps.Cells(rO, COL_OPS_DESCR).Value)
For Each v In col
If Not dict.exists(v) Then dict.Add v, New Collection 'new number?
dict(v).Add rO 'store current row number
Next v
Next rO
For Each v In dict.keys 'loop the unique numbers
Set colRows = dict(v) 'all Operations rows which contain this number...
bFAC = False
bNC = False
For Each rw In colRows 'loop rows and check "types"
Select Case rngOps.Cells(rw, COL_OPS_TYPE).Value
Case VAL_NC: bNC = True
Case VAL_FAC: bFAC = True
End Select
If bFAC And bNC Then Exit For 'already found both
Next rw
'loop over Details and see what rows can be matched to this number
' you'll need to figure out the details here...
For rD = 2 To rngDets.Rows.Count
If CStr(rngDets.Cells(rD, COL_DET_OPS_NUM).Value) = v Then
rngDets.Cells(rD, COL_DET_LINKED).Value = rngOps.Cells(colRows(1), COL_OPS_NUMBER).Value
'dataOps(rO, 4) = dataOps(rO, 4) + dataDets(rD, 2) 'fix this
If bNC And bFAC Then 'have both types?
rngDets.Cells(rD, COL_DET_NOTE).Value = "Done"
End If
'copy the "money" value from Details back to Operations
amt = rngDets.Cells(rD, COL_DET_MONEY).Value
For Each rw In colRows
If rngOps.Cells(rw, COL_OPS_TYPE).Value = VAL_NC Then
rngOps.Cells(rw, COL_OPS_WEB).Value = amt
amt2 = rngOps.Cells(rw, COL_OPS_NUMBER).Value
'rngOps.Cells(rw, COL_OPS_WEB).Value = amt2
rngDets.Cells(rD, COL_DET_LINKED).Value = amt2
End If
Next rw
End If
Next rD
Next v
End Sub
'return all 11-digit strings in v as a Collection
Function AllNumbers(v) As Collection
Const NUM_DIGITS As Long = 11
Dim m As Object, mc As Object, col As New Collection, txt, i As Long, patt, ss
txt = " " & v & " "
patt = String(NUM_DIGITS, "#")
i = 2
For i = 2 To Len(txt) - NUM_DIGITS
ss = Mid(txt, i, 11)
If ss Like patt Then
If Not Mid(txt, i - 1, 1) Like "#" Then
If Not Mid(txt, i + NUM_DIGITS, 1) Like "#" Then
col.Add ss
End If
End If
End If
Next i
Set AllNumbers = col
End Function
Related
Hello smart human beings out there
I have this setup in my Excel
Basically, what I'm trying to achieve here is automatically grab every single string from column A (and paste to column H) and return the frequency in column I. The script is below
Sub WordCountTester()
Dim d As Object, k, i As Long, ws As Worksheet
Set ws = ActiveSheet
With ws.ListObjects("Table3")
If Not .DataBodyRange Is Nothing Then
.DataBodyRange.Delete
End If
End With
Set d = WordCounts(ws.Range("A2:A" & ws.Cells(Rows.Count, "A").End(xlUp).Row), _
ws.Range("F2:F" & ws.Cells(Rows.Count, "F").End(xlUp).Row))
'list words and frequencies
For Each k In d.keys
ws.Range("H2").Resize(1, 2).Offset(i, 0).Value = Array(k, d(k))
i = i + 1
Next k
End Sub
'rngTexts = range with text to be word-counted, defined in set d= above
'rngExclude = 'range with words to exclude from count, defined in set d= above
Public Function WordCounts(rngTexts As Range, rngExclude As Range) As Object 'dictionary
Dim words, c As Range, dict As Object, regexp As Object, w, wd As String, m
Set dict = CreateObject("scripting.dictionary")
Set regexp = CreateObject("VBScript.RegExp") 'see link below for reference
With regexp
.Global = True
.MultiLine = True
.ignorecase = True
.Pattern = "[\dA-Z-]{3,}" 'at least 3 characters
End With
'loop over input range
For Each c In rngTexts.Cells
If Len(c.Value) > 0 Then
Set words = regexp.Execute(LCase(c.Value))
'loop over matches
For Each w In words
wd = w.Value 'the text of the match
If Len(wd) > 1 Then 'EDIT: ignore single characters
'increment count if the word is not found in the "excluded" range
If IsError(Application.Match(wd, rngExclude, 0)) Then
dict(wd) = dict(wd) + 1
End If
End If '>1 char
Next w
End If
Next c
Set WordCounts = dict
End Function
However, it currently count the string with 1 word only. I want to count strings with 2 and 3 words (and I will consider drive-by as 2 words). Can someone please tell me where in this code I have to fix to achieve that? I still want to keep column F there because there can be 2- or 3- word strings that I want to exclude. Thanks!
If you changed your mind and consider that also two words pairs 2-3, 4-5, 6-7 and so on are necessary, please test the next solution:
Private Sub WordPairsCountTester()
Dim d As Object, k, i As Long, ws As Worksheet, arrFin
Set ws = ActiveSheet
'Attention, please! The last parameter of the called function means How Many Consecutive Words to be counted
Set d = WordPairCountsSp(ws.Range("A2:A" & ws.cells(rows.count, "A").End(xlUp).row), _
ws.Range("F2:F" & ws.cells(rows.count, "F").End(xlUp).row), 3)
arrFin = Application.Transpose(Array(d.Keys, d.items)) 'place the dictionary in an array
'clear contents of the columns where a previous result was returned, if any...:
ws.Range("H2:I" & ws.Range("H" & ws.rows.count).End(xlUp).row).ClearContents
ws.Range("H2").Resize(UBound(arrFin), 2).Value = arrFin 'drop the array content at once
End Sub
Private Function WordPairCountsSp(rngTexts As Range, rngExclude As Range, nrNeigh As Long) As Object
Dim dict As Object, arr, arrCell, i As Long, pairWd As String, j As Long, k As Long
arr = rngTexts.Value 'place the range in an array for faster iteration
Set dict = CreateObject("scripting.dictionary")
For i = 1 To UBound(arr) 'iterate between the array elements
arrCell = Split(Replace(Replace(Replace(Replace(arr(i, 1), ",", ""), ".", ""), "?", ""), "!", "")) 'split the string by default delimiter (space)
If UBound(arrCell) + 1 >= nrNeigh Then
For j = 0 To UBound(arrCell) - nrNeigh + 1 'iterate between the array elements
pairWd = arrCell(j)
For k = 1 To nrNeigh - 1
pairWd = pairWd & " " & arrCell(j + k) 'create a string from nrNeigh neighbour words
Next k
If IsError(Application.match(pairWd, rngExclude, 0)) Then
dict(pairWd) = dict(pairWd) + 1 'place the unique pairs as keys and add occurrences as items
End If
Next j
End If
Next i
Set WordPairCountsSp = dict 'return the above created dictionary
End Function
Please help
I am trying to perform the following:
I have an excel file 'A' with 50000 rows.
I am creating another excel 'B' with 150 rows.
The 150 rows are picked from file 'A'.
The row selection criteria is based on values of 5 different columns as this set
First I want to make sure I select the rows with all different combination of these 5 columns
If I run out of combinations then I can pick combination which are repeated as have to reach the 150
What I have achieved till now is selecting 150 random rows from excel A and pasted in excel B
records = 150
With DataWs
SourceLastRow = .Cells(.Rows.count, "B").End(xlUp).Row
.Rows(1).Copy DestinationWs.Cells(DestLastRow, "A")
ar = RandomNumber(2, SourceLastRow, Records)
For r = 2 To UBound(ar)
DestLastRow = DestLastRow + 1
.Rows(ar(r)).Copy DestinationWs.Cells(DestLastRow, "A")
Next r
End With
Function RandomNumber(Bottom As Long, Top As Long, Amount As Long) As Variant
Dim i As Long, r As Long, temp As Long
ReDim iArr(Bottom To Top) As Long
For i = Bottom To Top: iArr(i) = i: Next i
For i = 1 To Amount
r = Int(Rnd() * (Top - Bottom + 1 - (i - 1))) _
+ (Bottom + (i - 1))
temp = iArr(r): iArr(r) = iArr(Bottom + i - 1): _
iArr(Bottom + i - 1) = temp
Next i
ReDim Preserve iArr(Bottom To Bottom + Amount - 1)
RandomNumber = iArr
End Function
This is maybe a bit complex but worked for me:
Sub PickRows()
Const COPY_ROWS As Long = 150
Dim dict As Object, data, DataWS As Worksheet, DestWS As Worksheet
Dim numCopied As Long, r As Long, k As String, destRow As Long
Dim combo As Long, keys, col As Collection, theRow As Long, t
Set DataWS = Sheet2 'for example
Set DestWS = Sheet3 'for example
'get the source data (at least the part with the key columns) in an array
data = DataWS.Range("A1:E" & DataWS.Cells(DataWS.Rows.Count, "B").End(xlUp).Row).Value
Set dict = CreateObject("scripting.dictionary")
'fill the dictionary - keys are combined 5 columns, values are collection
' containing the row number for each source row with that key
For r = 2 To UBound(data, 1)
k = RowKey(data, r, Array(1, 2, 3, 4, 5)) 'combination of the 5 columns
If Not dict.exists(k) Then
dict.Add k, New Collection 'new combination?
End If
dict(k).Add r
Next r
numCopied = 0
combo = 0
destRow = 2
'loop over the various key column combinations and pick a row from each
' keep looping until we've copied enough rows
Do While numCopied < COPY_ROWS
'see here for why the extra ()
'https://stackoverflow.com/questions/26585884/runtime-error-with-dictionary-when-using-late-binding-but-not-early-binding
Set col = dict.Items()(combo) 'a collection of all rows for this particular key
theRow = RemoveRandom(col)
'edit line below to copy more columns (eg change 5 to 10)
DataWS.Cells(theRow, 1).Resize(1, 5).Copy DestWS.Cells(destRow, 1)
destRow = destRow + 1 'next destination row
If col.Count = 0 Then dict.Remove dict.keys()(combo) 'remove if no more rows for this key
If dict.Count = 0 Then Exit Do 'run out of any rows to pick? (should not happen...)
combo = combo + 1
If combo >= dict.Count Then combo = 0 'start looping again
numCopied = numCopied + 1
Loop
End Sub
'Create a composite key from columns in arrKeyCols
Function RowKey(data, rowNum, arrKeyCols) As String
Dim rv, i, sep
For i = LBound(arrKeyCols) To UBound(arrKeyCols)
rv = rv & sep & data(rowNum, arrKeyCols(i))
sep = "~~"
Next i
RowKey = rv
End Function
'select a random item from a collection, remove it, and return the value
Function RemoveRandom(col As Collection)
Dim rv, num As Long
num = Application.RandBetween(1, col.Count)
RemoveRandom = col(num)
col.Remove num
End Function
I have a macro (Courtesy of another questions i posted on here) which works perfectly. It searched through data and rearranged it to my needs. It also only outputted information that matched a criteria given (E.g. A cell equals the specified string).
However, now i have added a list box in where the user can select multiple criteria. But i am not sure how i can use VBA to search through the data and only output the data which matches ANY of the list box selections. As shown in the image, if the user selects CONACC and CONPEND, the code should search through the data and then output the values where the cell equals either CONACC or CONPEND.
Any ideas?
Userform Screenshot
'type to manage data we use from each row
Type dataRow
notif As Variant
variable As Variant
sht As Variant
zone As Variant
End Type
Sub DoPivot()
Const SEP As String = "<>"
Dim rngData As Range, data, r As Long
Dim colDict As Object, rowDict As Object, comboDict As Object
Dim rd As dataRow, rngOutput As Range, col As Long, rw As Long, k
Dim k2, arr, dictCounts As Object
Dim wsOut As Worksheet, num As Long
Dim DataWS, OutputtedWS As Worksheet
Dim WS As Worksheet
Dim DataWb, OutputtedWb As Workbook
Dim DataFileName, DataSheetName, DataSheetFilter As String
Set colDict = CreateObject("scripting.dictionary")
Set rowDict = CreateObject("scripting.dictionary")
Set comboDict = CreateObject("scripting.dictionary")
Set dictCounts = CreateObject("scripting.dictionary")
DataFileName = DataFilter.FileLocationTextbox.Value
DataSheetName = DataFilter.SheetNameTextBox.Value
Set OutputtedWb = ThisWorkbook
Set DataWb = Workbooks.Open(DataFileName)
Set DataWS = DataWb.Sheets(DataSheetName)
Set OutputtedWS = Sheets.Add(After:=Sheets(Sheets.Count))
Dim DataWsLastCol As Long, DataWsLastColLet As String
Dim DataWsLastRow As Long
DataWsLastCol = DataWS.Cells(1, DataWS.Columns.Count).End(xlToLeft).Column
DataWsLastColLet = Split(Cells(1, DataWsLastCol).Address, "$")(1)
DataWsLastRow = DataWS.Range("A:" & DataWsLastColLet).SpecialCells(xlCellTypeLastCell).Row
data = DataWS.Range("A2:" & DataWsLastColLet & DataWsLastRow).Value 'source data
Set rngOutput = OutputtedWS.Range("A1") 'top-left cell for output
Set wsOut = rngOutput.Parent
rngOutput.Resize(5000, 5000).ClearContents
rngOutput.Resize(1, 6).Value = Array("Sheet", "Zone", "Feature Code", "Feature Description", "-TEN OGV KH73126 tolerance", "-TEN OGV KH73126 tolerance")
OutputtedWS.Cells(2, 5) = "Nominal"
OutputtedWS.Cells(2, 6) = "Tolerance"
Set notif = DataWS.Rows("1:1").Find(What:="Notification", lookat:=xlWhole)
Set variable = DataWS.Rows("1:1").Find(What:="Extent Var.", lookat:=xlWhole)
Set sht = DataWS.Rows("1:1").Find(What:="Sheet", lookat:=xlWhole)
Set zone = DataWS.Rows("1:1").Find(What:="Zone", lookat:=xlWhole)
Set CodeGroup = DataWS.Rows("1:1").Find(What:="Code group", lookat:=xlWhole)
notifcol = notif.Column
variablecol = variable.Column
shtcol = sht.Column
zonecol = zone.Column
CodeGroupCol = CodeGroup.Column
col = rngOutput.Column + 6 'start for notification# headers
rw = rngOutput.Row + 2
'first pass - assess data variables
For r = 1 To UBound(data, 1)
If data(r, CodeGroupCol) = [[[[[[THIS IS WHERE THE SELECTION WOULD GO]]]]]] Then
rd = rowData(data, r, notifcol, variablecol, shtcol, zonecol)
k = Join(Array(rd.sht, rd.zone, rd.notif), SEP) 'tracking how many unique combinations of these
comboDict(k) = comboDict(k) + 1 'increment count
'manage column header positions for unique notification numbers
If Not colDict.exists(rd.notif) Then
colDict.Add rd.notif, col 'store the column
rngOutput.EntireRow.Cells(1, col).Value = rd.notif 'add the header
col = col + 1
End If
End If
Next r
'figure out # of rows for each sheet-Zone pair
For Each k In comboDict.keys
arr = Split(k, SEP)
k2 = Join(Array(arr(0), arr(1)), SEP) 'sheet<>zone
'is this more rows than any previous same k2 value?
dictCounts(k2) = Application.Max(dictCounts(k2), comboDict(k))
Next k
'create the row headers
For Each k In dictCounts.keys
num = dictCounts(k)
rowDict(k) = rw 'record start row for each sheet<>zone combo
wsOut.Cells(rw, rngOutput.Column).Resize(num, 2).Value = Split(k, SEP)
dictCounts(k) = 0 'reset so we can track while adding data
rowDict(k) = rw
rw = rw + num
Next k
'last pass - populate the data based on the dictionaries
For r = 1 To UBound(data, 1)
If data(r, CodeGroupCol) = [[[[[[THIS IS WHERE THE SELECTION WOULD GO]]]]]] Then
rd = rowData(data, r, notifcol, variablecol, shtcol, zonecol)
k = Join(Array(rd.sht, rd.zone, rd.notif), SEP) '3-field combo
k2 = Join(Array(rd.sht, rd.zone), SEP) 'row key
wsOut.Cells(rowDict(k2) + (dictCounts(k)), _
colDict(rd.notif)).Value = rd.variable
dictCounts(k) = dictCounts(k) + 1 'increment this unique combo
End If
Next r
End Sub
'populate a Type instance for a given row
Function rowData(data, r As Long, notficationcol, variablecol, sheetcol, zonecol) As dataRow
Dim rv As dataRow
rv.notif = IfEmpty(data(r, notficationcol))
rv.variable = IfEmpty(data(r, variablecol))
rv.sht = IfEmpty(data(r, sheetcol))
rv.zone = IfEmpty(data(r, zonecol))
rowData = rv
End Function
'substitute EMPTY for zero-length value
Function IfEmpty(v)
IfEmpty = IIf(Len(v) = 0, "EMPTY", v)
End Function
Add another dictionary
Dim i As Integer, dictFilter As Object
Set dictFilter = CreateObject("scripting.dictionary")
With DataFilter.ListBox1
For i = 0 To .ListCount - 1
If .Selected(i) = True Then
dictFilter.Add .List(i), 1
End If
Next
End With
If dictFilter.exists(data(r, CodeGroupCol)) Then
'
'
End If
Is there a way (vba code or excel trick) to manipulate a 2 columnar list so that I get a table with all potential combinations depending on a unique identifier in the first column?
E.g. I have one column with Company Names and another with Country Locations. What I need is every set if combinations of the countries per company (see screenshot attached).
This vba module should solve your problem.
Just copy the code to a new module, declare the input and output columns and the number of the first row of your list.
Note that the code will stop once it hits a line where the "Unique Identifier" Cell is empty.
Also, it requires that your list is sorted with respect to your "Unique Identifier".
If a Unique Identifier only appears once, it will still be written into the output list, but only once and with the outColNation2 staying empty in that row. If this is not desired and it should be left out entirely, just delete the commented if-statement.
Example Image of output
Also note, that a unique identifier can repeat at most 100 times. I assume none of them appears that often as that would create a ridiculously long output list.
Option Compare Text
Sub COMBINATIONS()
Dim i As Long, j As Long, k As Long, l As Long, n As Long
Dim arr(100) As String
Dim UI As String
Dim inColUI As Integer, inColNation As Integer
Dim outColUI As Integer, outColNation1 As Integer, outColNation2 As Integer
Dim FirstRowOfData As Integer
Dim YourWS As Worksheet
inColUI = 1 'Column of the "Unique Identifier"
inColNation = 2 'Column of the "Nations" in your example
outColUI = 4
outColNation1 = 5 'output columns
outColNation2 = 6
FirstRowOfData = 2 'First Row of data
Set YourWS = Application.Worksheets("Sheet1") 'Put in your Worksheet Name here.
i = FirstRowOfData
n = FirstRowOfData
With YourWS
Do Until .Cells(i, inColUI) = ""
j = 0
UI = .Cells(i, inColUI)
Do Until .Cells(i - 1, inColUI) <> .Cells(i, inColUI) And j > 0 Or .Cells(i, inColUI) = ""
arr(j + 1) = .Cells(i, inColNation)
i = i + 1
j = j + 1
Loop
If j = 1 Then '<- remove this if-statement and the following marked lines if single appearing UIs should be omitted entirely
.Cells(n, outColUI) = UI '<---
.Cells(n, outColNation1) = arr(1) '<---
n = n + 1 '<---
Else '<---
For k = 1 To j
For l = 1 To j
If arr(k) <> arr(l) Then
.Cells(n, outColUI) = UI
.Cells(n, outColNation1) = arr(k)
.Cells(n, outColNation2) = arr(l)
n = n + 1
End If
Next l
Next k
End If '<---
Loop
End With
End Sub
Edit: cleaned up the code a little bit
Something like the following shows how to iterate through 2 ranges of cells
Dim Rng1 as Range, Rng2 as Range
Dim SrcCell as Range, OthrCell as Range
Dim FullList as string
Rng1 = Range("A1:A12")
Rng2 = Range("B1:B12")
FullList = ""
For Each SrcCell in Rng1
For Each OthrCell in Rng2
FullList = IIF(FullList="","",FullList & vbCrLf) & SrcCell.Value & OthrCell.Value
Next OthrCell
Next srcCell
The FullList string now contains all the combinations but you may require something else. Only intended to give you a start
You need to add code yourself to filter out duplicates
You can do the following (see code below). As another commentee mentioned, when there is only one record of company vs country, it will not show in the output.
The solutions is based on creating a dictionary, each entry is a company and the value is a comma separated string of countries. After the dictionary is created, the dictionary is looped, and a list of countries is then iterated over a nested loop. If the index of the outer loop is the same as the inner index of the loop then the loop is skipped i.e. that would be a Country 1 vs Country 1 combination. Otherwise is added to the output list.
Columns A,B is input and columns D,E,F is output.
Option Explicit
Public Sub sCombine()
Dim r As Range, dest As Range
Dim d As New Dictionary
Dim key As Variant
Dim countries() As String
Dim i As Integer, j As Integer
On Error GoTo error_next
Set r = Sheet1.Range("A1")
Set dest = Sheet1.Range("D:F")
dest.ClearContents
Set dest = Sheet1.Range("D1")
While r.Value <> ""
If d.Exists(r.Value) Then
d(r.Value) = d(r.Value) & "," & r.Offset(0, 1)
Else
d.Add r.Value, r.Offset(0, 1).Value
End If
Set r = r.Offset(1, 0)
Wend
For Each key In d.Keys
countries = Split(d(key), ",")
For i = LBound(countries) To UBound(countries)
For j = LBound(countries) To UBound(countries)
If i <> j Then
dest.Value = key
dest.Offset(0, 1).Value = countries(i)
dest.Offset(0, 2).Value = countries(j)
Set dest = dest.Offset(1, 0)
End If
Next j
Next i
Next key
Exit Sub
error_next:
MsgBox Err.Description
End Sub
I have a column of about 50 cells. Each cell contains a block of text, anywhere from 3-8 sentences.
Id like to populate a list of words being used and obtain their frequencies for the entire range (A1:A50).
Ive tried to manipulate other codes I've found in other posts but they seem to be tailored to cells that contain one word rather than multiple words.
This is the code I found that I was attempting to use.
Sub Ftable()
Dim BigString As String, I As Long, J As Long, K As Long
Dim Selection As Range
Set Selection = ThisWorkbook.Sheets("Sheet1").Columns("A")
BigString = ""
For Each r In Selection
BigString = BigString & " " & r.Value
Next r
BigString = Trim(BigString)
ary = Split(BigString, " ")
Dim cl As Collection
Set cl = New Collection
For Each a In ary
On Error Resume Next
cl.Add a, CStr(a)
Next a
For I = 1 To cl.Count
v = cl(I)
ThisWorkbook.Sheets("Sheet2").Cells(I, "B").Value = v
J = 0
For Each a In ary
If a = v Then J = J + 1
Next a
ThisWorkbook.Sheets("Sheet2").Cells(I, "C") = J
Next I
End Sub
Here you go, a dictionary is the best way to handle this I think as you can test if the dictionary already contains an item. Post back if there's anything you don't get.
Sub CountWords()
Dim dictionary As Object
Dim sentence() As String
Dim arrayPos As Integer
Dim lastRow, rowCounter As Long
Dim ws, destination As Worksheet
Set ws = Sheets("Put the source sheet name here")
Set destination = Sheets("Put the destination sheet name here")
rowCounter = 2
arrayPos = 0
lastRow = ws.Range("A1000000").End(xlUp).Row
Set dictionary = CreateObject("Scripting.dictionary")
For x = 2 To lastRow
sentence = Split(ws.Cells(x, 1), " ")
For y = 0 To UBound(sentence)
If Not dictionary.Exists(sentence(y)) Then
dictionary.Add sentence(y), 1
Else
dictionary.Item(sentence(y)) = dictionary.Item(sentence(y)) + 1
End If
Next y
Next x
For Each Item In dictionary
destination.Cells(rowCounter, 1) = Item
destination.Cells(rowCounter, 2) = dictionary.Item(Item)
rowCounter = rowCounter + 1
Next Item
End Sub
Try this (works for me with some long blocks of Lorem Ipsum text):
Sub Ftable()
Dim BigString As String, I As Long, J As Long, K As Long
Dim countRange As Range
Set countRange = ThisWorkbook.Sheets("Sheet1").Range("A1:A50")
BigString = ""
For Each r In countRange.Cells
BigString = BigString & " " & r.Value
Next r
BigString = Trim(BigString)
ary = Split(BigString, " ")
Dim cl As Collection
Set cl = New Collection
For Each a In ary
On Error Resume Next
cl.Add a, CStr(a)
Next a
For I = 1 To cl.Count
v = cl(I)
ThisWorkbook.Sheets("Sheet2").Cells(I, "B").Value = v
J = 0
For Each a In ary
If a = v Then J = J + 1
Next a
ThisWorkbook.Sheets("Sheet2").Cells(I, "C") = J
Next I
End Sub
I took it down to only looking at the 50 cells where you have data, as opposed to all >1 million in that column. I also fixed an issue where r was getting a length 1 array instead of a Range. And I renamed "Selection" to "countRange" because Selection is already defined in the application, so it was bad naming.
Also, notice that your code pulls from "Sheet1" and outputs into columns B and C of "Sheet2". Make sure you rename your worksheets or edit these values, or you'll get errors/data corruption.
This is how I'd approach the problem:
Sub Ftable()
Dim wordDict As New Dictionary
Dim r As Range
Dim countRange As Range
Dim str As Variant
Dim strArray() As String
Set countRange = ThisWorkbook.Sheets("Sheet1").Range("A1:A50")
For Each r In countRange
strArray = Split(Trim(r.Value), " ")
For Each str In strArray
str = LCase(str)
If wordDict.Exists(str) Then
wordDict(str) = wordDict(str) + 1
Else
wordDict.Add str, 1
End If
Next str
Next r
Set r = ThisWorkbook.Sheets("Sheet2").Range("B1")
For Each str In wordDict.Keys()
r.Value = str
r.Offset(0, 1).Value = wordDict(str)
Set r = r.Offset(1, 0)
Next str
Set wordDict = Nothing
End Sub
It uses a dictionary, so make sure you add a reference to the library (Tools > Add Reference > Microsoft Scripting Library). It also forces everything to lowercase - one big issue of the old code was that it failed to count capitalized and uncapitalized versions correctly, meaning it missed many words. Remove str = LCase(str) if you don't want this.
Bonus: this method ran about 8 times faster on my test sheet.