VBA, TRIM part of a Path - excel

Lets say I have a path : stack/overflow/question/help/please .
And end result is : help/please.
Does anyone have a code where I can state how many "/" I want to parse.
its similar to text to columns but I would like to keep it in one cell.
Thanks

You could write a function something like this:
Function RightPart(s As String, d As String, n As Long) As String
Dim A As Variant
Dim i As Long, ub As Long
Dim t As String
A = Split(s, d)
ub = UBound(A)
If n >= ub Then
RightPart = s
Exit Function
End If
For i = ub - n + 1 To ub
t = t & A(i) & IIf(i < ub, d, "")
Next i
RightPart = t
End Function
Then RightPart(":stack/overflow/question/help/please","/",2) evaluates to "help/please"

you could use this code (does a bit more but should be fine):
Public Function custDelim(ByVal str As String, ByVal delim As String, ByVal num As Long) As String
Dim holder As Variant
holder = Split(str, delim)
If num = 0 Then
custDelim = ""
ElseIf num > 0 Then
If num <= UBound(holder) Then
holder = Split(str, delim, UBound(holder) - num + 2)
custDelim = holder(UBound(holder))
Else
custDelim = str
End If
ElseIf num < 0 Then
If Abs(num) <= UBound(holder) Then
ReDim Preserve holder(Abs(num) - 1)
custDelim = Join(holder, delim)
Else
custDelim = str
End If
End If
End Function
=custDelim("very-long-string-in-here","-",2) would output "in-here" while using -2 would print "very-long".
If you still have questions, just ask :)

Option 1: excel-vba
I prefer using the Split function into a variant array when dealing with multiple parts of a string.
Function trim_part_of_a_path(str As String, _
Optional keep As Integer = 1, _
Optional delim As String = "/")
Dim a As Long, tmp As Variant
tmp = Split(str, delim)
If UBound(tmp) < keep Then
trim_part_of_a_path = str
Else
trim_part_of_a_path = tmp(UBound(tmp) - keep)
For a = UBound(tmp) - keep + 1 To UBound(tmp)
trim_part_of_a_path = _
trim_part_of_a_path & delim & tmp(a)
Next a
End If
End Function
You will likely want to change the defults for the optional parameters to whatever you use most commonly.
Syntax:    =trim_part_of_a_path(<original string> , [optional number to retain], [optional delimiter])
Examples:    =trim_part_of_a_path(A2)                    =trim_part_of_a_path(A2, C2, B2)                    =trim_part_of_a_path(A2, 1, "/")
Option 2: excel-formula
The SUBSTITUTE function has an optional [instance_num] parameter which allows you to change one occurrence of a repeated character to something unique which can be located in subsequent function calculation.
A pair of LEN functions with another SUBSTITUTE returns the total number of occurances of a character.
The MID function can use the FIND function to identify the portion of the original text to return from a modified string produced by the functions discussed above.
IFERROR function can return the original string if the parameters are out of bounds.
'return a portion of string while retaining x number of delimiters
=IFERROR(MID(A2, FIND(CHAR(167), SUBSTITUTE(A2, B2, CHAR(167), LEN(A2)-LEN(SUBSTITUTE(A2,B2,""))-C2))+1, LEN(A2)), A2)
A formula based solution probably works best when the parameters can be put into cells that the formula references.
   

Related

Remove alphanumeric chars in front of a defined char

