Extract an alphanumeric from sentence - vba

I would like to have an VBA to extract an alphanumeric value from a column G which is a sentence.
This sentence is generally a comment. So it includes characters and numbers.
The value always starts with AI0 and ends with 0. This can be 11 to 13 digits long. Sometimes the number is mentioned in the comment as AI038537500, also sometimes as AI038593790000.
I have researched through almost all the websites, but have not found any case like this. I know about the formulas, left, right, mid but in my case, it doesn't apply.
Any lead would be appreciable.

You may try something like this...
Place the following User Defined Function on a Standard Module and then use it on the sheet like
=GetAlphaNumericCode(A1)
UDF:
Function GetAlphaNumericCode(rng As Range)
Dim Num As Long
Dim RE As Object, Matches As Object
Set RE = CreateObject("VBScript.RegExp")
With RE
.Global = False
.Pattern = "AI\d{9,}0"
End With
If RE.Test(rng.Value) Then
Set Matches = RE.Execute(rng.Value)
GetAlphaNumericCode = Matches(0)
Else
GetAlphaNumericCode = "-"
End If
End Function

Why not give something like the following a try?
Sub findMatches()
Dim strLength As Integer
Dim i As Long
For i = 1 To Rows.Count
Dim AllWords As Variant
AllWords = Split(Cells(i, 7).Value, " ")
For Each Item In AllWords
strLength = Len(Item)
If strLength > 0 And strLength <= 13 And Item Like "A10*?#" Then
Cells(i, 8) = Item
End If
Next
Next i
End Sub
Test Cases:
I am apple and my batch number is: A10545440 so incase you needed to know
Result: A10545440
Some random comment… A20548650
Result: NO RESULT
A101234567891 is an awesome alphanumeric combo
Result: A101234567891
Another random comment… A10555
Result: A10555
Notice: The above example covers cases where the alphanumeric combo, starting with A10 is either:
In the middle of a sentence, or
Beginning of a sentence, or
At the end of a sentence
Also note: right now it is set to go through ALL the rows... so if you want to limit that, change the Rows.Count in the For statement to whatever your set limit is.
EDIT:
In the above code, I am explicitly asking it to look in column G

can you give this a try? I think it should do the job, also you should ammend the code with the column values, I tested it with the comments being in column C, while the code will be written in column D.
Option Explicit
Sub FindValue()
Dim i As Long
Dim lastrow As Long
Dim lFirstChr As Long
Dim lLastChr As Long
Dim CodeName As String
lastrow = activesheet.Range("c" & Rows.Count).End(xlUp).Row
' gets the last row with data in it
For i = 1 To lastrow
' shuffles through all cell in data
lFirstChr = InStr(1, Cells(i, 3), "A10") ' gets the coordinate of the first instance of "A10"
If lFirstChr = 0 Then GoTo NextIteration
lLastChr = InStr(lFirstChr, Cells(i, 3), " ") ' gets the coordinate of the first instansce of space after "A10"
If lLastChr = 0 Then 'if there is no space after A10 then sets lastchr to the lenght of the string
lLastChr = Len(Cells(i, 3))
End If
CodeName = Mid(Cells(i, 3).Value, lFirstChr, lLastChr - lFirstChr) ' extracts the codename from the string value
Range("d" & i).Value = CodeName
Goto NextTteration
NextIteration:
Next i
End Sub

Related

How can I remove non-numeric characters in cells and concatenate result to URL?

