Split Uppercase words in Excel and insert delimiters - vba

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

Related

How do I reverse individual words in a string in VBA?

I've managed to reverse the string as a whole using the code below:
Dim str As Variant
Dim i As Integer, strLen As Integer
Dim NewStr As String
str = "VBA is amazing"
strLen = Len(str)
NewStr = ""
For i = 1 To strLen
NewStr = NewStr & Mid(str, strLen - (i - 1), 1)
Next i
MsgBox (NewStr)
The outcome is: gnizama si ABV, but I want it to be: ABV si gnizama
Goal is not to use Split or StrReverse.
Goal is not to use Split or StrReverse.
But those are what to use:
Dim Straight As String
Dim Reversed As String
Dim Index As Long
Dim Words() As String
Straight = "VBA is amazing"
Words = Split(Straight)
For Index = LBound(Words) To UBound(Words)
Words(Index) = StrReverse(Words(Index))
Next
Reversed = Join(Words)
Debug.Print Reversed
' ABV si gnizama
If not, you must either roll your own substitutes or run a double loop indentifying spaces and, between these, collect and reverse the words. Slow, but doable.
Solution without Split or StrReverse:
Sub RevWords(Optional str As String = "VBA is amazing")
' RegExp: need to switch on «Microsoft VBScript Regular Expression 5.5» in menu «Tools/References»
Dim RegEx As New RegExp
Dim res As Object, revStr As String, i As Integer, s As Variant
With RegEx
.Global = True
.Pattern = "([^ ]+)"
Set res = .Execute(str)
End With
For Each s In res
e = Len(s)
Do While e > 0
revStr = revStr & Mid(s, e, 1)
e = e - 1
Loop
revStr = revStr & " "
Next
Debug.Print str & " >> " & Trim(revStr)
End Sub
' output: VBA is amazing >> ABV si gnizama
Try this:
Function CustomReverse(inputString As String) As String
Dim wordSeparator As String, reversedString As String
Dim i As Integer, j As Integer, k As Integer, vector As Integer
reversedString = inputString
wordSeparator = " "
i = 0
j = 1
Do
i = InStr(i + 1, inputString, wordSeparator, vbTextCompare)
If i = 0 Then i = Len(inputString) + 1
For k = j To i - 1
If Mid(reversedString, k, 1) = wordSeparator Then Exit For
vector = i - k - 1
Mid(reversedString, k, 1) = Mid(inputString, j + vector, 1)
Next
j = i + 1
Loop While i > 0 And j < Len(inputString)
CustomReverse = reversedString
End Function
Usage:
Sub Test()
Debug.Print CustomReverse("VBA is amazing")
'prints: "ABV si gnizama"
End Sub
Not sure why you don't want to use Split and StrReverse. The implementation is easy:
Sub Test()
Dim s As String
Dim words() As String
Dim i As Long
Dim word As Variant
s = "VBA is amazing"
words = Split(s, " ")
i = LBound(words, 1)
For Each word In words
words(i) = StrReverse(word)
i = i + 1
Next word
MsgBox Join(words, " ")
End Sub
If, for whatever reason, you cannot use Split or StrReverse then you need something like this:
Sub Test2()
Const SPACE As String = " "
Dim lastSpace As Long
Dim nextSpace As Long
Dim s As String
Dim word As String
Dim newStr As String
s = "VBA is amazing"
nextSpace = InStr(1, s, SPACE)
If nextSpace = 0 Then
newStr = ReverseWord(s)
Else
Do While nextSpace > 0
word = Mid$(s, lastSpace + 1, nextSpace - lastSpace - 1)
newStr = newStr & ReverseWord(word) & SPACE
lastSpace = nextSpace
nextSpace = InStr(nextSpace + 1, s, SPACE)
Loop
word = Right$(s, Len(s) - lastSpace)
newStr = newStr & ReverseWord(word)
End If
MsgBox newStr
End Sub
Private Function ReverseWord(ByVal word As String) As String
Dim i As Long
Dim res As String
For i = Len(word) To 1 Step -1
res = res & Mid$(word, i, 1)
Next i
ReverseWord = res
End Function
EDIT #1
The ReverseWord method can be optimized:
Private Function ReverseWord(ByVal word As String) As String
Dim i1 As Long: i1 = 1
Dim i2 As Long: i2 = Len(word)
Dim c As String
Do While i1 < i2
c = Mid$(word, i1, 1)
Mid$(word, i1, 1) = Mid$(word, i2, 1)
Mid$(word, i2, 1) = c
i1 = i1 + 1
i2 = i2 - 1
Loop
ReverseWord = word
End Function
other solution from me
Sub test()
Dim str As Variant
Dim i As Integer, strLen As Integer, iSp As Integer, n As Integer, f As Integer
Dim NewStr As String
Dim wordPart() As String
f = 0
n = 1
str = "VBA is amazing"
strLen = Len(str)
NewStr = ""
iSp = spacesCounter(str) + 1
ReDim wordPart(iSp)
For i = 1 To strLen
If Mid(str, i, 1) = " " Then
If n = 1 Then
wordPart(n) = Mid(str, 1, i - 1)
f = i + 1
n = n + 1
Else
wordPart(n) = Mid(str, f, (i) - f)
f = i + 1
n = n + 1
End If
ElseIf i = strLen Then
wordPart(n) = Mid(str, f)
End If
Next i
For i = 1 To iSp
MsgBox (wordPart(i))
For j = 1 To Len(wordPart(i))
If i <> 1 And j = 1 Then NewStr = NewStr & " "
NewStr = NewStr & Mid(wordPart(i), Len(wordPart(i)) - (j - 1), 1)
Next j
Next i
MsgBox (NewStr)
End Sub
Try,
Sub test()
Dim strL As Variant
Dim i As Integer, strLen As Integer
Dim NewStr As String
Dim s As String, s1 As String
strL = "VBA is amazing"
strLen = Len(strL)
NewStr = ""
For i = 1 To strLen
s = Mid(strL, i, 1)
If s = " " Then
NewStr = NewStr & s1 & s
s1 = ""
Else
s1 = s & s1
End If
Next i
NewStr = NewStr & s1
MsgBox (NewStr)
End Sub

