Excel VBA to create every possible combination (without repetition) - vba

i need help with the following excel and what looks like a VBA problem.
The idea here is to generate all the possible combination (without repetition) in each grouping.
INPUT
COLUMN A | COLUMN B
A | 1
X | 1
D | 1
C | 2
E | 2
OUTPUT
COLUMN A | COLUMN B
A | X
A | D
X | D
X | A
D | A
D | X
C | E
E | C
What I managed to do.... how do i let it run only if the data is in the same group.
Option Explicit
Sub Sample()
Dim i As Long, j As Long
Dim CountComb As Long, lastrow As Long
Application.ScreenUpdating = False
CountComb = 0: lastrow = 1
For i = 1 To 10: For j = 1 To 10
Range("G" & lastrow).Value = Range("A" & i).Value & "/" & _
Range("B" & j).Value
lastrow = lastrow + 1
CountComb = CountComb + 1
Next: Next
Application.ScreenUpdating = True
End Sub

see below. Note you need to add the reference Microsoft Scripting Runtime in Tools >> References. Change the Range("A1:A5") to either a dynamic named range or static range and the routine will handle the rest for you. It displays the results starting in G1 but you can also change this / make dynamic as an offset from the data range. Up to you.
Option Explicit
Option Base 1
Dim Data As Dictionary
Sub GetCombinations()
Dim dataObj As Variant
Dim returnData As Variant
Set Data = New Dictionary
Dim i As Double
dataObj = Range("A1:B5").Value2
' Group Data
For i = 1 To UBound(dataObj) Step 1
If (Data.Exists(dataObj(i, 2))) Then
Data(dataObj(i, 2)) = Data(dataObj(i, 2)) & "|" & dataObj(i, 1)
Else
Data.Add dataObj(i, 2), dataObj(i, 1)
End If
Next i
' Extract combinations from groups
returnData = CalculateCombinations().Keys()
Range("G1").Resize(UBound(returnData) + 1, 1) = Application.WorksheetFunction.Transpose(returnData)
End Sub
Private Function CalculateCombinations() As Dictionary
Dim i As Double, j As Double
Dim datum As Variant, pieceInner As Variant, pieceOuter As Variant
Dim Combo As New Dictionary
Dim splitData() As String
For Each datum In Data.Items
splitData = Split(datum, "|")
For Each pieceOuter In splitData
For Each pieceInner In splitData
If (pieceOuter <> pieceInner) Then
If (Not Combo.Exists(pieceOuter & "|" & pieceInner)) Then
Combo.Add pieceOuter & "|" & pieceInner, vbNullString
End If
End If
Next pieceInner
Next pieceOuter
Next datum
Set CalculateCombinations = Combo
End Function

Related

Frequency of occurence for every possible combination of values in two columns on the same row

