VBA generate a code - excel

there. I made this code that replaces a character for two number (e.g. 0 = 10; 1 = 11; 2 = 12; ...) and everything works fine except for the first element (the zero element). So, if I put "010a4" string on cell A1 and use my formula "=GENERATECODE(A1)", my expected return value is "1011102014" but I've got a "110111102014" string. So, only zero value occur this error and I can't figured out why. Any thoughts?
My code:
Function GENERATECODE(Code As String)
Dim A As String
Dim B As String
Dim i As Integer
Const AccChars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
Const RegChars = "1011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071"
For i = 1 To Len(AccChars)
A = Mid(AccChars, i, 1)
B = Mid(RegChars, 2 * i - 1, 2)
Code = Replace(Code, A, B)
Next
GENERATECODE = Code
End Function

Your problem is that your code first change each 0 to 10 then each 1 to 11. So each 0 give you 10 then 110.
If you want to keep the same kind of algorithm (which might not be a good choice), you need to change AccChars and RegChars so that a character is never replaced by a string that can give a character found later on the AccChars String. In your case just replace Const AccChars = "012 ... per Const AccChars = "102 ... and Const RegChars = "101112 ... per Const RegChars = "111012 ...
But it might be better to change your algorithm altogether. I would first suggest to not use in place editing of the string, but rather to use 2 Strings.

In addition to being incorrect, your current code is inefficient since it involves scanning over the code string multiple times instead of just once. Simply scan over the string once, gathering the substitutions into an array which is joined at the end:
Function GENERATECODE(Code As String) As String
Dim codes As Variant
Dim i As Long, n As Long
Dim c As String
n = Len(Code)
ReDim codes(1 To n)
For i = 1 To n
c = Mid(Code, i, 1)
Select Case c
Case "0" To "9":
codes(i) = "1" & c
Case "a" To "z":
codes(i) = Asc(c) - 77
Case "A" To "Z":
codes(i) = Asc(c) - 19
Case Else:
codes(i) = "??"
End Select
Next i
GENERATECODE = Join(codes, "")
End Function
Example:
?generatecode("010a4")
1011102014
The point of the two offsets is that you want "a" to map to 20 and "A" to map to 46. Note Asc("a") - 77 = 97 - 77 and Asc("A") - 19 = 65-19 = 46.

Related

Adding space every 2 chars in VB.NET

