VBA: Isolate a decimal number from a string so that I can add or subtract from it - vba

I'm attempting to write a program that involves finding strings with numerical values that are +1 and -1 from the numerical value located within another string. (The rest of the program is fine, it's just this section that I'm having a difficult time with).
For example:
If I have the parent string: name[CE18.2]-abritraryinfo
I need to find a way to isolate that 18.2 so that I can add 1 to it and subtract 1 from it to create two new numerical values of 19.2 and 17.2
I need to be able to do this in such a way that I can find this number in strings whose 'name' section and whose number after CE vary according to the different parent strings.
What I've tried already is this:
'''
Result = Empty 'Resets the value of the result after it changes to the next name
f = InStr(c, "CE") 'Finds at which position in the string CE is located. The position is the C of CE
z = Mid(c, f, 8) 'Pulls 8 units from the string starting at the position dictated by f
stringLength = Len(z) 'Gives the Length of the section pulled by Z
For i = 1 To stringLength 'From the first position to the final position
If IsNumeric(Mid(z, i, 1)) Then
Result = Result & Mid(z, i, 1) 'Gives the numbers in the string section pulled by Z
End If
Next i
'''
but it doesn't work as it ignores the decimal point.
Any advice would be incredibly helpful! Thanks in advance!

One of the simple solution is:
Sub test1()
inputS = "name[CE18.2]-abritraryinfo"
pos = InStr(inputS, "[CE")
If pos > 0 Then
x = Val(Mid(inputS, pos + 3))
Debug.Print x, x - 1, x + 1
End If
End Sub
Output:
18,2 17,2 19,2

String Between Two Strings
Option Explicit
Sub gsbtsTEST()
Const lStr As String = "CE"
Const rStr As String = "]"
Const sString As String = "name[CE18.2]-abritraryinfo"
Dim ResString As String
ResString = GetStringBetweenTwoStrings(sString, lStr, rStr)
Dim ResValue As Double
If IsNumeric(ResString) Then
ResValue = Val(ResString)
End If
Debug.Print ResString, ResValue - 1, ResValue, ResValue + 1
End Sub
Function GetStringBetweenTwoStrings( _
ByVal sString As String, _
ByVal lStr As String, _
ByVal rStr As String, _
Optional ByVal CompareMethod As VbCompareMethod = vbTextCompare) _
As String
Dim lPos As Long: lPos = InStr(1, sString, lStr, CompareMethod)
If lPos = 0 Then Exit Function
Dim rPos As Long: rPos = InStr(1, sString, rStr, CompareMethod)
If rPos = 0 Then Exit Function
lPos = lPos + Len(lStr)
If lPos < rPos Then
GetStringBetweenTwoStrings = Mid(sString, lPos, rPos - lPos)
End If
End Function

Related

MS- Access VBA Converting multiple characters to Asc

For a homework project I am trying to enter characters in a single textbox as (eg:"AbC" no spaces) and have the output in a captioned label as the corresponding ASCII value written out with commas and spaces. (eg: 65, 98, 67)
Private Sub cmdCode_Click()
Dim codeInt As Integer
strInput = txtInput.value
codeInt = Asc(strInput)
lblAnswer.Caption = codeInt & ", "
End Sub
I would like the result to look like: 65, 98, 67
I'm getting no errors but only receiving "65," as my output.
Here is my solution. It assumes that the input is always going to be three (3) characters long:
Private Sub cmdCode_Click()
Dim x As String
Dim y As String
Dim z As String
strInput = txtInput.value
x = Asc(Left(strInput, 1))
y = Asc(Mid(strInput, 2, 1))
z = Asc(Right(strInput, 1))
lblAnswer.Caption = x & ", " & y & ", " & z
End Sub
This can be done for generic usage - and a little smarter:
Public Function StrToAscList( _
ByVal Text As String) _
As String
Dim Chars() As Byte
Dim Item As Integer
Dim List As String
Chars() = StrConv(Text, vbFromUnicode)
For Item = LBound(Chars) To UBound(Chars)
If Item > 0 Then List = List & ", "
List = List & CStr(Chars(Item))
Next
StrToAscList = List
End Function
Then:
Me!lblAnswer.Caption = StrToAscList(strInput)

