Excel / VBA Dynamic Numbers and Text Separation - vba

I have a value I take from the internet, specifically the market cap of some cryptocurriencies. I get these from the "get external data" function in excel. This loads the data in text format e.g. for a 1000 bitcoin value instead of getting 1000 i will get a cell that had 1000 BTC. Therefore, I am not able to use that data further. If I use the functions right, left, given that this value can fluctuate from 1000 to 1 or 1000000 the right and left will result in text format once the barrier is hit. On the other hand i cannot use the text to column separator as every time i refresh the table goes back to its original state and gets rid of any columns added by the user. Any suggestions to extrapolate the numbers from each cell dynamically are welcome. Thanks

You can use the Val function.
Assuming the cell containing the value you are after is A1 of the active sheet, you can use:
Dim numericPart As Double
numericPart = Val(Range("A1").Value)
If you wanted an Excel formula instead, and there is always a space between the value and the text part, you could use:
=VALUE(LEFT(A1,FIND(" ",A1)-1))

Another worksheet function, which will work without a space separating the digits from the letters (so long as the digits come first):
=LOOKUP(9E+307,--MID(B9,1,{1,2,3,4,5,6,7,8,9,10}))
If there might be more than 10 places in the number, merely extend the array constant.

While getting the external data from the internet, the data may have leading or trailing spaces or non printable leading or trailing characters.
In that case the safest way is to use regular expression to get the numeric part from the string.
Place the following UDF (User Defined Function) on a Standard Module and then use this UDF on the worksheet like below...
Assuming " 1000 BTC " is in A2, then try the UDF like this..
=GetNumber(A2)
UDF:
Function GetNumber(rng As Range) As Long
Dim Num As Long
Dim RE As Object, Match As Object, Matches As Object
Set RE = CreateObject("VBScript.RegExp")
With RE
.Global = False
.Pattern = "\d+"
End With
If RE.test(rng.Value) Then
Set Matches = RE.Execute(rng.Value)
GetNumber = Matches(0)
End If
End Function

Related

How to get TrimEnd() to stop at a specific character

I have a series of percentage values saved in a database that look something like this:
Percentage
_____________
100.00000
50.00000
74.02500
When I display the values to the screen, I'd like to trim unnecessary zeroes from the end of the string along with the decimal point so the above examples become:
Percentage
_____________
100
50
74.025
I'm currently using the following code:
displayVal = rawVal.TrimEnd({"0"c, "."c})
but this code continues to trim after the decimal if there are additional zeroes. I also tried:
displayVal = rawVal.TrimEnd(New String({"0", "."}))
which almost works. It just leaves the decimal point.
Is there a way to do what I want using TrimEnd() or do I need to switch to regex?
As Tim already mentioned in the comments, if the data type in the DB is already some numerical type, it would be best to keep it in that type and then use the appropriate numeric formatting when converting it to a string for output. If, however, the input data is already a string, then that's not an option. In that cast, the simplest option is to just do two trims in series, like this:
Private Function RemoveUnecessaryZeros(input As String) As String
Return input.TrimEnd("0"c).TrimEnd("."c)
End Function
However, that doesn't give you a lot of flexibility, it doesn't remove preceding zeros, and it does nothing to reformat the string using the current culture. If that matters, you could instead parse the value into a numeric type and then use the desired string formatting options to re-output it to a string. For instance:
Private Function RemoveUnecessaryZeros(input As String) As String
Dim result As Double
If Double.TryParse(input, result) Then
Return result.ToString()
Else
Return input
End If
End Function
However, when you do it that way, you may potentially lose precision along the way, depending on the input numbers and the data type you choose to parse it with. If you need more control over the parsing/reformatting and you want to keep it purely in strings so no precision is lost, then you may want to consider using regex. For instance:
Private Function RemoveUnecessaryZeros(input As String) As String
Dim m As Match = Regex.Match(input, "[1-9]\d*(\.([1-9]|0+[1-9])+)?")
If m.Success Then
Return m.Value
Else
Return input
End If
End Function

Extracting substring based on different criterias and placing the extracted string in another cell of same row

