Excel VBA Code - Combinations with restrictions - vba

I have to produce a list of combinations which are indicators to fields as I am trying to produce some conditions to interrogate data.I have some code for the combinations from here.
In the example of Power/Torque/Cylinders with a combination of 1,2,4:
I need to work out the combinations of those 3 fields, so the output would be:
So essentially all combinations but not from the same 'bucket' if that makes sense?
Edit:
The number of combinations (ie 3 in the example) will change as per the link I provided. The combinations from the link determine which field to look at or use. Eg combination 123 would be the first 3 fields in the image. Combination 1,2 would be tge first 2 and 1,3 would be first and last. I have the code for that.
Now we have the combination buckets, need to work through the combinations across thise fields.
Also I am looking for guidance on how to approach the algo and not necessarily someone to do it for me 😊
As another example, if 1,2,3 were the column combination, the expected output would be:
20-0.5-200
20-0.5-300
20-0.5-400
etc

Nested Loop:
Sub allCombo(set1 As Range, set2 As Range, set3 As Range)
Dim c1, c2, c3, n
For Each c1 In set1
For Each c2 In set2
For Each c3 In set3
n = n + 1
Debug.Print "#" & n, c1, c2, c3
Next c3
Next c2
Next c1
End Sub
Example Usage:
Sub test()
allCombo [I2:I4], [J2:J3], [L2:L3]
End Sub
Result:
#1 20 0.5 4
#2 20 0.5 8
#3 20 0.8 4
#4 20 0.8 8
#5 30 0.5 4
#6 30 0.5 8
#7 30 0.8 4
#8 30 0.8 8
#9 40 0.5 4
#10 40 0.5 8
#11 40 0.8 4
#12 40 0.8 8

Here is an option that is completely dynamic:
Option Explicit
Sub MakeCombos()
Dim myCols As Variant, i As Long, j As Long, myCombos() As Variant
Dim temp() As Variant, LastRow As Long, lngCol As Long, myLens() As Long
Dim index() As Long, totalCombs As Long, count As Long
'' Prompt user for columns N.B. there is no
'' data validation, so enter with caution
myCols = Split(InputBox("Enter the columns as a comma separated list: ", "Column Combos 3000"), ",")
ReDim myCombos(0 To UBound(myCols))
ReDim index(0 To UBound(myCols))
ReDim myLens(0 To UBound(myCols))
totalCombs = 1
'' This loop is simply populating myCombos
'' with the chosen columns. We are also populating
'' myLens with the maximum length of each column
For i = 0 To UBound(myCols)
lngCol = CLng(myCols(i))
With ActiveSheet
LastRow = .Cells(.Rows.count, lngCol).End(xlUp).Row
End With
ReDim temp(0 To LastRow - 2)
For j = 2 To LastRow
temp(j - 2) = Cells(j, lngCol)
Next j
myCombos(i) = temp
myLens(i) = LastRow - 2
'' Get the total number of combinations
totalCombs = totalCombs * (LastRow - 1)
Next i
'' This is where the magic happens. Note, we
'' don't have nested for loops. Rather, we are keeping
'' up with the correct index with the appropriately
'' named array "index". When one of the indices exceeds
'' the maximum length, we reset that index and increment
'' the next index until we have enumerated every combo
While (count < totalCombs)
For j = 0 To UBound(myCols)
Cells(count + 20, j + 1) = myCombos(j)(index(j))
Next j
j = UBound(index)
index(j) = index(j) + 1
Do While index(j) > myLens(j)
index(j) = 0
j = j - 1
If j < 0 Then Exit Do
index(j) = index(j) + 1
Loop
count = count + 1
Wend
End Sub
Here is the example input:
And here is the top of the output for entering 1,2,4 at the prompt:
And here is the top of the output for entering 2,3 at the prompt:

Here is a sub that first determines the number of items in columns I, J, L and adjust the loops accordingly:
Sub SteveP()
Dim N1 As Long, N2 As Long, N3 As Long, K As Long
Dim m1 As Long, m2 As Long, m3 As Long
Dim a As Variant, b As Variant, c As Variant
N1 = Cells(Rows.Count, "I").End(xlUp).Row
N2 = Cells(Rows.Count, "J").End(xlUp).Row
N3 = Cells(Rows.Count, "L").End(xlUp).Row
K = 1
For m1 = 2 To N1
a = Cells(m1, "I")
For m2 = 2 To N2
b = Cells(m2, "J")
For m3 = 2 To N3
c = Cells(m3, "L")
Cells(K, "M") = a
Cells(K, "N") = b
Cells(K, "O") = c
K = K + 1
Next m3
Next m2
Next m1
End Sub

Related

writing subroutine to perform calculations on column

I have a column where some cells have values but most are blank.
What I want is to fill in the blanks by spreading the values. For example
1
blank
blank
3
blank
5
would be changed to
1
1
1
1
2.5
2.5
Here's what I have so far:
Sub ()
Set z = range("numbers")
For n = 2 To z.Rows.Count
x = z.Cells(n, 1)
If IsEmpty(x) Then
'do nothing
ElseIf x.End(xlUp).Row < z.Cells(1, 1).Row Then
k = x / (x.Row - z.Cells(1, 1).Row + 1)
z(Cells(1, 1), Cells(n, 1)).Offset(0, 5) = k
Else
k = x / (x.Row - x.End(xlUp).Row + 1)
z(Cells(x.End(xlUp), 1), Cells(n, 1)).Offset(0, 5) = k
End If
Next n
There are many problems with this code. There is an 'object required' error on the line of the for loop. Also endxlup would get me the final value rather than the first value I meet going up. I just wanted to show my efforts.
Taking a wild guess, but you want go from bottom to top, and where the there are blank cells, evenly distribute the value of the last filled cell.
Going to take a stab with top-of-head code, untested:
dim i as long, lr as long, j as double, k as long
lr = cells(rows.count,1).end(xlup).row
j = 0 'value to be divided
k = 0 'number of cells to divide j across
for i = lr to 2 step -1 'assumes header in row 1
if cells(i,1).value <> "" AND j <> 0 then
if cells(i,1).value <> "" then j = cells(i,1).value
if cells(i,1).value = "" then k = k+1
else
range(cells(i+1,1),cells(i+1+k,1)).value = j/k
j = 0
k = 0
end if
next i

Excel VBA - Go through range and copy each cell 9 times