How to stop numbers being repeated in this VBA script

I have found this VBA script (running in powerpoint) and I just wanted to know how to stop numbers from being repeated. I did some google searches and I think the solution would be to create an array, and have the selected number go into the array. The script would then generate a new number as long as it skips all numbers in the array.
I'm just not sure how to implement this as I do not know VBA.
here is the script:
Public Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Dim chosenNum As Integer
Dim I As Integer
Dim k As Integer
Sub randomNumber()
lowRand = 1
maxRand = 10
Randomize
For k = 1 To 10
chosenNum = Int((maxRand - lowRand) * Rnd) + lowRand
With ActivePresentation.SlideShowWindow.View.Slide.Shapes(2).TextFrame.TextRange
.Text = chosenNum
End With
For I = 1 To 1
Sleep (50)
DoEvents
Next
Next
End Sub
Any thoughts? Thanks.
This will collect 10 unique single digit numbers (0 to 9) into a string and then split them into an array. As each is returned to the slide, 1 is added so the resut is 1 to 10.
Sub randomNumber()
Dim lowRand As Long, maxRand As Long, strNum As String, chosenNum As String
Dim k As Long, vNUMs As Variant
lowRand = 0: maxRand = 10: strNum = vbNullString
Randomize
For k = 1 To 10
chosenNum = Format(Int((maxRand - lowRand) * Rnd) + lowRand, "0")
Do While CBool(InStr(strNum, chosenNum))
chosenNum = Format(Int((maxRand - lowRand) * Rnd) + lowRand, "0")
Loop
strNum = strNum & chosenNum
Next k
vNUMs = Split(StrConv(strNum, vbUnicode), Chr(0))
For k = LBound(vNUMs) To UBound(vNUMs)
With ActivePresentation.SlideShowWindow.View.Slide.Shapes(2).TextFrame.TextRange
.Text = vNUMs(k) + 1
End With
For I = 1 To 1
Sleep (50)
DoEvents
Next
Next k
End Sub
I just wrote this to help you. The function is designed to return random integer numbers in the range you specify until all numbers in the range have been returned when it will then return -1. There is a test sub included to show how to use the function to get all numbers from 5 to 10.
'----------------------------------------------------------------------------------
' Copyright (c) 2015 YOUpresent Ltd.
' Source code is provide under Creative Commons Attribution License
' This means you must give credit for our original creation in the following form:
' "Includes code created by YOUpresent Ltd. (YOUpresent.co.uk)"
' Commons Deed # http://creativecommons.org/licenses/by/3.0/
' License Legal # http://creativecommons.org/licenses/by/3.0/legalcode
'----------------------------------------------------------------------------------
Option Explicit
Option Base 0 ' Explicitly set the lower bound of arrays to 0
Private iUsed As Integer ' count of all used numebrs
Public arrTracking() As String
'----------------------------------------------------------------------------------
' Purpose: Returns a random number in a specified range without repeats
' Inputs: iLow - integer representing the low end of the range
' iHigh - integer representing the high end of the range
' bReset - boolean flag to optionally reset the array
' Outputs: returns an integer number or -1 if all numbers have been used
' Example first call: myNum = GetRandomNumber(10, 5, true)
' Example subsequent call: myNum = GetRandomNumber(10, 5)
'----------------------------------------------------------------------------------
Function GetRandomNumber(iLow As Integer, iHigh As Integer, Optional bReset As Boolean) As Integer
Dim iNum As Integer ' random number to be generated
Dim InArray As Boolean ' flag to test if number already used
Randomize
' Reset the tracking array as required
If bReset Then ReDim arrTracking(iHigh - iLow)
' If we've used all of the numbers, return -1 and quit
If iUsed = iHigh - iLow + 1 Then
GetRandomNumber = -1
Exit Function
End If
' Repeat the random function until we find an unused number and then
' update the tracking array, uncrease the counter and return the number
Do While Not InArray
iNum = Fix(((iHigh - iLow + 1) * Rnd + iLow))
If arrTracking(iNum - iLow) = "" Then
arrTracking(iNum - iLow) = "used"
iUsed = iUsed + 1
InArray = True
GetRandomNumber = iNum
Else
'Debug.Print iNum & " used"
End If
Loop
End Function
'----------------------------------------------------------------------------------
' Purpose: Test sub to get all random numbers in the range 5 to 10
' Inputs: None
' Outputs: Debug output of 6 numbers in the range 5 to 10 in then immediate window
'----------------------------------------------------------------------------------
Sub GetAllRand()
Dim iRndNum As Integer
' Get the initial number, restting the tracking array in the process
iRndNum = GetRandomNumber(5, 10, True)
Debug.Print iRndNum
Do While Not iRndNum = -1
iRndNum = GetRandomNumber(5, 10)
Debug.Print iRndNum
Loop
End Sub
Here's a UDF that you can use to populate an array with unique random numbers:
Function GetRandomDigits(amount As Integer, maxNumber As Integer) As Variant
With CreateObject("System.Collections.ArrayList")
Do
j = WorksheetFunction.RandBetween(1, maxNumber)
If Not .Contains(j) Then .Add j
Loop Until .Count = amount
GetRandomDigits = .ToArray()
End With
End Function
And here's an example of how to use it:
Sub MM()
Dim nums As Variant
nums = GetRandomDigits(10, 100)
For Each num In nums
Debug.Print num
Next
End Sub