I have an excel file, in which there is a column containing a specific string. This string doesn't follow any particular pattern. My requirement is to extract a sub-string (product id) which is a set of 8 consecutive numbers that have to be preceded/followed by any no of characters or must be at the start or end of the string.
Following are some examples.
Scenario 1:
product id is preceded by #
Id#53298632/BS TVR:003519
Function used in excel
=MID(N88,FIND("#",N88)+1,8)
* result : 53298632 *
Scenario 2:
product id is at the beginning
53298632:003519
Function used in excel
=MID(A1,1,8)
* result : 53298632 *
At the beginning I had to deal with only scenario 1 and hence used the specified formula. Now a days the string doesnt follow any particular pattern but my product id still comes as 8 digit consecutive numbers. I searched for a suitable solution and found this formula (which I dont clearly understand).
=LOOKUP(10^8,MID(N132,ROW(INDIRECT("1:"&LEN(N132)-7)),8)+0)
This does work in most of the cases but in some cases it fails
For example
Pdr#53298632/ QTY NOS 1031949
Here the result is 1031949 which is definitely not what I want. The result should have been 53298632
Please help me fix this. Can this be done using VBA macro? I am completely new to such excel functions VBA and macro.
Any help will be highly appreciated!
Thanks in advance.
If you are happy to specifically include the Microsoft RegEx module into your Excel project, regular expressions will solve this reasonably quickly.
To add the RegEx function to use in your Excel Macros, select the Developer menu in Excel and start the Visual Basic editor. Within the VBA for Applications window, Select Tools->References and select Microsoft VBScript Regular Expressions 5.5.
Create a new Module for your VBAProject (right-click on your Excel file name in the project tree and click Insert->Module)
Double click on the newly created Module (within the project tree) and enter the following code in the Module1 (Code) window:
Public Function getProductCode(source As String) As String
Dim strPattern As String: strPattern = "(\d{8})"
Dim result As String: result = ""
Dim results As Object
Dim regEx As New RegExp
With regEx
.Global = True
.MultiLine = False
.IgnoreCase = False
.Pattern = strPattern
End With
If regEx.Test(source) Then
Set results = regEx.Execute(source)
If (results.Count <> 0) Then
result = results.Item(0)
End If
End If
getProductCode = result
End Function
From the relevant cell in Excel, you can now call the macro:
=getProductCode(A1)
I guess you could also modify the original formula to pick up the first match of an 8-digit number
=MID(A1,MATCH(TRUE,ISNUMBER(MID(A1,ROW(INDIRECT("1:"&LEN(A1)-7)),8)+0),0),8)
(must be entered as an array formula using CtrlShiftEnter).

How do I assign an Excel VBA variable to a Defined Name

How do I return a value from a VBA Function variable to a Defined Name in Excel 2010?
In the example below, I want i to be returned to Excel as a Defined Name Value_Count that can be reused in other formulas, such that if MsgBox shows 7, then typing =Value_Count in a cell would also return 7.
Everything else below is about what I've tried, and why I'd like to do it. If it's inadvisable, I'd be happy to know why, and if there's a better method.
Function process_control_F(raw_data As Variant)
Dim i As Integer
i = 0
For Each cell In raw_data
i = i + 1
Next cell
MsgBox i
End Function
My goal is to have the value returned by the MsgBox be returned instead to a Defined Name that can be reused in other forumulas. However, I cannot get the value to show. I have tried a variety of forms (too numerous to recall, let alone type here) similar to
Names.Add Name:="Value_Count", RefersTo:=i
I am trying to accomplish this without returning a ton of extra info to cells, just to recall it, hence the desire to return straight to a Defined Name.
I'm using a Function rather than Sub to streamline my use, but if that's the problem, I can definitely change types.
I am creating a version of a Statistical Control Chart. My desired end result is to capture a data range (generally about 336 values) and apply a series of control rules to them (via VBA), then return any values that fall outside of the control parameters to Defined Names that can then be charted or otherwise manipulated.
I've seen (and have versions of) spreadsheets that accomplish this with digital acres of helper columns leading to a chart and summary statistics. I'm trying to accomplish it mostly in the background of VBA, to be called via Defined Names to Charts — I just can't get the values from VBA to the Charts.
The interest in using a Function rather than a Sub was to streamline access to it. I'd rather not design a user interface (or use one), if I can just keystroke the function into a cell and access the results directly. However, as pointed out by Jean-François Corbett, this is quickly turning into a circuitous route to my goal. However, I still think it is worthwhile, because in the long-term I have a lot of iterations of this analysis to perform, so some setup time is worth it for future time savings.
With minor changes to your function, you can use its return value to accomplish what you want:
Function process_control_F(raw_data As Variant) As Integer ' <~~ explicit return type
Dim i As Integer
Dim cell As Variant ' <~~~~ declare variable "cell"
i = 0
For Each cell In raw_data
i = i + 1
Next cell
process_control_F = i ' <~~~~ returns the value i
End Function
You can then use that function in formulas. For example:

VBA error: A value used in the formula is the wrong data type?

