Locate the last used cell in a block of cells - excel

I need a formula to locate the last used cell in a block. By last used cell I mean:
Locate the last column (right-most) containing non-null data
Locate the lowest cell in that column that contains non-null data
Return the address of that cell
For example, in the block B2:I16:
The function should return: I15 rather than D16. I already have a VBA UDF that I am trying to replace:
Public Function FindLast(r As Range) As String
Dim nLastRow As Long, nLastColumn As Long
Dim nFirstRow As Long, nFirstColumn As Long
Dim i As Long, j As Long
nLastRow = r.Rows.Count + r.Row - 1
nLastColumn = r.Columns.Count + r.Column - 1
nFirstRow = r.Row
nFirstColumn = r.Column
For i = nLastColumn To nFirstColumn Step -1
For j = nLastRow To nFirstRow Step -1
If Len(r(j, i)) > 0 Then
FindLast = r(j, i).Address(0, 0)
Exit Function
End If
Next j
Next i
End Function
As the worksheet must work in a macro-free environment.

This {array formula} works:
=ADDRESS(MAX(ROW(L1:P5)*(LEN(L1:P5)>0)*(COLUMN(L1:P5)=
MAX(COLUMN(L1:P5)*(LEN(L1:P5)>0)))),
MAX(COLUMN(L1:P5)*(LEN(L1:P5)>0)), 4)
Press Ctrl + Shift + Enter
Obviously the second term catches the correct column (which is the easy part). The first term includes the second term in it, in order to search that column for the last populated row.
In the figure below it was applied on the range L1:P5 and yielded the correct result O4.
The only shortcoming I found so far is that it will error out if the range contains error cells, but from reading the OP's UDF it doesn't seem to be an issue. If it does, some additional IFERROR will solve it:
=ADDRESS(MAX(ROW(L1:P5)*IFERROR(LEN(L1:P5)>0, 0)*(COLUMN(L1:P5)=
MAX(COLUMN(L1:P5)*IFERROR(LEN(L1:P5)>0,0)))),
MAX(COLUMN(L1:P5)*IFERROR(LEN(L1:P5)>0, 0)), 4)
Press Ctrl + Shift + Enter
EDIT: Added parameter 4 to the ADDRESS function to remove the $ from the result. The results in my tests match the OP's UDF after I modified it replacing r(j, i) with r.Parent.Cells(j, i).

Here is a non CSE version:
=ADDRESS(AGGREGATE(14,6,(ROW(INDEX(L1:P5,0,AGGREGATE(14,6,COLUMN(L1:P5)/(L1:P5<>""),1)-MIN(COLUMN(L1:P5))+1)))/(INDEX(L1:P5,0,AGGREGATE(14,6,COLUMN(L1:P5)/(L1:P5<>""),1)-MIN(COLUMN(L1:P5))+1)<>""),1),AGGREGATE(14,6,COLUMN(L1:P5)/(L1:P5<>""),1))

My first approach was similar to #ScottCraner's.
=ADDRESS(MOD(AGGREGATE(14,6,(ROW(L1:P5)+COLUMN(L1:P5)*10^7)*(L1:P5<>""),1),10^7),AGGREGATE(14,6,COLUMN(L1:P5)*(L1:P5<>""),1),4)
Here the first AGGREGATE is used to calculate maximum of:
COL_NUM*10^7+ROW_NUM
for nonempty cells (multiplication by 10^7 ensures column precedence). So this function technically returns both coordinates (e.g. for P4 it is 160000004 - 16th column and 4th row). MOD extracts row number.
But if one AGGREGATE can return both coordinates (as one number), the next step was to try find a formula to return the address using AGGREGATE only once. This is the best I could come up with:
=BASE(AGGREGATE(14,6,(DECIMAL(ROW(L1:P5),36)+36^6*(DECIMAL(ADDRESS(1,COLUMN(L1:P5),4),36)-1)*(L1:P5<>"")),1),36)
This formula:
decodes column letters from Base36 (shifted 6 digits left) to
decimal
decodes row number(!) from Base36 to decimal
calculates maximum for nonempty cells
encodes result as Base36
Drawbacks:
BASE was introduced in Excel2013
Formula return P000004 instead of P4 - but it is still valid cell address - can be use with INDIRECT
Performs a lot of calculations - it is only an attempt to solve the problem with one AGGREGATE only.