VBA - Setting multidimensional array values in one line

Right, so using Python I would create a multidimensional list and set the values on one line of code (as per the below).
aryTitle = [["Desciption", "Value"],["Description2", "Value2"]]
print(aryTitle[0,0] + aryTitle[0,1])
I like the way I can set the values on one line. In VBA I am doing this by:
Dim aryTitle(0 To 1, 0 To 1) As String
aryTitle(0, 0) = "Description"
aryTitle(0, 1) = "Value"
aryTitle(1, 0) = "Description2"
aryTitle(1, 1) = "Value2"
MsgBox (aryTitle(0, 0) & aryTitle(0, 1))
Is there a way to set the values in one line of code?
Not natively, no. But you can write a function for it. The only reason Python can do that is someone wrote a function to do it. The difference is that they had access to the source so they could make the syntax whatever they like. You'll be limited to VBA function syntax. Here's a function to create a 2-dim array. It's not technically 'one line of code', but throw it in your MUtilities module and forget about it and it will feel like one line of code.
Public Function FillTwoDim(ParamArray KeyValue() As Variant) As Variant
Dim aReturn() As Variant
Dim i As Long
Dim lCnt As Long
ReDim aReturn(0 To ((UBound(KeyValue) + 1) \ 2) - 1, 0 To 1)
For i = LBound(KeyValue) To UBound(KeyValue) Step 2
If i + 1 <= UBound(KeyValue) Then
aReturn(lCnt, 0) = KeyValue(i)
aReturn(lCnt, 1) = KeyValue(i + 1)
lCnt = lCnt + 1
End If
Next i
FillTwoDim = aReturn
End Function
Sub test()
Dim vaArr As Variant
Dim i As Long
Dim j As Long
vaArr = FillTwoDim("Description", "Value", "Description2", "Value2")
For i = LBound(vaArr, 1) To UBound(vaArr, 1)
For j = LBound(vaArr, 2) To UBound(vaArr, 2)
Debug.Print i, j, vaArr(i, j)
Next j
Next i
End Sub
If you supply an odd number of arguments, it ignores the last one. If you use 3-dim arrays, you could write a function for that. You could also write a fancy function that could handle any dims, but I'm not sure it's worth it. And if you're using more than 3-dim arrays, you probably don't need my help writing a function.
The output from the above
0 0 Description
0 1 Value
1 0 Description2
1 1 Value2
You can write a helper function:
Function MultiSplit(s As String, Optional delim1 As String = ",", Optional delim2 As String = ";") As Variant
Dim V As Variant, W As Variant, A As Variant
Dim i As Long, j As Long, m As Long, n As Long
V = Split(s, delim2)
m = UBound(V)
n = UBound(Split(V(0), delim1))
ReDim A(0 To m, 0 To n)
For i = 0 To m
For j = 0 To n
W = Split(V(i), delim1)
A(i, j) = Trim(W(j))
Next j
Next i
MultiSplit = A
End Function
Used like this:
Sub test()
Dim A As Variant
A = MultiSplit("Desciption, Value; Description2, Value2")
Range("A1:B2").Value = A
End Sub

