I am looking for a formula or macro which can employ the following: I need to sum-square the amount per week. This should be done from the beginning on. My data structure is as follow:
Col A Col B Column C
Year Week Amount
2000 1 368
2000 2 8646
… … …
2000 52 46846
2001 1 656
2001 2 846
… … …
2001 52 4651
2002 1 489
… … …
2014 52 46546
I would have a column D in which I have the sum-squared of the amount per week. So Cell(Column "D", "week 2000w1") should be,
=SUMSQ(Amount 2000w1)
For the first year, this is easy. The problem occurs in the next year. In Cell (Column "D", week "2001w1") the formula should be,
=SUMSQ(Amount 2000w1;Amount 2001w1)
For the last year, cell (Column "D", week "2014w1") should be the formula,
=SUMSQ(Amount 2000w1;Amount 2001w1; Amount 2002w1;Amount 2003w1;Amount 2004w1; Amount 2005w1;Amount 2006w1;Amount 2007w1; Amount 2008w1;Amount 2009w1;Amount 2010w1; Amount 2011w1;Amount 2012w1;Amount 2013w1)
This should be done for the weeks 1 till 52 for all the years. Is there a quick way to do this?
This is a solution with worksheetfunction, you can develop similar with a macro too, but I think now it's easier without it:
=SUMSQ(INDEX([Amount]*([Week]=[#Week])*([Year]<=[#Year]),0))
Thank you Juhász Máté for stealing my jerb. Now I feel dumb for having this developed.
Anyways, here's a VBA solution.
For your sample dataset =SUMQ($C:$C, $B:$B, $B2) would give 804,881 i.e. the sum of *week1*s squares.
The advanced use as =SUMQ($C:$C, $B:$B, $B2, $A:$A, "<=", $A2) will give 135,424 that is sum of *week1*s for years lower than or equal 2000.
Public Function SUMQ(NumsToSquare As Range, Filter1 As Range, FilterCriterion As Variant, _
Optional Filter2 As Range, Optional FilterRelation As String, Optional FilterCriterion2 As Variant) As Long
Set NumsToSquare = Intersect(NumsToSquare, NumsToSquare.Worksheet.UsedRange)
Set Filter1 = Intersect(Filter1, Filter1.Worksheet.UsedRange)
RowsCount = Filter1.Rows.Count
ColumnsCount = Filter1.Columns.Count
If Not Filter2 Is Nothing Then Advanced = True
If Advanced Then Set Filter2 = Intersect(Filter2, Filter2.Worksheet.UsedRange)
On Error Resume Next
For i = 1 To RowsCount
For j = 1 To ColumnsCount
If Not Advanced Then
If Filter1(i, j).Value2 = FilterCriterion Then SUMQ = SUMQ + NumsToSquare(i, j).Value2 ^ 2
Else
If Filter1(i, j).Value2 = FilterCriterion And Judge(Filter2(i, j).Value2, FilterRelation, FilterCriterion2) Then SUMQ = SUMQ + NumsToSquare(i, j).Value2 ^ 2
End If
Next j
Next i
End Function
Private Function Judge(var1 As Variant, FilterRelation As String, var2 As Variant) As Boolean
Judge = False
On Error GoTo err:
Select Case FilterRelation 'cf. https://msdn.microsoft.com/en-us/library/aa711633(v=vs.71).aspx
Case "=" 'The = operator tests whether the two operands are equal.
Judge = (var1 = var2)
Case "<>" 'The <> operator tests whether the two operands are not equal.
Judge = (var1 <> var2)
Case "<" 'The < operator tests whether the first operand is less than the second operand.
Judge = (var1 < var2)
Case ">" 'The > operator tests whether the first operand is greater than the second operand.
Judge = (var1 > var2)
Case "<=" 'The <= operator tests whether the first operand is less than or equal to the second operand.
Judge = (var1 <= var2)
Case ">=" 'The >= operator tests whether the first operand is greater than or equal to the second operand.
Judge = (var1 >= var2)
End Select
err:
End Function
Related
I am using SUMPRODUCT to match a cell equaling one of many things.
Using the below formula, I am attempting to match the value 2147 (A single value from a column of many values) to the below variations of the number 2147:
=SUMPRODUCT( -- ("2147"=Table6[data])) > 0
Table6[data] Return Value
1 2147 TRUE
2 2147, 500 FALSE
3 2146-2148 FALSE
4 21475 FALSE
The first TRUE and last FALSE values are as expected (success), but I need the middle two to match TRUE (Identify the 2147 next to the , 500 and between the range 2146-2148.
This uses a custom function. If you use it, I suggest you give it a more meaningful name.
So you'd put this formula in B1 and copy down
=Match2(2147,A1)
In outline, the function checks if a hyphen exists (using Split) and if so, checks the desired value against the lower and upper limits.
If not, using Split again, we split by commas, and if any element of the resultant array equals our desired value we return TRUE.
Function Match2(d As Double, r As Range) As Boolean
Dim v As Variant, i As Long
v = Split(r, "-")
If UBound(v) = 1 Then
If Val(v(0)) <= d And Val(v(1)) >= d Then
Match2 = True
Else
Match2 = False
End If
'we could shorten the five lines above to
'Match2 = (Val(v(0)) <= d And Val(v(1)) >= d)
Else
v = Split(r, ",")
For i = LBound(v) To UBound(v)
If Val(v(i)) = d Then
Match2 = True
Exit Function
End If
Next i
Match2 = False
End If
End Function
Just for an an FYI, this is the formula:
=SUM(IF(ISNUMBER(SEARCH("-",TRIM(MID(SUBSTITUTE(A2,",",REPT(" ",99)),(ROW(INDEX(XFD:XFD,1):INDEX(XFD:XFD,LEN(A2)-LEN(SUBSTITUTE(A2,",",""))+1))-1)*99+1,99)))),(2147 >= --LEFT(TRIM(MID(SUBSTITUTE(A2,",",REPT(" ",99)),(ROW(INDEX(XFD:XFD,1):INDEX(XFD:XFD,LEN(A2)-LEN(SUBSTITUTE(A2,",",""))+1))-1)*99+1,99)),FIND("-",TRIM(MID(SUBSTITUTE(A2,",",REPT(" ",99)),(ROW(INDEX(XFD:XFD,1):INDEX(XFD:XFD,LEN(A2)-LEN(SUBSTITUTE(A2,",",""))+1))-1)*99+1,99)))-1))*(2147<=--MID(TRIM(MID(SUBSTITUTE(A2,",",REPT(" ",99)),(ROW(INDEX(XFD:XFD,1):INDEX(XFD:XFD,LEN(A2)-LEN(SUBSTITUTE(A2,",",""))+1))-1)*99+1,99)),FIND("-",TRIM(MID(SUBSTITUTE(A2,",",REPT(" ",99)),(ROW(INDEX(XFD:XFD,1):INDEX(XFD:XFD,LEN(A2)-LEN(SUBSTITUTE(A2,",",""))+1))-1)*99+1,99)))+1,99)),--(2147 = --TRIM(MID(SUBSTITUTE(A2,",",REPT(" ",99)),(ROW(INDEX(XFD:XFD,1):INDEX(XFD:XFD,LEN(A2)-LEN(SUBSTITUTE(A2,",",""))+1))-1)*99+1,99)))))>0
It is an array formula that needs to be confirmed with Ctrl-Shift-Enter instead of Enter when exiting edit mode.
Trying to figure out a way to calculate the minimum percentage match when comparing a string to a column.
Example:
Column A Column B
Key Keylime
Key Chain Status
Serious
Extreme
Key
Where
Column A Column B Column C Column D
Key Temp 100% Key
Key Chain Status 66.7% Key Ch
Ten Key Ch 100% Tenure
Extreme
Key
Tenure
To expand on this:
Column A is the column with strings to individually match
Column B is the reference column
Column C provides the highest percent match the column A string has with any string in column B.
Column D provides the word from column B associated with the highest percent match
To expand on Column C - when looking at Key Chain - the highest match to any word it has in column B is for Key Ch where 6 out of the 9 characters (including space) of Key Chain match to give a percentage match of (6/9) = 66.7%
That being said, this isn't a deal breaker but it is something that sticks out. The logic above fails when there's no way to penalize for matches where you see an example like Ten occur. Where Ten has 3 out of 3 characters that match against Tenure giving it an inflated 100% match that I still can't think of a way to correct against.
This should work (i haven't tested it, currently on Linux). Call getStrMatch for each string.
Type StrMatch
Percent As Double
Word As String
End Type
Function getStrMatch(s As String, RefRange As Range) As StrMatch
Dim i As Long, ref_str As String
Dim BestMatch As StrMatch: BestMatch.Percent = -1
Dim match_pc As Double
With RefRange
For i = 1 to .Cells.Count
ref_str = .Cells(i).Value2
match_pc = getMatchPc(s, ref_str)
If match_pc > BestMatch.Percent Then
BestMatch.Percent = match_pc
BestMatch.Word = ref_str
End If
Next i
End With
getStrMatch = BestMatch
End Function
Function getMatchPc(s As String, ref_str As String) As Double
Dim s_len As Long: s_len = Len(s)
Dim ref_len As Long: ref_len = Len(ref_str)
Dim longer_len as Long
If s_len > ref_len Then longer_len = s_len Else longer_len = ref_len
Dim m As Long: m = 1
While m <= longer_len
If Mid(s, m, 1) <> Mid(ref_str, m, 1) Then Exit While
m = m + 1
Wend
getMatchPc = (m - 1.0) / longer_len
End Function
Note that you have to put this in a module or else declare Private Type and Private Function.
Also, if you're matching a lot of strings you probably should create a trie instead, as this is only doing naive string compares and each getStrMatch costs O(mn) where m is the size of the RefRange and n is the average ref_str length.
I've tried several different variations of the below code to try to find a method that may work. My last iteration left me at:
Dim D As Range
Dim x As Integer, c As Long, lre As Long
c = Range("A" & Rows.Count).End(xlUp).Row
lre = Sheets("Exception Report").Cells(Sheets("ExceptionReport").Rows.Count, "A").End(xlUp).Row
Range("D2:D" & lre).TextToColumns
Range("F2:F" & lre).TextToColumns
For x = c To 2 Step -1
If -0.1 < CDbl((Cells(x, "D").Value) - (Cells(x, "E").Value)) < 0.1 And -0.1 < CDbl((Cells(x, "F").Value) - (Cells(x, "G").Value)) < 0.1 Then
Cells(x, 1).EntireRow.Delete
End If
Next
The code in this part look at two pairs of cells, and I subtract one from the other in order to look at the difference. If it is within the range above (The -0.1 < code < 0.1) then it is to be deleted, basically leaving certain outliers. The text to columns part turns some of the data (which is stored as text) into number datatype so I can work with it for this purpose.
The problem I have been facing is that no matter the numbers used, it evaluates as true which then deletes the entire row, leaving the report blank, even though I know that there are rows which do not meet the criteria.
I've run out of ideas and my novice experience with excel has left me unable to debug this. Any help/comments/ideas?
You can't have multiple comparisons like a < b < c, you need to use a < b And b < c.
I am not exactly sure what happens but one of the <s is evaluated first and the result (True or False) is then compared to the last number which mixes things up.
Edit: the first < seems to be evaluated first, giving True or False since True is evaluated as -1 and False as 0 when being cast as Double, you always get True when checking <0.1.
I want to do an INDEX-MATCH-like lookup between two documents, except my MATCH's index array doesn't stay in one column.
In Vague-English: I want a value from a known column that matches another value that may be found in any column.
Refer to the image below. Let's call everything to the left of the bold vertical line on column H doc1, and the right side will be doc2.
Doc2 has a column "Find This", which will be the INDEX's array. It is compared with "ID1" from doc1 (Note that the values in "Find This" will not be in the same order as column ID1, but it's easier to undertsand this way).
The "[Result]" column in doc2 will be the value from doc1's "Want This" column from the row that matches "FIND THIS" ...However, sometimes the value from "FIND THIS" is not in the "ID1" column, and is instead in "ID2","ID3", etc.
So, I'm trying to generate Col K from Col J. This would be like pressing Ctrl+F and searching for a value in Col J, then taking the value from Col D in that row and copying it to Col K.
I made identical values from a column the same color in the other doc to make it easier to visualize where they are coming from.
Note also that in column F of doc1, the same value from doc2's "Find This" can be found after some other text.
Also note that the column headers are only there as examples, the ID columns are not actually numbered.
I would simply hard-code the correct column to search from, but I'm not in control of doc1, and I'm worried that future versions may have new "ID" columns, with other's being removed.
I'd prefer this to be a solution in the form of a formula, but VB will do.
To generate column K based on given values of column J then you could use the following:
=INDEX(doc1!$D$2:$D$14,SUMPRODUCT((doc1!$B$2:$H$14=J2)*ROW(doc1!$B$2:$H$14))-1)
Copy that formula down as far as you need to go.
It basically only returns the row of the where a matching column J is found. we then find that row in the index of your D range to get your value in K.
Proof of concept:
UPDATE:
If you are working with non unique entities n column J. That is the value on its own can be found in multiple rows and columns. Consider using the following to return the Last row where there J value is found:
=INDEX(doc1!$D$2:$D$14,AGGREGATE(14,6,(doc1!$B$2:$H$14=J2)*ROW(doc1!$B$2:$H$14),1)-1)
UPDATE 2:
And to return the first row where what you are looking in column J is found use:
=INDEX($D$2:$D$14,AGGREGATE(15,6,1/($B$2:$H$14=J2)*ROW($B$2:$H$14)-1,1))
Thanks to Scott Craner for the hint on the minimum formula.
To determine if you have UNIQUE data from column J in your range B2:H14 you can enter this array formula. In order to enter an array formula you need to press CTRL+SHFT+ENTER at the same time and not just ENTER. You will know you have done it right when you see {} around your formula in the formula bar. You cannot at the {} manually.
=IF(MAX(COUNTIF($B$2:$H$14,J2:J14))>1,"DUPLICATES","UNIQUE")
UPDATE 3
AGGREGATE - A relatively new function to me but goes back to Excel 2010. Aggregate is 19 functions rolled into 1. It would be nice if they all worked the same way but they do not. I think it is functions numbered 14 and up that will perform the same way an array formula or a CSE formula if you prefer. The nice thing is you do not need to use CSE when entering or editing them. SUMPRODUCT is another example of a regular formula that performs array formula calculations.
The meat of this explanation I believe is what is happening inside of the AGGREGATE brackets. If you click on the link you will get an idea of what the first two arguments are. The first defines which function you are using, and the second tell AGGREGATE how to deal with Errors, hidden rows, and some other nested functions. That is the relatively easy part. What I believe you want to know is what is happening with this:
(doc1!$B$2:$H$14=J2)*ROW(doc1!$B$2:$H$14)
For illustrative purpose lets reduce this formula to something a little smaller in scale that does the same thing. I'll avoid starting in A1 as that can make life a little easier when counting since it the 1st row and first column. So by placing the example range outside of it you can see some more special considerations potentially.
What I want to know is what row each of the items list in Column C occurs in column B
| B | C
3 | DOG | PLATYPUS
4 | CAT | DOG
5 | PLATYPUS |
The full formula for our mini example would be:
{=($B$3:$B$5=C2)*ROW($B$3:$B$5)}
And we are going to look at the following as an array
=INDEX($B$3:$B$5,AGGREGATE(14,6,($B$3:$B$5=C2)*ROW($B$3:$B$5),1)-2)
So the first brackets is going to be a Boolean array as you noted. Every cell that is TRUE will TRUE until its forced into a math calculation. When that happens, True becomes 1 and False becomes 0.I that formula was entered as a CSE formula and place in D2, it would break down as follows:
FALSE X 3
FALSE X 4
TRUE X 5
The 3, 4 and 5 come from ROW() returning the value of the row number that it is dealing with at the time of the array math operation. Little trick, we could have had ROW(1:3). Just need to make sure the size of the array matches! This is not matrix math is just straight across multiplication. And since the Boolean is now experiencing a math operation we are now looking at:
0 X 3 = 0
0 X 4 = 0
1 X 5 = 5
So the array of {0,0,5} gets handed back to the aggregate for more processing. The important thing to note here is that it contains ONLY 0 and the individual row numbers where we had a match. So with the first aggregate formula, formula 14 was chosen which is the LARGE function. And we also told it to ignore errors, which in this particular case does not matter. So after providing the array to the aggregate function, there was a ,1) to finish off the aggregate function. The 1 tells the aggregate function that we want the 1st larges number when the array is sorted from smallest to largest. If that number was 2 it would be the 2nd largest number and so on. So the last row or the only row that something is found on is returned. So in our small example it would be 5.
But wait that 5 was buried inside another function called Index. and in our small example that INDEX formula would be:
=INDEX($B$3:$B$5,AGGREGATE(...)-2)
Well we know that the range is only 3 rows long, so asking for the 5th row, would have excel smacking you up side the head with an error because your index number is out of range. So in comes the header row correction of -1 in the original formula or -2 for the small example and what we really see for the small example is:
=INDEX($B$3:$B$5,5-2)
=INDEX($B$3:$B$5,3)
and here is a weird bit of info, That last one does not become PLATYPUS...it becomes the cell reference to =B5 which pulls PLATYPUS. But that little nuance is a story for another time.
Now in the comments Scott essentially told me to invert for the error to get the first row. And this is important step for the aggregate and it had me running in circles for awhile. So the full equation for the first row option in our mini example is
=INDEX($B$3:$B$5,AGGREGATE(15,6,1/($B$3:$B$5=C2)*ROW($B$3:$B$5),1)-2)
And what Scott Craner was actually suggesting which Skips one math step is:
=INDEX($B$3:$B$5,AGGREGATE(15,6,ROW($B$3:$B$5)/($B$3:$B$5=C2),1)-2)
However since I only realized this after writing this all up the explanation will continue with the first of these two equations
So the important thing to note here is the change from function 14 to function 15 which is SMALL. Think of it a finding the minimum. And this time that 6 plays a huge factor along with the 1/. So our array in the middle this time equates to:
1/FALSE X 3
1/FALSE X 4
1/TRUE X 5
Which then becomes:
1/0 X 3
1/0 X 4
1/1 X 5
Which then has excel slapping you up side the head again because you are trying to divide by 0:
#div/0 X 3
#div/0 X 4
1/1 X 5
But you were smart and you protected yourself from that slap upside the head when you told AGGREGATE to ignore error when you used 6 as the second argument/reference! Therefore what is above becomes:
{5}
Since we are performing a SMALL, and we passed ,1) as the closing part of the AGGREGATE, we have essentially said give me the minimum row number or the 1st smallest number of the resulting array when sorted in ascending order.
The rest plays out the same as it did for the LARGE AGGREGATE method. The pitfall I fell into originally is I did not use the 1/ to force an error. As a result, every time I tried getting the SMALL of the array I was getting 0 from all the false results.
SUMPRODUCT works in a very similar fashion, but only works when your result array in the middle only returns 1 non zero answer. The reason being is the last step of the SUMPRODUCT function is to all the individual elements of the resulting array. So if you only have 1 non zero, you get that non zero number. If you had two rows that matched for instance 12 and 31, then the SUMPRODUCT method would return 43 which is not any of the row numbers you wanted, where as aggregate large would have told you 31 and aggregate small would have told you 12.
Something like this maybe, starting in K2 and copied down:
=IFERROR(INDEX(D:D,MAX(IFERROR(MATCH(J2,B:B,0),-1),IFERROR(MATCH(J2,E:E,0),-1),IFERROR(MATCH(J2,G:G,0),-1),IFERROR(MATCH(J2,H:H,0),-1))),"")
If you want to keep the positions of the columns for the Match variable, consider creating generic range names for each column you want to check, like "Col1", "Col2", "Col3". Create a few more range names than you think you will need and reference them to =$B:$B, =$E:$E etc. Plug all range names into Match functions inside the Max() statement as above.
When columns are added or removed from the table, adjust the range name definitions to the columns you want to check.
For example, if you set up the formula with five Matches inside the Max(), and the table changes so you only want to check three columns, point three of the range names to the same column. The Max() will only return one result and one lookup, even if the same column is matched several times.
I came up with a vba solution if I understood correctly:
Sub DisplayActiveRange()
Dim sheetToSearch As Worksheet
Set sheetToSearch = Sheet2
Dim sheetToOutput As Worksheet
Set sheetToOutput = Sheet1
Dim search As Range
Dim output As Range
Dim searchCol As String
searchCol = "J"
Dim outputCol As String
outputCol = "K"
Dim valueCol As String
valueCol = "D"
Dim r As Range
Dim currentRow As Integer
currentRow = 1
Dim maxRow As Integer
maxRow = sheetToOutput.UsedRange.Rows.Count
For currentRow = 1 To maxRow
Set search = Range("J" & currentRow)
For Each r In sheetToSearch.UsedRange
If r.Value <> "" Then
If r.Value = search.Value Then
Set output = sheetToOutput.Range(outputCol & currentRow)
output.Value = sheetToSearch.Range(valueCol & currentRow).Value
currentRow = currentRow + 1
Set search = sheetToOutput.Range(searchCol & currentRow)
End If
End If
Next
Next currentRow
End Sub
There might be better ways of doing it, but this will give you what you want. We assume headers in both "source" and "destination" sheets. You will need to adapt the "Const" declarations according to how your sheets are named. Press Control & G in Excel to bring up the VBA window and copy and paste this code into "This Workbook" under the "VBA Project" group, then select "Run" from the menu:
Option Explicit
Private Const sourceSheet = "Source"
Private Const destSheet = "Destination"
Public Sub FindColumns()
Dim rowCount As Long
Dim foundValue As String
Sheets(destSheet).Select
rowCount = 1 'Assume a header row
Do While Range("J" & rowCount + 1).value <> ""
rowCount = rowCount + 1
foundValue = FncFindText(Range("J" & rowCount).value)
Sheets(destSheet).Select
Range("K" & rowCount).value = foundValue
Loop
End Sub
Private Function FncFindText(value As String) As String
Dim rowLoop As Long
Dim colLoop As Integer
Dim found As Boolean
Dim pos As Long
Sheets(sourceSheet).Select
rowLoop = 1
colLoop = 0
Do While Range(alphaCon(colLoop + 1) & rowLoop + 1).value <> "" And found = False
rowLoop = rowLoop + 1
Do While Range(alphaCon(colLoop + 1) & rowLoop).value <> "" And found = False
colLoop = colLoop + 1
pos = InStr(Range(alphaCon(colLoop) & rowLoop).value, value)
If pos > 0 Then
FncFindText = Mid(Range(alphaCon(colLoop) & rowLoop).value, pos, Len(value))
found = True
End If
Loop
colLoop = 0
Loop
End Function
Private Function alphaCon(aNumber As Integer) As String
Dim letterArray As String
Dim iterations As Integer
letterArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
If aNumber <= 26 Then
alphaCon = (Mid$(letterArray, aNumber, 1))
Else
If aNumber Mod 26 = 0 Then
iterations = Int(aNumber / 26)
alphaCon = (Mid$(letterArray, iterations - 1, 1)) & (Mid$(letterArray, 26, 1))
Else
'we deliberately round down using 'Int' as anything with decimal places is not a full iteration.
iterations = Int(aNumber / 26)
alphaCon = (Mid$(letterArray, iterations, 1)) & (Mid$(letterArray, (aNumber - (26 * iterations)), 1))
End If
End If
End Function
Wondering if anyone could help me. I'm stumped. It's been ages since I used excel....
I have 9 columns with different values in each cell, different numbers of cells per column.
I need a formula/macro to spit out all combinations of the cells and yet still remain in the exact same order of the columns.
For example
Columns:
D / 003 / 23 / 3 / 3R / C / VFX
... / 005 / 48 / 3 / 12 / .. / VDF
... / 007 / ... / 1 / ... /... / HSF
And it spits out like this:
D0032333RCVFX
D0032333RCVDF
D0032333RCHSF
D0034833RCVFX
D0034833RCVDF
and so on....
and so on.....
Presumably you will want to call this function with a "serial number" - so that you can call "the Nth combination". The problem then breaks into two parts:
Part 1: figure out, for a given "serial number", which element of each column you need. If you had the same number of elements E in each column it would be simple: it's like writing N in base E. When the number of elements in each column is different, it's a little bit trickier - something like this:
Option Base 1
Option Explicit
Function combinationNo(r As Range, serialNumber As Integer)
' find the number of entries in each column in range r
' and pick the Nth combination - where serialNumber = 0
' gives the top row
' assumes not all columns are same length
' but are filled starting with the first row
Dim ePerRow()
Dim columnIndex As Integer
Dim totalElements As Integer
Dim i, col
Dim tempString As String
ReDim ePerRow(r.Columns.Count)
totalElements = 1
i = 0
For Each col In r.Columns
i = i + 1
ePerRow(i) = Application.WorksheetFunction.CountA(col)
totalElements = totalElements * ePerRow(i)
Next
If serialNumber >= totalElements Then
combinationNo = "Serial number too large"
Exit Function
End If
tempString = ""
For i = 1 To UBound(ePerRow)
totalElements = totalElements / ePerRow(i)
columnIndex = Int(serialNumber / totalElements)
tempString = tempString & r.Cells(columnIndex + 1, i).Value
serialNumber = serialNumber - columnIndex * totalElements
Next i
combinationNo = tempString
End Function
You call this function with the range where your columns are, and a serial number (starting at 0 for "top row only"). It assumes that any blank space is at the bottom of each column. Otherwise, it will return a string that is the concatenation of combinations of values in each column, just as you described.
EDIT perhaps the following picture, which shows how this is used and what it actually does, helps. Note that the first reference (to the table of columns of different length) is an absolute reference (using the $ sign, so when you copy it from one cell to another, it keeps referring to the same range) while the second parameter is relative (so it points to 0, 1, 2, 3 etc in turn).