Related

Using COUNTIF across a range rather than a single column/row

I'm trying to count the number of times a word appears in a range of cells using COUNTIF.
The formula I have tried is =COUNTIF($A$2:$T$9,C7)
Which is incorrect, adolescence appears 4 times across my data set. The strange thing is I can see that correct result if I use the formula builder/inserter to check the formula:
Everything I've looked at so far has pointed me towards array functions (or Control-Shift-Enter) but this doesn't work either.
What exactly is happening in the 'Insert Function' box that's not happening in the formula bar?
When you first entered that equation, you almost certainly saw a circular reference warning. And, even if you ignored it, you should look at the bottom left where you'll probably see the following helpful indicator:
With circular references, Excel is very careful not to get caught in an infinite loop as it tries to follow all the dependency chains.
In the case where you have circular dependencies between each of a great many cells (as your case does), this escalates very quickly and I'd be surprised if Excel didn't just berate you and exit in protest. Or, more likely, it just sets them to zero since it warned you and you chose to ignore it :-)
The most likely reason it works in the dialog box is because that's not actually a cell that would cause a circular reference. It's not until the formula is placed into a cell does that occur.
The solution, of course, is to get rid of the circular dependencies, by removing the count columns from the lookups used by countif.
Probably the simplest way to do that (if you want to stick with built-in functions) is to make the cells work on just the theme columns explicitly, with a formula like (in b2):
=countif($a$2:$a$9,a2) + countif($c$2:$c$9,a2) + countif($e$2:$e$9,a2) + countif($g$2:$g$9,c2)
I've only gone up to column g since I used your image as a test case, you'll obviously need to expand that to use all your columns, { a, c, e, g, i, k, m, o, q, s }.
Admittedly, that's a rather painful formula but you only need type it in once (in b2) then copy and paste to cells b3:b9, d2:d9, up to t2:t9.
Alternatively, you can use a combination of indirect, countif, and sum to achieve the same result with a shorter formula (again, expanding out to use all the individual column ranges up to s):
=sum(countif(indirect({"$a$2:$a$9","$c$2:$c$9","$e$2:$e$9","$g$2:$g$9"}),b2))
The next step beyond that is a user-defined function (UDF) that can do the heavy lifting for you. Opening up the VBA editor, you can create a module for your workbook (if one does not already exist), and enter the following UDF:
Function HowManyOf(lookFor, firstCell, lastCell, colSkip, rowSkip)
' What we are looking for.
needVal = lookFor.Value
' Get cells.
startCol = firstCell.Column
startRow = firstCell.Row
endCol = lastCell.Column
endRow = lastCell.Row
' Ensure top left to bottom right, and sane skips.
If startCol > endCol Then
temp = startCol
startCol = endCol
endCol = temp
End If
If startRow > endRow Then
temp = startRow
startRow = endRow
endRow = temp
End If
If colSkip < 0 Then colSkip = -colSkip
If colSkip = 0 Then colSkip = 1
If rowSkip < 0 Then rowSkip = -rowSkip
If rowSkip = 0 Then rowSkip = 1
' Process each column.
HowManyOf = 0
For thisCol = startCol To endCol Step colSkip
' Process row within column.
For thisRow = startRow To endRow Step rowSkip
If Cells(thisRow, thisCol).Value = needVal Then
HowManyOf = HowManyOf + 1
End If
Next
Next
End Function
Then you can simply enter the formula (again, start in b2):
' Args are:
' The cell with the thing you want to count.
' One corner of the range.
' The opposite corner of the range.
' Column skip.
' Row skip.
' Corners can be any corner as long as they're opposite.
' Protected against negative and zero skips.
=howmanyof(a2, $a$2, $h$9, 2, 1)
Then, copying that formula into all the other cells will give you what you want:
Alternatively, instead, using of many COUNIF() you can use FILTER() function with few other function to make it workable and avoid circular reference.
=SUM(--(FILTER($A$2:$T$9,MOD(SEQUENCE(,COLUMNS($A$2:$T$9)),2))=A2))
You can make it dynamic array to spill results automatically can use BYROW() lambda function like-
=BYROW(A2:A9,LAMBDA(x,SUM(--(FILTER($A$2:$T$9,MOD(SEQUENCE(,COLUMNS($A$2:$T$9)),2))=x))))
If the COUNTIF() is outside the range then it counts correctly and will show so in the formula builder BUT it will show 0 if there is some other COUNTIF() which causes a Circular Reference. Only if all Circular References are removed (other COUNTIF()s within the range for example) then it will show the count correctly. As an alternative to check the formula builder you could switch to Workbook Calculation Manual and calculate just this one cell using F2 to see the correct result.