I have a string in a cell composed of several shorter strings of various lengths with blank spaces and commas in between. In some cases only one or more blanks are in between.
I want to remove every blank space and comma and only leave behind 1 comma between each string element. The result must look like this:
The following doesn't work. I'm not getting an error but the strings are truncated at the wrong places. I don't understand why.
Sub String_adaption()
Dim i, j, k, m As Long
Dim STR_A As String
STR_A = "01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
i = 1
With Worksheets("table")
For m = 1 To Len(.Range("H" & i))
j = 1
Do While Mid(.Range("H" & i), m, 1) = "," And Mid(.Range("H" & i), m - 1, 1) <> Mid(STR_A, j, 1) And m <> Len(.Range("H" & i))
.Range("H" & i) = Mid(.Range("H" & i), 1, m - 2) & Mid(.Range("H" & i), m, Len(.Range("H" & i)))
j = j + 1
Loop
Next m
End With
End Sub
I'd use a regular expression to replace any combination of spaces and comma's. Something along these lines:
Sub Test()
Dim str As String: str = "STRING_22 ,,,,,STRING_1 , , ,,,,,STRING_333 STRING_22 STRING_4444"
Debug.Print RegexReplace(str, "[\s,]+", ",")
End Sub
Function RegexReplace(x_in, pat, repl) As String
With CreateObject("vbscript.regexp")
.Global = True
.Pattern = pat
RegexReplace = .Replace(x_in, repl)
End With
End Function
Just for the sake of alternatives:
Formula in B1:
=TEXTJOIN(",",,TEXTSPLIT(A1,{" ",","}))
The following function will split the input string into pieces (words), using a comma as separator. When the input string has multiple commas, it will result in empty words.
After splitting, the function loops over all words, trims them (remove leading and trailing blanks) and glue them together. Empty words will be skipped.
I have implemented it as Function, you could use it as UDF: If your input string is in B2, write =String_adaption(B2) as Formula into any cell.
Function String_adaption(s As String) As String
' Remove duplicate Commas and Leading and Trailing Blanks from words
Dim words() As String, i As Long
words = Split(s, ",")
For i = 0 To UBound(words)
Dim word As String
word = Trim(words(i))
If word <> "" Then
String_adaption = String_adaption & IIf(String_adaption = "", "", ",") & word
End If
Next i
End Function
P.S.: Almost sure that this could be done with some magic regular expressions, but I'm not an expert in that.
If you have recent Excel version, you can use simple worksheet function to split the string on space and on comma; then put it back together using the comma deliminater and ignoring the blanks (and I just noted #JvdV had previously posted the same formula solution):
=TEXTJOIN(",",TRUE,TEXTSPLIT(A1,{" ",","}))
In VBA, you can use a similar algorithm, using the ArrayList object to collect the non-blank results.
Option Explicit
Function commaOnly(s As String) As String
Dim v, w, x, y
Dim al As Object
Set al = CreateObject("System.Collections.ArrayList")
v = Split(s, " ")
For Each w In v
x = Split(w, ",")
For Each y In x
If y <> "" Then al.Add y
Next y
Next w
commaOnly = Join(al.toarray, ",")
End Function
This preserves the spaces within the smaller strings.
Option Explicit
Sub demo()
Const s = "STRING 22,,,, ,,STRING 1,,,, ,,STRING 333 , , , STRING_22 STRING_44"
Debug.Print Cleanup(s)
End Sub
Function Cleanup(s As String) As String
Const SEP = ","
Dim regex, m, sOut As String, i As Long, ar()
Set regex = CreateObject("vbscript.regexp")
With regex
.Global = True
.MultiLine = False
.IgnoreCase = True
.Pattern = "([^,]+)(?:[ ,]*)"
End With
If regex.Test(s) Then
Set m = regex.Execute(s)
ReDim ar(0 To m.Count - 1)
For i = 0 To UBound(ar)
ar(i) = Trim(m(i).submatches(0))
Next
End If
Cleanup = Join(ar, SEP)
End Function
Code categories approach
For the sake of completeness and to show also other ways "leading to Rome", I want to demonstrate an approach allowing to group the string input into five code categories in order to extract alphanumerics by a tricky match (see [B] Function getCats()):
To meet the requirements in OP use the following steps:
1) remove comma separated tokens if empty or only blanks (optional),
2) group characters into code categories,
3) check catCodes returning alpha nums including even accented or diacritic letters as well as characters like [ -,.+_]
Function AlphaNum(ByVal s As String, _
Optional IgnoreEmpty As Boolean = True, _
Optional info As Boolean = False) As String
'Site: https://stackoverflow.com/questions/15723672/how-to-remove-all-non-alphanumeric-characters-from-a-string-except-period-and-sp/74679416#74679416
'Auth.: https://stackoverflow.com/users/6460297/t-m
'Date: 2023-01-12
'1) remove comma separated tokens if empty or only blanks (s passed as byRef argument)
If IgnoreEmpty Then RemoveEmpty s ' << [A] RemoveEmpty
'2) group characters into code categories
Dim catCodes: catCodes = getCats(s, info) ' << [B] getCats()
'3) check catCodes and return alpha nums plus chars like [ -,.+_]
Dim i As Long, ii As Long
For i = 1 To UBound(catCodes)
' get current character
Dim curr As String: curr = Mid$(s, i, 1)
Dim okay As Boolean: okay = False
Select Case catCodes(i)
' AlphaNum: cat.4=digits, cat.5=alpha letters
Case Is >= 4: okay = True
' Category 2: allow only space, comma, minus
Case 2: If InStr(" -,", curr) <> 0 Then okay = True
' Category 3: allow only point, plus, underline
Case 3: If InStr(".+_", curr) <> 0 Then okay = True
End Select
If okay Then ii = ii + 1: catCodes(ii) = curr ' increment counter
Next i
ReDim Preserve catCodes(1 To ii)
AlphaNum = Join(catCodes, vbNullString)
End Function
Note: Instead of If InStr(" -,", curr) <> 0 Then in Case 2 you may code If curr like "[ -,]" Then, too. Similar in Case 3 :-)
[A] Helper procedure RemoveEmpty
Optional clean-up removing comma separated tokens if empty or containing only blanks:
Sub RemoveEmpty(ByRef s As String)
'Purp: remove comma separated tokens if empty or only blanks
Const DEL = "$DEL$" ' temporary deletion marker
Dim i As Long
Dim tmp: tmp = Split(s, ",")
For i = LBound(tmp) To UBound(tmp)
tmp(i) = IIf(Len(Trim(tmp(i))) = 0, DEL, Trim(tmp(i)))
Next i
tmp = Filter(tmp, DEL, False) ' remove marked elements
s = Join(tmp, ",")
End Sub
[B] Helper function getCats()
A tricky way to groups characters into five code categories, thus building the basic logic for any further analyzing:
Function getCats(s, Optional info As Boolean = False)
'Purp.: group characters into five code categories
'Auth.: https://stackoverflow.com/users/6460297/t-m
'Site: https://stackoverflow.com/questions/15723672/how-to-remove-all-non-alphanumeric-characters-from-a-string-except-period-and-sp/74679416#74679416
'Note: Cat.: including:
' 1 ~~> apostrophe '
' 2 ~~> space, comma, minus etc
' 3 ~~> point separ., plus etc
' 4 ~~> digits 0..9
' 5 ~~> alpha (even including accented or diacritic letters!)
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'a) get array of single characters
Const CATEG As String = "' - . 0 A" 'define group starters (case indep.)
Dim arr: arr = Char2Arr(s) ' << [C] Char2Arr()
Dim chars: chars = Split(CATEG)
'b) return codes per array element
getCats = Application.Match(arr, chars) 'No 3rd zero-argument!!
'c) display in immediate window (optionally)
If info Then Debug.Print Join(arr, "|") & vbNewLine & Join(getCats, "|")
End Function
[C] Helper function Char2Arr
Assigns every string character to an array:
Function Char2Arr(ByVal s As String)
'Purp.: assign single characters to array
s = StrConv(s, vbUnicode)
Char2Arr = Split(s, vbNullChar, Len(s) \ 2)
End Function