Good day, experts. I'm getting 16 chars string value from uart, like this "0000000000001110", then i want to add space every 2 chars: "00 00 00 00 00 00 11 10". What i was thinking it's making for-next loop count every 2 chars in a "data", then add a space between it. But i'm really have no ideas how to accomplish it. That's what i tried so far:
Dim i As Long
Dim data As String = "0000000000001110"
For i = 0 To Len(data) Step 2 ' counting every 2 chars
data = Mid(data, i + 1, 2) ' assign 2 chars to data
' stucked here
Next i
Any input appreciated, thanks.
You can use a StringBuilder and a backwards loop:
Dim data As String = "0000000000001110"
Dim builder As New StringBuilder(data)
Dim startIndex = builder.Length - (builder.Length Mod 2)
For i As int32 = startIndex to 2 Step -2
builder.Insert(i, " "c)
Next i
data = builder.ToString()
The conditional operator(in VB If) using the Mod is used to find the start index(loooking from the end of the string). Because it will be different if the string has an even/odd number of characters. I use the backwards loop to prevent the problem that inserting characters changes the size of the string/StringBuilder, hence causing wrong indexes in the for-loop.
Here is an extension method that encapsulates the complexity and improves reusability:
Imports System.Runtime.CompilerServices
Imports System.Text
Module StringExtensions
<Extension()>
Public Function InsertEveryNthChar(str As String, inserString As String, nthChar As Int32) As String
If string.IsNullOrEmpty(str) then Return str
Dim builder as New StringBuilder(str)
Dim startIndex = builder.Length - (builder.Length Mod nthChar)
For i As int32 = startIndex to nthChar Step -nthChar
builder.Insert(i, inserString)
Next i
return builder.ToString()
End Function
End Module
Usage:
Dim data = "00000000000011101"
data = data.InsertEveryNthChar("[foo]", 3) ' 000[foo]000[foo]000[foo]000[foo]111[foo]01
I know you have already accepted an answer, however you could also do the required task like this.Make sure to import System.Text so you can use the StringBuilderOutput : 00 00 00 00 00 00 11 10
Dim data As String = "0000000000001110"
Dim sb As New StringBuilder()
Dim addSpace As Boolean = False
For Each c As Char In data
If addSpaceThen
sb.Append(c + " ")
addSpace = False
Else
sb.Append(c)
addSpace = True
End If
Next
sb.Length = sb.Length - 1 ''Remove last space on string
Console.WriteLine(sb.ToString())
If you NuGet "System.Interactive" you gt a very neat Buffer operator for IEnumerable(Of T). Then this works:
Dim data As String = "0000000000001110"
Dim result = String.Join(" ", data.Buffer(2).Select(Function (x) New String(x.ToArray())))
If you want to use straight LINQ then this works:
Dim result = String.Join(" ", data.Select(Function (x, n) New With { x, n }).GroupBy(Function (x) x.n \ 2, Function (x) x.x).Select(Function (y) New String(y.ToArray())))
'This is the easiest and a layman level solution
Dim i As Long
Dim A, b, C As String
A = Mid(mac, i + 1, 2) 'assign the first 2 value to the variable
C = A 'transfer it to main section
For i = 0 To Len(mac) - 4 Step 2 ' counting every 2 chars and looping should be 4 characters less.
b = Mid(mac, i + 3, 2) ' assign 2 chars to data
b = "-" + b'put the dashes in front of every other character
C = C + b
Next i

How do you separate text in excel vba by "CamelCase" and numbers

