Cell character counting formula? - excel

I need a formula that counts the total number of characters in a cell but also will count how many times that number of characters occurs.
For example in column A:
A range of 10 cells with 201, 202, 203, 204, 205, 1001, 1002, 1003, 1004, 1005.
There are 5 cells with 3 characters and 5 cells with 4 characters.
What would the formula be for this? Not sure how high I would need to go in terms of how many characters in each cell to count but I'm hoping that's an easy edit in the formula later on.

With data in column A, in B1 enter:
=SUMPRODUCT(--(LEN(A:A)=ROW()))
and copy downwards:

Add a helper column with the formula =len(A1) and copy that for column A.
Then you can have a list of the different values 3,4,5... and countif(B:B,C1) and that will give you the count of values with length 3.

For the number of chars, use another column with =len(cell).
Then make a dynamic table where you put the len column at the left side of the table and the len column again at the middle. Make sure the table is saying "count of len" or something very similar to this.

Cell Characters Count Array Version
Option Explicit
Sub CellCharactersCount()
Const cVntWsName As Variant = "Sheet1" ' Worksheet Name or Index
Const cStrFirstSource As String = "A1" ' First Cell of Source Column
Const cStrFirstTarget As String = "B1" ' First Cell of Target Column
Dim vntSource As Variant ' Source Array
Dim vntLngTarget As Variant ' Target Array (As Long)
Dim lng1 As Long, lng2 As Long ' Array Row Counters
Dim lngMax As Long ' Maximum Characters
' Paste range into Source Array
With ThisWorkbook.Worksheets(cVntWsName)
vntSource = .Range(.Range(cStrFirstSource), .Cells(Rows.Count, _
.Range(cStrFirstSource).Column).End(xlUp))
End With
' Count the maximum number of chars.
For lng1 = 1 To UBound(vntSource)
If Len(vntSource(lng1, 1)) > lngMax Then
lngMax = Len(vntSource(lng1, 1))
End If
Next
' Copy Len Data to Target Array.
ReDim vntLngTarget(1 To lngMax, 1 To 1) As Long
For lng1 = 1 To UBound(vntSource)
If Len(vntSource(lng1, 1)) <> 0 Then
For lng2 = 1 To lngMax
If Len(vntSource(lng1, 1)) = lng2 Then
vntLngTarget(lng2, 1) = vntLngTarget(lng2, 1) + 1
End If
Next
End If
Next
With ThisWorkbook.Worksheets(cVntWsName)
' Clear contents of Target Column Range.
.Range(cStrFirstTarget) _
.Resize(Rows.Count - .Range(cStrFirstTarget).Row + 1).ClearContents
' Paste Target Array into Target Range.
.Range(cStrFirstTarget).Resize(lngMax) = vntLngTarget
End With
End Sub

Related

Counting a specific number from a group of numbers separated by comma in a range of cells in excel