Clear text of specific characters in a celll with VBA

I'm looking for some help please with some VBA.
I have the next table
header1
000Model Test0Model Val00User0
Perman000User0Model Name000000
000Perman00000000000000000000Name
So I need to replace all Ceros with only one "," like this
header1
,Model Test,Model Val,User,
Perman,User,Model Name,
,Perman,Name
Is there a combination of formulas to do this? or with code in VBA?
Please, try the next function:
Function replace0(x As String) As String
Dim matches As Object, mch As Object, arr, k As Long
ReDim arr(Len(x))
With CreateObject("VbScript.regexp")
Pattern = "[0]{1,30}"
.Global = True
If .test(x) Then
replace0 = .replace(x, ",")
End If
End With
End Function
It can be tested using:
Sub replaceAllzeroByComma()
Dim x As String
x = "000Perman00000000000000000000Name"
'x = "000Model Test0Model Val00User0"
'x = "Perman000User0Model Name000000"
Debug.Print replace0(x)
End Sub
Uncheck the checked lines, one at a time and see the result in Immediate Window (Ctrl + G, being in VBE)
If you have Microsoft 365, you can use:
=IF(LEFT(A1)="0",",","")&TEXTJOIN(",",TRUE,TEXTSPLIT(A1,"0"))&IF(RIGHT(A1)="0",",","")
Split on the zero's
Join the split text with a comma delimiter
Have to specially test first character
and also the last character as pointed out by #T.M.
Another option would be to check a character array as follows:
a) atomize input string to a tmp array of single characters via String2Arr()
b) check for zero characters in tmp via CheckChar
c) execute a negative filtering preserving first zeros in each 0-sequence via Filter(tmp, delChar, False)
d) return joined string
Function Rep0(ByVal s As String, Optional delChar As String = "0")
'Purp.: replace first zero in each 0-sequence by ",", delete any remaining zeros
Dim tmp: tmp = String2Arr(s) ' a) atomize string to character array
Dim i As Long
For i = LBound(tmp) To UBound(tmp) ' b) check zero characters
Dim old As String: CheckChar tmp, i, old, delChar
Next
tmp = Filter(tmp, delChar, False) ' c) negative filtering preserving non-deletes
Rep0 = Join(tmp, vbNullString) ' d) return cleared string
End Function
Help procedures
Sub CheckChar(ByRef arr, ByRef i As Long, ByRef old As String, _
ByVal delChar As String, Optional replChar As String = ",")
'Purp.: replace 1st delChar "0" in array (depending on old predecessor)
If Left(arr(i), 1) = delChar Then ' omit possible string end character
If Not old Like "[" & delChar & replChar & "]" Then arr(i) = replChar
End If
old = arr(i) ' remember prior character
End Sub
Function String2Arr(ByVal s As String)
'Purp.: atomize input string to single characters array
s = StrConv(s, vbUnicode)
String2Arr = Split(s, vbNullChar, Len(s) \ 2)
End Function