I have a bunch of cells with string like this:
WFM 1601
And this:
WFM 2231, WFM 2402
And this too:
Campaign 1680, 2402, 2784
I used code, below, to split the string in a single cell into multiple columns (max of 3).
Dim Rng As Range
Dim WorkRng As Range
On Error Resume Next
lRow = Range("U" & Rows.Count).End(xlUp).Row
Set MyRows = Range("U19:U" & lRow)
For Each cell In MyRows
splitVals = Split(cell.Value, ",")
totalVals = UBound(splitVals)
Range(Cells(cell.Row, ActiveCell.Column + 1), Cells(cell.Row, ActiveCell.Column + 1 + totalVals)).Value = splitVals
Next
Now, I'm trying to figure out a way to get rid of all NON numeric characters and leave only numbers. Then, concatenate these numbers, which are all IDs for processes in a SharePoint site that I work with, so I want to place the URL for each number, at the end of a static string, and next to the number that was just split into separate columns.
Here is a screen shot.
I have Column U, and I want to generate Column V to Column AA.
I can extract only numbers using the function below.
Function GetNums(target As Range)
Dim MyStr As String, i As Integer
MyStr = ""
If Len(target.Value) = 0 Then GoTo GoExit
If target.Value = "None" Then GoTo GoNone
For i = 1 To Len(target.Value)
If IsNumeric(Mid(target, i, 1)) Then MyStr = MyStr & Mid(target, i, 1)
Next i
GoTo GoExit
GoNone:
GetNums = "None"
Exit Function
GoExit:
GetNums = MyStr
End Function
However, this won't meet the requirement as it checks all characters in a cell, and just turns this: WFM 2231, WFM 2402 . . .
Into this: 22312402
I really need some way to distinguish the two IDs: 2231 2402
I would use Regular Expressions to extract the number groups. If it turns out there are other criteria for what constitutes a valid digit sequence, that would be easier to implement by changing the regex.
Here's an example with your original Data in Column A of the active sheet.
Option Explicit
Sub CreateURL()
Dim RE As Object, MC As Object, M As Object
Const sPat As String = "\b\d+\b" 'whole words that are all digits
Const sBaseURL As String = "htpps://collaborate.process...&ID="
Dim I As Long, J As Long
Dim rSrc As Range, C As Range
'This will be on active sheet
'Suggest you specify actual worksheet
Set rSrc = Range(Cells(1, 1), Cells(Rows.Count, "A").End(xlUp))
Set RE = CreateObject("vbscript.regexp")
With RE
.Pattern = sPat
.Global = True
End With
For Each C In rSrc
If RE.test(C.Text) = True Then
Set MC = RE.Execute(C.Text)
J = -1
For Each M In MC
J = J + 2
C.Offset(0, J) = M
C.Offset(0, J + 1) = sBaseURL & M
Next M
End If
Next C
End Sub
And here's the results of running this macro against data in column A:
Here is a formal explanation of the Regex, with links to more detail that hopefully still work:
\b\d+\b
\b\d+\b
Options: Case insensitive; ^$ match at line breaks
Assert position at a word boundary (position preceded or followed—but not both—by an ASCII letter, digit, or underscore) \b
Match a single character that is a “digit” (ASCII 0–9 only) \d+
Between one and unlimited times, as many times as possible, giving back as needed (greedy) +
Assert position at a word boundary (position preceded or followed—but not both—by an ASCII letter, digit, or underscore) \b
Created with RegexBuddy
I can help for the 1st part, to check if a value is numeric or not.
You did the split. Now, you can check if the variables you get are numeric or not. Example :
We want to check if the value in A1 is numeric :
isnum = isNumeric(range("A1"))
isnum is true if the value in A1 is numeric, else it is false.

finding the lowest value in a cell Excel VBA