I'm trying to make a molecular composition calculator but i can seem to separate a formula by case and numbers into different cells.
Is it possible to do this in excel?
Eg:
Cl2H0 ----> Cl | 2 | H | 0
A bit crude but you could write a parsing function like this that returns an array:
Public Function parseChem(str As String) As Variant()
'should error-check first that entire string is correct
Dim retArr() As Variant
Dim i As Long, numBlocks As Long
Dim currentChar As String, currentElement As String, typeOfChar As String
Dim digitChain As Boolean
For i = 1 To Len(str)
currentChar = Mid(str, i, 1)
typeOfChar = charType(currentChar)
Select Case typeOfChar
Case Is = "upperCase"
If currentElement <> "" Then
'possibly cast numbers to longs here, and at the end...
retArr(numBlocks) = currentElement
End If
numBlocks = numBlocks + 1
ReDim Preserve retArr(1 To numBlocks)
currentElement = currentChar
digitChain = False
Case Is = "lowerCase"
currentElement = currentElement & currentChar
Case Is = "digit"
If digitChain Then
currentElement = currentElement & currentChar
Else
'new digit block
retArr(numBlocks) = currentElement
numBlocks = numBlocks + 1
ReDim Preserve retArr(1 To numBlocks)
digitChain = True
currentElement = currentChar
End If
Case Else
'do something to flag error
End Select
Next i
retArr(numBlocks) = currentElement
parseChem = retArr
End Function
Private Function charType(str As String) As String
Dim ascii As Long
ascii = Asc(str)
If ascii >= 65 And ascii <= 90 Then
charType = "upperCase"
Exit Function
Else
If ascii >= 97 And ascii <= 122 Then
charType = "lowerCase"
Exit Function
Else
If ascii >= 48 And ascii <= 57 Then
charType = "digit"
Exit Function
End If
End If
End If
End Function
OK the algorithm in the end is very simple
If at any point in the formula you have a number, then look for the next capital letter and output all characters up to that point.
If at any point in the formula you have a letter, then look for the next capital letter *or number* and output all characters up to that point.
The formula is rather long
=IF(ISNUMBER(MID($A$1,SUM(LEN($B$1:B1))+1,1)+0),
MID(MID($A$1,SUM(LEN($B$1:B1))+1,9),1,MIN(FIND( MID("ABCDEFGHIJKLMNOPQRSTUVWXYZ",ROW($1:$26),1),MID($A$1,SUM(LEN($B$1:B1))+2,9)&"ABCDEFGHIJKLMNOPQRSTUVWXYZ" ))),
MID(MID($A$1,SUM(LEN($B$1:B1))+1,9),1,MIN(FIND( MID("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",ROW($1:$36),1),MID($A$1,SUM(LEN($B$1:B1))+2,9)&"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" ))))
must be entered as an array formula using CtrlShiftEnter and the 9 would need increasing ( or changing to len($a1) ) if the formula was longer than 9 characters.
Here's a shorter version that doesn't have to be entered as an array formula
=IF(ISNUMBER(MID($A1,SUMPRODUCT(LEN($B1:B1))+1,1)+0),
MID(MID($A1,SUMPRODUCT(LEN($B1:B1))+1,9),1,AGGREGATE(15,6,FIND( MID("ABCDEFGHIJKLMNOPQRSTUVWXYZ",ROW($1:$26),1),MID($A1,SUMPRODUCT(LEN($B1:B1))+2,9)&"A" ),1)),
MID(MID($A1,SUMPRODUCT(LEN($B1:B1))+1,9),1,AGGREGATE(15,6,FIND( MID("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",ROW($1:$36),1),MID($A1,SUMPRODUCT(LEN($B1:B1))+2,9)&"A"),1)))
If you are familiar with VBA then you could write a function which reads in the cell value (e.g. Cl2H0) and then a For Loop that splits the string into seperate values. You would then write these seperated values (Cl, 2, H and 0) back to indivisual columns on the excel sheet.
One way of doing this would be to use the Asc() function in a loop which will give you the Ascii number corresponding to an indivisual charachter. Ascii charachters 65 to 90 are Upper Case charachters. In your case you would want to split the string when the charachter does not fall within this range.
If you want to try this and post your example then I can give some more guidance but its hard to give more advide without first understanding if you are trying to achieve this with VBA or some other means.

Replace a string with a format string