I have a special case where I need to count a specific number from a range of cells or a column, which will look like this
1 A
2 1,2,3
3 1,4,5
4 1,3,5,6
I need to count the "1" alone from this column A. Same way for every other numbers e.g., '2', '3' etc..
I have tried the following code, however it gives me the unique numbers count from a single cell
Public Function Count(r As Range) As Long
Dim c As Collection
Set c = New Collection
ary = Split(r.Text, ",")
On Error Resume Next
For Each a In ary
c.Add a, CStr(a)
If Err.Number = 0 Then
Count = Count + 1
Else
Err.Number = 0
End If
Next a
On Error GoTo 0
End Function`
How do I change this to a range as well as only specific to counting one number from that range?
You can do something like this:
Public Function CountNum(rng As Range, num) As Long
Dim rv As Long, c As Range, arr, a
num = CStr(num)
For Each c In rng.Cells
If Len(c.Value) > 0 Then
arr = Split(c.Value, ",")
For Each a In arr
If a = num Then rv = rv + 1
Next a
End If
Next c
CountNum = rv
End Function
To call (for example):
=countnum(A2:A4,1)
Count Delimited String Occurrences (UDF)
The Code
'***********************************************************************
' Title: Count Delimited String Occurrences
' Purpose: Counts the number of occurrences of a value in delimited parts
' of cells of a range containing not numeric values.
' Inputs:
' CountRange: Required. The range which cells to search.
' CountValue: Required. The value to search for. Variant.
' CountDelimiter: Optional. The delimiter by which each part of each
' cell will be checked against CountValue. Default is ",".
' CompareBinary0Text1: Optional. The method how the check will be
' performed. (Binary) - 0 i.e. AA <> Aa <> aa. Default.
' (Textual) - 1 i.e. AA = Aa = aa.
' All0OnlyOne1: Optional. Determines if all (0 - Default) or only
' the first (1) occurrence in each cell has to be found.
'*************************************************************************
Function CDSO(CountRange As Range, CountValue As Variant, _
Optional CountDelimiter As String = ",", _
Optional CompareBinary0Text1 As Long = 0, _
Optional All0OnlyOne1 As Long) As Long
Dim rng As Range ' Current Range (of Areas Collection)
Dim vntR As Variant ' Range Array (2D 1-based)
Dim vntC As Variant ' Cell Array (1D 0-based)
Dim vntCell As Variant ' Cell Variant
Dim i As Long ' Current Cell Row Counter
Dim j As Long ' Current Cell Column Counter
Dim k As Long ' CountRange Areas Counter
Dim m As Long ' Cell Array Element Counter
Dim ValCount As Long ' Value Counter
Dim strVal As String ' Value String
Dim strCell As String ' Cell String
' Convert CountValue to string (CStr), because arrays created
' using Split do only contain strings.
' Write CountValue to Value String.
strVal = CStr(CountValue)
' Loop through Areas Collection (ranges) of CountRange.
For k = 1 To CountRange.Areas.Count
' Check if Current Range contains one cell only.
If CountRange.Areas(k).Cells.Count = 1 Then
' Write value of Current Range (one cell only) to Cell Variant.
vntCell = CountRange.Areas(k)
' Go to Occurrences Counter Subroutine.
GoSub OccurrencesCounter
Else
' Copy Current Range to Range Array.
vntR = CountRange.Areas(k)
' Loop through rows of Range Array.
For i = 1 To UBound(vntR)
' Loop through columns of Range Array.
For j = 1 To UBound(vntR, 2)
' Write value of current element of Range Array to Cell
' Variant.
vntCell = vntR(i, j)
' Go to Occurrences Counter Subroutine.
GoSub OccurrencesCounter
Next
Next
End If
Next
' Write value of Value Counter to Count String Occurrences (CDSO).
CDSO = ValCount
Exit Function
' Occurrences Counter
' Purpose: Count the number of occurrences of CountValue in Cell String.
OccurrencesCounter:
' Check if Cell Variant is a number.
If IsNumeric(vntCell) Then Return
' Write value of Cell Variant converted to string to Cell String.
strCell = CStr(vntCell)
' Check if Cell String is not empty ("").
If strCell = "" Then Return
' Split Cell String by CountDelimiter into Cell Array.
vntC = Split(strCell, CountDelimiter)
' Loop through elements of Cell Array.
For m = 0 To UBound(vntC)
' Sometimes the values contain deliberate or accidental
' spaces, so Trim is used to remove them.
' If you want to use the vbTextCompare i.e. AA = Aa, AA = aa,
' in the formula set CompareBinary0Text1 to 1.
' Check if value of current element in Cell Array
' is equal to CountValue.
If StrComp(Trim(vntC(m)), strVal, CompareBinary0Text1) = 0 Then
' Count the occurrence i.e. increase Value Counter.
ValCount = ValCount + 1
' Note: If only the first occurrence in each cell is needed,
' increase efficiency with Exit For i.e. in the formula
' set All0OnlyOne1 to 1.
' Check if All0OnlyOne1 is equal to 1.
If All0OnlyOne1 = 1 Then
' Stop looping, occurrence found.
Exit For
End If
End If
Next
Return
End Function
'******************************************************************************

Conditional Concatenation in Excel

As you can see in the image, there are some 1 and 0s rearranged in 3 rows and one English Alphabet for each column. What I need to do is concatenate the English Alphabets for each row when the respective column value is 0. How can I do it?
Here is a VBA solution that can handle any number of columns (assuming that the letter associated with each column is the standard column label):
Function ZeroColumns(R As Range) As String
Dim n As Long
Dim count As Long
Dim cols As Variant
Dim cell As Range
n = R.Cells.count
ReDim cols(1 To n)
For Each cell In R.Cells
If cell.Value = 0 Then
count = count + 1
cols(count) = Split(cell.Address, "$")(1)
End If
Next cell
ReDim Preserve cols(1 To count)
ZeroColumns = Join(cols, "")
End Function
The code shouldn't be too hard to tweak if the stated assumption doesn't hold.
Conditionally Concatenate Row (UDF)
Arguments
SourceRowRange: The range containing the values that will be
written toCCROW e.g. A, B, C ... Required.
CriteriaRowRange: The range that will be checked for
CriteriaValue. Required.
CriteriaValue: The value that the cells in CriteriaRowRange will
be checked against. Default is 0. Optional.
JoinString: The value that will be put between the values that will
be written to CCROW. Default is "". Optional.
' Copy the following code to a standard module i.e. in VBE go to Insert>Module.
The Code
Function CCROW(ByVal SourceRowRange As Range, ByVal CriteriaRowRange As Range, _
Optional ByVal CriteriaValue As Variant = 0, _
Optional ByVal JoinString As String) As String
Dim vntS As Variant ' Source Array
Dim vntC As Variant ' Criteria Array
Dim NoC As Long ' Number of Columns
Dim j As Long ' Arrays Column Counter
Dim strB As String ' String Builder
Dim strC As String ' Criteria String
' Calculate number of columns of the narrower Range.
NoC = WorksheetFunction.Min(SourceRowRange.Columns.count, _
CriteriaRowRange.Columns.count)
' Copy resized (adjust them to same size) Ranges to Arrays.
vntS = SourceRowRange.Resize(1, NoC)
vntC = CriteriaRowRange.Resize(1, NoC)
' Loop through columns of either Array.
For j = 1 To NoC
' Write current value of Criteria Array to Criteria String.
strC = vntC(1, j)
' Check if Criteria String is NOT empty.
If strC <> "" Then
' Check if Criteria String is equal to Criteria Value.
If strC = CriteriaValue Then
' Check if String Builder is NOT empty.
If strB <> "" Then ' NOT empty.
strB = strB & JoinString & vntS(1, j)
Else ' IS empty (only once).
strB = vntS(1, j)
End If
End If
End If
Next
' Write String Builder to Conditionally Concatenate Row.
CCROW = strB
End Function
Usage in Excel
=CCROW(A$1:I$1,A3:I3) ' Result: ADG
=CCROW(A$1:I$1,A4:I4) ' Result: CFI
=CCROW(A$1:I$1,A5:I5) ' Result: DG
If you add JoinString:
=CCROW(A$1:I$1,A3:I3,,",") ' Result: A,D,G
=CCROW(A$1:I$1,A3:I3,0,",") ' Result: A,D,G
=CCROW(A$1:I$1,A3:I3,0,", ") ' Result: A, D, G
IF you change CriteriaValue:
=CCROW(A$1:I$1,A3:I3,1) ' Result: BCEFHI
=CCROW(A$1:I$1,A4:I4,1) ' Result: ABDEGH
=CCROW(A$1:I$1,A5:I5,1) ' Result: ABCEFHI
Remarks
Lock ($) the row of SourceRowRange to keep it the same when the formula is copied down.
You can do it all in one formula if you like:
=CONCATENATE(IF($A1=0,'A',''),IF($B1=0,'B',''), ...)
Or put the intermediate strings in a separate row and then concatenate them (to save wear and tear on your fingers).
Are you going to this to many more columns, or just the ones you've mentioned? As long as the number of columns is relatively small, as in your picture, you can concatenate IF functions to achieve your result.
Here's what I did:
Using that formula will get you a result like the one you have:
Assuming also that you have the values in a worksheet like mine, just paste the formula =IF(B3=1,"",B$1)&IF(C3=1,"",C$1)&IF(D3=1,"",D$1)&IF(E3=1,"",E$1)&IF(F3=1,"",F$1)&IF(G3=1,"",G$1)&IF(H3=1,"",H$1)&IF(I3=1,"",I$1)&IF(J3=1,"",J$1)
in B7 and then drag to B8 and B9 to get the rest of the results.
Of course, if you are going to do this for many more columns, it's maybe best to use VBA.
Here, add this function to a module.
You can then call it directly via excel. Nice one.
Function conc(ref As Range, Optional Separator As String) As String
Dim Cell As Range
Dim Result As String
For Each Cell In ref
If Cell.Value = 0 Then
Result = Result & chr(64 + Cell.Column) & Separator
End If
Next Cell
If Separator <> "" Then conc = Left(Result, Len(Result) - 1) Else: conc = Result
End Function
The following array formula will do the job (enter it with Ctrl+Shift+Enter):
=CONCAT(IF($A1:$I1=0,UNICHAR(64+COLUMN($A1:$I1)),""))
For older Excel versions, use the legacy functions CONCATENATE() and CHAR() in place of these functions.

Copy Sheet Rows to New Sheet Based on Split Cell Text

I have a problem I need help with involving Excel and VBA. I know next to nothing about Excel/VBA, and I need a coding solution to help me avoid performing the extremely tedious task of doing this manually (think hundreds of lines that need to be parsed where one row could become multiple rows in a new sheet). I've been searching the web for solutions, but I just keep getting confused by the answers (because I don't know anything about VB and using it to program a macro in Excel), so I figured I'd seek help for my specific problem.
Here is the rundown: I have a spreadsheet where I need to copy rows from a source sheet to a target sheet. The source sheet has 2 columns (A & B) that can be thought of as a key/value pair where col A contains the key and col B contains the value. The problem lies with the values in col B. The values can either be a single line of text or a numbered list of different texts
What I want to do is for each row in the source:
split the values in col B to get an array of each individual value (if the value is in the form of a numbered list)
create new rows in the target sheet by looping over the split array of values such that a new row will be created where:
new row col A = source row col A key and new row col B = current iteration index from the array of split values.
if no numbered list, just copy the source row into target sheet
Source
A B
key1 1. text1
2. text2
key2 1. text3
Target
A B
key1 text1
key1 text2
key2 text3
The numbered list in a cell will be multiple lines where each line of text is prepended by a decimal and a dot. This applies to single line cells as well.
(Update) Bear in mind that the values in either col A or B are not simple text values. These are full on sentences. So, I'm not sure a simple formula is going to work.
Split Multi Line
It is unclear which line separator occurs in the multi line cells. Choose one, vbLf worked for me.
Adjust the values in the constants section to fit your needs.
The Code
Sub SplitMultiLine()
Const cSheet1 As Variant = "Sheet1" ' Source Worksheet Name/Index
Const cFirstR As Integer = 1 ' Source First Row Number
Const cFirstC As Variant = "A" ' Source First Column Letter/Number
Const cLastC As Variant = "C" ' Source Last Column Letter/Number
Const cMulti As Integer = 2 ' Multi Column
Const cSplit As String = vbLf ' Split Char(vbLf, vbCrLf, vbCr)
Const cDot As String = "." ' Dot Char (Delimiter)
Const cSheet2 As Variant = "Sheet1" ' Target Worksheet Name/Index
Const cTarget As String = "E1" ' Target First Cell Address
Dim vntS As Variant ' Source Array
Dim vntSplit As Variant ' Split Array
Dim vntT As Variant ' Target Array
Dim lastR As Long ' Source Last Row
Dim i As Long ' Source Array Row Counter
Dim j As Integer ' Source/Target Array Column Counter
Dim k As Long ' Target Array Row Counter
Dim m As Integer ' Split Array Row Counter
' Paste Source Range into Source Array.
With Worksheets(cSheet1)
lastR = .Cells(.Rows.Count, cFirstC).End(xlUp).Row
vntS = .Range(.Cells(cFirstR, cFirstC), .Cells(lastR, cLastC))
End With
' Count the number of rows in target array.
For i = 1 To UBound(vntS)
k = k + UBound(Split(vntS(i, cMulti), cSplit)) + 1
Next
' Write from Source to Target Array.
ReDim vntT(1 To k, 1 To UBound(vntS, 2))
k = 0
For i = 1 To UBound(vntS)
k = k + 1
vntSplit = Split(vntS(i, cMulti), cSplit)
For m = 0 To UBound(vntSplit)
If InStr(vntSplit(m), cDot) > 0 Then
vntT(k, cMulti) = Trim(Right(vntSplit(m), Len(vntSplit(m)) _
- InStr(vntSplit(m), cDot)))
Else
vntT(k, cMulti) = vntSplit(m)
End If
For j = 1 To UBound(vntS, 2)
If j <> cMulti Then
vntT(k, j) = vntS(i, j)
End If
Next
k = k + 1
Next
k = k - 1
Next
' Paste Target Array into Target Range calculated from Target Frist Cell.
With Worksheets(cSheet2).Range(cTarget)
.Resize(UBound(vntT), UBound(vntT, 2)) = vntT
End With
End Sub
An Over-Commenting
Sub SplitMultiLineOverCommented()
Const cSheet1 As Variant = "Sheet1" ' Source Worksheet Name/Index
Const cFirstR As Integer = 1 ' Source First Row Number
Const cFirstC As Variant = "A" ' Source First Column Letter/Number
Const cLastC As Variant = "C" ' Source Last Column Letter/Number
Const cMulti As Integer = 2 ' Multi Column
Const cSplit As String = vbLf ' Split Char(vbLf, vbCrLf, vbCr)
Const cDot As String = "." ' Dot Char (Delimiter)
Const cSheet2 As Variant = "Sheet1" ' Target Worksheet Name/Index
Const cTarget As String = "E1" ' Target First Cell Address
Dim vntS As Variant ' Source Array
Dim vntSplit As Variant ' Split Array
Dim vntT As Variant ' Target Array
Dim lastR As Long ' Source Last Row
Dim i As Long ' Source Array Row Counter
Dim j As Integer ' Source/Target Array Column Counter
Dim k As Long ' Target Array Row Counter
Dim m As Integer ' Split Array Row Counter
' Paste Source Range into Source Array.
With Worksheets(cSheet1)
' The last row of data is usually calculated going from the bottom up,
' it is like selecting the last cell and pressing CTRL UP and returning
' =ROW() in Excel.
lastR = .Cells(.Rows.Count, cFirstC).End(xlUp).Row
' Paste a range into an array actually means copying it. The array
' created is a 1-based 2-dimensional array which has the same number
' of rows and columns as the Source Range.
vntS = .Range(.Cells(cFirstR, cFirstC), .Cells(lastR, cLastC))
End With
' Count the number of rows in Target Array.
' You refer to the last row of the array with UBound(vntS) which is short
' for UBound(vntS, 1) which reveals that it is referring to the first
' dimension (rows).
For i = 1 To UBound(vntS)
' We are splitting the string by cSplit which is the line
' separator (delimiter). When you enter something into a cell and
' hold left Alt and press ENTER, the vbLf character is set in place
' of the line separator. But the data may have been imported from
' another system that uses another line separator. When splitting the
' string, a 0-based array is 'created' and its UBound is the last
' row, but since it is 0-based we have to add 1.
k = k + UBound(Split(vntS(i, cMulti), cSplit)) + 1
Next
' Write from Source to Target Array.
' After we have calculated the number of rows, we have to resize the
' Target Array. To avoid confusion, I always use '1 To' to be certain that
' it is a 1-based array. Since the number columns of the Source Array and
' the Target Array is the same, we use the UBound of the Source Array to
' resize the second dimension of the Target Array - UBound(vntS, 2) where
' 2 is indicating the second dimension, columns.
ReDim vntT(1 To k, 1 To UBound(vntS, 2))
' We will use again k as the row counter since its value is no more
' needed. This is what I have many times forgotten, so maybe it is
' better to use a different variable.
k = 0
' Loop through the columns of Source Array.
For i = 1 To UBound(vntS)
' Increase the row of Target Array or e.g. align it for writing.
k = k + 1
' Split the string (lines) in the Multi Column into the 0-based
' Split Array.
vntSplit = Split(vntS(i, cMulti), cSplit)
' Loop through the values of the Split Array
For m = 0 To UBound(vntSplit)
' Check if value contains cDot. The Instr function returns 0 if
' a string has not been found, it's like =FIND(".",A1) in Excel,
' except that Excel would return an error if not found.
If InStr(vntSplit(m), cDot) > 0 Then
' If cDot was found then write the right part after cDot
' to the current row of column cMulti but trim the result
' (remove space before and after.
' It's like =TRIM(RIGHT(A1,LEN(A1)-FIND(".",A1))) in Excel.
vntT(k, cMulti) = Trim(Right(vntSplit(m), Len(vntSplit(m)) _
- InStr(vntSplit(m), cDot)))
Else
' If cDot was not found then just write the value to the
' current row.
vntT(k, cMulti) = vntSplit(m)
End If
' Loop through all columns.
For j = 1 To UBound(vntS, 2)
If j <> cMulti Then
' Write to other columns (Not cMulti)
vntT(k, j) = vntS(i, j)
End If
Next ' Next Source/Target Array Column
' Increase the current row of Target Array before going to next
' value in Split Array.
k = k + 1
Next ' Next Split Array Row
' Since we have increased the last current row but haven't written to
' it, we have to decrease one row because of the "k = k + 1" right below
' "For i = 1 To UBound(vntS)" which increases the row of Target Array
' for each next row in Source Array.
k = k - 1
Next ' Next Source Array Row
' Paste Target Array into Target Range calculated from Target Frist Cell.
' Like we pasted a range into an array, we can also paste an array into
' a range, but it has to be the same size as the array, so by using
' the Resize method we adjust the Target Range First Cell to the Target
' Range, using the last row and column of the Target Array. Again,
' remember UBound(vntT) is short for UBound(vntT, 1) (rows).
With Worksheets(cSheet2).Range(cTarget)
.Resize(UBound(vntT), UBound(vntT, 2)) = vntT
End With
End Sub
You can do this with two formulas.
I'm assuming your data is in Sheet1.
For the first columns, use the following formula:
=IF(ISBLANK(Sheet1!A2),A1,Sheet1!A2)
For the second one use:
=IFERROR(RIGHT(Sheet1!B2,LEN(Sheet1!B2)-FIND(". ",Sheet1!B2)-1),Sheet1!B2)
And populate down.
edit:
The first formula will look at the corresponding cell in Sheet1, column A. If it is blank, it will take the value of the cell above where the formula is. If it isn't blank, it will take the value of the cell in Sheet1, column A that it just checked.
The second formula looks for the string ". " in the cells in Sheet1 column B and removes it and everything to the left of it from the text. If the string in question (". ") is not found (meaning there is no numbering in that given cell) it would return an error, so the whole thing is wrapped in an IFERROR statement which returns the value of the cell in Sheet1 column B if it is triggered.

Excel - transpose rows (differently sized groups) to columns

I have some excel data which are now in rows and I want to get them into columns in an easy an effective way and I am not able to figure out how to do it. Any advice will be welcome! Thanks.
Example: turn sth like this in Excel
Team A
John
Team B
Peter
John
Team C
John
Peter
Oliver
Anna
Team D
Anna
into:
Team A John
Team B Peter John
Team C John Peter Oliver Anna
Team D Anna
I'm guessing your real data is much longer than this list so here is what i would do in that case.
First, put the list in column B and add a formula that copies the Team down column A:
*note you have to copy and paste the value from b2 into a2 and start the formula on a3.
Type the formula =IF(LEFT(B3,4)="Team",B3,A2) in cell a3 and drag it down (or control shift down then control d to fill down). What is this formula doing? It looks at the B cell, if it starts with "Team" it uses the value of that cell, otherwise it uses the value of the cell above (which will be another "Team").
Then, copy and paste and values column A so you don't loose the formula results after the next steps:
Filter column B "player" on the search term "team" and delete those entire rows:
Now you have column A of teams, Column B of players and use this formula in column C: =IF(A2=A1,CONCATENATE(C1," ",B2),CONCATENATE(A2," ",B2)).
This formula looks at the Team column and if it differs, it start a new chain of team and player otherwise it adds the player to the chain above of team and player.
I hope you can follow the logic here and accomplish what you are trying to do. Let me know how it goes.
Column to Vertical List
Option Explicit
'*******************************************************************************
' Purpose: Processes a one-column range containing groups of title-values data,
' transposing the titles to the first column of a range and the values
' to columns next to the title thus creating a vertical list.
'*******************************************************************************
Sub ColumnToVerticalList()
Const cStrSheet As String = "Sheet1" ' Worksheet Name
Const cLngFirstRow As Long = 2 ' First Row of Source Data
Const cStrColumn As String = "A" ' Column of Source Data
Const cStrSearch As String = "Team" ' Search String
Const cStrCell As String = "C2" ' Target Cell
Dim arrSource As Variant ' Source Array
Dim lngArr As Long ' Source Array Row Counter
Dim arrTarget As Variant ' Target Array
Dim lngRows As Long ' Number of Rows (Counter) in Target Array
Dim iCols As Integer ' Number of Columns (Counter) in Target Array
Dim iColsTemp As Integer ' Target Array Columns Counter
Dim strTargetRange As String ' Target Range
' Paste the calculated source range into the source array - arrSource.
With ThisWorkbook.Worksheets(cStrSheet)
arrSource = .Range( _
.Cells(cLngFirstRow, cStrColumn), _
.Cells(.Cells(Rows.Count, cStrColumn).End(xlUp).Row, cStrColumn))
End With
' Calculate the number of rows and columns of the target array - arrTarget.
iColsTemp = 1
For lngArr = LBound(arrSource) To UBound(arrSource)
If InStr(1, arrSource(lngArr, 1), cStrSearch, vbTextCompare) <> 0 Then
If iColsTemp > iCols Then
iCols = iColsTemp
End If
iColsTemp = 1
Debug.Print arrSource(lngArr, 1)
lngRows = lngRows + 1
Else
iColsTemp = iColsTemp + 1
End If
Next
' Calculate the target range address.
strTargetRange = Range(Cells(Range(cStrCell).Row, Range(cStrCell).Column), _
Cells(Range(cStrCell).Row + lngRows - 1, _
Range(cStrCell).Column + iCols - 1)).Address
' Resize the target array.
ReDim arrTarget(1 To lngRows, 1 To iCols)
' Write data from source array to target array.
lngRows = 0
iCols = 1
For lngArr = LBound(arrSource) To UBound(arrSource)
If InStr(1, arrSource(lngArr, 1), cStrSearch, vbTextCompare) <> 0 Then
iCols = 1
lngRows = lngRows + 1
arrTarget(lngRows, 1) = arrSource(lngArr, 1)
Else
iCols = iCols + 1
arrTarget(lngRows, iCols) = arrSource(lngArr, 1)
End If
Next
' Paste data of the target array into the target range
ThisWorkbook.Worksheets(cStrSheet).Range(strTargetRange) = arrTarget
End Sub

Get closest cell with a value before the current

In a column filled mostly by blank cells, I need to get the value of the cell thats closest to the currently selected one, in a row before it, and isn't blank.
While the "isn't blank" part can be easily achieved with the an IF and ISBLANK statement, I can't figure out how to get the position of the first cell upwards from the current one that isn't blank.
In this example spreadsheet, in the cell C7 I want to display the value of B7 minus the value of B4 (=B7-B4). The problem is that these values are separated by an unpredictable number of cells, which can range from 0 to 20 or even more. Needing to parse a spreadsheet with thousands of rows and frequent data added, selecting the upper cell manually is not an option.
In continuation of my example, I want C13 to display =B13-B10, but lets say there were two more blank rows in between them (row 13 becomes row 15), I would want it to display =B15-B10.
Can the position be obtained with a MATCH query, knowing that the Types will always be T1 or T2?
Thank you in advance.
Enter this formula in cell C2 and drag it to the bottom.
=IFERROR(IF(B2="","",B2-LOOKUP(9.99999999999999E+307,B$1:B1)),"")
Writing formulae via VBA
Below you find a VBA approach writing your subtracting formulae to column C.
BTW, searching via array is much more quicker than looping through a range.
Code
Option Explicit
Sub subtractLastValueRow()
' declare vars
Dim oSht As Worksheet ' work sheet
Dim a As Variant ' one based 2-dim data field array
Dim n As Long ' last row in column B
Dim i As Long ' item no
Dim ii As Long ' last item no
Dim j As Long
Dim s As String
' set sheet
Set oSht = ThisWorkbook.Worksheets("MySheet") ' fully qualified reference to worksheet
' get last row number of search column
n = oSht.Range("B" & oSht.Rows.Count).End(xlUp).Row
If n < 2 Then Exit Sub ' only if data avaible (row 1 = title line)
' get range (after title line) values to one based 2dim data field array
a = oSht.Range("B2:C" & n).Value ' array gets data from e.g. "A2:A100"
' loop through column B to find keyword sKey
If Len(a(1, 1) & "") > 0 Then ii = 1 + 1 ' first item in array
For i = LBound(a) + 1 To UBound(a) ' array boundaries counting from 1+1 to n -1 (one off for title line)
' value found
If Len(a(i, 1) & "") > 0 Then
For j = i + 1 To UBound(a)
If Len(a(j, 1) & "") > 0 Then
' write .Formula (alternatively use .Value, if value wanted)
oSht.Range("C" & i + 1).Formula = "=B" & i + 1 & "-B" & ii
ii = i + 1 ' note last found i
Exit For
End If
Next j
End If
Next
If Len(a(UBound(a), 1) & "") > 0 Then ' last item in array
oSht.Range("C" & UBound(a) + 1).Formula = "=B" & UBound(a) + 1 & "-B" & ii
End If
End Sub
Note
If you want to write values instead of work sheet formulae, simply replace .Formula with .Value.
In cell C3 put following formula:
=B3-LOOKUP(2,1/$B$1:B2,$B$1:B2)
And copy down in all T2 cells as required.

Resources