I have a data-set with Product 1 in Column A and Product 2 in Column B.
I would like to build a new table which counts the number of rows on which every possible combination of Product 1 and Product 2 occur. (Preferably regardless of the order in which they occur, but I can clean that up after if needed)
I can build this manually, however I am dealing with hundreds of possible combinations and would like to automate the process with a macro or any other recommendations anyone has.
Example of raw data:
Product 1 Product 2
Cheese Apple
Crackers Sausage
Cheese Sausage
Crackers Sausage
Apple Crackers
Apple Cheese
Cheese Apple
Cherry Apple
Example of new summarized table:
Combo | Count of Combo Occurrences
Cheese and Apple | 3
Cheese and Sausage | 1
Cherry and Apple | 1
Crackers and Sausage| 2
Apple and Crackers | 1
Thanks in advance
Late to the party but your question seemed like a fun exercise. For kicks I decided to add an extra layer of complexity by writing it to use any size range & outputting the results to a specified range (or sheet).
Sub Test()
Call CountUniqueCombinations(Range("A2:D7"), Range("F2"))
End Sub
Private Sub CountUniqueCombinations(ByVal SourceRange As Range, ByVal DestinationRange As Range)
Dim oRowIndex As Long
Dim oColIndex As Long
Dim oRow As New Collection
For oRowIndex = 0 To SourceRange.Rows.Count - 1
oValue = ""
Set oRow = Nothing
' Sort Current Row (Output to String)
For oColIndex = 1 To SourceRange.Columns.Count
oRow.Add SourceRange(oRowIndex + 1, oColIndex).Value
Next
oValue = SortCollection(oRow)
' See if Sorted row already Exists if so +1
Dim oDestRowIndex As Long
Dim oFound As Boolean
oFound = False
For oDestRowIndex = 1 To DestinationRange.Rows.Count
If DestinationRange(oDestRowIndex, 1).Value = oValue Then
DestinationRange(oDestRowIndex, 2).Value = CInt(DestinationRange(oDestRowIndex, 2).Value) + 1
oFound = True
Exit For
End If
Next
' if Sorted row doesn't exist add it
If Not oFound Then
DestinationRange(DestinationRange.Rows.Count, 1) = oValue
DestinationRange(DestinationRange.Rows.Count, 1).Offset(0, 1) = 1
Set DestinationRange = DestinationRange.Resize(DestinationRange.Rows.Count + 1, 1)
End If
Next
End Sub
Private Function SortCollection(ByVal oCollection As Collection) As String
Dim oX As Long, oY As Long
Dim oTempValue As String
For oX = 1 To oCollection.Count - 1
For oY = oX + 1 To oCollection.Count
If oCollection(oX) > oCollection(oY) Then
oTempValue = oCollection(oY)
oCollection.Remove (oY)
oCollection.Add oTempValue, oTempValue, oX
End If
Next
Next
For oX = 1 To oCollection.Count
If oCollection.Item(oX) <> "" Then
SortCollection = SortCollection & oCollection.Item(oX) & " & "
End If
Next
SortCollection = Left(SortCollection, Len(SortCollection) - 3)
End Function
Just in case some poor soul will need this in VBA:
Option Explicit
Sub ComboOccurences()
' Remember to check Microsoft Scripting Runtime in References!
Dim dict As Scripting.Dictionary
Dim i As Integer, r As Integer, LastRow As Integer
Dim ColAB As String, ColBA As String
Set dict = New Scripting.Dictionary
LastRow = Sheets("Sheet1").Cells(Rows.Count, "A").End(xlUp).Row
For i = 2 To LastRow
ColAB = Range("A" & i).Value & " and " & Range("B" & i).Value
ColBA = Range("B" & i).Value & " and " & Range("A" & i).Value
If Not dict.Exists(ColAB) And Not dict.Exists(ColBA) Then
dict.Add (ColAB), 1
ElseIf dict.Exists(ColAB) Then
dict(ColAB) = dict(ColAB) + 1
ElseIf dict.Exists(ColBA) Then
dict(ColBA) = dict(ColBA) + 1
End If
Next
r = 2
For i = 0 To dict.Count - 1
Range("D" & r).Value = dict.Keys(i)
Range("E" & r).Value = dict.Items(i)
r = r + 1
Next
End Sub
Result:
Hope this will help somebody!

Row to string with space between items

