I am looking to extract multiple text values from a column in Excel and populate another column with these text values.
To be more specific, I am looking to extract the STLS ticket numbers.
For example, one row may contain "ABCD-4, STLS-5644, ABBD-33, STLS-421", another row may contain "ABB-567, STLS-56435" and another row may contain no STLS tickets.
What would be the best way to approach this problem?
You could try this code:
Option Explicit
Sub testExtract()
Dim i As Long, j As Long, jUp As Long, lFirstRow As Long, lLastRow As Long
Dim lColFrom As Long, lColTo As Long, nTicks As Long
Dim str1 As String
Dim varArray
'
' define source column number and the destination one:
'
lColFrom = 1
lColTo = 2
'
' initialize range to analyze:
'
lFirstRow = 1
lLastRow = Cells(Rows.Count, 1).End(xlUp).Row
'
' loop over the rows:
'
For i = lFirstRow To lLastRow
'
' split the string in the cell in an array:
'
varArray = Split(Cells(i, lColFrom).Value, ",")
jUp = UBound(varArray)
nTicks = 0
str1 = ""
'
' check the array element by element if we have some ticket:
'
For j = 0 To jUp
'
' trim spaces:
'
varArray(j) = Trim(varArray(j))
'
' check if we have ticks and count them:
'
If (InStr(1, varArray(j), "STLS-") > 0) Then
If (nTicks > 0) Then
str1 = str1 & ", "
End If
str1 = str1 & varArray(j)
nTicks = nTicks + 1
End If
Next
'
' save ticks:
'
If (str1 <> "") Then
Cells(i, lColTo).Value = str1
End If
Next
End Sub
If your Excel has the FILTERXML function (windows Excel 2013+) and the TEXTJOIN function, you don't need VBA.
You can use:
=IFERROR(TEXTJOIN(",",TRUE,FILTERXML("<t><s>" & SUBSTITUTE(A1,",","</s><s>")&"</s></t>","//s[contains(.,'STLS')]")),"")
If you don't have those functions, you can use this VBA UDF:
Option Explicit
Function getTickets(s As String, ticket As String) As String
Dim v, w, x, col As Collection, i As Long
v = Split(s, ",")
Set col = New Collection
For Each w In v
If Trim(w) Like ticket & "*" Then col.Add Trim(w)
Next w
i = 0
If col.Count = 0 Then
getTickets = ""
Else
ReDim x(col.Count - 1)
For Each w In col
x(i) = w
i = i + 1
Next w
getTickets = Join(x, ",")
End If
End Function
Related
I have the follow code to fill cells in excel one by one and it works the way I want it to but it gives me this error when it runs through the array. How do I fix this error? Thanks
The error is "Subscript out of range. Error: 9"
Dim item As Variant
Dim splitString() As String
Dim finalSplit() As String
i = 1
For Each item In splitString
finalSplit = Split(splitString(s), ",")
Cells(i, 1) = finalSplit(0)
Cells(i, 2) = finalSplit(1)
Cells(i, 3) = finalSplit(2)
Cells(i, 4) = finalSplit(3)
i = i + 1
s = s + 1
Next
I checked if finalSplit contains enough values like Thomas said and it worked.This is the new code below.
Dim item As Variant
Dim splitString() As String
Dim finalSplit() As String
i = 1
For Each item In splitString
finalSplit = Split(splitString(s), ",")
If UBound(finalSplit) > 1 Then
Cells(i, 1) = finalSplit(0)
Cells(i, 2) = finalSplit(1)
Cells(i, 3) = finalSplit(2)
Cells(i, 4) = finalSplit(3)
i = i + 1
s = s + 1
End If
Next
As other commenters have pointed out, why not add another control variable?
Dim item As Variant
Dim splitString() As String
Dim finalSplit() As String
Dim i As Integer, j As Integer, s As Integer
i = 1
For Each item In splitString
finalSplit = Split(splitString(s), ",")
For j = 0 To UBound(finalSplit)
Cells(i, j + 1) = finalSplit(j)
Next j
i = i + 1
s = s + 1
Next
Be aware that this can loop more than the 4 times you expect. A lazy way to solve this would be to add If j > 3 Then Exit For before Next j
I tested this with the following code (it works!), as I have no idea what splitString() or finalSplit() is in your case:
Sub test()
Dim finalSplit As Variant
Dim j As Integer
finalSplit = Split("1,2,3,4,5", ",")
For j = 0 To UBound(finalSplit)
Cells(1, j + 1) = finalSplit(j)
If j > 3 Then Exit For
Next j
End Sub
Looping Through Elements of Arrays
An array created by the Split function is always 0-based (even if Option Base 1). Similarly, not quite related, an array created by the Array function is dependent on Option Base unless you use its parent VBA e.g. arr = VBA.Array(1,2,3). Then it is always zero-based.
Looping through the elements of an array (1D array) is done in the following two ways:
For Each...Next
Dim Item As Variant
For Each Item In Arr
Debug.Print Item
Next Item
For...Next
Dim i As Long
For i = LBound(Arr) To Ubound(Arr)
Debug.Print Arr(i)
Next i
Since we have established that Split always produces a zero-based array, in the second example we could use 0 instead of LBound(Arr):
`For...Next`
Dim i As Long
For i = 0 To Ubound(Arr)
Debug.Print Arr(i)
Next i
Option Explicit
Sub DoubleSplit()
Const IniString As String = "A,B,C,D/E,F,G,H/I,J,K/L/M,N,O,P,Q,R"
Dim SplitString() As String: SplitString = Split(IniString, "/")
Dim ws As Worksheet: Set ws = ActiveSheet ' be more specific
ws.Cells.ClearContents ' remove previous data; clears the whole worksheet
Dim FinalSplit() As String
Dim Item As Variant ' SplitString Control Variable
Dim r As Long ' Worksheet Row Counter
Dim f As Long ' FinalSplit Element Counter
' For Each...Next
For Each Item In SplitString
r = r + 1
FinalSplit = Split(Item, ",")
Debug.Print Join(FinalSplit, ",")
For f = 0 To UBound(FinalSplit)
ws.Cells(r, f + 1).Value = FinalSplit(f)
Next f
Next Item
r = r + 1 ' add an empty row
Dim s As Long ' SplitString Element Counter
' For...Next
For s = 0 To UBound(SplitString)
r = r + 1
FinalSplit = Split(SplitString(s), ",")
Debug.Print Join(FinalSplit, ",")
For f = 0 To UBound(FinalSplit)
ws.Cells(r, f + 1).Value = FinalSplit(f)
Next f
Next s
' Results
' A,B,C,D
' E,F,G,H
' I,J,K
' L
' M,N,O,P,Q,R
'
' A,B,C,D
' E,F,G,H
' I,J,K
' L
' M,N,O,P,Q,R
End Sub
I have a workbook of about 30 sheets which I am attempting to put in alphanumeric order. Ex: "New York 9, New York 10, New York 11"
My code fails to order double digit numbers after single digit ones. "10, 11, 9"
Is anyone familiar with the method for accounting for this? Many thanks!
Sub AscendingSortOfWorksheets()
'Sort worksheets in a workbook in ascending order
Dim SCount, i, j As Integer
Application.ScreenUpdating = False
SCount = Worksheets.Count
For i = 1 To SCount - 1
For j = i + 1 To SCount
If Worksheets(j).Name < Worksheets(i).Name Then
Worksheets(j).Move before:=Worksheets(i)
End If
Next j
Next i
End Sub
As mentioned in the comments, you need to pad the numbers with zeros, in your case single digit numbers need to be padded with 1 zero. Use this function
Function PadNumber(sName As String, lNumOfDigits As Long) As String
Dim v As Variant
Dim vPrefixList As Variant
Dim sTemp As String
Dim i As Long
' Add all other possible prefixes in this array
vPrefixList = Array("New York")
sTemp = sName
For Each v In vPrefixList
sTemp = Replace(LCase(sTemp), LCase(v), "")
Next v
sTemp = Trim(sTemp)
PadNumber = sTemp
For i = Len(sTemp) + 1 To lNumOfDigits
PadNumber = "0" & PadNumber
Next i
PadNumber = Replace(sName, sTemp, PadNumber)
End Function
Then change the line If Worksheets(j).Name < Worksheets(i).Name Then to
If PadNumber(LCase(Worksheets(j).Name), 2) < PadNumber(LCase(Worksheets(i).Name), 2) Then
Note I added LCase in the comparison. Case-sensitivity might not matter for you in this particular case but it is something you always need to keep in mind.
Here is one way to achieve it
Logic:
Create a 2D array to store the number after space and sheet name
Sort the array
Arrange the sheets
Code:
Sub Sample()
Dim SheetsArray() As String
'~~> Get sheet counts
Dim sheetsCount As Long: sheetsCount = ThisWorkbook.Sheets.Count
'~~> Prepare our array for input
'~~> One part will store the number and the other will store the name
ReDim SheetsArray(1 To sheetsCount, 1 To 2)
Dim ws As Worksheet
Dim tmpAr As Variant
Dim sheetNo As Long
Dim i As Long: i = 1
Dim j As Long
'~~> Loop though the worksheest
For Each ws In ThisWorkbook.Sheets
tmpAr = Split(ws.Name)
'~~> Extract last number after space
sheetNo = Trim(tmpAr(UBound(tmpAr)))
'~~> Store number and sheet name as planned
SheetsArray(i, 1) = sheetNo
SheetsArray(i, 2) = ws.Name
i = i + 1
Next ws
'~~> Sort the array on numbers
Dim TempA, TempB
For i = LBound(SheetsArray) To UBound(SheetsArray) - 1
For j = i + 1 To UBound(SheetsArray)
If SheetsArray(i, 1) > SheetsArray(j, 1) Then
TempA = SheetsArray(j, 1): TempB = SheetsArray(j, 2)
SheetsArray(j, 1) = SheetsArray(i, 1): SheetsArray(j, 2) = SheetsArray(i, 2)
SheetsArray(i, 1) = TempA: SheetsArray(i, 2) = TempB
End If
Next j
Next i
'~~> Arrange the sheets
For i = UBound(SheetsArray) To LBound(SheetsArray) Step -1
ThisWorkbook.Sheets(SheetsArray(i, 2)).Move After:=ThisWorkbook.Sheets(sheetsCount)
sheetsCount = sheetsCount - 1
Next i
End Sub
Assumptions:
The sheet names have space in their names
The sheet names are in the format New York #
Here I have code that only counts the number of words and I dont know what to do to make it count the words that start with letter "A" and "a" in column M
Sub CountWords()
Dim xRg As Range
Dim xRgEach As Range
Dim xAddress As String
Dim xRgVal As String
Dim xRgNum As Long
Dim xNum As Long
On Error Resume Next
xAddress = ActiveWindow.RangeSelection.Address
Set xRg = Application.InputBox("Introduceti diapazonul:", "Selectare", xAddress, , , , , 8)
If xRg Is Nothing Then Exit Sub
Application.ScreenUpdating = False
If Application.WorksheetFunction.CountBlank(xRg) = xRg.Count Then
MsgBox "Numarul de cuvinte este: 0", vbInformation, ""
Exit Sub
End If
For Each xRgEach In xRg
xRgVal = xRgEach.Value
xRgVal = Application.WorksheetFunction.Trim(xRgVal)
If xRgEach.Value <> "" Then
xNum = Len(xRgVal) - Len(Replace(xRgVal, " ", "")) + 1
xRgNum = xRgNum + xNum
End If
Next xRgEach
MsgBox "Numarul de cuvinte: " & Format(xRgNum, "#,##0"), vbOKOnly, "Raspuns"
Application.ScreenUpdating = True
End Sub
Assuming that each cell contains a single word, use:
Sub ACount()
Dim i As Long, N As Long, Kount As Long
Dim ch As String
Kount = 0
N = Cells(Rows.Count, "M").End(xlUp).Row
For i = 1 To N
ch = Left(Cells(i, "M").Value, 1)
If ch = "a" Or ch = "A" Then Kount = Kount + 1
Next i
MsgBox Kount
End Sub
EDIT#1:
If the cells can contain more than one word (separated by spaces), the use:
Sub ACount()
Dim i As Long, N As Long, Kount As Long
Dim ch As String
Kount = 0
N = Cells(Rows.Count, "M").End(xlUp).Row
For i = 1 To N
arr = Split(Cells(i, "M").Value, " ")
For Each A In arr
ch = Left(A, 1)
If ch = "a" Or ch = "A" Then Kount = Kount + 1
Next A
Next i
MsgBox Kount
End Sub
Alternative via arrays including found word list display
It might be helpful to include a list of all valid words to the demanded count result.
Just to demonstrate a similar approach as Gary, but using arrays instead of a range loop,
I condensed the main procedure to three steps using a help function for step [1]:
[1] get data and provide for a sufficient wrds array by calling a help function getData()
[2] count & collect valid words in a loop through all words,
[3] display count cnt (or: UBound(wrds) plus list of valid words (►1-based 1-dim arraywrds)
Furthermore it's possible to analyze single words as well as word groups separated by spaces.
Sub ACount2()
Const SEARCHLETTER As String = "a" ' << change to any wanted search letter
'[1] get data and provide for sufficient wrds array
Dim allWrds, wrds: allWrds = getData(Sheet1, wrds) ' << change Sheet1 to your sheet's Code(Name)
'[2] count & collect valid words
Dim i As Long, letter As String, cnt As Long
For i = LBound(allWrds) To UBound(allWrds) ' loop through original words
letter = LCase(Left(allWrds(i), 1)) ' compare with search letter (lower case)
If letter = SEARCHLETTER Then cnt = cnt + 1: wrds(cnt) = allWrds(i)
Next i
ReDim Preserve wrds(1 To cnt)
'[3] display count plus list of valid words
MsgBox cnt & " words starting with {A|a}:" & _
vbNewLine & vbNewLine & _
Join(wrds, ", "), vbInformation
End Sub
Help function getData() called by above procedure
Function getData(sht As Worksheet, wrds, Optional ByVal col = "M", Optional ByVal StartRow As Long = 2)
'Purpose: get column data of a given worksheet and return to a "flat" array; provide for a sufficient wrds array
'a) get 2-dim data (starting in cell M2 by default) and transpose to 1-dim "flat" array
Dim lastRow As Long: lastRow = sht.Cells(sht.Rows.Count, col).End(xlUp).Row
Dim data: data = Split(Join(Application.Transpose(sht.Range(col & StartRow & ":" & col & lastRow)), " "), " ")
'b) provide for maximum elements in found words in calling procedure (implicit ByRef!)
ReDim wrds(1 To UBound(data))
'c) return 1-based "flat" 1-dim data array
getData = data
End Function
How do I count the total number of "alt" and "first" that appeared in a cell and do the same for other cells as well while ignoring empty cells in the process? For instance, if a cell has first, first, alt, first, first, first, it should give me firstcounter = 5 (where firstcounter is the total count for first) and altcounter= 1(altcounter is the total count for alt). After that I can use the value of firstcounter and altcounter found to concatenate them into a string as shown in column B in the form of "first-" & firstcounter, "alt-"& altcounter.
Dim ia As Long
Dim lastrow2 As Long
Dim ws1 As Worksheet
Set ws1 = ThisWorkbook.Sheets(1)
lastrow2 = ws1.Range("A" & ws1.Rows.count).End(xlUp).Row
For ia = 2 To lastrow2
Dim arr() As Variant
' Split the string to an array
arr = Split(ws1.Cells(ia, "A"), ",").Value
'what should i do after split
Enter the following into a code module...
Function CountWords$(r)
Dim a&, f&, w
For Each w In Split(r, ",")
If w = "alt" Then a = a + 1
If w = "first" Then f = f + 1
Next
If (a + f) Then CountWords = "first-" & f & ",alt-" & a
End Function
Then in cell B2 enter this formula:
=CountWords(A2)
Now copy it downwards as far as you need.
Update
To use the above function from VBA without entering formulas in the worksheet you can do it like this...
Sub Cena()
Dim i&, v
With [a2:a8]
v = .Value2
For i = 1 To UBound(v)
v(i, 1) = CountWords(v(i, 1))
Next
.Offset(, 1) = v
End With
End Sub
Function CountWords$(r)
Dim a&, f&, w
For Each w In Split(r, ",")
If w = "alt" Then a = a + 1
If w = "first" Then f = f + 1
Next
If (a + f) Then CountWords = "first-" & f & ",alt-" & a
End Function
Update #2
In response to your questions in the comments, you can use this variation instead...
Sub Cena()
Dim i&, v
With [a2].Resize(Cells(Rows.Count, 1).End(xlUp).Row - 1)
v = .Value2
For i = 1 To UBound(v)
v(i, 1) = CountWords(v(i, 1))
Next
.Cells = v
End With
End Sub
Function CountWords$(r)
Dim a&, f&, w
For Each w In Split(r, ",")
If w = "alt" Then a = a + 1
If w = "first" Then f = f + 1
Next
If (a + f) Then CountWords = "first-" & f & ",alt-" & a
End Function
In order to make this independent from the words alt and first and whitespaces in the string I would use the following functions
Option Explicit
'Add a reference to Microsoft VBScript Regular Expressions 5.5
Public Function RemoveWhiteSpace(target As String) As String
With New RegExp
.Pattern = "\s"
.MultiLine = True
.Global = True
RemoveWhiteSpace = .Replace(target, vbNullString)
End With
End Function
'Add a reference to Microsoft Scripting Runtime
Function CountWordsA(rg As Range) As String
On Error GoTo EH
Dim dict As Dictionary
Set dict = New Dictionary
Dim vDat As Variant
vDat = RemoveWhiteSpace(rg.Value)
vDat = Split(vDat, ",")
Dim i As Long
For i = LBound(vDat) To UBound(vDat)
If dict.Exists(vDat(i)) Then
dict(vDat(i)) = dict(vDat(i)) + 1
Else
dict.Add vDat(i), 1
End If
Next i
Dim vKey As Variant
ReDim vDat(1 To dict.Count)
i = 1
For Each vKey In dict.Keys
vDat(i) = vKey & "-" & dict(vKey)
i = i + 1
Next vKey
CountWordsA = Join(vDat, ",")
Exit Function
EH:
CountWordsA = ""
End Function
Sub TestIt()
Dim rg As Range
Set rg = Range("A2:A8")
Dim sngCell As Range
For Each sngCell In rg
sngCell.Offset(, 1) = CountWordsA(sngCell)
Next sngCell
End Sub
More about dictionaries and regular expressions
Alternative using Filter() function
This demonstrates the use of the Filter() function to count words via function UBound():
Function CountTerms() (usable also in formulae)
Function CountTerms(ByVal WordList As String, Optional TermList As String = "first,alt", Optional DELIM As String = ",") As String
'Purpose: count found terms in wordlist and return result as list
'[1] assign lists to arrays
Dim words, terms
words = Split(WordList, DELIM): terms = Split(TermList, DELIM)
'[2] count filtered search terms
Dim i As Long
For i = 0 To UBound(terms)
terms(i) = terms(i) & "-" & UBound(Filter(words, terms(i), True, vbTextCompare)) + 1
Next i
'[3] return terms as joined list, e.g. "first-5,alt-1"
CountTerms = Join(terms, ",")
End Function
Example call (due to comment) & help function getRange()
In order to loop over the entire range and replace the original data with the results list:
Sub ExampleCall()
'[1] get range data assigning them to variant temporary array
Dim rng As Range, tmp
Set rng = getRange(Sheet1, tmp) ' << change to sheet's Code(Name)
'[2] loop through array values and get counts
Dim i As Long
For i = 1 To UBound(tmp)
tmp(i, 1) = CountTerms(tmp(i, 1))
Next i
'[3] write to target (here: overwriting due to comment)
rng.Offset(ColumnOffset:=0) = tmp
End Sub
Function getRange(mySheet As Worksheet, tmp) As Range
'Purpose: assign current column A:A data to referenced tmp array
With mySheet
Set getRange = .Range("A2:A" & .Cells(.Rows.Count, 1).End(xlUp).Row)
tmp = getRange ' assign range data to referenced tmp array
End With
End Function
I have a column of about 50 cells. Each cell contains a block of text, anywhere from 3-8 sentences.
Id like to populate a list of words being used and obtain their frequencies for the entire range (A1:A50).
Ive tried to manipulate other codes I've found in other posts but they seem to be tailored to cells that contain one word rather than multiple words.
This is the code I found that I was attempting to use.
Sub Ftable()
Dim BigString As String, I As Long, J As Long, K As Long
Dim Selection As Range
Set Selection = ThisWorkbook.Sheets("Sheet1").Columns("A")
BigString = ""
For Each r In Selection
BigString = BigString & " " & r.Value
Next r
BigString = Trim(BigString)
ary = Split(BigString, " ")
Dim cl As Collection
Set cl = New Collection
For Each a In ary
On Error Resume Next
cl.Add a, CStr(a)
Next a
For I = 1 To cl.Count
v = cl(I)
ThisWorkbook.Sheets("Sheet2").Cells(I, "B").Value = v
J = 0
For Each a In ary
If a = v Then J = J + 1
Next a
ThisWorkbook.Sheets("Sheet2").Cells(I, "C") = J
Next I
End Sub
Here you go, a dictionary is the best way to handle this I think as you can test if the dictionary already contains an item. Post back if there's anything you don't get.
Sub CountWords()
Dim dictionary As Object
Dim sentence() As String
Dim arrayPos As Integer
Dim lastRow, rowCounter As Long
Dim ws, destination As Worksheet
Set ws = Sheets("Put the source sheet name here")
Set destination = Sheets("Put the destination sheet name here")
rowCounter = 2
arrayPos = 0
lastRow = ws.Range("A1000000").End(xlUp).Row
Set dictionary = CreateObject("Scripting.dictionary")
For x = 2 To lastRow
sentence = Split(ws.Cells(x, 1), " ")
For y = 0 To UBound(sentence)
If Not dictionary.Exists(sentence(y)) Then
dictionary.Add sentence(y), 1
Else
dictionary.Item(sentence(y)) = dictionary.Item(sentence(y)) + 1
End If
Next y
Next x
For Each Item In dictionary
destination.Cells(rowCounter, 1) = Item
destination.Cells(rowCounter, 2) = dictionary.Item(Item)
rowCounter = rowCounter + 1
Next Item
End Sub
Try this (works for me with some long blocks of Lorem Ipsum text):
Sub Ftable()
Dim BigString As String, I As Long, J As Long, K As Long
Dim countRange As Range
Set countRange = ThisWorkbook.Sheets("Sheet1").Range("A1:A50")
BigString = ""
For Each r In countRange.Cells
BigString = BigString & " " & r.Value
Next r
BigString = Trim(BigString)
ary = Split(BigString, " ")
Dim cl As Collection
Set cl = New Collection
For Each a In ary
On Error Resume Next
cl.Add a, CStr(a)
Next a
For I = 1 To cl.Count
v = cl(I)
ThisWorkbook.Sheets("Sheet2").Cells(I, "B").Value = v
J = 0
For Each a In ary
If a = v Then J = J + 1
Next a
ThisWorkbook.Sheets("Sheet2").Cells(I, "C") = J
Next I
End Sub
I took it down to only looking at the 50 cells where you have data, as opposed to all >1 million in that column. I also fixed an issue where r was getting a length 1 array instead of a Range. And I renamed "Selection" to "countRange" because Selection is already defined in the application, so it was bad naming.
Also, notice that your code pulls from "Sheet1" and outputs into columns B and C of "Sheet2". Make sure you rename your worksheets or edit these values, or you'll get errors/data corruption.
This is how I'd approach the problem:
Sub Ftable()
Dim wordDict As New Dictionary
Dim r As Range
Dim countRange As Range
Dim str As Variant
Dim strArray() As String
Set countRange = ThisWorkbook.Sheets("Sheet1").Range("A1:A50")
For Each r In countRange
strArray = Split(Trim(r.Value), " ")
For Each str In strArray
str = LCase(str)
If wordDict.Exists(str) Then
wordDict(str) = wordDict(str) + 1
Else
wordDict.Add str, 1
End If
Next str
Next r
Set r = ThisWorkbook.Sheets("Sheet2").Range("B1")
For Each str In wordDict.Keys()
r.Value = str
r.Offset(0, 1).Value = wordDict(str)
Set r = r.Offset(1, 0)
Next str
Set wordDict = Nothing
End Sub
It uses a dictionary, so make sure you add a reference to the library (Tools > Add Reference > Microsoft Scripting Library). It also forces everything to lowercase - one big issue of the old code was that it failed to count capitalized and uncapitalized versions correctly, meaning it missed many words. Remove str = LCase(str) if you don't want this.
Bonus: this method ran about 8 times faster on my test sheet.