I have a spreadsheet with data as follows:
G H I J K L M N O P Q R S T U V W X Y Z AA AB AC AD ... etc.
2 1
3 2
4 4 8 12 16 20 24 28 32 36 40
5 8 16 24 32 40
That is, G2 = 1, G3 = 1 ... M4 = 28 and so on...
What I need help with is going through this range, which can be dynamic as people are entering data into this range when they need to change stuff. I need to iterate through the rows and then columns and, for each cell that has a value, I need to paste it into a different sheet in column D, 9 times for each cell.
That is, on the 2nd sheet, the data above would come across as:
Column
D
1
1
1
1
1
1
1
1
1
2
2
2
2
2
2
2
2
2
4
4
.. etc...
How do I iterate through each row, and then each column and then for each cell that has a value, copy that 9 times into column D on another sheet, and then for the next cell with a value, copy that BELOW what was pasted and so on?
Try the following. It assumes you want to go column by column iterating through all the populated cells in that column, repeating the value 9 times.
Option Explicit
Public Sub OutputRepeatedValues()
Dim arr()
Const DELIMITER As String = ","
Const NUMOFTIMES As Long = 9
With ThisWorkbook.Worksheets("Sheet1")
arr = .Range(.Range("G2"), .Range("G2").SpecialCells(xlLastCell)).Value
End With
Dim i As Long, j As Long, output As String
For i = LBound(arr, 2) To UBound(arr, 2) '<== iterate rows with a column, column by column
For j = LBound(arr, 1) To UBound(arr, 1)
If Not IsEmpty(arr(j, i)) Then output = output & Replicate(arr(j, i), NUMOFTIMES, DELIMITER)
Next j
Next i
output = Left$(output, Len(output) - 1)
ThisWorkbook.Worksheets("Sheet2").Range("B1").Resize(Len(output), 1) = Application.WorksheetFunction.Transpose(Split(output, DELIMITER))
End Sub
'Adapted from #this https://codereview.stackexchange.com/questions/159080/string-repeat-function-in-vba?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa
Public Function Replicate(ByVal RepeatString As String, ByVal NUMOFTIMES As Long, Optional ByVal DELIMITER As String = ",")
Dim s As String, c As Long, l As Long, i As Long
l = Len(RepeatString) + 1
c = l * NUMOFTIMES
s = Space$(c)
For i = 1 To c Step l
Mid(s, i, l) = RepeatString & DELIMITER
Next i
Replicate = s
End Function
Notes:
Test dataset laid out as shown below
I assume that you want to work with what ever data is down or the right of G2, including G2. In order to do this I am using SpecialCells(xlLastCell) to find the last used cell. I then construct a range with .Range(.Range("G2"), .Range("G2").SpecialCells(xlLastCell)), which in this case is $G$2:$Q$5, and read that into an array.
Assume that you indeed iterate rows with a column before moving onto next column as described in your question. I concatenate the populated cells values whilst at the same time calling the Replicate function described in 4).
I have hijacked, and adapted, a performant function by #this, to handle the string repeat. I have added in an optional argument for delimiter. A delimiter is added so I can later split on this to write out the results to individual cells within the target worksheet.
I split the string, output, on the delimiter, this creates an array of the repeated values, which I transpose, so I can write out to a column in the target sheet.
Example output:
Edit:
If instead you want to loop the rows, then columns, use with the above function the following instead:
Public Sub OutputRepeatedValues()
Dim arr()
Const DELIMITER As String = ","
Const NUMOFTIMES As Long = 9
With ThisWorkbook.Worksheets("Sheet1")
arr = .Range(.Range("G2"), .Range("G2").SpecialCells(xlLastCell)).Value
End With
Dim i As Long, j As Long, output As String
For i = LBound(arr, 1) To UBound(arr, 1) '<== iterate rows with a column, column by column
For j = LBound(arr, 2) To UBound(arr, 2)
If Not IsEmpty(arr(i, j)) Then output = output & Replicate(arr(i, j), NUMOFTIMES, DELIMITER)
Next j
Next i
output = Left$(output, Len(output) - 1)
ThisWorkbook.Worksheets("Sheet2").Range("B1").Resize(Len(output), 1) = Application.WorksheetFunction.Transpose(Split(output, DELIMITER))
End Sub
My vba is rusty but I think this (pseudo) code might help you.
def last_row as integer, last_col as integer, row as integer, col as integer, target as integer
'I like something like this to get the value but you have to know the largest column: Cells(Rows.Count, col_to_check).End(xlUp).Row
target = 1
for col = 7 to last_col '7 = G
for row = 2 to last_row
if(Not IsEmpty(Cells(row,col)) then
Range(Cells(target*9-8, 4), Cells(target*9, 4))= Cells(row,col)
target = target +1
end
next row
next col
this iterates through all cols and rows checks if there is a value and the copies it to a 9-cell range then iterates the target so it will point to the next 9 cells.

Creating all possible combinations from multiple sets

I've limited knowledge in maths so pardon the terms if i'm mistaken.
I need to create all possible combinations from multiple sets with at least 1 item included from that set.
- SetA: [1, 2, 3, 4, 5, 6, 7]
- SetB: [a, b, c, d]
- SetC: [!, #, #, $, %]
Example Output:
- [1,a,!]
- [1,2,a,c,#]
- [1,2,3,4,5,6,7,a,b,c,d,!,#,#,$,%]
Is there a specific combination formula for this since i can only come up with nested loops and i'm not sure if it is even correct.
#barrycarter had good idea of obtaining the power set. However, we don't need to reject anything as we are not getting the power set of the union of sets (this would end up being inefficient as there will be many rejects as the number of sets grows). We simply get the power set of each set, then get all combinations of those power sets. The subroutine below, works for an arbitrary number sets of arbitrary length.
Sub CreateAllCombs()
Dim ArrayOfPowSets() As Variant, mySet() As Variant, ArrCounter() As Long, myPS As Variant
Dim myCombs() As Variant, nextComb() As Variant, ParentComb() As Variant, ArrMax() As Long
Dim i As Long, j As Long, k As Long, count1 As Long, count2 As Long, CombExist As Boolean
Dim tempCol As Long, myMax As Long, maxRow As Long, totalCombs As Long
With ActiveSheet
maxRow = .Cells(.Rows.count, "A").End(xlUp).Row
End With
ReDim ArrayOfSets(1 To maxRow, 1 To 1)
ReDim ArrCounter(1 To maxRow)
ReDim ArrMax(1 To maxRow)
myMax = 0
For i = 1 To maxRow
With ActiveSheet
tempCol = .Cells(i, .Columns.count).End(xlToLeft).Column
End With
ReDim mySet(1 To tempCol)
For j = 1 To tempCol: mySet(j) = Cells(i, j): Next j
myPS = PowerSet(mySet)
ArrMax(i) = UBound(myPS)
If ArrMax(i) > myMax Then
myMax = ArrMax(i)
ReDim Preserve ArrayOfPowSets(1 To maxRow, 1 To ArrMax(i))
End If
For j = 1 To ArrMax(i)
ArrayOfPowSets(i, j) = myPS(j)
Next j
ArrCounter(i) = 1
Next i
CombExist = True
totalCombs = 0
Do While CombExist
count1 = 1
ReDim ParentComb(1 To 1)
For i = 1 To maxRow - 1
For j = 1 To UBound(ArrayOfPowSets(i, ArrCounter(i)))
ReDim Preserve ParentComb(1 To count1)
ParentComb(count1) = ArrayOfPowSets(i, ArrCounter(i))(j)
count1 = count1 + 1
Next j
Next i
For i = 1 To ArrMax(maxRow)
count2 = count1
nextComb = ParentComb
For j = 1 To UBound(ArrayOfPowSets(maxRow, i))
ReDim Preserve nextComb(1 To count2)
nextComb(count2) = ArrayOfPowSets(maxRow, i)(j)
count2 = count2 + 1
Next j
totalCombs = totalCombs + 1
ReDim Preserve myCombs(1 To totalCombs)
myCombs(totalCombs) = nextComb
Next i
k = maxRow - 1
Do While (ArrCounter(k) >= ArrMax(k))
ArrCounter(k) = 1
k = k - 1
If k = 0 Then Exit Do
Loop
If k > 0 Then ArrCounter(k) = ArrCounter(k) + 1 Else CombExist = False
Loop
Sheets("Sheet2").Select
For i = 1 To totalCombs
For j = 1 To UBound(myCombs(i))
Cells(i, j) = myCombs(i)(j)
Next j
Next i
End Sub
I used a slightly modified version of the power set function written by John Coleman found here
Function PowerSet(Items As Variant) As Variant
Dim PS As Variant
Dim i As Long, j As Long, k As Long, n As Long
Dim subset() As Variant
n = UBound(Items)
ReDim PS(1 To 1 + 2 ^ n - 2)
For i = 1 To 2 ^ n - 1
ReDim subset(1 To n)
k = 0
For j = 0 To n - 1
If i And 2 ^ j Then
k = k + 1
subset(k) = Items(j + 1)
End If
Next j
ReDim Preserve subset(1 To k)
PS(i) = subset
Next i
PowerSet = PS
End Function
This assumes that SetA is on row 1, SetB is on row 2, etc. Observe:
Additionally, the reader should be warned that this may take a while as there is over 14 million possible combinations.
(2^3 - 1) * (2^5 - 1) * (2^16 - 1) = 7 * 31 * 65535 = 14221095
Also, all combinations are written out generically to Sheet2.
I think i found my solution please verify.
First, for each sets, i created all possible combinations and checked the length using the sum of pascal triangle without the null or this formula:
n!/(r!(n-r)!) - 1
e.g.
SetB: [a, b, c, d] -> [a,b,c,d,ab,ac,ad,bc,bd,cd,abc,abd,acd,bcd,abcd]
After creating all possible combinations for each sets, I just used product rule
[SetA] X [SetB] X [SetC]
Which resulted for all possible combinations for:
multiple items
multiple sets
no repeat
no order
Reference: https://www.mathsisfun.com/combinatorics/combinations-permutations-calculator.html
EDIT1: checking for amount of combinations per set can also be (2^n)-1 where n= length of set
Have you tried using nested for loops.
Sub Hello()
MsgBox ("Hello, world!")
Dim arr1
arr1 = Array("1", "2", "3")
Dim arr2
arr2 = Array("a", "b", "c")
Dim arr3
arr3 = Array("!", "#", "$")
For i = 0 To UBound(arr1)
For j = 0 To UBound(arr2)
For k = 0 To UBound(arr3)
MsgBox (arr1(i) & arr2(j) & arr3(k))
Next
Next
Next
End Sub

Listing all the numbers within a range where range is specified in two cells - vba

I have an excel sheet with the following data:
col1 col2 col3 col4
dvdtable 6 52 57
tvunit 2 30 31
I need to copy each row in another sheet, however making 6 copies of the dvdtable row and 2 copies of the tvunit row. (col2 is referring to the quantity). In addition I need to create a new column where for each of the 6 dvdtable rows I include 52,53,54,55,56,57 respectively in the new column. See the result below:
col1 col2 col3
dvdtable 6 52
dvdtable 6 53
dvdtable 6 54
dvdtable 6 55
dvdtable 6 56
dvdtable 6 57
tvunit 2 30
tvunit 2 31
I managed to produce the code that makes multiple copies of rows thanks to another question in your forum, but I am stuck with the last part of the programming, where I need to create the list of numbers within the range given in column 3 and column 4 for each type of furniture.
You likely have to change the sheetnames.
Option Explicit
Sub whyDidIDoThisForYou()
Dim i, j, k As Integer
Dim numbRows As Integer
Dim curWriteRow As Integer
Dim temp As Integer
Dim values() As String
numbRows = Range("a1").End(xlDown).Row - 1 'assumes heading
curWriteRow = 1
ReDim values(1 To numbRows, 1 To 4)
For i = 1 To numbRows
'read all values in from initial datasheet
For j = 1 To 4
values(numbRows, j) = Sheets("Sheet1").Cells(i + 1, j).Value
Next j
'write to next sheet
'get number of things to write
temp = values(numbRows, 4) - values(numbRows, 3)
'start writing the "output" sheet!
For j = 0 To temp
Sheets("Sheet2").Cells(curWriteRow, 1).Value = values(numbRows, 1)
Sheets("Sheet2").Cells(curWriteRow, 2).Value = values(numbRows, 2)
Sheets("Sheet2").Cells(curWriteRow, 3).Value = values(numbRows, 3) + j
curWriteRow = curWriteRow + 1
Next j
Next i
End Sub
You could use arrays as below which is much quicker than writing to ranges cell by cell
The code below
reads the orginal data into a variant array Y
loops through each row of Y (lngCnt2)
runs through that Y by the number of times specifiec in colulmB (lngCnt3)
dumps the new records to a second variant array X
dumps x to a range starting in E1 when finished
Sub SplicenDice()
Dim rng1 As Range
Dim lngCnt As Long
Dim lngCnt2 As Long
Dim lngCnt3 As Long
Dim lngCnt4 As Long
Dim X
Dim Y
Set rng1 = Range([a1], Cells(Rows.Count, "D").End(xlUp))
Y = rng1.Value2
lngCnt = Application.WorksheetFunction.Sum(Range("B:B"))
ReDim X(1 To lngCnt, 1 To 3)
For lngCnt2 = 1 To UBound(Y, 1)
For lngCnt3 = 1 To Y(lngCnt2, 2)
lngCnt4 = lngCnt4 + 1
X(lngCnt4, 1) = Y(lngCnt2, 1)
X(lngCnt4, 2) = Y(lngCnt2, 2)
X(lngCnt4, 3) = Y(lngCnt2, 3) + lngCnt3 - 1
Next
Next
[e1].Resize(UBound(X, 1), UBound(X, 2)).Value2 = X
End Sub

Excel Loop through list,transpose and create a matrix based on cell content

I am receiving a large file 500k+ lines but all the content is in column A. I need to run a macro that will transpose the data into matrix form but will only create a new row when it finds "KEY*" in the ActiveCell. For example:
| KEY 4759839 | asljhk | 35049 | | sklahksdjf|
| KEY 359 | skj | 487 |y| 2985789 |
The above data in my file would originally look like this in column A:
KEY 4759839
asljhk
35049
sklahksdjf
KEY 359
skj
487
y
2985789
Considerations:
Blank cells need to be transposed as well, so the macro cant stop based on emptyCell
The number of cells between KEY's is not constant so it actually needs to read the cell to know if it should create a new row
It can either stop based on say 20 empty cells in a row or prompt for a max row number
(Optional) It would be nice if there was some sort of visual indicator for the last item in a row so that its possible to tell if the last item(s) were blank cells
I searched around and found a macro that had the same general theme but it went based on every 6 lines and I did not know enough to try to modify it for my case. But in case it helps here it is:
Sub kTest()
Dim a, w(), i As Long, j As Long, c As Integer
a = Range([a1], [a500000].End(xlUp))
ReDim w(1 To UBound(a, 1), 1 To 6)
j = 1
For i = 1 To UBound(a, 1)
c = 1 + (i - 1) Mod 6: w(j, c) = a(i, 1)
If c = 6 Then j = j + 1
Next i
[c1].Resize(j, 6) = w
End Sub
I would greatly appreciate any help you can give me!
This works with the sample data you provided in your question - it outputs the result in a table starting in B1. It runs in less than one second for 500k rows on my machine.
Sub kTest()
Dim originalData As Variant
Dim result As Variant
Dim i As Long
Dim j As Long
Dim k As Long
Dim countKeys As Long
Dim countColumns As Long
Dim maxColumns As Long
originalData = Range([a1], [a500000].End(xlUp))
countKeys = 0
maxColumns = 0
'Calculate the number of lines and columns that will be required
For i = LBound(originalData, 1) To UBound(originalData, 1)
If Left(originalData(i, 1), 3) = "KEY" Then
countKeys = countKeys + 1
maxColumns = IIf(countColumns > maxColumns, countColumns, maxColumns)
countColumns = 1
Else
countColumns = countColumns + 1
End If
Next i
'Create the resulting array
ReDim result(1 To countKeys, 1 To maxColumns) As Variant
j = 0
k = 1
For i = LBound(originalData, 1) To UBound(originalData, 1)
If Left(originalData(i, 1), 3) = "KEY" Then
j = j + 1
k = 1
Else
k = k + 1
End If
result(j, k) = originalData(i, 1)
Next i
With ActiveSheet
.Cells(1, 2).Resize(UBound(result, 1), UBound(result, 2)) = result
End With
End Sub
Tested and works:
Sub test()
Row = 0
col = 1
'Find the last not empty cell by selecting the bottom cell and moving up
Max = Range("A650000").End(xlUp).Row 'Or whatever the last allowed row number is
'loop through the data
For i = 1 To Max
'Check if the left 3 characters of the cell are "KEY" and start a new row if they are
If (Left(Range("A" & i).Value, 3) = "KEY") Then
Row = Row + 1
col = 1
End If
Cells(Row, col).Value = Range("A" & i).Value
If (i > Row) Then
Range("A" & i).Value = ""
End If
col = col + 1
Next i
End Sub