I have this:
| col1 | col2 | col 3 |
| 5 | FA | OFF |
| 107 | FA | ON |
| 96 | FO | ON |
and I want to MsgBox each row like that
Dim str As String
Dim r As Long
r = 2
While Celles(r,1).Value <> ""
str = Rows(r) ' don't know how get row with space between items
MsgBox str
Set WshShell = CreateObject("WScript.Shell")
Set WshShellExec = WshShell.Exec("""C:\mypath\prog.exe"" " & str)
r = r+1
Wend
I want 3 MsgBox to appear 5 FA OFF 107 FA ON 96 FO ON.
Therefore, how to get a row properly and add space between items ?
(After i want to call WshShellExec with parameters)
Sub x()
For i = 1 To 3
a = Application.Transpose(Application.Transpose(Range("a1:c1").Offset(i, 0).Value))
Debug.Print Join(a, "|")
Next i
End Sub
Try the code below to combine the Strings per row:
Option Explicit
Sub CombStringinRow()
Dim str As String
Dim r As Long, Col As Long
Dim LastCol As Long
Dim LastRow As Long
LastRow = Cells(Rows.Count, "A").End(xlUp).Row ' get last row with data from column "A"
For r = 2 To LastRow
LastCol = Cells(r, Columns.Count).End(xlToLeft).Column ' get last column in current row
For Col = 1 To LastCol
If str <> "" Then
str = str & " " & Cells(r, Col)
Else
str = Cells(r, Col)
End If
Next Col
MsgBox str
str = ""
Next r
End Sub
Please give this a try...
Sub ConcatenateRowValues()
Dim x
Dim i As Long
Dim Str As String
x = Range("A1").CurrentRegion.Value
For i = 2 To UBound(x, 1)
Str = Join(Application.Index(x, i, 0), " ")
MsgBox Str
Next i
End Sub
Give this a go
Sub example()
Dim str As String
Dim r As Long
Dim c
' I'd recommend changing this to your actual sheet
With ActiveSheet
For r = 2 To .Cells(.Rows.Count, 1).End(xlUp).Row
str = vbNullString
For Each c In Range(.Cells(r, 1), .Cells(r, .Cells(r, .Columns.Count).End(xlToLeft).Column))
str = str & " " & c.Value2
Next c
MsgBox WorksheetFunction.Trim(str)
Next r
End With
End Sub
just build str from values in all columns
Dim i as Long 'HERE EDITED
For i = 0 To 2
str = str & " " & Cells(r, 1).Offset(,i) 'HERE EDITED
Next i
MsgBox str
str = ""

Using two do loops to insert data into database properly

I am attempting to insert data from Excel to a SQL datbase by means of VBA. I am using the following structure in Excel:
I am using the following code:
Private Sub CommandButton1_Click()
Dim i As Integer
Dim p As Integer
Dim product As String
Dim version As String
Dim opt As String
Dim visible As String
Excel.Worksheets("Blad2").Select
i = 3
Do Until IsEmpty(Cells(i, 1))
opt = ActiveSheet.Cells(i, 1)
p = 3
Do Until IsEmpty(Cells(1, p))
product = ActiveSheet.Cells(1, p)
version = ActiveSheet.Cells(2, p)
visible = ActiveSheet.Cells(i, p)
Debug.Print product & version & opt & visible
p = p + 1
Loop
i = i + 1
Loop
End Sub
The result of running the script is as follows:
product#1 version#1 option#1 0
product#1 version#2 option#1 1
option#1
While I want it to result in:
product#1 version#1 option#1 0
product#1 version#2 option#1 1
product#1 version#1 option#2 0
product#1 version#2 option#2 0
Could someone help me out?
This is something that should work for the input of this:
bringing this:
product1 version1 option1 0
product1 version2 option1 1
product1 version1 option2 0
product1 version2 option2 0
Option Explicit
Public Sub TestMe()
Dim k As Long
Dim i As Long
Dim p As Long
Dim product As String
Dim version As String
Dim opt As String
Dim visible As String
With ActiveSheet
i = 3
Do Until IsEmpty(.Cells(i, 1))
p = 3
k = 0
Do Until IsEmpty(.Cells(1, p))
opt = .Cells(i, 1)
product = .Cells(1, p)
visible = .Cells(i, p)
version = .Cells(2, 3 + k)
k = k + 1
Debug.Print product & " " & version & " " & opt & " " & visible
p = p + 1
Loop
i = i + 1
Loop
End With
End Sub
In general, consider using better names for the variables and using Long instead of Integer.
I might be missing something but seems the variable visible is stucked with row 3 .Cells(3, p), that's why it only inserting Option 1 and disregarding the first loop.
try to change it with visible = ActiveSheet.Cells(i, p)
Edit: You said that is it not working, but seems when i tested it, I am getting the right result.
There's a possibility that when SQL string to be executed is the issue here.

Creating a unique entry for each line item in Excel

I need help in creating a macro in Excel wherein it grabs a certain cell and copies the entire row x number of times depending on the cell's contents.
To make it clear, let's say I have 2 rows:
| Order # | Item | Qty |
| 30001 | bag | 3 |
| 30002 | pen | 1 |
What I want the macro to do is grab the number under the Qty column and copy the entire row and insert a new line with the exact same contents under it. The number of times it does this depends on the number in the Qty cell. Also, it appends a three digit number in the Order # cell to make it a unique reference point. What the end-result should be:
| Order # | Item | Qty |
| 30001-001 | bag | 1 |
| 30001-002 | bag | 1 |
| 30001-003 | bag | 1 |
| 30002-001 | pen | 1 |
It's hard to explain it here but I hope you get the point. Thanks in advance, gurus!
The following code supports blank lines in the middle of the data.
If Qty = 0, it won't write the Item in the output table.
Please insert at least 1 row of data, because it won't work if there is no data :)
Option Explicit
Sub caller()
' Header at Row 1:
' "A1" = Order
' "B1" = Item
' "C1" = Qty
'
' Input Data starts at Row 2, in "Sheet1"
'
' Output Data starts at Row 2, in "Sheet2"
'
' Sheets must be manually created prior to running this program
Call makeTheThing(2, "Sheet1", "Sheet2")
End Sub
Sub makeTheThing(lStartRow As Long, sSheetSource As String, sSheetDestination As String)
Dim c As Range
Dim rOrder As Range
Dim sOrder() As String
Dim sItem() As String
Dim vQty As Variant
Dim sResult() As String
Dim i As Long
' Reads
With ThisWorkbook.Sheets(sSheetSource)
Set rOrder = .Range(.Cells(lStartRow, 1), .Cells(.Cells(.Rows.Count, 1).End(xlUp).Row, 1)) ' It will work if there are blank lines in the middle!
i = rOrder.Rows.Count
ReDim sOrder(1 To i)
ReDim sItem(1 To i)
ReDim vQty(1 To i)
i = 1
For Each c In rOrder
sOrder(i) = Trim(c.Text)
sItem(i) = Trim(c.Offset(0, 1).Text)
vQty(i) = c.Offset(0, 2).Value
i = i + 1
Next c
End With
' Processes
sResult = processData(sOrder, sItem, vQty)
' Writes
ThisWorkbook.Sheets(sSheetDestination).Range("A" & lStartRow).Resize(UBound(sResult, 1), UBound(sResult, 2)).Value = sResult
End Sub
Function processData(sOrder() As String, sItem() As String, vQty As Variant) As String()
Dim i As Long
Dim j As Long
Dim k As Long
Dim sResult() As String
j = WorksheetFunction.Sum(vQty) ' That's why vQty had to be Variant!
ReDim sResult(0 To j, 1 To 3)
k = 0
For i = 1 To UBound(sOrder)
For j = 1 To vQty(i)
sResult(k, 1) = sOrder(i) & "-" & Format(j, "000")
sResult(k, 2) = sItem(i)
sResult(k, 3) = "1"
k = k + 1
Next j
Next i
processData = sResult
End Function
I hope it helps you. I had fun making it!
One way: Walk down the qty column inserting as needed then jumping to the next original row;
Sub unwind()
Dim rowCount As Long, cell As Range, order As String, i As Long, r As Long
Set cell = Range("C1")
rowCount = Range("C" & rows.Count).End(xlUp).Row
For i = 1 To rowCount
order = cell.Offset(0, -2).Value
For r = 0 To cell.Value - 1
If (r > 0) Then cell.Offset(r).EntireRow.Insert
cell.Offset(r, 0).Value = 1
cell.Offset(r, -1).Value = cell.Offset(0, -1).Value
cell.Offset(r, -2).Value = order & "-" & Format$(r + 1, "000")
Next
Set cell = cell.Offset(r, 0)
Next
End Sub

VBA - Split string into individual cells

I have a string compressed into one cell. I need to separate each part of the string into their own cell, while copying the data from the same row.
Here is my example data:
A | B
Row1 ABC ABD ABE ABF | CODE1
Row2 BCA DBA EBA FBA | CODE2
Row3 TEA BEF | CODE3
The result would be:
A B
ABC CODE1
ABD CODE1
ABE CODE1
ABF CODE1
BCA CODE2
DBA CODE2
EBA CODE2
FBA CODE2
TEA CODE3
BEF CODE3
I have about 2000 rows and would literally take 30 years to use the text to column function for this. So I am trying to write a vba macro. I think I am making this harder than it needs to be. Any thoughts or pushes in the right direction would be appreciated. Thanks in advance for any help.
This will work, (but it's mighty inefficient unless you do it in an array... nevertheless for only 2000 rows, you won't even notice the lag)
Function SplitThis(Str as String, Delimiter as String, SerialNumber as Long) As String
SplitThis = Split(Str, Delimiter)(SerialNumber - 1)
End Function
Use it as
= SPLITTHIS("ABC EFG HIJ", " ", 2)
' The result will be ...
"EFG"
You will still need to put in a whole lot of extra error checking, etc. if you need to use it for a distributed application, as the users might put in values greater than the number of 'split elements' or get delimiters wrong, etc.
I like iterating over cells for problems like this post.
' code resides on input sheet
Sub ParseData()
Dim wksOut As Worksheet
Dim iRowOut As Integer
Dim iRow As Integer
Dim asData() As String
Dim i As Integer
Dim s As String
Set wksOut = Worksheets("Sheet2")
iRowOut = 1
For iRow = 1 To UsedRange.Rows.Count
asData = Split(Trim(Cells(iRow, 1)), " ")
For i = 0 To UBound(asData)
s = Trim(asData(i))
If Len(s) > 0 Then
wksOut.Cells(iRowOut, 1) = Cells(iRow, 2)
wksOut.Cells(iRowOut, 2) = s
iRowOut = iRowOut + 1
End If
Next i
Next iRow
MsgBox "done"
End Sub
Assuming your data is on the first sheet, this populates the second sheet with the formatted data. I also assume that the data is uniform, meaning there is the same type of data on every row until the data ends. I did not attempt the header line.
Public Sub FixIt()
Dim fromSheet, toSheet As Excel.Worksheet
Dim fromRow, toRow, k As Integer
Dim code As String
Set fromSheet = Me.Worksheets(1)
Set toSheet = Me.Worksheets(2)
' Ignore first row
fromRow = 2
toRow = 1
Dim outsideArr() As String
Dim insideArr() As String
Do While Trim(fromSheet.Cells(fromRow, 1)) <> ""
' Split on the pipe
outsideArr = Split(fromSheet.Cells(fromRow, 1), "|")
' Split left of pipe, trimmed, on space
insideArr = Split(Trim(outsideArr(0)), " ")
' Save the code
code = Trim(outsideArr(UBound(outsideArr)))
' Skip first element of inside array
For k = 1 To UBound(insideArr)
toSheet.Cells(toRow, 1).Value = insideArr(k)
toSheet.Cells(toRow, 2).Value = code
toRow = toRow + 1
Next k
fromRow = fromRow + 1
Loop
End Sub
Let me try as well using Dictionary :)
Sub Test()
Dim r As Range, c As Range
Dim ws As Worksheet
Dim k, lrow As Long, i As Long
Set ws = Sheet1 '~~> change to suit, everything else as is
Set r = ws.Range("B1", ws.Range("B" & ws.Rows.Count).End(xlUp))
With CreateObject("Scripting.Dictionary")
For Each c In r
If Not .Exists(c.Value) Then
.Add c.Value, Split(Trim(c.Offset(0, -1).Value))
End If
Next
ws.Range("A:B").ClearContents
For Each k In .Keys
lrow = ws.Range("A" & ws.Rows.Count).End(xlUp).Row
If lrow = 1 Then i = 0 Else i = 1
ws.Range("A" & lrow).Offset(i, 0) _
.Resize(UBound(.Item(k)) + 1).Value = Application.Transpose(.Item(k))
ws.Range("A" & lrow).Offset(i, 1).Resize(UBound(.Item(k)) + 1).Value = k
Next
End With
End Sub
Above code loads all items in Dictionary and then return it in the same Range. HTH.
Here is an approach using a User Defined Type, Collection and arrays. I've been using this lately and thought it might apply. It does make writing the code easier, once you get used to it.
The user defined type is set in a class module. I called the type "CodeData" and gave it two properties -- Code and Data
I assumed your data was in columns A & B starting with row 1; and I put the results on the same worksheet but in columns D & E. This can be easily changed, and put on a different worksheet if that's preferable.
First, enter the following code into a Class Module which you have renamed "CodeData"
Option Explicit
Private pData As String
Private pCode As String
Property Get Data() As String
Data = pData
End Property
Property Let Data(Value As String)
pData = Value
End Property
Property Get Code() As String
Code = pCode
End Property
Property Let Code(Value As String)
pCode = Value
End Property
Then put the following code into a Regular module:
Option Explicit
Sub ParseCodesAndData()
Dim cCodeData As CodeData
Dim colCodeData As Collection
Dim vSrc As Variant, vRes() As Variant
Dim V As Variant
Dim rRes As Range
Dim I As Long, J As Long
'Results start here. But could be on another sheet
Set rRes = Range("D1:E1")
'Get Source Data
vSrc = Range("A1", Cells(Rows.Count, "B").End(xlUp))
'Collect the data
Set colCodeData = New Collection
For I = 1 To UBound(vSrc, 1)
V = Split(vSrc(I, 1), " ")
For J = 0 To UBound(V)
Set cCodeData = New CodeData
cCodeData.Code = Trim(vSrc(I, 2))
cCodeData.Data = Trim(V(J))
colCodeData.Add cCodeData
Next J
Next I
'Write results to array
ReDim vRes(1 To colCodeData.Count, 1 To 2)
For I = 1 To UBound(vRes)
Set cCodeData = colCodeData(I)
vRes(I, 1) = cCodeData.Data
vRes(I, 2) = cCodeData.Code
Next I
'Write array to worksheet
Application.ScreenUpdating = False
rRes.EntireColumn.Clear
rRes.Resize(rowsize:=UBound(vRes, 1)) = vRes
Application.ScreenUpdating = True
End Sub
Here is the solution I devised with help from above. Thanks for the responses!
Sub Splt()
Dim LR As Long, i As Long
Dim X As Variant
Application.ScreenUpdating = False
LR = Range("A" & Rows.Count).End(xlUp).Row
Columns("A").Insert
For i = LR To 1 Step -1
With Range("B" & i)
If InStr(.Value, " ") = 0 Then
.Offset(, -1).Value = .Value
Else
X = Split(.Value, " ")
.Offset(1).Resize(UBound(X)).EntireRow.Insert
.Offset(, -1).Resize(UBound(X) - LBound(X) + 1).Value = Application.Transpose(X)
End If
End With
Next i
Columns("B").Delete
LR = Range("A" & Rows.Count).End(xlUp).Row
With Range("B1:C" & LR)
On Error Resume Next
.SpecialCells(xlCellTypeBlanks).FormulaR1C1 = "=R[-1]C"
On Error GoTo 0
.Value = .Value
End With
Application.ScreenUpdating = True
End Sub