Remove item from Array at Specific Index - excel

I have an array of numbers, once i "use" a number I want to remove using a specific index I have stored for that value.
I know there is no direct method but is there a way I work around this?
Example:
ShiftArray(1,25,12)
Pos = 1
Shiftarray(pos).delete
The array then should be = ShiftArray(1,12)
(I know such a method does not exist, just for clarity of intention's sake)
I tried the following but it gave me an error:
ShiftHeadsArray(pos - 1) = " "
StringUse = Application.WorksheetFunction.Trim(Join(ShiftHeadsArray, " "))
ShiftHeadsArray = Split(StringUse, " ")
Where pos is the position of the number I want to remove within the array.

Try this code
Sub Test()
Dim arr
arr = Array(1, 25, 12)
DeleteItem arr, 1
Debug.Print Join(arr, ", ")
End Sub
Sub DeleteItem(ByRef arr, v)
Dim a(), i As Long, n As Long, x As Long, y As Long
x = LBound(arr): y = UBound(arr)
ReDim a(x To y)
For i = x To y
If i <> v Then a(i - n) = arr(i) Else n = n + 1
Next i
If (y - n) >= x Then ReDim Preserve a(x To y - n)
arr = a
End Sub

Having a array-only based solution isn't so trivial, if you're interested in catching possible errors.
Remark: Instead of just returning an empty array or the input array like in this example, you should raise an error if the input isn't proper. But this depends on how you like the function to behave.
Public Sub Test()
'Some common tests:
Debug.Assert Join(RemoveItemByIndex(Array(), 1), "-") = vbNullString
Debug.Assert Join(RemoveItemByIndex(Array(1), 0), "-") = vbNullString
Debug.Assert Join(RemoveItemByIndex(Array(1), 1), "-") = vbNullString
Debug.Assert Join(RemoveItemByIndex(Array(1, 25, 12), 1), "-") = "1-12"
Debug.Assert Join(RemoveItemByIndex(Array(1, 25, 12), 10), "-") = "1-25-12"
Debug.Assert Join(RemoveItemByIndex(Array(1, 25, 12), -1), "-") = "1-25-12"
Debug.Assert Join(RemoveItemByIndex("foo", -1), "-") = vbNullString
'Your working sample:
Dim originalArray() As Variant
originalArray = Array(1, 25, 12)
Dim item As Variant
For Each item In RemoveItemByIndex(originalArray, 1)
Debug.Print item
Next item
End Sub
Public Function RemoveItemByIndex(ByVal arrayToWorkOn As Variant, ByVal indexToRemove As Long) As Variant()
RemoveItemByIndex = Array()
If Not IsArray(arrayToWorkOn) Then Exit Function
If Not IsArrayInitialized(arrayToWorkOn) Then Exit Function
If UBound(arrayToWorkOn) - LBound(arrayToWorkOn) = 0 Then Exit Function
RemoveItemByIndex = arrayToWorkOn
If indexToRemove < LBound(arrayToWorkOn) _
Or indexToRemove > UBound(arrayToWorkOn) Then Exit Function
ReDim resultingArray(UBound(arrayToWorkOn) - 1) As Variant
Dim index As Long
Dim resultingIndex As Long
For index = LBound(arrayToWorkOn) To UBound(arrayToWorkOn): Do
If index = indexToRemove Then Exit Do
resultingArray(resultingIndex) = arrayToWorkOn(index)
resultingIndex = resultingIndex + 1
Loop While False: Next index
RemoveItemByIndex = resultingArray
End Function
Public Function IsArrayInitialized(ByVal arrayToWorkOn As Variant) As Boolean
On Error Resume Next
IsArrayInitialized = IsArray(arrayToWorkOn) And _
Not IsError(LBound(arrayToWorkOn, 1)) And _
LBound(arrayToWorkOn, 1) <= UBound(arrayToWorkOn, 1)
End Function
Regarding the : Do and Loop While False:: This is a neat trick to simulate a 'continue'.
See here for more information: VBA - how to conditionally skip a for loop iteration

Remove Array Item by Index
Option Explicit
Sub TESTremoveArrayItemByIndex()
Dim Addresses As Variant: Addresses = Array("A1", "A2", "A3")
Dim Values As Variant: Values = Array(1, 25, 10)
Dim mIndex As Variant
mIndex = Application.Match(Application.Max(Values), Values, 0)
Dim dAddress As String: dAddress = Application.Index(Addresses, mIndex)
' If you are sure that 'Addresses' is zero-based, instead of the previous
' line you can do:
'Dim cAddress As String: cAddress = Addresses(Index - 1)
removeArrayItemByIndex Addresses, mIndex
Debug.Print "Addresses(After): " & Join(Addresses, ",")
removeArrayItemByIndex Values, mIndex
Debug.Print "Values(After): " & Join(Values, ",")
End Sub
Sub TESTremoveArrayItemByIndexDebugPrint()
Dim Addresses As Variant: Addresses = Array("A1", "A2", "A3")
Debug.Print "Addresses(Before): " & Join(Addresses, ",")
Dim Values As Variant: Values = Array(1, 25, 10)
Debug.Print "Values(Before): " & Join(Values, ",")
Dim mIndex As Variant
mIndex = Application.Match(Application.Max(Values), Values, 0)
Debug.Print "Maximum Index: " & mIndex
Dim dAddress As String: dAddress = Application.Index(Addresses, mIndex)
' If you are sure that 'Addresses' is zero-based, instead of the previous
' line you can do:
'Dim cAddress As String: cAddress = Addresses(Index - 1)
Debug.Print "Delete Address: " & dAddress
removeArrayItemByIndex Addresses, mIndex
Debug.Print "Addresses(After): " & Join(Addresses, ",")
removeArrayItemByIndex Values, mIndex
Debug.Print "Values(After): " & Join(Values, ",")
End Sub
Sub removeArrayItemByIndex( _
ByRef arr As Variant, _
ByVal Index As Long)
Dim n As Long
For n = Index + LBound(arr) - 1 To UBound(arr) - 1
arr(n) = arr(n + 1)
Next n
ReDim Preserve arr(LBound(arr) To n - 1)
End Sub

Related

Function to summarize text in Vba Excel

Now I'm using MS Excel 2019. I desire to make function to get text at Summary Steps column and Sumary Values column from Steps and Values Column
It's described as .
I tried with this function. However, It doesn't work at all
Function Congdoan_Time(Congdoan As Range, Time As Range, gtri As Boolean) As String
Dim xValue, TimeValue As String
Dim xChar As String
Dim xOutValue, xTimeValue As String
xValue = Congdoan.Value
TimeValue = Time.Value
Dim arr, timearr As Variant
Dim text, texttime As String
Dim nextarr As Variant
arr = Split(xValue, ",")
timearr = Split(TimeValue, "-")
Dim i As Long
Dim vallue As Variant
vallue = timearr(0)
For i = LBound(arr) To UBound(arr) - 1
If arr(i) = arr(i + 1) And i < UBound(arr) - 1 Then
vallue = Val(vallue) + Val(timearr(i + 1))
End If
If arr(i) = arr(i + 1) And i = UBound(arr) - 1 Then
End If
If arr(i) <> arr(i + 1) Then
xOutValue = xOutValue & "," & arr(i)
xTimeValue = xTimeValue & "-" & vallue
vallue = Val(timearr(i + 1))
End If
Next i
If xOutValue = "" Then
xOutValue = Join(arr, ",")
xTimeValue = vallue
End If
text = Right(xOutValue, Len(xOutValue) - 1)
nextarr = Split(text, ",")
If arr(UBound(arr)) <> nextarr(UBound(nextarr)) Then
text = text & "," & arr(UBound(arr))
xTimeValue = xTimeValue & "-" & Val(vallue) + Val(timearr(UBound(arr)))
End If
If gtri = True Then
Congdoan_Time = text
Else
Congdoan_Time = xTimeValue
End If
End Function
Formula at Sumary Steps Column
at Sumary Values Column
Please help to make another funtion that's work for me
Thank you
My two cents using a dictionary:
Function Summary(steps As String, vals As String, pick As Boolean) As String
Dim arr_steps As Variant, arr_vals As Variant
Dim new_steps() As Variant, new_vals() As Variant
arr_steps = Split(steps, ",")
arr_vals = Split(vals, "-")
ReDim new_steps(UBound(arr_steps))
ReDim new_vals(UBound(arr_steps))
For x = 0 To UBound(arr_steps)
If x = 0 Then
new_steps(x) = arr_steps(x)
new_vals(x) = arr_vals(x)
ElseIf arr_steps(x) = arr_steps(x - 1) Then
new_vals(x) = CDbl(new_vals(x - 1)) + CDbl(arr_vals(x))
new_vals(x - 1) = ""
Else
new_steps(x) = arr_steps(x)
new_vals(x) = arr_vals(x)
End If
Next
If pick Then
Summary = Join(new_steps, ",")
Else
Summary = Join(new_vals, "-")
End If
With CreateObject("VBScript.RegExp")
.Global = True
.Pattern = "(?:^-+|[-,]+([-,])|,+$)"
Summary = .Replace(Summary, "$1")
End With
End Function
Formula in C1:
=Summary(A1,B1,1)
Formula in D1:
=Summary(A1,B1,0)
Note: My locale uses decimal-comma instead of point. It should work out fine if yours is using dots. I just had to change these in the input.
I'm not sure I fully understand the question but looking at your examples it looks like you want to deduplicate a comma delimited string but only where duplicates are in series. Something like this does that quite optimally:
Function SerialDedupe(ByVal s As String) As String
Dim v: v = Split(s, ",")
Dim iLB As Long: iLB = LBound(v)
Dim index As Long: index = iLB
Dim i As Long
For i = iLB + 1 To UBound(v)
If v(i) <> v(index) Then
index = index + 1
v(index) = v(i)
End If
Next
ReDim Preserve v(iLB To index)
SerialDedupe = Join(v, ",")
End Function
Tests:
1,2,2,2,3,3,2 ==> 1,2,3,2
A,B,B,C,B,B,C,D,B,C,B,A,A,B ==> A,B,C,B,C,D,B,C,B,A,B
1,2,2 ==> 1,2
1,1,2 ==> 1,2
1,2 ==> 1,2
1 ==> 1

How to compare two sheets in excel & output similarities AND differences?

I have a current code that compares the first two sheets and then outputs the differences in another. I am now trying to figure out how to also output the similarities into another worksheet.
Here is my current code:
Option Explicit
Sub CompareIt()
Dim ar As Variant
Dim arr As Variant
Dim Var As Variant
Dim v()
Dim i As Long
Dim n As Long
Dim j As Long
Dim str As String
ar = Sheet1.Cells(10, 1).CurrentRegion.Value
With CreateObject("Scripting.Dictionary")
.CompareMode = 1
ReDim v(1 To UBound(ar, 2))
For i = 2 To UBound(ar, 1)
For n = 1 To UBound(ar, 2)
str = str & Chr(2) & ar(i, n)
v(n) = ar(i, n)
Next
.Item(str) = v: str = ""
Next
ar = Sheet2.Cells(10, 1).CurrentRegion.Resize(, UBound(v)).Value
For i = 2 To UBound(ar, 1)
For n = 1 To UBound(ar, 2)
str = str & Chr(2) & ar(i, n)
v(n) = ar(i, n)
Next
If .exists(str) Then
.Item(str) = Empty
Else
.Item(str) = v
End If
str = ""
Next
For Each arr In .keys
If IsEmpty(.Item(arr)) Then .Remove arr
Next
Var = .items: j = .Count
End With
With Sheet3.Range("a10").Resize(, UBound(ar, 2))
.CurrentRegion.ClearContents
.Value = ar
If j > 0 Then
.Offset(1).Resize(j).Value = Application.Transpose(Application.Transpose(Var))
End If
End With
Sheet3.Activate
End Sub
Any ideas?
Since your question is:
Any ideas?
I do have an idea that does rely on:
Your excel license (TEXTJOIN function is available if you have Office 2019, or if you have an Office 365 subscription)
Your data size (If the resulting string exceeds 32767 characters (cell limit), TEXTJOIN returns the #VALUE! error.)
But it's an idea :)
Sheet1 & Sheet2
Run this code:
Sub Test()
Dim Var() As String
With ThisWorkbook.Sheets("Sheet3")
Var() = Split(Evaluate("=TEXTJOIN("","",TRUE,IF(Sheet1!A1:A6=TRANSPOSE(Sheet2!A1:A5),Sheet1!A1:A6,""""))"), ",")
.Cells(1, 1).Resize(UBound(Var) + 1).Value = Application.Transpose(Var)
End With
End Sub
Output on sheet3:
Obviously it's simplified, but you can add variables in the EVALUATE.

Find Digits between two characters without using Regex

I've been looking for a while for a code that would give me the digits between without using regex (I want my macro to be used by anyone especially non-computer friendly people). This is a small part of a code creating series for a chart dynamically creating the chart etc.
Here is the type of data I am dealing with "C23H120N5O4Cl" so I'd like to save in a variable 23 then in another one 120 the rest should not matter (it could be nothing).
My digits will likely be between single characters (C,H,or else) but I need the numbers after C and H. So at the moment here is my code :
RangeOccupied = Range("C2").End(xlDown).row
For i = 1 To RangeOccupied
If i <> RangeOccupied Then
'Look for digits after C
pos = InStr(1, Cells(i + 1, 2), "C") + 1
pos1 = InStr(pos, Cells(i + 1, 2), "H")
NumC = Mid(Cells(i + 1, 2), pos, pos1 - pos)
'Look for digits after H
pos = InStr(1, Cells(i + 1, 2), "H") + 1
pos1 = InStr(pos, Cells(i + 1, 2), "O")
NumH = Mid(Cells(i + 1, 2), pos, pos1 - pos)
End If
Next
Ideally I'd like the pos1 numbers not to be dependent on a specific character but any character. i.e having pos1=InStr(pos,Cells(i+1,2),"ANY NON-NUMBER CHARACTER").
I do not know if it is possible without using regex.
This function will return an array of the digit strings in a text string
Option Explicit
Function myDigits(str As String) As String()
Dim col As Collection
Dim I As Long, S() As String
I = 0
Set col = New Collection
Do Until I > Len(str)
I = I + 1
If IsNumeric(Mid(str, I, 1)) Then
col.Add Val(Mid(str, I, Len(str)))
I = I + 1
Do Until Not IsNumeric(Mid(str, I, 1))
I = I + 1
Loop
End If
Loop
ReDim S(0 To col.Count - 1)
For I = 1 To col.Count
S(I - 1) = col(I)
Next I
myDigits = S
End Function
Okay, I'm absolutely certain there is a more efficient way of doing this. But I think the following example makes it fairly clear on one way to separate your values.
Option Explicit
Sub test()
Dim testValues() As String
Dim val1 As Long
Dim val2 As Long
testValues = Split("C23H120N5O4Cl,C23O120N5H4Cl,C4H120", ",")
Dim testValue As Variant
For Each testValue In testValues
ExtractValues testValue, val1, val2
Debug.Print "For " & testValue & ": " & val1 & " and " & val2
Next testValue
End Sub
Public Sub ExtractValues(ByVal inString As String, _
ByRef output1 As Long, _
ByRef output2 As Long)
Dim outString1 As String
Dim outString2 As String
Dim stage As String
stage = "Begin"
Dim thisCharacter As String
Dim i As Long
For i = 1 To Len(inString)
thisCharacter = Mid$(inString, i, 1)
Select Case stage
Case "Begin"
If thisCharacter = "C" Then stage = "First Value"
Case "First Value"
If (Asc(thisCharacter) >= Asc("0")) And _
(Asc(thisCharacter) <= Asc("9")) Then
outString1 = outString1 & thisCharacter
Else
'--- if we get here, we're done with this value
output1 = CLng(outString1)
'--- verify the next character is the "H"
If thisCharacter = "H" Then
stage = "Second Value"
Else
stage = "Next Value"
End If
End If
Case "Next Value"
If thisCharacter = "H" Then stage = "Second Value"
Case "Second Value"
If (Asc(thisCharacter) >= Asc("0")) And _
(Asc(thisCharacter) <= Asc("9")) Then
outString2 = outString2 & thisCharacter
Else
'--- if we get here, we're done with this value
output2 = CLng(outString2)
stage = "Finished"
Exit For
End If
End Select
Next i
If Not (stage = "Finished") Then
output2 = CLng(outString2)
End If
End Sub
Here's another method that's more generic and efficient than my first solution. This approach uses a function to extract the number following a given substring -- in this case it's a single letter "C" or "H". The function accounts for the value being at the end of the input value as well.
Option Explicit
Sub test()
Dim testValues() As String
Dim val1 As Long
Dim val2 As Long
testValues = Split("C23H120N5O4Cl,C23O120N5H4Cl,C4H120", ",")
Dim testValue As Variant
For Each testValue In testValues
val1 = NumberAfter(testValue, "C")
val2 = NumberAfter(testValue, "H")
Debug.Print "For " & testValue & ": " & val1 & " and " & val2
Next testValue
End Sub
Private Function NumberAfter(ByVal inString As String, _
ByVal precedingString As String) As Long
Dim outString As String
Dim thisToken As String
Dim foundThisToken As Boolean
foundThisToken = False
Dim i As Long
For i = 1 To Len(inString)
thisToken = Mid$(inString, i, 1)
If thisToken = precedingString Then
foundThisToken = True
ElseIf foundThisToken Then
If thisToken Like "[0-9]" Then
outString = outString & thisToken
Else
Exit For
End If
End If
Next i
NumberAfter = CLng(outString)
End Function
I found this solution from here Extract numbers from chemical formula
Public Function ElementCount(str As String, element As String) As Long
Dim i As Integer
Dim s As String
For i = 1 To 3
s = Mid(str, InStr(str, element) + 1, i)
On Error Resume Next
ElementCount = CLng(s)
On Error GoTo 0
Next i
End Function
Which works but if simple molecules like CH4 are put in it does not work since no number are shown... but I (we) can probably work that out.
Thanks again for all the solutions !
EDIT:
Here is the function I use that I think takes all possible scenarios into account ! Thanks again for your help !
Public Function ElementCount(str As String, element As String) As Long
Dim k As Integer
Dim s As String
For k = 1 To Len(str)
s = Mid(str, InStr(str, element) + 1, k)
On Error Resume Next
ElementCount = CLng(s)
On Error GoTo 0
If InStr(str, element) > 0 And ElementCount = 0 Then
ElementCount = 1
End If
Next k
End Function
EDIT
Changed the function to use and return dictionaries having keys of "C" and "H" paired with their numbers. Included a screenshot below.
Made sure it handles for tricky situations where multiple letters are packed ontop of each other:
Code:
Sub mainLoop()
Dim numbers As Scripting.Dictionary: Set numbers2 = New Scripting.Dictionary
For i = 1 To 5
Set numbers = returnDict(Cells(i, 1).Value)
printout numbers, i
Next
End Sub
Function returnDict(cellValue As String) As Scripting.Dictionary
Dim i As Integer: i = 1
Dim holder As String: holder = ""
Dim letter As String
Set returnStuff = New Scripting.Dictionary
While i < Len(cellValue)
If Mid(cellValue, i, 1) = "C" Or Mid(cellValue, i, 1) = "H" Then
i = i + 1
If IsNumeric(Mid(cellValue, i, 1)) Then
letter = (Mid(cellValue, i - 1, 1))
Do While IsNumeric(Mid(cellValue, i, 1))
holder = holder & Mid(cellValue, i, 1)
i = i + 1
If i > Len(cellValue) Then Exit Do
Loop
returnStuff.Add letter, holder
holder = ""
ElseIf Mid(cellValue, i, 1) <> LCase(Mid(cellValue, i, 1)) Then
returnStuff.Add Mid(cellValue, i - 1, 1), "1"
End If
Else
i = i + 1
End If
Wend
End Function
And heres a quick little function used to print out the contents of the dictionary
Sub printout(dict As Scripting.Dictionary, row As Integer)
Dim i As Integer: i = 2
For Each Key In dict.Keys
Cells(row, i).Value = Key & ": " & dict.Item(Key)
i = i + 1
Next
End Sub
My 2c:
Sub tester()
Dim r, arr, v
arr = Array("C", "Z", "Na", "N", "O", "Cl", "Br", "F")
For Each v In arr
Debug.Print v, ParseCount("C15H12Na2N5O4ClBr", v)
Next v
End Sub
Function ParseCount(f, s)
Const ALL_SYMBOLS As String = "Ac,Al,Am,Sb,Ar,As,At,Ba,Bk,Be,Bi,Bh,Br,Cd,Ca,Cf,Ce,Cs,Cl," & _
"Cr,Co,Cn,Cu,Cm,Ds,Db,Dy,Es,Er,Eu,Fm,Fl,Fr,Gd,Ga,Ge,Au,Hf,Hs,He,Ho,In,Ir,Fe,Kr,La,Lr," & _
"Pb,Li,Lv,Lu,Mg,Mn,Mt,Md,Hg,Mo,Mc,Nd,Ne,Np,Ni,Nh,Nb,No,Og,Os,Pd,Pt,Pu,Po,Pr,Pm,Pa,Ra," & _
"Rn,Re,Rh,Rg,Rb,Ru,Rf,Sm,Sc,Sg,Se,Si,Ag,Na,Sr,Ta,Tc,Te,Ts,Tb,Tl,Th,Tm,Sn,Ti,Xe,Yb,Zn," & _
"Zr,B,C,F,H,I,N,O,P,K,S,W,U,V,Y"
Dim atoms, rv, pos, i As Long
atoms = Split(ALL_SYMBOLS, ",")
rv = 0 'default return value
If IsError(Application.Match(s, atoms, 0)) Then
rv = -1 'not valid atomic symbol
Else
i = 1
pos = InStr(i, f, s, vbBinaryCompare)
If pos > 0 Then
If Len(s) = 2 Then
'should be a true match...
rv = ExtractNumber(f, pos + 2)
ElseIf Len(s) = 1 Then
'check for false positives eg "N" matches on "Na"
Do While pos > 0 And Mid(f, pos + 1, 1) Like "[a-z]"
i = pos + 1
pos = InStr(i, f, s, vbBinaryCompare)
Loop
If pos > 0 Then rv = ExtractNumber(f, pos + 1)
Else
'exotic chemistry...
End If
End If
End If
ParseCount = rv
End Function
'extract consecutive numeric digits from f starting at pos
' *returns 1 if no number present*
Function ExtractNumber(f, pos)
Dim rv, s, i As Long
Do While (pos + i) <= Len(f)
If Not Mid(f, pos + i, 1) Like "#" Then Exit Do
i = i + 1
Loop
ExtractNumber = IIf(i = 0, 1, Mid(f, pos, i))
End Function

Macro to insert a formula and drag it down

I am trying to split the contents of a column into various columns. The column has content that looks like this:
3-BW16569*AW34586*AW34587
3- LVA18140 & LVA19222
3-LVA22841
3- JDSC RELOAD
3 - LV1 TO JDSC 6/21
3- LVU21690
3-LVA19520*LVU21739
3- R241974/R241974
The column is not in a particular format but always has different symbols between the elements to separate them. Can a macro code help with this or a excel function. Thank you!
All thanks to alainbryden for the function SplitMultiDelims() . . don't change it, foo() may help you through in how to use it in your problem...
Sub foo()
Dim ws As Worksheet
Dim sizeArr, index As Integer
Dim Arr() As String
Set ws = ThisWorkbook.Worksheets("Sheet1")
Dim str As String
Dim dilimiters As String
Dim str1 As String
dilimiters = " -*" ' provide all of them
str = "3-BW16569*AW34586*AW34587" ' read the string
'str = ws.Cells(1, 1).Value
Debug.Print str
Arr = SplitMultiDelims(str, dilimiters) ' delimit
sizeArr = UBound(Arr) ' get no of different strings you have
For index = 0 To sizeArr Step 1
str1 = Arr(index) ' get the string
Debug.Print str1
' now paste where evere you want
Next
End Sub
Function SplitMultiDelims(ByRef Text As String, ByRef DelimChars As String, _
Optional ByVal IgnoreConsecutiveDelimiters As Boolean = False, _
Optional ByVal Limit As Long = -1) As String()
Dim ElemStart As Long, N As Long, M As Long, Elements As Long
Dim lDelims As Long, lText As Long
Dim Arr() As String
lText = Len(Text)
lDelims = Len(DelimChars)
If lDelims = 0 Or lText = 0 Or Limit = 1 Then
ReDim Arr(0 To 0)
Arr(0) = Text
SplitMultiDelims = Arr
Exit Function
End If
ReDim Arr(0 To IIf(Limit = -1, lText - 1, Limit))
Elements = 0: ElemStart = 1
For N = 1 To lText
If InStr(DelimChars, Mid(Text, N, 1)) Then
Arr(Elements) = Mid(Text, ElemStart, N - ElemStart)
If IgnoreConsecutiveDelimiters Then
If Len(Arr(Elements)) > 0 Then Elements = Elements + 1
Else
Elements = Elements + 1
End If
ElemStart = N + 1
If Elements + 1 = Limit Then Exit For
End If
Next N
'Get the last token terminated by the end of the string into the array
If ElemStart <= lText Then Arr(Elements) = Mid(Text, ElemStart)
'Since the end of string counts as the terminating delimiter, if the last character
'was also a delimiter, we treat the two as consecutive, and so ignore the last elemnent
If IgnoreConsecutiveDelimiters Then If Len(Arr(Elements)) = 0 Then Elements = Elements - 1
ReDim Preserve Arr(0 To Elements) 'Chop off unused array elements
SplitMultiDelims = Arr
End Function

Excel 2007 VBA code to automate extracting and storing numeric values from a string with special characters

I have a string which is in A1 of Sheet1 and it refreshes regularly.
it looks like this -
{"rows":[{"advances":637,"declines":836,"unchanged":76,"total":1549}],"success":"true","results":1}
I want to extract numeric values 637 and 836 and 76 and store it in separate columns. The values keeps on changing, like 637 can be sometimes 1200.
I want a VBA code, not an user defined function to automatically extract the numeric data and store it.
I am using this code, but I am not getting anything. What is wrong?
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Address = "$A$1" Then
Dim s As String
Dim fieldNames As Variant
Dim iField As Long
Dim nFields As Long
Dim v As Variant
' specify what you want
fieldNames = Array("advances", "declines", "unchanged")
nFields = UBound(fieldNames) - LBound(fieldNames) + 1
s = [a1].Value ' read in the string
' parse it, put values in a Variant array
ReDim v(1 To 1, 1 To nFields)
For iField = 1 To nFields
s = Mid(s, InStr(s, """" & fieldNames(iField - 1) & """:") _
+ Len(fieldNames(iField - 1)) + 3)
v(1, iField) = Left(s, InStr(s, ",") - 1)
Next iField
' write array to sheet
[b1].Resize(1, nFields).Value = v
End If
Range("=Sheet1!$E$1:$G$1").Copy Destination:=Sheets("Updated").Range("B" & Rows.Count).End(xlUp).Offset(1)
Sheets("Updated").Range("A" & Rows.Count).End(xlUp).Offset(1).Value = Now
End Sub
While I would be tempted to use standard functions for this you could run a VBA UDF like so
to get the first match from A1 (as a number)
=RegexMatches(A1,1)
the second match
=RegexMatches(A1,2)
and so on
The UDF returns "No Match" where no matches are found, or a message "Less than X matches" if you try to extract a match from a position that doesb't exist
Function RegexMatches(strIn As String, LngPos As Long)
Dim objRegex
Dim objRegexMC
Set objRegex = CreateObject("vbscript.regexp")
With objRegex
.Global = True
.Pattern = ":(\d+),"
If .test(strIn) Then
Set objRegexMC = .Execute(strIn)
If objRegexMC.Count >= LngPos Then
RegexMatches = CLng(objRegexMC(LngPos - 1).submatches(0))
Else
RegexMatches = "Less than " & LngPos & " matches"
End If
Else
RegexMatches = "No Match"
End If
End With
End Function
[Update: added sheet event code]
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rng1 As Range
Set rng1 = Sheets("Updated").Range("B" & Rows.Count).End(xlUp).Offset(1, 0)
'set row1 as first row if it is blank
If rng1.Row = 2 And rng1.Offset(-1, 0) = vbNullString Then Set rng1 = rng1.Offset(-1, 0)
rng1.Resize(1, 3).Value = Range("B1:D1").Value
rng1.Offset(0, -1).Value = Now
End Sub
You can use an instance of the windows script control to parse the text for you:
Sub Tester()
Dim JSON As String
Dim sc As Object
JSON = "{""rows"":[{""advances"":637,""declines"":836," & _
"""unchanged"":76,""total"":1549}]" & _
",""success"":""true"",""results"":1}"
'JSON = ActiveSheet.Range("A1").Value
Set sc = CreateObject("ScriptControl")
sc.Language = "JScript"
sc.Eval "var o = eval(" & JSON & ")" 'evaluate to an object
Debug.Print "success", sc.Eval("o.success")
Debug.Print "#results", sc.Eval("o.results")
Debug.Print " advances", sc.Eval("o.rows[0].advances")
Debug.Print " declines", sc.Eval("o.rows[0].declines")
Debug.Print " unchanged", sc.Eval("o.rows[0].unchanged")
End Sub
I needed something similar and developed the following code. I have no doubts that it could be made more sophisticated but it provided me with the required result.
Dim cr_ref As String ' Original reference - alpha and numer
Dim new_ref As String ' Resultant numeirc only reference
Dim iAsciiCode As Integer
Dim sCount As Integer ' Length of reference
cr_ref = Trim(Cells(4, 19).Value) ' Get reference from source
sCount = Len(cr_ref)
new_ref = "" ' Start with empty result
For i = 0 To sCount - 1 ' Strip out all except digits
iAsciiCode = Asc(Mid(cr_ref, i + 1, 1)) ' Give me Ascii code
If iAsciiCode >= 48 And iAsciiCode <= 57 Then ' I only want 0-9
new_ref = new_ref & Mid(cr_ref, i + 1, 1) ' Append numeric digit
End If
Next i
' Result is in new_ref
This works:
Dim s As String
Dim fieldNames As Variant
Dim iField As Long
Dim nFields As Long
Dim v As Variant
' specify what you want
fieldNames = Array("advances", "declines", "unchanged")
nFields = UBound(fieldNames) - LBound(fieldNames) + 1
s = [a1].Value ' read in the string
' parse it, put values in a Variant array
ReDim v(1 To 1, 1 To nFields)
For iField = 1 To nFields
s = Mid(s, InStr(s, """" & fieldNames(iField - 1) & """:") _
+ Len(fieldNames(iField - 1)) + 3)
v(1, iField) = Left(s, InStr(s, ",") - 1)
Next iField
' write array to sheet
[b1].Resize(1, nFields).Value = v

Resources