I have the following input:
Dim str_format as string = "XXXXX00000"
Dim str as string = "INV"
Dim int as integer = "56"
How can I replace XXXXX with INV and replace 00000 with 56?
For the example above the result should be INVXX00056.
X can only replace with alphabet and 0 can only replace with integer, if str has more than five alphabet. The extra alphabets will be thrown away because str_format only has five X. The same algorithm is true for the integer.
Example 2
Dim str_format as string = "XXX00000"
Dim str as string = "ABCD"
Dim int as integer = 654321
Expected result: ABC54321
Process:
1. ABCD XXX00000 654321
2. ABC DXX000006 54321
3. AB CDX00065 4321
4. A BCD00654 321
5. ABC06543 21
6. ABC65432 1
7. ABC54321
As Spidey mentioned... show some code. That said the process you describe is a bit long-winded.
The Letter part of the solution can be done by grabbing the first 3 characters of str using Left(str,3) this will bring in the leftmost 3 character (if there are less it will get what is there). Then check that you have 3 characters using str.Length(). If the length is less than 3 then append the appropriate number of 'X'.
The Numeric part can be done in a similar way. Your int is actually a string in your code above. If it was a real integer you can cast it to string. Use Right(int,5). Again check to see you have 5 digits and if not prepend with appropriate number of 0.
Have a go... if you run into problems post your code and someone is bound to help.
UPDATE
As there have been actual answers posted here is my solution
Function FormatMyString(str As String, num as String) As String
Dim result As String
result = Left(str,3).PadRight(3, "X"c).ToUpper() & Right(num,5).PadLeft(5, "0"c)
Return result
End Function
UPDATE 2
based on Wiktors answer... made an amendment to my solution to cope with different formats
Function FormatMyString(str As String, num as String, alpha as Integer, digits as Integer) As String
Dim result As String
result = Left(str, alpha).PadRight(alpha, "X"c).ToUpper() & Right(num, digits).PadLeft(digits, "0"c)
Return result
End Function
To use...
FormatMyString("ABCDE", "56",3 5) will return ABC00056
FormatMyString("ABCDE", "123456",4 3) will return ABCD456
FormatMyString("AB", "123456",4 3) will return ABXX456
Here is a possible solution that just uses basic string methods and PadLeft/PadRight and a specific method to count occurrences of specific chars in the string. It assumes the format string can only contain X and 0 in the known order.
Public Function CountCharacter(ByVal value As String, ByVal ch As Char) As Integer
Return value.Count(Function(c As Char) c = ch)
End Function
Public Sub run1()
Dim str_format As String = "XXXXX00000" '"XXX00000"
Dim str As String = "INV"
Dim int As Integer = 56 ' ABC54321
Dim xCnt As Integer = CountCharacter(str_format, "X")
Dim zCnt As Integer = CountCharacter(str_format, "0")
Dim result As String
If xCnt > str.Length Then
result = str.PadRight(xCnt, "X")
Else
result = str.Substring(0, xCnt)
End If
If zCnt > int.ToString().Length Then
result = result & int.ToString().PadLeft(zCnt, "0")
Else
result = result & int.ToString().Substring(int.ToString().Length-zCnt
End If
Console.WriteLine(result)
End Sub
Output for your both scenarios is as expected.
Take a look at this sample
Dim str_format As String = str_format.Replace("XXX", "ABC")
Msgbox(str_format )
As we assume that the X is 3 only. I dont want to give you more it is a start and everything will be easy.
If that kind of format is fix I mean the number of X will go or down then you can make a conditional statement based on the length of string

Replace one character with two using replace function

I am trying to convert accented characters to regular characters. Some characters need to be replaced with two characters. I tried MID(string,i,2).
Function ChangeAccent(thestring As String)
Dim A As String * 1
Dim B As String * 1
Dim C As String * 1
Dim D As String * 1
Dim i As Integer
Const LatChars="ßÄÖÜäöü"
Const OrgChars= "SSAEOEUEaeoeue"
For i = 1 To Len(LatChars)
A = Mid(LatChars, i, 1)
B = Mid(OrgChars, i, 2)
thestring = Replace(thestring, A, B)
Next
Const AccChars="ŠŽšžŸÀÁÂÃÄÅÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖÙÚÛÜÝàáâãäåçèéêëìíîïðñòóôõöùúûüýÿ"
Const RegChars= "SZszYAAAAAACEEEEIIIIDNOOOOOUUUUYaaaaaaceeeeiiiidnooooouuuuyy"
For i = 1 To Len(AccChars)
C = Mid(AccChars, i, 1)
D = Mid(RegChars, i, 1)
thestring = Replace(thestring, C, D)
Next
ChangeAccent = thestring
End Function
The code is working for one by one replacement (1 character by 1 character).
I want to replace one character in the variable LatChars with 2 characters in OrgChars. i.e ß with SS, Ä with AE and so on.
The Mid(OrgChars, i,2) is not extracting two characters.
Minor changes:
Dim B As String * 2
B = Mid(OrgChars, i * 2 - 1, 2)
Option Explicit
Function ChangeAccent(thestring As String)
Dim A As String * 1
Dim B As String * 2
Dim C As String * 1
Dim D As String * 1
Dim i As Integer
Const LatChars = "ßÄÖÜäöü"
Const OrgChars = "SSAEOEUEaeoeue"
For i = 1 To Len(LatChars)
A = Mid(LatChars, i, 1)
B = Mid(OrgChars, i * 2 - 1, 2)
thestring = Replace(thestring, A, B)
Next
Const AccChars = "ŠŽšžŸÀÁÂÃÄÅÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖÙÚÛÜÝàáâãäåçèéêëìíîïðñòóôõöùúûüýÿ"
Const RegChars = "SZszYAAAAAACEEEEIIIIDNOOOOOUUUUYaaaaaaceeeeiiiidnooooouuuuyy"
For i = 1 To Len(AccChars)
C = Mid(AccChars, i, 1)
D = Mid(RegChars, i, 1)
thestring = Replace(thestring, C, D)
Next
ChangeAccent = thestring
End Function
B = Mid(OrgChars, i,2)
Should probably be
B = Mid(OrgChars, i*2-1,2)
One method is to use two arrays. One that contains the character you wish to replace and the other its replacement. This method depends on both arrays being in sync with one another. Element 1 in the first array must match element 1 in the second, and so on.
This method allows you to ignore the string lengths. There is no longer any need to process 1 and 2 character replacement strings separately. This code can also scale to 3, 4 or more character replacements without a logic change.
I've used the split function to build the arrays. I find this saves time when typing out the code. But you may prefer to define the elements individually, which is arguably easier to read.
Example
Sub Demo001()
' Demos how to replace special charaters of various lenghts.
Dim ReplaceThis() As String ' Array of characters to replace.
Dim WithThis() As String ' Array of replacement characters.
Dim c As Integer ' Counter to loop over array.
Dim Sample As String ' Contains demo string.
' Set up demo string.
Sample = "ß - Ä - Š"
' Create arrays using split function and comma delimitor.
ReplaceThis = Split("ß,Ä,Š", ",")
WithThis = Split("SS,AE,S", ",")
' Loop over replacements.
For c = LBound(ReplaceThis) To UBound(ReplaceThis)
Sample = Replace(Sample, ReplaceThis(c), WithThis(c))
Next
' Show result.
MsgBox Sample
End Sub
Returns
SS - AE - S
EDIT: Answer rewritten as first attempt misunderstood - and did not answer - op question

How can you convert HEX to BIN, one character at a time in EXCEL 2010

I am trying to find a way to take a string of HEX values and convert them to BIN. I need to convert 1 HEX character at a time:
For example: HEX = 0CEC
BIN = 0000 1100 1110 1100
I need to do this in Excel. Any help would be great.
Thanks,
Larry
In a module:
Public Function HEX2BIN(strHex As String) As String
Dim c As Long, i As Long, b As String * 4, j As Long
For c = 1 To Len(strHex)
b = "0000"
j = 0
i = Val("&H" & Mid$(strHex, c, 1))
While i > 0
Mid$(b, 4 - j, 1) = i Mod 2
i = i \ 2
j = j + 1
Wend
HEX2BIN = HEX2BIN & b & " "
Next
HEX2BIN = RTrim$(HEX2BIN)
End Function
For:
=HEX2BIN("0CEC")
0000 1100 1110 1100
Yes, I had to do this recently. I'm late to the game, but other people will have to do this from time to time, so I'll leave the code where everyone can find it:
Option Explicit
Public Function HexToBinary(strHex As String, Optional PadLeftZeroes As Long = 5, Optional Prefix As String = "oX") As String
Application.Volatile False
' Convert a hexadecimal string into a binary
' As this is for Excel, the binary is returned as string: there's a risk that it will be treated as a number and reformatted
' Code by Nigel Heffernan, June 2013. Http://Excellerando.Blogspot.co.uk THIS CODE IS IN THE PUBLIC DOMAIN
' Sample Usage:
'
' =HexToBinary("8E")
' oX0010001110
'
' =HexToBinary("7")
' oX001111
'
' =HexToBinary("&HD")
' oX01101
Dim lngHex As Long
Dim lngExp As Long
Dim lngPad As Long
Dim strOut As String
Dim strRev As String
If Left(strHex, 2) = "&H" Then
lngHex = CLng(strHex)
Else
lngHex = CLng("&H" & strHex)
End If
lngExp = 1
Do Until lngExp > lngHex
' loop, bitwise comparisons with successive powers of 2
' Where bitwise comparison is true, append "1", otherwise append 0
strRev = strRev & CStr(CBool(lngHex And lngExp) * -1)
lngExp = lngExp * 2
Loop
' As we've done this in ascending powers of 2, the results are in reverse order:
If strRev = "" Then
HexToBinary = "0"
Else
HexToBinary = VBA.Strings.StrReverse(strRev)
End If
' The result is padded by leading zeroes: this is the expected formatting when displaying binary data
If PadLeftZeroes > 0 Then
lngPad = PadLeftZeroes * ((Len(HexToBinary) \ PadLeftZeroes) + 1)
HexToBinary = Right(String(lngPad, "0") & HexToBinary, lngPad)
End If
HexToBinary = Prefix & HexToBinary
End Function
You can use HEX2BIN(number, [places]).
The HEX2BIN function syntax has the following arguments:
Number Required. The hexadecimal number you want to convert. Number cannot contain more than 10 characters. The most significant bit of number is the sign bit (40th bit from the right). The remaining 9 bits are magnitude bits. Negative numbers are represented using two's-complement notation.
Places Optional. The number of characters to use. If places is omitted, HEX2BIN uses the minimum number of characters necessary. Places is useful for padding the return value with leading 0s (zeros).
I would use a simple formula as follows:
=HEX2BIN(MID(S23,1,2))&HEX2BIN(MID(S23,3,2))&HEX2BIN(MID(S23,5,2))&HEX2BIN(MID(S23,7,2)&HEX2BIN(MID(S23,9,2)&HEX2BIN(MID(S23,11,2)&HEX2BIN(MID(S23,13,2))
cell S23 = BFBEB991, Result = 10111111101111101011100110010001
This would allow it to be as long you need. Just add as many repetitions as you need incrementing the start position by 2 (eg 1, 3, 5, 7, 9, 11, 13, 15, ....). Note that the missing characters will be ignored.
For me, it gives this (sorry, in VBA, but has the advantage of not asking you the length of your string to convert). Be careful, I put a comment in the lower part for which you can add a space between each section of 4 bits. Some don't want the space and some will need it:
Length = Len(string_to_analyse)
For i = 1 To Length
Value_test_hexa = Left(Right(string_to_analyse, Length - (i - 1)), 1)
'get the asci value of each hexa character (actually can work as a decimal to binary as well)
Value_test = Asc(Value_test_hexa)
If Value_test > 47 And Value_test < 58 Then
Value_test = Value_test - 48
End If
' Convert A to F letters to numbers from 10 to 15
If Value_test > 64 And Value_test < 71 Then
Value_test = Value_test - 55
End If
'identify the values of the 4 bits for each character (need to round down)
a = WorksheetFunction.RoundDown(Value_test / 8, 0)
b = WorksheetFunction.RoundDown((Value_test - a * 8) / 4, 0)
c = WorksheetFunction.RoundDown((Value_test - a * 8 - b * 4) / 2, 0)
d = (Value_test - a * 8 - b * 4 - c * 2)
Value_converted = Value_converted & a & b & c & d ' can eventually add & " " in order to put a space every 4 bits
Next i
Tested OK so you can go with it.
Just leaving this here for anyone who needs it.
Instead of manually converting from hex to binary, I used Excel's built-in HEX2BIN function.
Function hexToBin(hexStr As String) As String
Dim i As Integer, b As String, binStr As String
For i = 1 To Len(hexStr)
b = Application.hex2bin(Mid(hexStr, i, 1), 4)
binStr = binStr & b
Next i
hexToBin = binStr
End Function

Resources