I am new to this. I am trying to find the lowest value in a cell with multiple values inside. For example,
48
44.50
41.00
37.50
I am trying to find 37.50. What should be the code for it?
Thanks
Based on your posted example:
Sub FindMin()
Dim s As String, CH As String
Dim wf As WorksheetFunction
Dim bry() As Double
Set wf = Application.WorksheetFunction
s = ActiveCell.Text
CH = Chr(10)
ary = Split(s, CH)
ReDim bry(LBound(ary) To UBound(ary))
For i = LBound(ary) To UBound(ary)
bry(i) = CDbl(ary(i))
Next i
MsgBox wf.Min(bry)
End Sub
This assumes that there is a hard return (ASCII-10) between the fields in the cell.
EDIT#1:
To make it into a function, remove the sub and replace with:
Public Function FindMin(r As Range) As Variant
Dim s As String, CH As String
Dim wf As WorksheetFunction
Dim bry() As Double
Set wf = Application.WorksheetFunction
s = r.Text
CH = Chr(10)
ary = Split(s, CH)
ReDim bry(LBound(ary) To UBound(ary))
For i = LBound(ary) To UBound(ary)
bry(i) = CDbl(ary(i))
Next i
FindMin = wf.Min(bry)
End Function
EDIT#2:
based on your comment, here is an example of input vs output:
Note that all the values are in a single cell and the values are separated by hard returns rather than spaces.
By code with same cell and a " " as delimiter to break
temp = Range("A1").Value
temp = Split(temp, " ")
Low = CInt(temp(0))
For i = 0 To UBound(temp) - 1
If CInt(temp(i)) < Low Then Low = CInt(temp(i))
Next
Range("a2").Value = Low
if they are in a range you can use a formula
=MIN(A1:A4)
This question is pretty close to one previously asked:
VBA/EXCEL: extract numbers from one cell that contained multiple values with comma
If you take the code from that answer and replace the comma with whatever is separating your values, you will be able to get access to them in VBA. Then you can write code to find the minimum.
You can make a macro to split the values for each cell you selected and then check for the highest value. And a quick check to make sure you are not parsing all the empty rows (when you selected a column).
The macro below will set the highest value in the next column.
Sub lowest()
Dim Values As Variant
Dim LowestValue As Double
Dim a As Range
Set a = Selection
For Each Row In a.Rows
For Each Cell In Row.Cells
LowestValue = -1
Values = Split(Cell.Value, Chr(10))
For Each Value In Values
If LowestValue = -1 Then
LowestValue = Value
ElseIf Value < LowestValue Then
LowestValue = Value
End If
Next
Cells(Cell.Row, Cell.Column + 1).Value = LowestValue
If IsEmpty(Cell.Value) Then GoTo EndLoop
Next Cell
Next Row
EndLoop:
End Sub

VBA to find the font color of a string

I am new to VBA..I am writing a macro for some file comparison..My requirement is if a string has red color font,that string should be ignored for iteration and code should move to next iteration..I have tried the following code.
Dim compare = {"test1","test2","test3",.....etc}
i=0
For j=1 to Ubound(compare)
j=1
If compare.Characters(j,Len(compare(i))).Font.Color <> vbRed Then
' Some Code
End If
i=i+1
Next
However during the execution I am getting runtime error 424 "Object Required.Please help me to complete this task
Thanks in advance.
Say we have cells A1 thru A4 like:
Then this code will find the non-red characters:
Sub ColorTest()
Dim I As Long, J As Long
For I = 1 To 4
For J = 1 To Len(Cells(I, 1).Value)
If Cells(I, 1).Characters(Start:=J, Length:=1).Font.Color <> vbRed Then
MsgBox "non-red found at cell A" & I & " position " & J
End If
Next J
Next I
End Sub
May I assume that the source for your data is an excel-sheet (as pointed out by previous comments, a pure string does not hold information on colours), and for one reason or another you want to use an array. Then this might be a way to solve the problem (prerequisite: full string is in one colour only)
(Just saw that there's a solution provided by Gary's student without using arrays as well...AND providing for cases where only part of the string is red...nice one!)
Sub colour()
Dim arr_DB As Variant
Dim i As Long
ReDim arr_DB(1, 1) 'Array size to be adjusted as needed, Base 0 !
For i = 1 To 2
arr_DB(i - 1, 0) = ActiveSheet.Cells(i, 1).Value 'Value of Cell
arr_DB(i - 1, 1) = ActiveSheet.Cells(i, 1).Font.Color 'Colour of Font in Cell
Next
If arr_DB(i - 1, 1) = 255 Then ' No. 255 is colour RED
'skip.....
End If
End Sub

Excel VBA - Looking for ways to simplify loop