IF cells begin with AND Create random number

I want to make a sub, which determines if the cells in the 12th column starts with 262015. If it does start with this, it should create a new random 8-digit number starting with "18" and then 6 randomly created unique digits.
My code does not seem to figure out if the cell starts with 262015, and I have not been able to find help on creating the 8-digit number with these requirements.
Hope you can help me!
Sub Opgave8()
For i = 2 To 18288
If Left(Worksheets("arab").Cells(i, 12), 6) = "262015" Then
Worksheets("arab").Cells(i, 3) = "18" & studyid(6)
End If
Next i
Function UniqueRandDigits(x As Long) As String
Dim i As Long
Dim n As Integer
Dim s As String
Do
n = Int(Rnd() * 10)
If InStr(s, n) = 0 Then
s = s & n
i = i + 1
End If
Loop Until i = x + 1
UniqueRandDigits = s
End Function
End Sub
For i = 2 To 18288
If Left(Worksheets("Base").Cells(i, 12), 6) = "262015" Then
Worksheets("Base").Cells(i, 3) = "18" & Randdigits(6)
End If
Next i
Function RandDigits(x As Long) As String
Dim i As Long
Dim s As String
For i = 1 To x
s = s & Int(Rnd() * 10)
Next i
RandDigits = s
End Function
EDIT: here's one where all digits are different
Function UniqueRandDigits(x As Long) As String
Dim i As Long
Dim n As Integer
Dim s As String
Do
n = Int(Rnd() * 10)
If InStr(s, n) = 0 Then
s = s & n
i = i + 1
End If
Loop Until i = x + 1
UniqueRandDigits = s
End Function
EDIT2: And here is one that forces all numbers to be different
dim n as string
dim ok as boolean
For i = 2 To 18288
If Left(Worksheets("Base").Cells(i, 12), 6) = "262015" Then
ok = false
do
n = UniqueRandDigits(6)
If Application.WorksheetFunction.CountIf(Worksheets("Base").Range("L2:L18288"), n) = 0 Then
Worksheets("Base").Cells(i, 3) = "18" & n
ok = true
end if
loop until ok
End If
Next i
Using Left function, you need to specify the String, then the number of characters from the left, and then you check if it's equal to "262015".
Try the code below:
For i = 2 To 18288
If Left(Worksheets("Base").Cells(i, 12), 6) = "262015" Then
Worksheets("Base").Cells(i, 3) = "XXX"
End If
Next i