extract substring in vb.net

I have following string, and would need to extract the X and Y values cut to a single digit after the point.
A234X78.027Y141.864D1234.2
There are a few variables that can change here:
the string can have any length and contain any number of values
I know that X and Y are Always present, but they do not have to be in a specific order in the string
Each value for X or Y can have any lenght.. for example x can be 1.1 or 1234.1
it is not imperative that X or Y do have a point. it can also be a round number, for example X78Y141.34561 (note that X has no point) If there is no point I am ok with the value, but if there is a point then I would need the first digit after the point. (rounded)
As a Result of the above string I would need two string variables containing the values 78.0 and 141.9
EDIT: Updated the last sentence, the variables should contain JUST the value, no X and Y. Sorry for the mistake
Update, code as requested
Dim objReader As New System.IO.StreamReader(FILE_NAME)
Do While objReader.Peek() <> -1
Dim curline As String = objReader.ReadLine() 'curline = G1X39.594Y234.826F1800.0
If curline.Contains("X") Then
Dim t As String = ExtractPoint(curline, "X"c) 't = "39.594"
Dim d As Double = Math.Round(Convert.ToDouble(t), 1) 'd= 39594.0
destx = d * 10 'destx = 395940
End If
Loop
Function ExtractPoint(dataString As String, character As Char) As String
Dim substring As String = String.Empty
Dim xIndex As Integer = dataString.IndexOf(character) + 1
substring += dataString(xIndex)
xIndex = xIndex + 1
While (xIndex < dataString.Length AndAlso Char.IsLetter(dataString(xIndex)) = False)
substring += dataString(xIndex)
xIndex = xIndex + 1
End While
Return substring
End Function
Your sample data indicates that fields are separated by letters, and the last letter ends with the string. Knowing that you can parse your desired letters out manually and round to 1 decimal point.
This also takes into account when there is no decimal point, but it will display a .0 at the end of the number.
EDIT
Moved common code to a function
Update
Doesn't include the letter as part of the output
Sub Main()
Dim dataString As String = "G1X39.594Y234.826F1800.0"
Dim xString As String = ExtractPoint(dataString, "X"c)
Dim yString As String = ExtractPoint(dataString, "Y"c)
Dim xDouble As Double = Math.Round(Convert.ToDouble(xString), 1)
Dim yDouble As Double = Math.Round(Convert.ToDouble(yString), 1)
Console.WriteLine(xDouble.ToString("F1"))
Console.WriteLine(yDouble.ToString("F1"))
Console.WriteLine((xDouble * 10).ToString("F1"))
Console.WriteLine((yDouble * 10).ToString("F1"))
Console.ReadLine()
End Sub
Function ExtractPoint(dataString As String, character As Char) As String
Dim substring As String = String.Empty
Dim xIndex As Integer = dataString.IndexOf(character) + 1
substring += dataString(xIndex)
xIndex = xIndex + 1
While (xIndex < dataString.Length AndAlso Char.IsLetter(dataString(xIndex)) = False)
substring += dataString(xIndex)
xIndex = xIndex + 1
End While
Return substring
End Function
Results:
Have you looked into Regular Expressions?
Dim x As System.Text.RegularExpressions.Match = System.Text.RegularExpressions.Regex.Match(TextBox1.Text, "X\d+([.]\d{1})?")
Dim y As System.Text.RegularExpressions.Match = System.Text.RegularExpressions.Regex.Match(TextBox1.Text, "Y\d+([.]\d{1})?")
MsgBox(x.ToString & " -- " & y.ToString)
I believe this will do what you are looking for if I understood correctly.
EDIT For Only getting the numbers after X and Y
Based off my original code, you could do something like this.
This also rounds the numbers to the nearest one decimal place.
Dim x As System.Text.RegularExpressions.Match = System.Text.RegularExpressions.Regex.Match(TextBox1.Text, "X(\d+([.]\d{2})?)")
Dim y As System.Text.RegularExpressions.Match = System.Text.RegularExpressions.Regex.Match(TextBox1.Text, "Y(\d+([.]\d{2})?)")
MsgBox(Math.Round(CDbl(x.Groups(1).Value), 1) & " -- " & Math.Round(CDbl(y.Groups(1).Value), 1))
Updated code for added code
Dim s As String = "A234X78.027Y141.864D1234.2"
Dim dX As Double = Extract(s, "X")
Dim dY As Double = Extract(s, "Y")
MsgBox(dX * 10 & "-" & dY * 10)
Private Function Extract(ByRef a As String, ByRef l As String) As Double
Dim x As System.Text.RegularExpressions.Match = System.Text.RegularExpressions.Regex.Match(a, l & "(\d+([.]\d{2})?)")
Return Math.Round(CDbl(x.Groups(1).Value), 1)
End Function
Here is a simple LINQ function that should do it for you (no regex, no long code):
Private Function ExtractX(s As String, symbol As Char) As String
Dim XPos = s.IndexOf(symbol)
Dim s1 = s.Substring(XPos + 1).TakeWhile(Function(c) Char.IsDigit(c)).ToArray()
If (XPos + 1 + s1.Length < s.Length) AndAlso s.Substring(XPos + 1 + s1.Length)(0) = "."c AndAlso Char.IsDigit(s.Substring(XPos + 1 + s1.Length)(1)) Then
Return String.Join("", s1, s.Substring(XPos + 1 + s1.Length, 2))
Else
Return s1
End If
End Function
Call it like this:
Dim s = "A234X78.027Y141.864D1234.2"
Dim x = ExtractX(s, "X"c)
Dim y = ExtractX(s, "Y"c)

