I have a report that includes a bunch of text in one cell. The first part of the text is a product# but the length varies. The product number is separated from the other information by a space.
I'm looking to write a macro that will replace just the first space with a delimiting character. I usually use "~". This will then allow me to script a text-to-columns command that will isolate the product number in one column.
You can do this with a formula:
=LEFT(A1, FIND(" ", A1, 1)-1) & "~" & RIGHT(A1,LEN(A1) - FIND(" ", A1, 1))
Copy that down. Copy/PasteSpecial Values. Then text-to-column that result
With VBA, the following approach is possible:
Locate the first empty string position and write it to a variable
Take the left part of the string to the position and append the replacement string
Take the right part of the string from the position to the end and append the rest
This is the function:
Public Function ReplaceFirstSpace(myInput As String, _
Optional replacement As String = "~") As String
Dim position As Long
position = InStr(1, myInput, " ")
If position = 0 Then
ReplaceFirstSpace = myInput
Else
ReplaceFirstSpace = Left(myInput, position - 1) & _
replacement & Right(myInput, Len(myInput) - position)
End If
End Function
And some tests:
Sub TestMe()
Debug.Print ReplaceFirstSpace("my name is")
Debug.Print ReplaceFirstSpace("slim shaddy")
Debug.Print ReplaceFirstSpace("tikitiki")
Debug.Print ReplaceFirstSpace(" taram")
Debug.Print ReplaceFirstSpace("tam ")
Debug.Print ReplaceFirstSpace("")
End Sub
Use REPLACE:
=REPLACE(A1,FIND(" ",A1),1,"~")
Related
I need help with my code that displays an input box and the user inputs a name then the code splits the names and counts the names displaying the following:
Sub ParseName()
Dim Name As String
Dim Count As Integer
Dim Cell As Object
Dim n As Integer
Count = 0
Name = InputBox("Enter First Name, Middle Name, and Last Name")
If Name = "" Then
For Each Cell In Selection
n = InStr(1, Cell.Value, Name)
While n <> 0
Count = Count + 1
n = InStr(n + 1, Cell.Value, Name)
Next Cell
MsgBox Count & " Occurrences of " & Name
End If
End Sub
Absolutely. You could split the names, but it probably wouldn't help. VBA's split doesn't allow you to split on single characters, AFAIK, as other languages might if you split them a specific delimiter. So you could just loop through the characters using MID to see if each letter is a space or not.
There is a way without any splitting or looping. You can just replace the space and get the length of the what's left.
Len(Replace(Name, " ", ""))
where REPLACE just replaces one string with another, in this case replacing all the spaces with nothing, and LEN just counts the characters in a string.
Here's your code rewritten to use this method, with the unnecessary code and variables removed. I would also change the Name variable, since I believe that is a reserved word in VBA. It will let you do it, but you're potentially impacting some existing behavior. For this particular purpose, using a standalone function to get the character count is someh
Sub ParseName()
Dim fullName As String, charCount As Integer
fullName = InputBox("Enter First Name, Middle Name, and Last Name")
If fullName <> "" Then
charCount = Len(Replace(fullName, " ", ""))
MsgBox fullName & " has " & charCount & " characters"
End If
End Sub
Bear in mind, however, that there are plenty of other character codes you might not want to count. Tabs, new lines, any of a number of whitespace characters. Non-character symbols. Things of that nature.
Also, this code does not check that the string even contains letters, or that the user input has three names, or that it is in the format First Middle Last.
I have a mathematical problem: these five strings are IDs for the same object. Due to these differences, objects appear multiple times in my Access table/query. Although there are a lot of these mutations, but I take this as an example.
76 K 6-18
76 K 6-18(2)
0076 K 0006/ 2018
0076 K 0006/2018
76 K 6/18
How would the VBA-code have to look like to recognize that these numbers stand for the same thing , so a general formatting with "RegEx()" or "format()" or "replace()"...but they must not only refer to this example but to the kind.
The common factor of these and all other mutations is always the following:
1) includes "-", no zeros left of "-", just 18 an not 2018 (year) at the end.
2) is like the first but with (2) (which can be dropped).
3) includes "/", zeros left of "/", and 2018 as year at the end.
4) is like third, but without space after "/".
5) is like the first one, but with a "/" instead of "-".
Character is always one single "K"! I suppose the best way would be to convert all 5 strings to 76 K 6 18 or in ohter cases for example to 1 K 21 20 or 123 K 117 20 . Is this possible with one elegant code or formula? Thanks
Here is a fun alternative using a rather complex but intuitive regular expression:
^0*(\d+) (K) 0*(\d+)[-\/] ?\d{0,2}(\d\d)(?:\(\d+\))?$
See an online demo
^ - Start line anchor.
0* - 0+ zeros to catch any possible leading zeros.
(\d+) - A 1st capture group of 1+ digits ranging 0-9.
- A space character.
(K) - 2nd Capture group capturing the literal "K".
- A space character.
(\d+) - A 3rd capture group of 1+ digits ranging 0-9.
[-\/] - Character class of either a hyphen or forward slash.
? - An optional space character.
\d{0,2} - 0-2 digits ranging from 0-9.
(\d\d) - A 4th capture group holding exactly two digits.
(?:\(\d+\))? - An optional non-capture group holding 1+ digits inside literal paranthesis.
$ - End line anchor.
Now just replace the whole string by the 4 capture groups with spaces in between.
Let's test this in VBA:
'A code-block to call the function.
Sub Test()
Dim arr As Variant: arr = Array("76 K 6-18", "76 K 6-18(2)", "0076 K 0006/ 2018", "0076 K 0006/2018", "76 K 6/18")
For x = LBound(arr) To UBound(arr)
Debug.Print Transform(CStr(arr(x)))
Next
End Sub
'The function that transform the input.
Function Transform(StrIn As String) As String
With CreateObject("vbscript.regexp")
.Global = True
.Pattern = "^0*(\d+) (K) 0*(\d+)[-\/] ?\d{0,2}(\d\d)(?:\(\d+\))?$"
Transform = .Replace(StrIn, "$1 $2 $3 $4")
End With
End Function
All the elements from the initial array will Debug.Print "76 K 6 18".
Hope it helps, happy coding!
EDIT: If your goal is just to check if your string compiles against the pattern, the pattern itself can be shortened a little and you can return a boolean instead:
'A code-block to call the function.
Sub Test()
Dim arr As Variant: arr = Array("76 K 6-18", "76 K 6-18(2)", "0076 K 0006/ 2018", "0076 K 0006/2018", "76 K 6/18")
For x = LBound(arr) To UBound(arr)
Debug.Print Transform(CStr(arr(x)))
Next
End Sub
'The function that checks the input.
Function Transform(StrIn As String) As Boolean
With CreateObject("vbscript.regexp")
.Global = True
.Pattern = "^0*\d+ K 0*\d+[-\/] ?\d{2,4}(?:\(\d+\))?$"
Transform = .Test(StrIn)
End With
End Function
As #Vincent has suggested, look at using a custom function to convert all of the different data to be consistent. Based on what you have described, the following seems to work:
Function fConvertFormula(strData As String) As String
On Error GoTo E_Handle
Dim astrData() As String
strData = Replace(strData, "/", " ")
strData = Replace(strData, "-", " ")
strData = Replace(strData, " ", " ")
astrData = Split(strData, " ")
If UBound(astrData) = 3 Then
astrData(0) = CLng(astrData(0))
astrData(2) = CLng(astrData(2))
If InStr(astrData(3), "(") > 0 Then
astrData(3) = Left(astrData(3), InStr(astrData(3), "(") - 1)
End If
If Len(astrData(3)) = 4 Then
astrData(3) = Right(astrData(3), 2)
End If
fConvertFormula = Join(astrData, " ")
End If
fExit:
On Error Resume Next
Exit Function
E_Handle:
MsgBox Err.Description & vbCrLf & vbCrLf & "fConvertFormula", vbOKOnly + vbCritical, "Error: " & Err.Number
Resume fExit
End Function
It starts by replacing "field" delimiters with spaces, and then does a replace of double spaces. It then removes any leading zeroes from the first and third elements, if there is a bracket in the last element then delete that part, and finally converts to a 2 digit value before joining it all back up.
You may have other cases that you need to deal with, so I would suggest creating a query with the original data and the data converted by this function, and seeing what it throws out.
This function unifies the given string by the rules you defined in your question:
Public Function UnifyValue(ByVal inputValue As String) As String
'// Remove all from "(" on.
inputValue = Split(inputValue, "(")(0)
'// Replace / by blank
inputValue = Replace(inputValue, "/", " ")
'// Replace - by blank
inputValue = Replace(inputValue, "-", " ")
'// Replace double blanks by one blank
inputValue = Replace(inputValue, " ", " ")
'// Split by blank
Dim splittedInputValue() As String
splittedInputValue = Split(inputValue, " ")
'// Create the resulting string
UnifyValue = CLng(splittedInputValue(0)) & _
" " & splittedInputValue(1) & _
" " & CLng(splittedInputValue(2)) & _
" " & Right(CLng(splittedInputValue(3)), 2)
End Function
It always returns 76 K 6 18 regarding to your sample values.
I use the following VBA code to insert the column number from Cell C1 into Cell B1:
Sub AdressColumn()
Sheet1.Range("B1").Value = Sheet1.Range("C1").Column
End Sub
In this case the result on my spreadsheet looks like this:
A B C
1 3
2
3
All this works fine so far.
However, instead of inserting the 3 I would prefer to insert the letter of the column. In this case the letter C should be inserted into Cell B1.
I also tried to go with the formula here but I could not make it work since in my case I do not use a given number. Instead I refer to a Column with the .Column function.
What do I have to change in my formula to make it work?
Split the $ out of an absolute cell address.
Sub AdressColumn()
Sheet1.Range("B1").Value = split(Sheet1.Range("C1").address, "$")(1)
End Sub
... or split the colon out of the relative full column address.
Sub AdressColumn()
Sheet1.Range("B2").Value = Split(Sheet1.Range("C1").EntireColumn.Address(0, 0), ":")(0)
End Sub
user4039065 is very close, but the subscript at the end of the line should be (2). Then, e.g., 677 represents column "ZA" and column 16384 represents "XFD" by the function below:
Const MAX_COL_NUMBER = 16384
...
Function columnNumberToColumnString(col As Integer) As String
If col > MAX_COL_NUMBER Or col < 1 Then
columnNumberToColumnString = "ERROR": Exit Function
Else
columnNumberToColumnString = Split(Columns(col).Address, "$")(2)
End If
' delete code block below after seeing how Split works
msg = "Split <" & Columns(col).Address & ">"
For i = 0 To UBound(Split(Columns(col).Address, "$"))
msg = msg + Chr(13) & Chr(10) & "Substring " & i & _
" is <" & Split(Columns(col).Address, "$")(i) & ">"
Next
MsgBox msg
End Function
In fact, if I use (1) in place of my (2), for column 26 I get Z:, not just Z, as explained below.
The Split function, when used with a valid Excel column address, returns an array of length 3, showing in stages how the final result is arrived at.
For 256, for example, the results displayed by the msg code block are:
Address of column number 256 is <$IV:$IV>
Substring 0 is <> (since first $ is first character, strip it and all after)
Substring 1 is <IV:> (since second $ is after the :, strip it and all after)
Substring 2 is <IV> (since : above is a usual delimiter, strip it)
Split "Returns a zero-based, one-dimensional array containing ... 'all substrings' " (if the limit (third) argument is omitted) of the given expression (first argument).
I have two columns with data. The first one has some terms and the other one contains single words.
what I have
I'm looking for a way to identify which words from each cell from the first column appear in the second, so the result should look something like this (I don't need the commas):
what I need
My question is somehow similar to Excel find cells from range where search value is within the cell but not exactly, because I need to identify which words are appearing in the second column and there can be more than one word.
I also tried =INDEX($D$2:$D$7;MATCH(1=1;INDEX(ISNUMBER(SEARCH($D$2:$D$7;A2));0);))
but it also returns only one word.
If you are willing to use VBA, then you can define a user defined function:
Public Function SearchForWords(strTerm As String, rngWords As Range) As String
Dim cstrDelimiter As String: cstrDelimiter = Chr(1) ' A rarely used character
strTerm = cstrDelimiter & Replace(strTerm, " ", cstrDelimiter) & cstrDelimiter ' replace any other possible delimiter here
SearchForWords = vbNullString
Dim varWords As Variant: varWords = rngWords.Value
Dim i As Long: For i = LBound(varWords, 1) To UBound(varWords, 1)
Dim j As Long: For j = LBound(varWords, 2) To UBound(varWords, 2)
If InStr(1, strTerm, cstrDelimiter & varWords(i, j) & cstrDelimiter) <> 0 Then
SearchForWords = SearchForWords & varWords(i, j) & ", "
End If
Next j
Next i
Dim iLeft As Long: iLeft = Len(SearchForWords) - 2
If 0 < iLeft Then
SearchForWords = Left(SearchForWords, Len(SearchForWords) - 2)
End If
End Function
And you can use it from the Excel table like this:
=SearchForWords(A2;$D$2:$D$7)
I have a partial solution:
=IF(1-ISERROR(SEARCH(" "&D2:D7&" "," "&A2&" ")),D2:D7&", ","")
This formula returns an array of the words contained in the cell (ranges are according to your picture). This array is sparse: it contains empty strings for each missing word. And it assumes that words are always separated by one space (this may be improved if necessary).
However, native Excel functions are not capable of concatenating an array, so I think the rest is not possible with native formulas only.
You would need VBA but if you use VBA you should not bother with the first part at all, since you can do anything.
You can create a table with the words you want to find across the top and use a formula populate the cells below each word if it's found. See screenshot.
[edit] I've noticed that it's incorrectly picking up "board" in "blackboard" but that should be easily fixed.
=IFERROR(IF(FIND(C$1,$A2,1)>0,C$1 & ", "),"")
Simply concatinate the results
=CONCATENATE(C2,D2,E2,F2,G2,H2)
or
=LEFT(CONCATENATE(C2,D2,E2,F2,G2,H2),LEN(CONCATENATE(C2,D2,E2,F2,G2,H2))-2)
to take off the last comma and space
I've edited this to fix the problem with "blackboard"
new formula for C2
=IF(OR(C$1=$A2,ISNUMBER(SEARCH(" "&C$1&" ",$A2,1)),C$1 & " "=LEFT($A2,LEN(C$1)+1)," " & C$1=RIGHT($A2,LEN(C$1)+1)),C$1 & ", ","")
New formula for B2 to catch the error if there are no words
=IFERROR(LEFT(CONCATENATE(C2,D2,E2,F2,G2,H2,I2),LEN(CONCATENATE(C2,D2,E2,F2,G2,H2,I2))-2),"")
I am trying to extract numbers from a text.
If I have an entry like 12&6&2014, how can I extract the 12 (the number that is before the first &) and 2014 (the number that occurs after the second &)?
To get first number:
=LEFT(A1, FIND("&", A1)-1)
To get last number after the second &:
=RIGHT(A1, 4)
Otherwise, if that's not always a year:
=MID(A1, FIND(CHAR(1), SUBSTITUTE(A1, "&", CHAR(1), 2))+1, LEN(A1))
you can loop through each character int he string and check to see if it is numeric
Sub getNumberValues()
Dim s As String
Dim c As New Collection
Dim sNewString As String
s = "12&6&2014"
For v = 1 To Len(s)
If IsNumeric(Mid(s, v, 1)) Then
sNewString = sNewString & Mid(s, v, 1)
Else
c.Add sNewString
sNewString = ""
End If
Next v
'add the last entry
c.Add sNewString
sNewString = ""
For Each x In c
sNewString = sNewString & x & Chr(13)
Next
MsgBox sNewString
End Sub
If what I understand is correct which is that the characters that separate vary as well as how many digits are used you might look into something like this:
Function CleanUp(Txt)
For x = 1 To 255
Select Case x
Case 45, 47, 65 To 90, 95, 97 To 122
Txt = WorksheetFunction.Substitute(Txt, Chr(x), "") <- "" can be replaced
End Select with "&" to do a
Next x MID() using & as
the delimiter
CleanUp = Txt
End Function
If you can use VBA, this will replace your characters with a blank, but you could put in your own character and then use your formulas to separate from a specific delimiter.
The original code can be found here:
http://www.mrexcel.com/forum/excel-questions/380531-extract-only-numbers.html
Simplest might be Text to Columns with & as the delimiter, then delete the middle column. This overwrites the original data, so a copy might be appropriate.
Another simple way would be to create two copies, and for one Find what: &* and Replace with: nothing, for the other Find what: *& and Replace with: nothing.
An alternative formula solution might be:
=DAY(SUBSTITUTE($A1,"&","/"))
and
=YEAR(SUBSTITUTE($A1,"&","/"))