counting multiple instances of a number in a range

I have a range of numbers and I need to identify if the first number of each cell is repeated anywhere in the corresponding row.
For example, in row 2 below, column 2 and column 3 both start with a 3. I know that if I do =LEFT(TRIM(cell)) to get just the first number but how do I find the rows that have repeated numbers in the row so row 1 isn't marked but row 2 is?
100|600|203|700| |
202|302|301|400|600|
Use a helper column with this as an array formula:
=SUMPRODUCT(--(COLUMN($A1:$E1)<>MATCH(INT($A1:$E1/100),INT($A1:$E1/100),0)))>0
Being an array formula it must be confirmed with Ctrl-Shift-Enter instead of Enter when exiting edit mode. If done correctly then Excel will put {} around the formula.
Consider the following UDF():
Public Function AnyDups(rng As Range) As Boolean
Dim valu() As String
Dim i As Long, L As Long
L = rng.Count
ReDim valu(1 To L)
AnyDups = False
If L = 1 Then Exit Function
i = 1
For Each r In rng
valu(i) = Left(r.Value, 1)
i = i + 1
Next r
For i = 1 To L - 1
v = valu(i)
For j = i + 1 To L
If v = valu(j) Then AnyDups = True
Next j
Next i
End Function
For example:
The code just loops through the possible combinations of left-most characters in the cells.
It should work with either text or numeric data.
One way to do it would be to use this formula as a basis:
=IF(ISERROR(FIND(LEFT(TRIM(A1),1),B1)),FALSE,"Row "& ROW(A1))
Depending on how you want to check your row, you can adapt it. You could either have one formula to check one cell (Lock the A1 reference and drag right) - which would allow you to know where the match is but take more space on the sheet.
Or, if you don't have too many cells to check in each row, you could concatenate all cells in the same formula:
=IF(ISERROR(FIND(LEFT(TRIM(A1),1),B1&C1&D1&E1)),FALSE,"Row "& ROW(A1))
I'm sure Gary's Student will have a more elegant answer though!

Splitting text to columns in Excel