I recently made a loop that takes the string in each cell, searches for a "_" in the string, and if there is one cuts off that bit and any character after it. Looking at the code I realized it might be too elaborate and could be shortened or simplified, but I'm not quite sure how to do so. Is there a way to make this bit of code more efficient?
Sub Name_Change()
Sheets("Sheet1").Activate
Dim tg_row As Integer
tg_row = 1
For Each nm_cl In Range("Table1[Name]")
If InStr(1, nm_cl, "_", vbTextCompare) = 0 Then
Range("Table1[Name]").Cells(tg_row, 1).Value = nm_cl.Value
Else
Range("Table1[Name]").Cells(tg_row, 1) = _
Left(nm_cl, InStr(1, nm_cl, "_", vbTextCompare) - 1)
End If
tg_row = tg_row + 1
Next nm_cl
End Sub
Thank you for your help!
A first attempt at optimizing this would be to note that you are calling InStr multiple times. You can speed things up by computing it once, and storing the result.
Along with that I would note that presumably Range("Table1[Name]") only has one column (otherwise you would be overwriting the first column with data from the other columns). So, you can replace Range("Table1[Name]").Cells(tg_row, 1) with nm_cl. In doing this, we notice the redundant statement of nm_cl.Value = nm_cl.Value can be removed. This leads to the following code:
Sub Name_Change()
Sheets("Sheet1").Activate
Dim index As Long
For Each nm_cl In Range("Table1[Name]")
index = InStr(1, nm_cl, "_", vbTextCompare)
If index <> 0 Then
nm_cl = Left(nm_cl, index - 1)
End If
Next nm_cl
End Sub
If you need more efficiency, beyond this, you can load your data into a variant by using
dim data as Variant
data = Range("Table1[Name]").Value
process all of your data within VBA, and then put it back to the worksheet using
Range("Table1[Name]").Value = data
This will increase your speed, as transfering data between Excel and VBA is slow and this means you will have 1 read and 1 write, instead of 1 read and 1 write per line, but it will require a (minor) rewrite of your algorithm as the syntax for working with an array within a variant is different from working with ranges. Note that this will not work if you go beyond the 65536 rows. I beleive that it is a legacy constraint from Excel 2003 and earlier.
You could adjust your loop to only modify the cells that contain "_".
If Not InStr(1, nm_cl, "_", vbTextCompare) = 0 Then
Range("Table1[Name]").Cells(tg_row, 1) = _
Left(nm_cl, InStr(1, nm_cl, "_", vbTextCompare) - 1)
End If
EDIT:
Here's a working example that includes #Degustaf's suggestions. Just change the name of the range to fit your worksheet.
Sub Name_Change()
Dim selectedRange As Range
Dim rangeData As Variant 'Array containing data from specified range
Dim col As Long 'Selected column from range
Dim row As Long 'Selected row from range
Dim cellValue As String 'Value of selected cell
Dim charPosition As Long 'Position of underscore
Sheets("Sheet1").Activate
Set selectedRange = Range("YOUR-NAMED-RANGE-HERE")
If selectedRange.Columns.Count > 65536 Then
MsgBox "Too many columns!", vbCritical
ElseIf selectedRange.Rows.Count > 65536 Then
MsgBox "Too many rows!", vbCritical
Else
rangeData = selectedRange.Value
If UBound(rangeData, 1) > 0 And UBound(rangeData, 2) > 0 Then
'Iterate through rows
For row = 1 To UBound(rangeData, 1)
'Iterate through columns
For col = 1 To UBound(rangeData, 2)
'Get value of cell
cellValue = CStr(rangeData(row, col))
'Get position of underscore
charPosition = InStr(1, cellValue, "_", vbTextCompare)
'Update cell data stored in array if underscore exists
If charPosition <> 0 Then
rangeData(row, col) = Left(cellValue, charPosition - 1)
End If
Next col
Next row
'Overwrite range with array data
selectedRange.Value = rangeData
End If
End If
End Sub
You could use a user defined function to return the truncated strings in cells.
The Worksheet-function could look like:
Public function truncateAt( s as String) as string
dim pos as integer
pos = instr (1, s,"_")
If pos> 0 then
truncateAt= left (s, pos)
Else
truncateAt= s
End If
End function

Compare and copy matching data from adjacent cells

