Here is my code. I am taking the following designators and shorthanding grouping them to AR2-AR4,AR15,AT3-AT4,C68,C76,C316,C319,FL14-FL18,J1-J6,L2-5,etc. This is all working good except when the filter applies "L" in which it returns FL14,FL15,FL16,FL17,FL8,L2,L3,L4,L5. I need a way to do an exact character match or something.
Sub FormatAsRanges()
Dim Lne As String, arr, s
Dim n As Long, v As Long, prev As Long, inRange As Boolean
Dim test As String
Dim x As Variant
Dim filterarray As Variant
inRange = False
Lne = "AR15,AR2,AR3,AR4,AT3,AT4,C316,C319,C68,C76,FL14,FL15,FL16,FL17,FL18,FL6,J1,J2,J3,J4,J5,J6,L2,L3,L4,L5,T4,T5,T6,U38"
arr = Split(Lne, ",") 'Break apart references into array items
x = Prefix(arr) 'Get the Prefix's (AR,AT,C,FL,J,L,T,U)
x = Split(x, ",") ' Split them in an array
For j = 0 To UBound(x)
inRange = False 'Initialize to False
arr = Split(Lne, ",") ' Redifine arr since it is being filtered and use in the j loop for each prefix
filterarray = Filter(arr, x(j)) ' Apply filter
For i = 0 To UBound(filterarray)
filterarray(i) = Replace(filterarray(i), x(j), "")
Next i
arr = ArraySort(filterarray)
prev = -999 'dummy value
For n = LBound(filterarray) To UBound(filterarray)
v = CLng(filterarray(n))
If v - prev = 1 Then 'starting or continuing a range?
inRange = True 'wait until range ends before adding anything
Else
If inRange Then 'ending a range ?
s = s & "-" & x(j) & prev 'close out current range with previous item
inRange = False
End If
s = s & IIf(Len(s) > 0, ",", "") & x(j) & v 'add the current item
End If
prev = v
Next n
If inRange Then s = s & "-" & x(j) & prev 'close out last item if in a range
Debug.Print s
s = Empty
filterarray = Empty
Next j
End Sub
Function ArraySort(MyArray As Variant)
Dim First As Long, last As Long
Dim i As Long, j As Long, Temp
First = LBound(MyArray)
last = UBound(MyArray)
For i = First To last - 1
For j = i + 1 To last
If CLng(MyArray(i)) > CLng(MyArray(j)) Then
Temp = MyArray(j)
MyArray(j) = MyArray(i)
MyArray(i) = Temp
End If
Next j
Next i
ArraySort = MyArray
End Function
'get the character prefix (up to the first digit)
Public Function Prefix(a As Variant)
Dim rv As String, c As String, i As Long, j As Long, k As Integer, Prf As String
Dim flt(10) As String
Prf = "*" 'Initialize string
k = 0 'initialize
For j = 0 To UBound(a)
If InStr(a(j), Prf) Then
'Debug.Print "Yes"
Else
Prf = Empty
For i = 0 To Len(a(j))
c = Mid(a(j), i + 1, 1)
If c Like "#" Then
Exit For
Else
rv = rv & c
End If
Next i
Prf = rv
flt(k) = Prf
k = k + 1
rv = Empty
End If
Next j
For l = 0 To UBound(flt) 'Output as string so to define an array that is the correct size in the main program
If flt(l) Like "?" Then
rtn = rtn + flt(l) + ","
ElseIf flt(l) Like "??" Then
rtn = rtn + flt(l) + ","
ElseIf flt(l) Like "???" Then
rtn = rtn + flt(l) + ","
End If
Next l
rtn = Left(rtn, Len(rtn) - 1)
Prefix = rtn
End Function
You can move more of the code into separate methods:
Sub Tester()
Dim Lne As String, arr, allPrefixes, arrFilt, arrSorted, s, prefix
Lne = "AR15,AR2,AR3,AR4,AT3,AT4,C316,C319,C68,C76,FL14,FL15," & _
"FL16,FL17,FL18,FL6,J1,J2,J3,J4,J5,J6,L2,L3,L4,L5,T4,T5,T6,U38"
arr = Split(Lne, ",") 'split to an array
allPrefixes = UniquePrefixes(arr) 'All unique character prefixes
Debug.Print "All prefixes: " & Join(allPrefixes, ",")
'process each prefix in turn
For Each prefix In allPrefixes
arrFilt = FilterPrefixNumbers(arr, prefix) 'items for this prefix (numbers only)
Debug.Print , "'" & prefix & "' items:", Join(arrFilt, ",")
arrSorted = ArraySort(arrFilt) 'numeric parts, sorted ascending
Debug.Print , "Sorted:", Join(arrSorted, ",")
s = s & iif(s<>"", ",", "") & FormatAsRanges(arrSorted, prefix)
'Debug.Print FormatAsRanges(arrSorted, prefix)
Next prefix
Debug.Print s 'the whole thing
End Sub
Function FormatAsRanges(arr, prefix) As String
Dim s As String, n As Long, v As Long, prev As Long, inRange As Boolean
prev = -999 'dummy value
For n = LBound(arr) To UBound(arr)
v = CLng(arr(n))
If v - prev = 1 Then 'starting or continuing a range?
inRange = True 'wait until range ends before adding anything
Else
If inRange Then 'ending a range ?
s = s & "-" & prefix & prev 'close out current range with previous item
inRange = False
End If
s = s & IIf(Len(s) > 0, ",", "") & prefix & v 'add the current item
End If
prev = v
Next n
If inRange Then s = s & "-" & prefix & prev 'close out last item if in a range
FormatAsRanges = s
End Function
Function ArraySort(MyArray As Variant)
Dim First As Long, last As Long
Dim i As Long, j As Long, Temp
First = LBound(MyArray)
last = UBound(MyArray)
For i = First To last - 1
For j = i + 1 To last
If CLng(MyArray(i)) > CLng(MyArray(j)) Then
Temp = MyArray(j)
MyArray(j) = MyArray(i)
MyArray(i) = Temp
End If
Next j
Next i
ArraySort = MyArray
End Function
'return an array *of numbers* from all items in "arr" with the given prefix
Function FilterPrefixNumbers(arr, prefix)
Dim rv(), e, n As Long
ReDim rv(LBound(arr) To UBound(arr))
n = LBound(arr)
For Each e In arr
If GetPrefix(CStr(e)) = prefix Then
rv(n) = Replace(e, prefix, "") 'return just the numeric parts...
n = n + 1
End If
Next e
ReDim Preserve rv(LBound(arr) To n - 1) 'shrink to remove any empty slots
FilterPrefixNumbers = rv
End Function
'all unique character prefixes
Function UniquePrefixes(arr)
Dim dict, e
Set dict = CreateObject("scripting.dictionary")
For Each e In arr
dict(GetPrefix(CStr(e))) = True
Next e
UniquePrefixes = dict.keys
End Function
'get the character prefix (all non-digit characters preceding the first digit)
Function GetPrefix(v As String) As String
Dim rv As String, c As String, i As Long
For i = 1 To Len(v)
c = Mid(v, i, 1)
If c Like "#" Then
Exit For
Else
rv = rv & c
End If
Next i
GetPrefix = rv
End Function
Eliminate Filter() and Replace() functions. Given that input data is already sorted alphabetically by prefix, following revised procedure works:
Sub FormatAsRanges()
Dim Lne As String, arr, s
Dim n As Long, v As Long, prev As Long
Dim inRange As Boolean
Dim j As Integer, i As Integer
Dim x As Variant
Dim filterarray As Variant
Lne = "AR15,AR2,AR3,AR4,AT3,AT4,C316,C319,C68,C76,FL14,FL15,FL16,FL17,FL18,FL6,J1,J2,J3,J4,J5,J6,L2,L3,L4,L5,T4,T5,T6,U38"
arr = Split(Lne, ",") 'Break apart references into array items
x = Split(Prefix(arr), ",") 'Get the Prefix's (AR,AT,C,FL,J,L,T,U)
For j = 0 To UBound(x)
inRange = False 'Initialize to False
Do While arr(i) Like x(j) & "*" And i <= UBound(arr)
If arr(i) Like x(j) & "*" Then
s = s & Mid(arr(i), Len(x(j)) + 1) & ","
If i = UBound(arr) Then
Exit Do
Else
i = i + 1
End If
End If
Loop
If Right(s, 1) = "," Then s = Left(s, Len(s) - 1)
filterarray = ArraySort(Split(s, ","))
prev = -999 'dummy value
s = ""
For n = LBound(filterarray) To UBound(filterarray)
v = CLng(filterarray(n))
If v - prev = 1 Then 'starting or continuing a range?
inRange = True 'wait until range ends before adding anything
Else
If inRange Then 'ending a range ?
s = s & "-" & x(j) & prev 'close out current range with previous item
inRange = False
End If
s = s & IIf(Len(s) > 0, ",", "") & x(j) & v 'add the current item
End If
prev = v
Next n
If inRange Then s = s & "-" & x(j) & prev 'close out last item if in a range
Debug.Print s
s = Empty
filterarray = Empty
Next j
End Sub
I have this string:
RugbyFunny RugbyGirls RugbyBoys RugbyWomens Rugby
Basically, I would like to split the words by capital letters and put a delimiter like ;.
I have found a useful VBA function that does part of the job:
Function splitbycaps(inputstr As String) As String
Dim i As Long
Dim temp As String
If inputstr = vbNullString Then
splitbycaps = temp
Exit Function
Else
temp = inputstr
For i = 1 To Len(temp)
If Mid(temp, i, 1) = UCase(Mid(temp, i, 1)) Then
If i <> 1 Then
temp = Left(temp, i - 1) + " " + Right(temp, Len(temp) - i + 1)
i = i + 1
End If
End If
Next i
splitbycaps = temp
End If
End Function
How can I put a delimiter between each word? I would like to produce this outcome:
Rugby;Funny Rugby;Girls Rugby;Boys Rugby;Womens Rugby;
Many thanks in advance for your help!
Change the function to this:
Function SplitByCaps(InputStr As String) As String
Dim i As Long
Dim temp As String
If InputStr = vbNullString Then
SplitByCaps = temp
Exit Function
Else
temp = InputStr
Do While i < Len(temp)
i = i + 1
If Mid(temp, i, 1) <> LCase(Mid(temp, i, 1)) Then
If i <> 1 Then
If Mid(temp, i - 1, 1) <> " " Then
temp = Left(temp, i - 1) & ";" & Right(temp, Len(temp) - i + 1)
i = i + 1
End If
End If
End If
DoEvents
Loop
SplitByCaps = temp
End If
End Function
Edit: Changed it to a Do loop the For counted incorrectly as #Vityata pointed out.
Public Sub Test()
Dim str As String
str = "RugbyFunny RugbyGirls RugbyBoys RugbyWomens Rugby"
Debug.Print SplitByCaps(str)
'Rugby;Funny Rugby;Girls Rugby;Boys Rugby;Womens Rugby
End Sub
Compare based on ASCII values
For i = 1 To Len(TEMP)
If i <> 1 Then
If Asc(Mid(TEMP, i, 1)) >= 65 And Asc(Mid(TEMP, i, 1)) <= 90 Then
TEMP = Left(TEMP, i - 1) + ";" + Right(TEMP, Len(TEMP) - i + 1)
i = i + 1
End If
End If
Next i
First you need to find all the positions of the string, where:
The char is an UpperCase
The char is actually a letter
There is no space before the char
Then, these positions can be saved in a collection. This is a function, finding the next uppercase position, returning -1, if there is not such:
Public Function NextUpperCasePosition(str As String, marker As Long) As Long
Dim i As Long
Dim isUpper As Boolean
Dim isLetter As Boolean
Dim noSpaceBefore As Boolean
If marker = 1 Then
NextUpperCasePosition = 1
Exit Function
End If
For i = marker To Len(str)
noSpaceBefore = CBool(Len(Trim(Mid(str, i - 1, 1))) > 0)
isUpper = CBool(Mid(str, i, 1) = UCase(Mid(str, i, 1)))
isLetter = CBool(LCase(Mid(str, i, 1)) <> UCase(Mid(str, i, 1)))
If isUpper And isLetter And noSpaceBefore Then
NextUpperCasePosition = i
Exit Function
End If
Next i
NextUpperCasePosition = -1
End Function
Once you are able to find the positions and add them to a collection of positions, you can loop through the collection and split the string to an array, based on these numbers. Once the array is ready, the Join(arr, "; ") works quite ok to produce the needed string:
Public Sub SplitByUpperCase()
Dim str As String
str = "KRugbyFunny RugbyGirls RugbyBoys RugbyWomens Rugby K TB"
Dim i As Long
Dim result As New Collection
Dim nextPosition As Long: nextPosition = 1
For i = 1 To Len(str) Step 1
If i = nextPosition Then
nextPosition = NextUpperCasePosition(str, nextPosition)
If nextPosition >= 1 Then result.Add (nextPosition)
nextPosition = nextPosition + 1
End If
Next i
Dim resultArr As Variant
ReDim resultArr(result.Count - 1)
Dim lenOfWord As Long
For i = 1 To result.Count
If i = result.Count Then
lenOfWord = Len(str) - result(i) + 1
Else
lenOfWord = result(i + 1) - result(i)
End If
resultArr(i - 1) = Mid(str, result(i), lenOfWord)
Next i
Debug.Print Join(resultArr, "; ")
End Sub
Given an array of numbers:
[1,3,4,5,8,9,11]
What's the simplest way in VBA to convert that list to a readable string, e.g:
1, 3-5, 8-9, 11
I could just rewrite my VB.net function to VBA but it's already quite long winded and it will end up even longer in VBA.
Public Shared Function GroupedNumbers(nums As List(Of Long))
If nums Is Nothing OrElse nums.Count = 0 Then Return "-"
If nums.Count = 1 Then Return nums(0)
Dim lNums = nums.Distinct().OrderBy(Function(m) m).ToList
Dim curPos As Long = 1
Dim lastNum As Long = lNums(0)
Dim i As Long = 0
Dim numStr As String = lNums(0)
Dim isGap As Boolean = False
Do Until i >= lNums.Count - 1
Do Until i >= lNums.Count - 1 OrElse lNums(i) + 1 <> lNums(i + 1)
i += 1
isGap = True
Loop
If isGap Then
numStr += "-" & lNums(i)
End If
If i <> lNums.Count - 1 Then
numStr += ", " & lNums(i + 1)
isGap = False
i += 1
End If
Loop
Return numStr
End Function
Just wondering if anyone has a better way of doing this before i go about rewriting it for VBA?
If you want a simple method, you might use something like the following:
Function GroupedNumbers(nums() As Long) As String
SortMe (nums) 'No built-in sort method in VBA,
'so you need to implement one yourself (see links below).
Dim numStr As String
numStr = nums(0)
For i = 1 To UBound(nums)
If nums(i) = nums(i - 1) + 1 Then
numStr = numStr & IIf(nums(i) + 1 = nums(i + 1), "", "-" & nums(i))
Else
numStr = numStr & ", " & nums(i)
End If
Next i
GroupedNumbers = numStr
End Function
For array sorting you might refer to this question.
And if you want something more simple, check this answer which use the .NET version of ArrayList for sorting. Hence you would need to adapt the above function to work with ArrayList instead of Array.
Hope that helps :)
Well i took the long route:
Public Sub SortCollection(ByRef c As Collection)
Dim tmp
For i = 1 To c.Count - 1
For j = i + 1 To c.Count
If c(i) > c(j) Then
vTemp = c(j)
c.Remove j
c.Add tmp, tmp, i
End If
Next j
Next i
End Sub
Public Function NumberListGrouped(cells As Range) As String
If cells.Count = 0 Then
AnimalIdListGrouped = "-"
ElseIf cells.Count = 1 Then
AnimalIdListGrouped = cells(1, 1)
End If
Dim c As New Collection
On Error Resume Next
For Each cell In cells
c.Add CInt(cell.Value), CStr(cell.Value)
Next cell
SortCollection c
On Error GoTo 0
Dim i As Long: i = 1
Dim numStr As String: numStr = c(1)
Dim isGap As Boolean: isGap = False
Do Until i >= c.Count
DoEvents
Do Until i >= c.Count Or c(i) + 1 <> c(i + 1)
i = i + 1
isGap = True
DoEvents
Loop
If isGap Then
numStr = numStr & "-" & c(i)
End If
If i <> c.Count Then
numStr = numStr & ", " & c(i + 1)
isGap = False
i = i + 1
End If
Loop
NumberListGrouped = numStr
End Function
Should you ever use VBA in Excel, you could have it do the work for you as follows
Function GroupedNumbers(nums() As Long) As String
Dim strng As String
Dim i As Long
For i = LBound(nums) To UBound(nums) - 1
strng = strng & CStr(nums(i)) & ",A"
Next i
strng = "A" & strng & CStr(nums(i))
GroupedNumbers = Replace(Replace(Replace(Intersect(Columns(1), Range(strng)).Address(False, False), ",A", ", "), "A", ""), ":", "-")
End Function
I'm looking for code in VBA to generate all subsets of the items in a passed array.
Below is simple code to select all N choose 2 subsets of array size N.
Looking to augment this for N choose (N-1)... all the way down to N choose 1.
Option Base 1
Sub nchoose2()
iarray = Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
n = UBound(iarray)
x = 1
t = 0
r = 0
Do While (n - x) >= 1
For i = 1 To (n - x)
Cells((i + t), 1) = iarray(x)
Cells((i + t), 2) = iarray(i + x)
Next i
x = x + 1
t = t + (n - (1 + r))
r = r + 1
Loop
End Sub
In addition to the Gray-code algorithm, you can also exploit the correspondence between subsets of an n-element set and binary vectors of length n. The following code illustrates this approach:
Sub AddOne(binaryVector As Variant)
'adds one to an array consisting of 0s and 1s
'thought of as a binary number in little-endian
'the vector is modified in place
'all 1's wraps around to all 0's
Dim bit As Long, carry As Long, i As Long, n As Long
carry = 1
n = UBound(binaryVector)
i = LBound(binaryVector)
Do While carry = 1 And i <= n
bit = (binaryVector(i) + carry) Mod 2
binaryVector(i) = bit
i = i + 1
carry = IIf(bit = 0, 1, 0)
Loop
End Sub
Function listSubsets(items As Variant) As Variant
'returns a variant array of collections
Dim lb As Long, ub As Long, i As Long, j As Long, numSets As Long
Dim vect As Variant 'binary vector
Dim subsets As Variant
lb = LBound(items)
ub = UBound(items)
ReDim vect(lb To ub)
numSets = 2 ^ (1 + ub - lb)
ReDim subsets(1 To numSets)
For i = 1 To numSets
Set subsets(i) = New Collection
For j = lb To ub
If vect(j) = 1 Then subsets(i).Add items(j)
Next j
AddOne vect
Next i
listSubsets = subsets
End Function
Function showCollection(c As Variant) As String
Dim v As Variant
Dim i As Long, n As Long
n = c.Count
If n = 0 Then
showCollection = "{}"
Exit Function
End If
ReDim v(1 To n)
For i = 1 To n
v(i) = c(i)
Next i
showCollection = "{" & Join(v, ", ") & "}"
End Function
Sub test()
Dim stooges As Variant
Dim stoogeSets As Variant
Dim i As Long
stooges = Array("Larry", "Curly", "Moe")
stoogeSets = listSubsets(stooges)
For i = LBound(stoogeSets) To UBound(stoogeSets)
Debug.Print showCollection(stoogeSets(i))
Next i
End Sub
Running the code results in the following output:
{}
{Larry}
{Curly}
{Larry, Curly}
{Moe}
{Larry, Moe}
{Curly, Moe}
{Larry, Curly, Moe}
I asked a similar question a while back (2005) and received this excellent code from John Coleman:
Sub MAIN()
Dim i As Long, st As String
Dim a(1 To 12) As Integer
Dim ary
For i = 1 To 12
a(i) = i
Next i
st = ListSubsets(a)
ary = Split(st, vbCrLf)
For i = LBound(ary) To UBound(ary)
Cells(i + 1, 1) = ary(i)
Next i
End Sub
Function ListSubsets(Items As Variant) As String
Dim CodeVector() As Integer
Dim i As Integer
Dim lower As Integer, upper As Integer
Dim SubList As String
Dim NewSub As String
Dim done As Boolean
Dim OddStep As Boolean
OddStep = True
lower = LBound(Items)
upper = UBound(Items)
ReDim CodeVector(lower To upper) 'it starts all 0
Do Until done
'Add a new subset according to current contents
'of CodeVector
NewSub = ""
For i = lower To upper
If CodeVector(i) = 1 Then
If NewSub = "" Then
NewSub = Items(i)
Else
NewSub = NewSub & ", " & Items(i)
End If
End If
Next i
If NewSub = "" Then NewSub = "{}" 'empty set
SubList = SubList & vbCrLf & NewSub
'now update code vector
If OddStep Then
'just flip first bit
CodeVector(lower) = 1 - CodeVector(lower)
Else
'first locate first 1
i = lower
Do While CodeVector(i) <> 1
i = i + 1
Loop
'done if i = upper:
If i = upper Then
done = True
Else
'if not done then flip the *next* bit:
i = i + 1
CodeVector(i) = 1 - CodeVector(i)
End If
End If
OddStep = Not OddStep 'toggles between even and odd steps
Loop
ListSubsets = SubList
End Function
The original question and answer:
John Coleman
For example, I have this string that reads "IRS150Sup2500Vup". It could also be "IRS250Sdown1250Vdown".
I am looking to extract the number between the two S. Hence for the first case, it will be 150 and second case, it is 250. The numbers are not always 3 digits. It could vary.
What I have tried:
Dim pos As Integer
Dim pos1 As Integer
pos = InStr("IRS150Sup2500Vup", "S")
pos1 = InStrRev("IRS250Sdown1250Vdown","S")
After this, I am stuck how to get the number out.
Need some guidance on how to do this.
As i suggested here, the simplest way is to use Regex.
Sub Test()
Dim r As VBScript_RegExp_55.RegExp
Dim sPattern As String, myString As String
Dim mc As VBScript_RegExp_55.MatchCollection, m As VBScript_RegExp_55.Match
myString = "IRS150Sup2500Vup"
sPattern = "\d+" 'searches for numbers
Set r = New VBScript_RegExp_55.RegExp
r.Pattern = sPattern
Set mc = r.Execute(myString)
For Each m In mc ' Iterate Matches collection.
MsgBox "number: '" & m.Value & "' founded at: " & m.FirstIndex & " length: " & m.Length
Next
End Sub
Here is an option:
Public Sub Test4()
Dim pos As Integer
Dim pos1 As Integer
Dim strOrig As String
Dim strString As String
strOrig = "IRS150Sup2500Vup"
pos = InStr(1, strOrig, "S") + 1
pos1 = InStr(pos, strOrig, "S")
strString = Mid(strOrig, pos, pos1 - pos)
MsgBox strString
End Sub
Try using this function:
pos = Mid("IRS150Sup2500Vup", 4, 6)