I have a column in Excel that consists of data in the following format: "NAME OF BAND Album Title". I'd like to split this cell into two--one for the all-caps band name and another for Album Title. Below are a few examples from the data:
Column A
ABSORBED Demo '98
ABSTRACT CELL THEORY Act
ABSTRACT SATAN Elite 7512
ABSTRACT SATAN Aryan Blitzkrieg Union
ABSTRACT SATAN Satanic Blood Circle
ABSTRACT SHADOWS Symphony of Hakel
Splitting by space doesn't work since bands have varying numbers of words in their name. Any help would be appreciated.
Here is a formula solution. No VBA required.
Assuming your list starts in cell A1, enter the following formula in cell B1:
=LEFT(A1,MATCH(,--(CODE(MID(A1,ROW(OFFSET($A$1,,,LEN(A1))),1))<96),)-3)
This is an array formula and must be confirmed with Ctrl+Shift+Enter.
And then in cell C1, enter this:
=MID(A1,MATCH(,--(CODE(MID(A1,ROW(OFFSET($A$1,,,LEN(A1))),1))<96),)-1,99)
This is an array formula and must be confirmed with Ctrl+Shift+Enter.
Now select the range B1:C1 and copy downward as far as needed.
.
Here is how they work. We'll discuss the first formula.
The MID function splits the value of cell A1 into individual characters. The CODE function returns the ASCII code number for each char. We test each code number to see if it is less than 96, which is "a" the first lower case char.
This gives us an array of Boolean values (TRUE or FALSE), one Booelan value for each char in cell A1.
We convert the Booleans to ONES and ZEROES by the double unary (--).
We search the array for the location of the first ZERO by using the MATCH function.
The end of the contiguous upper case letters is three char locations prior to the location returned by MATCH.
That's it.
What makes all of that possible is the array at the heart of the formula produced by the ROW/OFFSET combination. In conjunction with the LEN function, this combo produces a vector array that looks something like {1;2;3;4;5;6;7;8;9;10;11}. That array's last and largest number is equal to the length of the value in cell A1.
.
UPDATE
Here is a sample workbook showing these formulas work on the question's example data: http://www.excelhero.com/samples/torentino_excelhero.xlsx
Something like this might split off the band names from the albums.
Sub splt()
Dim rw As Long, p As Long, r As Long, sp As Long, v As Long
Dim bnd As String, ttl As String, tmp As Variant
With Worksheets("Sheet1")
For rw = 2 To .Cells(Rows.Count, 1).End(xlUp).Row
p = 1
bnd = vbNullString
ttl = Trim(.Cells(rw, 1).Value2) & Chr(32)
tmp = Split(ttl)
For v = LBound(tmp) To UBound(tmp)
If UCase(tmp(v)) = tmp(v) Then
bnd = Join(Array(bnd, tmp(v)), Chr(32))
Else
Exit For
End If
Next v
.Cells(rw, 2) = bnd
.Cells(rw, 3) = Trim(Right(ttl, Len(ttl) - Len(bnd)))
Next rw
End With
End Sub

Using MIN/MAX in excel array formula

I have a simple array formula in excel that doesn't work in the way I wish. In columns A and B there is data (A1 is paired with B1 and so on) while in column F there is the calculation based on the parameter in column E.
In cell F1 the formula is:
{=SUM(MAX(A$1:A$9, E1)*B$1:B$9)}
What this formula does is:
=MAX(A$1:A$9, E1)*B$1 + MAX(A$1:A$9, E1)*B$2 + ...
Instead, I need a formula that does this:
=MAX(A$1, E1)*B$1 + MAX(A$2, E1)*B$2 + ...
In words, the formula I wrote (the first one) always finds the max between the values from A1 to A9 and E1, multiplies it by the i-th B value and sums the results. What I need is a formula that finds the max between the i-th A value and E1, and not between all the A values.
What I'm looking for is easily done by adding in column C the formula =MAX(A1;E$1)*B1 and then in F1 just =SUM(A1:A9), but I can't use this solution because in column F the same formula is repeated, with the E parameter changing every time.
I can use a IF instruction: in F1 I can write
{=SUM(IF(A$1:A$9>E1, A$1:A$9, E1)*B$1:B$9)}
While this formula does what I need in this case, I think it's a bad solution because I find it difficult to read and to expand. For example, if there is another parameter in column D and the factor is MIN(MAX(A$1:A$9;E1);D1), using IF will result in a very long and very unreadable and complicated formula.
Are there better solutions to my problem? Thank you all!
NOTE: syntax may vary a little because I am using the italian version of excel.
The problem is that MAX takes an array as an argument. Functions that normally take an array never return an array - they were designed to turn an array into one number. No matter how many arrays you throw at MAX, it's always just going to return one number.
I couldn't come up with a good solution, so here's a bad one
=SUMPRODUCT(((A1:A9*(A1:A9>E1))+(E1*(A1:A9<=E1)))*B1:B9)
I don't think that really increases the maintainability of the IF-based formula that you're trying to avoid. I think you're stuck with IF or a helper column.
Another possibility is a VBA function.
Public Function SumMaxMin(rRng1 As Range, rRng2 As Range, ParamArray vaMinMax() As Variant) As Double
Dim rCell As Range
Dim dReturn As Double
Dim aMult() As Double
Dim lCnt As Long
Dim i As Long
ReDim aMult(1 To rRng1.Cells.Count)
For Each rCell In rRng1.Cells
lCnt = lCnt + 1
aMult(lCnt) = rCell.Value
For i = LBound(vaMinMax) To UBound(vaMinMax) Step 2
If Not Evaluate(aMult(lCnt) & vaMinMax(i + 1) & vaMinMax(i)) Then
aMult(lCnt) = vaMinMax(i)
End If
Next i
Next rCell
For i = LBound(aMult) To UBound(aMult)
dReturn = dReturn + aMult(i) * rRng2.Cells(i).Value
Next i
SumMaxMin = dReturn
End Function
For your example
=SumMaxMin(A1:A9,B1:B9,E1,">")
Adding another condition
=SumMaxMin(A1:A9,B1:B9,E1,">",D1,"<")
It will error if your ranges aren't the same number of cells or you pass arguments that don't work with Evaluate.
Another possibility for avoiding repetitions of cell references is:
=SUM(B1:B9*ABS(A1:A9-E1*{1,-1}))/2
assuming values are non-negative. More generally to return an array of pairwise max values:
=MMULT((A1:A9-E1*{1,-1})^{2,1}^{0.5,1},{1;1}/2)
which returns:
MAX(A1,E1)
MAX(A2,E1)
...
MAX(A9,E1)
I don't remember ever cracking this problem, but for maintainability I'd probably do something like this:
{=SUM((A1:A9<E1)*E1*B$1:B$9) + SUM((A1:A9>=E1)*A1:A9*B$1:B$9)}
If I understand the problem correctly, using IF instead of MAX should do:
=SUM(IF($A$1:$A$9>E1;$A$1:$A$9;E1)*$B$1:$B$9)