I was having some trouble with a macro I have been writing. I am trying to find a match in column A and column D. When I detect a match I want to copy the adjacent cells of each I.E copy the contents of B of the line of the first match to E where the match occurs in D. Whenever I do this I never get the right copy. It will copy the values that match but put them in the completely wrong space. I only encounter a problem when the order is mixed up or there is a white space. Any suggestions would be helpful.
Thanks
Nick.
Note: In this version of my code I was using input boxes to pick what two columns of data the user wants to compare and the one he wants to copy from and paste too. It should not make a big difference.
Sub Copy()
Dim column1 As String
Dim column2 As String
Dim from As String
Dim too As String
numrows = Sheet1.Range("A1").Offset(Sheet1.Rows.Count - 1, 0).End(xlUp).Row
'MsgBox numrows
column1 = InputBox("which column do you want to select from")
column2 = InputBox("which column do you want to compare to ")
from = InputBox("which column do you want to copy data from")
too = InputBox("which column do you want to copy data to")
Dim lngLastRow As Long
Dim lngLoopCtr As Long
Dim i As Long
Dim j As Long
Dim value As String
lngLastRow = Range(column1 & Rows.Count).End(xlUp).Row
lngLastRow2 = Range(column2 & Rows.Count).End(xlUp).Row
'lngLastRow = Sheet1.Range("A1").Offset(Sheet1.Rows.Count - 1, 0).End(xlUp).Row
Dim temp As String
For i = 1 To lngLastRow Step 1
temp = Cells(i, column1).value
value = Cells(i, from).value
'MsgBox "temp"
'MsgBox (temp)
If Cells(i, column1).value <> "" Then
For j = 1 To lngLastRow2 Step 1
' MsgBox "cell"
' MsgBox (Cells(j, column2).value)
If Cells(j, column2).value = "" Then
Cells(j, column2).Offset(1, 0).Select
End If
If Cells(j, column2).value <> "" Then
If temp = Cells(j, column2).value Then
'MsgBox "equal"
'MsgBox "i"
'MsgBox i
'MsgBox "j"
'MsgBox j
'value = Cells(j, from).value
'MsgBox Cells(i, too).value
'Cells(i, too).value = Cells(j, from).value
'Dim num As Integer
'On Error Resume Next
'num = Application.WorksheetFunction.VLookup(temp, Sheet1.Range("A0:M13"), 3, False)
Cells(i, too).value = Cells(j, from).value
'MsgBox j
' MsgBox (Cells(i, column1).value)
' MsgBox "="
' MsgBox (Cells(j, column2).value)
End If
End If
Next j
End If
Next i
End Sub
I have studied your text and your macro and think the macro below does what you want.
If this macro does what you want, your problem was caused by your use of meaningless variable names such as: column1, column2, i and j. This meant you did not notice you were using the wrong variables in the statement that copied values.
I have renamed all your variables. I am not asking you to like my naming convention but I am recommending you have a naming convention. I can look at macros I wrote years ago and know what all the variables are because I developed my convention in my early days of VBA programming and have used it every since. This makes my life much easier when I need to update old macros.
I have added Option Explicit at the top of the module. Without this statement, a misspelt variable name becomes a declaration:
Dim Count As Long
Lots of statements
Count = Conut + 1
This causes Conut to be declared with a value of zero. Such errors can be a nightmare to find.
I have used a With Statement to make explicit which worksheet I am using.
You checked both cells to not be empty. I only check the first because it is not necessary to check the second since, if the second is empty, it will not match the first.
Your code did not stop working down the Compare column if it found a match so my code does the same. This is correct if values can repeat in the Compare column. If they cannot repeat, you may wish to add Exit For to exit the inner loop after a match has been processed.
I believe the above explains all the changes I hve made.
Option Explicit
Sub Copy()
Dim ColCompare As String
Dim ColCopyFrom As String
Dim ColCopyTo As String
Dim ColSelect As String
Dim RowCrntCompare As Long
Dim RowCrntSelect As Long
Dim RowLastColCompare As Long
Dim RowLastColSelect As Long
Dim SelectValue As String
With Sheet1
ColSelect = InputBox("which column do you want to select ColCopyFrom")
ColCompare = InputBox("which column do you want to compare to ")
ColCopyFrom = InputBox("which column do you want to copy data ColCopyFrom")
ColCopyTo = InputBox("which column do you want to copy data to")
RowLastColSelect = .Range(ColSelect & .Rows.Count).End(xlUp).Row
RowLastColCompare = .Range(ColCompare & .Rows.Count).End(xlUp).Row
For RowCrntSelect = 1 To RowLastColSelect Step 1
SelectValue = .Cells(RowCrntSelect, ColSelect).value
If SelectValue <> "" Then
For RowCrntCompare = 1 To RowLastColCompare Step 1
If SelectValue = Cells(RowCrntCompare, ColCompare).value Then
.Cells(RowCrntCompare, ColCopyTo).value = _
.Cells(RowCrntSelect, ColCopyFrom).value
End If
Next RowCrntCompare
End If
Next RowCrntSelect
End With
End Sub