Speed up large string data parser function

I currently have a file with 1 million characters.. the file is 1 MB in size. I am trying to parse data with this old function that still works but very slow.
start0end
start1end
start2end
start3end
start4end
start5end
start6end
the code, takes about 5 painful minutes to process the whole data.
any pointers and suggestions are appreciated.
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim sFinal = ""
Dim strData = textbox.Text
Dim strFirst = "start"
Dim strSec = "end"
Dim strID As String, Pos1 As Long, Pos2 As Long, strCur As String = ""
Do While InStr(strData, strFirst) > 0
Pos1 = InStr(strData, strFirst)
strID = Mid(strData, Pos1 + Len(strFirst))
Pos2 = InStr(strID, strSec)
If Pos2 > 0 Then
strID = Microsoft.VisualBasic.Left(strID, Pos2 - 1)
End If
If strID <> strCur Then
strCur = strID
sFinal += strID & ","
End If
strData = Mid(strData, Pos1 + Len(strFirst) + 3 + Len(strID))
Loop
End Sub
The reason that is so slow is because you keep destroying and recreating a 1 MB string over and over. Strings are immutable, so strData = Mid(strData... creates a new string and copies the remaining of the 1 MB string data to a new strData variable over and over and over. Interestingly, even VB6 allowed for a progressive index.
I would have processed the disk file LINE BY LINE and plucked out the info as it was read (see streamreader.ReadLine) to avoid working with a 1MB string. Pretty much the same method could be used there.
' 1 MB textbox data (!?)
Dim sData As String = TextBox1.Text
' start/stop - probably fake
Dim sStart As String = "start"
Dim sStop As String = "end"
' result
Dim sbResult As New StringBuilder
' progressive index
Dim nNDX As Integer = 0
' shortcut at least as far as typing and readability
Dim MagicNumber As Integer = sStart.Length
' NEXT index of start/stop after nNDX
Dim i As Integer = 0
Dim j As Integer = 0
' loop as long as string remains
Do While (nNDX < sData.Length) AndAlso (i >= 0)
i = sData.IndexOf(sStart, nNDX) ' start index
j = sData.IndexOf(sStop, i) ' stop index
' Extract and append bracketed substring
sbResult.Append(sData.Substring(i + MagicNumber, j - (i + MagicNumber)))
' add a cute comma
sbResult.Append(",")
nNDX = j ' where we start next time
i = sData.IndexOf(sStart, nNDX)
Loop
' remove last comma
sbResult.Remove(sbResult.ToString.Length - 1, 1)
' show my work
Console.WriteLine(sbResult.ToString)
EDIT: Small mod for the ad hoc test data