Concatenate permutations between two columns

I need help with an excel assignment.
Name City
---------------
John London
Maxx NY
Ashley DC
Paris
Solution for this must be:
John-london
John-NY
John-DC
John-Paris
Maxx-london
Maxx-NY
.
.
.
.so on.
Simply I have to add text of all elements in one column to text of all elements in other column. I will appreciate if a solution without macros or VB is provided.
You can use this formula (start in Row 1 and fill down until you run out of combinations):
=IFERROR(INDEX(L_1, CEILING(ROW()/COUNTA(L_2),1) ,1) & "-" &
INDEX(L_2, 1+MOD(ROW()-1, COUNTA(L_2)) ,1), "That's it!")
I'm using named ranges "L_1" and "L_2" to refer to the first and second lists respectively
Here's an Array Formula you can use, though you will need to modify the size of the matrix depending on how many entries you have
=CONCATENATE(INDEX(A:A,MMULT(ROW(A1:A3),TRANSPOSE(ROW(B1:B4))/TRANSPOSE(ROW(B1:B4)))),"-",INDEX(B:B,MMULT(ROW(A1:A3)/ROW(A1:A3),TRANSPOSE(ROW(B1:B4)))))
Assuming Column A is Names and Column B is Cities, you would select 12 cells (3 rows high, 4 columns wide), paste the above formula in the first cell and press Ctrl + Shift + Enter to create the array formula.
If you want to see a little simpler version to see what it does before the INDEX references, check with the same area:
=CONCATENATE(MMULT(ROW(A1:A3),TRANSPOSE(ROW(B1:B4))/TRANSPOSE(ROW(B1:B4))),"-",MMULT(ROW(A1:A3)/ROW(A1:A3),TRANSPOSE(ROW(B1:B4))))
Here's a screenshot (with the formula split in 2 lines) of the single formula displaying the output over multiple cells:
This is a simple example in VBA. It is intended to show the concept, not the best practices. Please use it to get you started, and get back here if you need more help, if you want to improve the performances, etc.
The example assumes that the two lists are in A1:An and B1:Bm, and the resulting list goes in column C.
Sub Test()
Dim R1 As Integer, R2 As Integer, R As Integer, NR As Integer
NR = ActiveSheet.UsedRange.Rows.Count
Columns(3).Clear
For R1 = 1 To NR
If Not IsEmpty(Cells(R1, 1)) Then
For R2 = 1 To NR
If Not IsEmpty(Cells(R2, 2)) Then
R = R + 1
Cells(R, 3) = Cells(R1, 1) & " - " & Cells(R2, 2)
End If
Next R2
End If
Next R1
End Sub
resulting column formula should be
=column1&"-"&column2

Resources