I'm trying to create a function which generates a secret encoded message. It takes in three things: a string, for example, "testingtestingonetwothree", as well as the desired number of characters, for example 5, and the desired number of words, for example 5. It generates the message by starting at the first character, and extracting every fifth character through the string, putting these characters into a codeword, then starting at the second character and extracting every fifth character through the string, putting these into a second codeword, and so on. It just outputs a string, with the codewords separated by a space. So for this example it would produce: "tntnt egieh stntr tegwe isooe".
I'm okay at coding but new to VBA. I've made what I think is a valid function, but when it's used in the spreadsheet I get a #VALUE! error: "A value used in the formula is the wrong data type". This is the user defined function I made:
Function encode(strng, numchars, numwords)
Dim word As Integer
Dim step As Integer
Dim temp As String
Dim output As String
For word = 1 To numchars
step = word
temp = ""
Do While step <= Len(strng)
temp = temp & Mid(strng, 1, step)
step = step + numchars
Loop
If word = 1 Then output = temp Else output = output & " " & temp
Next word
encode = output
End Function
And when it's used in the spreadsheet I just call it, as in
=encode(A16,A7,A10)
Where A16 contains testingtestingonetwothree, A7 contains 5 and A10 contains 5.
Does my function seem okay? And is there anything you guys can see which could be giving the value error? Any help would be greatly appreciated, thanks a lot for reading.
EDIT: This now outputs a value, but the wrong value. It outputs: "ttestintestingtesttestingtestingontestingtestingonetwot tetestingtestingtestitestingtestingonetestingtestingonetwoth ", when it should output: "tntnt egieh stntr tegwe isooe". Is there anything you guys can see that my function is doing wrong?
EDIT2: After fixing the Mid function, to
temp = temp & Mid(strng, step, 1)
as per vacip's answer, the function now produces the correct answer.
Ok, everyone says it works, but for me, it doesn't produce the desired output. What the...???
Anyway, I think your Mid function is in the wrong order, try it like this:
temp = temp & Mid(strng, step, 1)
Also, make sure to properly declare your variables, like this:
Function encode(strng As String, numchars As Integer, numwords As Integer) As String
I have also rewritten your IF statement, that one-line thing is strange for me...
If word = 1 Then
output = temp
Else
output = output & " " & temp
End If
This way it worked for me.
Other people have addressed the type problem. Here is a different suggestion. The cipher that you are describing is a simple transposition cipher, specifically a columnar transposition ( https://en.wikipedia.org/wiki/Transposition_cipher#Columnar_transposition )
The way people did this pre-computer was to write the characters into a grid row by row then read them off column by column. In fact -- this is probably still the easiest way to implement it even with computers. Declare a variant which can be redimensioned to be an array with e.g. 5 columns (where 5 is the skip between letters) and the number of rows is chosen to be large enough so that the grid can hold the string. After you load up the characters row by row, read them off column by column using nested for loops.
Once you get a basic example working, you can try to implement a version which uses a key to determine the order that you read off the columns for added security.
Coding classical cryptography/cryptanalysis as an excellent way to learn a programming language. Almost the first thing I do when I try to learn a new language is to implement a Vigenere cipher in it. Even though it is long out of print and can be somewhat tricky to translate to modern dialects of Basic the book "Cryptanalysis for Microcomputers" by Caxton Foster is great fun and can be purchased for just a few dollars from online used bookstores.
You need to define your Function's type. So in this case I believe you would want
Function encode(strng, numchars, numwords) As String
I tested your code exactly as it is, and it worked fine.
So, your problem may be:
A certain argument of your function is not the right type. (I bet the len method is the problem in there).
Check if A16 is really a string. If not, consider converting it to a string before if you want to pass numbers too:
Function encode(strng as variant, numchars as integer, numwords as integer) as string
strng = str(strng)
Check also if A7 and A10 are really integers.

String comparison in Excel VBA

There is a huge set of data in two columns. I have to print whether the two strings are matching or not into a third column as match/not match.
For example- YES & YeS match
yes & YES match
ye2 & yes No match & so on
I have been doing this manually, checking values in each column then writing as match/no match. Would appreciate some help to make the process automated.
You don't really need VBA to do this, but if yes then you can simply write an UDF specifying (on top of the module) that you're using the Compare text option.
Option Compare Text
Public Function compareText(ByVal str1 As String, ByVal str2 As String) As String
If str1 = str2 Then
compareText = "Match"
Else
compareText = "Do not match"
End If
End Function
Once this is added, say you have the data in cells A1 and B1, then you simply add into the cell C1 the formula =compareText(A1,B1) and you scroll it down for the other values.
Please note that this is the most efficient approach, since the Option Compare Text will perform a binary comparison of the strings (not having to convert to UPPER any string before comparing, something that would affect performance if the amount of data is really huge as you say).