Simplest way to convert a number list to a readable string in VBA

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

Extracting multiple numbers from single string cell in order to look results

I am working on small project. I have encountered a problem that I am not able to bypass. Any help would be highly appreciated.
I have the following sheets:
Sheet1
Sheet2
I need a function that extracts those 3 figures from Sheet1 (there can be more or less than 3), they are always limited by "()" and look for values in Sheet2 based on figures in column A1.
I was able to write the following code (with help of this question) for extracting figures, but I do not know how to isolate figures from single cell and look based on it in sheet2:
Edit:
I thought I will manage with the rest, but I was wrong. I would appreciate additional help to expand the code to return column B from Sheet2. Generally, logic is that function splits cell from sheet1 and then each item is looked in Sheet2. The final result of this function would be:
Test1
Test2
Test3
I have updated the code with what I tried myself.
Function onlyDigits(s As String) As String
Dim retval As String
Dim i,j As Integer
Dim TestRng as Range
Dim NoArr() as String
Dim TestRes() as String
retval = ""
s = Replace(s, ")", " ")
For i = 1 To Len(s)
If Mid(s, i, 1) >= "0" And Mid(s, i, 1) <= "9" Or Mid(s, i, 1) = " " Then
retval = retval + Mid(s, i, 1)
End If
Next
'deletes last unnecessary space
retval = Left(retval, Len(retval) - 1)
'array with results after extracting numbers
NoArr() = Split(retval, " ", , vbTextCompare)
'vlookedup range
set TestRng = Worksheets("Sheet2").Range("A1:B3")
For j = LBound(NoArr) To UBound(NoArr)
TestRes(j) = Application.WorksheetFunction.VLookup(NoArr(j), TestRng, 2, 0)
Next j
onlyDigits = TestRes
End Function
Keeping with your current method, I modified your function to return the value you need by passing in a place holder. I modified the first and second to last lines.
Function onlyDigits(s As String, pos As Integer) As String
Dim retval As String
Dim i As Integer
retval = ""
s = Replace(s, ")", " ")
For i = 1 To Len(s)
If Mid(s, i, 1) >= "0" And Mid(s, i, 1) <= "9" Or Mid(s, i, 1) = " " Then
retval = retval + Mid(s, i, 1)
End If
Next
'deletes last unnecessary space
retval = Left(retval, Len(retval) - 1)
onlyDigits = Split(retval, " ", , vbTextCompare)(pos)
End Function
To call in cell write: =onlyDigits(A1,0) the zero is the position to return
Example
Column E shows the equation used in column D
ok I solved my problem with following code:
F Function onlyDigits(s As String) As String
Dim retval As String
Dim i, j As Integer
Dim TestRng As Range
Dim NoArr() As String
Dim TestRes() As String
retval = ""
s = Replace(s, ")", " ")
For i = 1 To Len(s)
If Mid(s, i, 1) >= "0" And Mid(s, i, 1) <= "9" Or Mid(s, i, 1) = " " Then
retval = retval + Mid(s, i, 1)
End If
Next
'deletes last unnecessary space
retval = Left(retval, Len(retval) - 1)
'array with results after extracting numbers
NoArr() = Split(retval, " ", , vbTextCompare)
'vlookedup range
Set TestRng = Worksheets("Sheet2").Range("A1:B3")
For j = LBound(NoArr) To UBound(NoArr)
ReDim Preserve TestRes(j)
TestRes(j) = Application.WorksheetFunction.VLookup(CLng(NoArr(j)), TestRng, 2, False)
Next j
onlyDigits = Join(TestRes, vbNewLine)
End Function

Generate List of All 2^n subsets

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