Reverser function that can group the value within a cell

I have been searching and doing some study on all the different posts I could find but can't say this request or question has been discussed before.
This is a basic reverse function, what I´ll like to accomplish is (as I call it) a group reversed function. Will Attach a picture to graphicly explain this better.
The goal is then to use =StrReverse($A1)
Function Reversestr(str As String) As String
Reversestr = StrReverse(Trim(str))
End Function
Please, use the next function:
Function reversePairOfDigits(strText As String) As String
Dim i As Long, strRes As String
For i = 1 To Len(strText) Step 2
strRes = Mid(strText, i, 2) & strRes
Next
reversePairOfDigits = strRes
End Function
It can be used in the next simple way. Select a cell containing the string to be processed and run it:
Sub testReversePairOfDigits()
Debug.Print reversePairOfDigits(ActiveCell.value)
End Sub
You can see the result in Immediate Window (Ctrl + G, being in VBE)
If the strings to be processed are in a specific range, is needed to iterate between its cells and call the supplied function. To make the code faster, the range should be placed in an array, then work on that and finally drop the processed result. If you clearly define the range to be processed, I can show you how to do it efficiently.
Reverse String (UDF)
Option Explicit
Function ReverseString( _
ByVal Word As String, _
Optional ByVal CharCount As Long = 1) _
As String
Dim wLen As Long: wLen = Len(Word)
Dim First As Long: First = wLen Mod CharCount
If First > 0 Then ReverseString = Left(Word, First)
Dim n As Long
For n = First + 1 To wLen Step CharCount
ReverseString = Mid(Word, n, CharCount) & ReverseString
Next n
End Function

Extracting common values from two cells containing comma-separated values in Excel

Is there a simple way to extract common numbers from two cells with comma-separated numbers?
I have cells with 12 comma separated numbers in each cell. (They are not all unique. Some numbers can be repeated twice. but never more than twice. Numbers are all positive, and one or two digit numbers only)
My data is like so: they are in column A:
11,11,13,15,16,18,20,20,26,27,28,29
8,9,10,12,13,14,18,20,21,22,24,28
13,13,14,14,15,17,18,19,20,21,23,25
6,6,8,10,12,14,15,17,18,20,20,25
11,13,17,18,19,19,22,25,26,28,28,31
7,9,15,16,17,18,23,24,24,25,26,27
7,9,11,12,12,15,16,16,18,18,20,23
9,11,13,15,18,22,23,24,25,28,29,29
7,9,10,11,12,12,13,14,15,16,19,22
5,10,11,12,12,16,17,18,20,22,24,25
7,10,13,16,16,17,18,19,21,23,24,24
10,14,16,18,18,19,21,23,23,25,27,28
The result I would like to have is like so:
I need a solution without separating values into different columns, please.
Thanks for your help.
Since there can be numbers repeating twice in some cases, I am also open to a solution like this, too.
Matching Sub Strings
Here 's the easier 'duplicates' solution:
In Excel use it like this:
=comStr(A2,A3)
Copy the code into a standard module e.g. Module1
The Code
Option Explicit
Function comStr(String1 As String, _
String2 As String, _
Optional ByVal Delimiter As String = ",") _
As String
Dim Data1, Data2, Result(), i As Long, j As Long, l As Long
Data1 = Split(String1, Delimiter)
Data2 = Split(String2, Delimiter)
For i = 0 To UBound(Data1)
For j = 0 To UBound(Data2)
If Data1(i) = Data2(j) Then GoSub writeResult: Exit For
Next j
Next i
comStr = Join(Result, Delimiter)
Exit Function
writeResult:
ReDim Preserve Result(l)
Result(l) = Data1(i)
l = l + 1
Return
End Function
EDIT:
Here is the 'full' version where you can choose if duplicates are allowed.
In Excel use it like this:
=comStr(A2,A3,TRUE) to allow duplicates (like in the version above) or
=comStr(A2,A3) or =comStr(A2,A3,FALSE) to not allow them.
Function comStr(String1 As String, _
String2 As String, _
Optional allowDupes As Boolean = False, _
Optional ByVal Delimiter As String = ",") _
As String
Dim Data1, Data2, Result(), Curr, i As Long, j As Long, l As Long, n As Long
Data1 = Split(String1, Delimiter)
Data2 = Split(String2, Delimiter)
For i = 0 To UBound(Data1)
Curr = Data1(i)
For j = 0 To UBound(Data2)
If Data2(j) = Curr Then GoSub writeResult: Exit For
Next j
Next i
If l = 0 Then Exit Function
comStr = Join(Result, Delimiter)
Exit Function
writeResult:
If Not allowDupes Then
If l > 0 Then
For n = 0 To l - 1
If Result(n) = Curr Then Exit For
Next
If n <= l - 1 Then Return
End If
End If
ReDim Preserve Result(l)
Result(l) = Data1(i)
l = l + 1
Return
End Function
It's possible without VBA:
Formula in B1:
=TEXTJOIN(",",,UNIQUE(IF(ISNUMBER(FIND(","&FILTERXML("<t><s>"&SUBSTITUTE(A1,",","</s><s>")&"</s></t>","//s")&",",","&A2&",")),FILTERXML("<t><s>"&SUBSTITUTE(A1,",","</s><s>")&"</s></t>","//s"),"")))
And if you don't have UNIQUE you could use XPATH to return unique values:
=TEXTJOIN(",",,IF(ISNUMBER(FIND(","&FILTERXML("<t><s>"&SUBSTITUTE(A1,",","</s><s>")&"</s></t>","//s[not(preceding::*=.)]")&",",","&A2&",")),FILTERXML("<t><s>"&SUBSTITUTE(A1,",","</s><s>")&"</s></t>","//s[not(preceding::*=.)]"),""))
In that case you also need to confirm through CtrlShiftEnter

Excel Extract nth first words from a string

I'd like to create a function in vba to extract the first nth words from a string and to look like this
ExtractWords(affected_text, delimiter, number_of_words_to_extract)
I tried a solution but it only extracts the first two words.
Function FirstWords(myStr As Variant, delimiter,words_to_extract) As Variant
FirstWords = Left(myStr, InStr(InStr(1, myStr, delimiter) + 1, myStr, delimiter, vbTextCompare) - 1)
End Function
Any ideas? Thanks
Use Split() function. It returns array of String, split using the delimiter and limit of words you specify.
Dim Result As Variant
Result = Split("Alice,Bob,Chuck,Dave", ",") 'Result: {"Alice,"Bob","Chuck","Dave"}
Result = Split("Alice,Bob,Chuck,Dave", ",", 2) 'Result: {"Alice,"Bob"}
#Taosique's answer using Split is excellent, but if you want the result returned as a string you can do the following:
Function FirstWords(myStr As String, delimiter As String, words_to_extract As Long) As Variant
Dim i As Long, k As Long
For i = 1 To Len(myStr)
If Mid(myStr, i, 1) = delimiter Then
k = k + 1
If k = words_to_extract Then
FirstWords = Mid(myStr, 1, i)
Exit Function
End If
End If
Next I
'if you get to here -- trouble
'unless the delimiter count is words_to_extract - 1
If k = words_to_extract - 1 Then
FirstWords = myStr
Else
FirstWords = CVErr(xlErrValue)
End If End Function
Sub test()
Debug.Print FirstWords("This is a test. I hope it works", " ", 4)
Debug.Print FirstWords("This is a test. I hope it works", " ", 10)
End Sub
When test is run it first displays the string "This is a test." then prints an error condition.
Much the same effect as the above can be achieved by first splitting the string using Split and then rejoining it using Join. A subtle difference is the behavior if there are less than words_to_extract words. The Split then Join approach will return the whole string. The above code treats this as an error condition and, if used as a UDF worksheet function, will display #VALUE! in any cell that contains it.

Resources