Macro that sorts call signs containing letters and numbers - excel

All the call signs will be in column A and when the macro is run should sort them. The sort is case insensitive usually in all caps. A call sign consists of 1-2 letters(prefix), 1-2 numbers(numbers), then 1-3 letters(suffix) I want to sort each sign by the number, suffix, then prefix in that order.
W9K, BB3C, W9GFO, AB8VN, G3G, A77Bc, KB8HTM, K9DOG, W8AER, K1ZZ, W4BFT, W0CQC, WA6FV, W6TRW, AA5B, W4IY, N4C, K5UZ, K4LRG

I will bite. Half the fun of coding is solving a problem for the simple pleasure of knowing you figured it out.
Here is a user defined function (Formula) that you can use to convert the call sign into the format for sorting. Note the numeric portion is zero padded so ones and tens do not sort together before twos and twenties.
Option Explicit
Public Function FormatCallSign(aCell As Range)
Dim Nbr As String
Dim i As Integer
Dim tmp As String
Dim vList As Variant
For i = 1 To Len(aCell.Value)
If InStr(1, "1234567890", UCase(Mid(aCell.Value, i, 1))) > 0 Then
Nbr = Nbr & Mid(aCell.Value, i, 1)
tmp = tmp & ","
tmp = Replace(tmp, ",,", ",")
Else
If InStr(1, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", UCase(Mid(aCell.Value, i, 1))) > 0 Then
tmp = tmp & Mid(aCell.Value, i, 1)
End If
End If
Next i
vList = Split(tmp, ",")
FormatCallSign = vList(1) & Right("0" & Nbr, 2) & vList(0)
End Function
Put the formula in cell B2, for example by using the formulas command on the ribbon and selecting the function from the user defined section.
As asked earlier if the call sign had delimiters in it already, you could use a simple formula to rearrange the parts and exclude the delimiters.
=CONCATENATE(MID(A3,SEARCH("-",A3)+1,4),RIGHT("0"&MID(A3,SEARCH("/",A3)+1,SEARCH("-",A3)-SEARCH("/",A3)-1),2),LEFT(A3,SEARCH("/",A3)-1))
To build a formula like the above, start by constructing it in parts.
First write a Search function to find the "/", then copy it to find the "-"
Then write a mid function to get the characters to the right of the dash, left of the slash, then the numeric section. paste the formulas into a single formula for your masterpiece.
Since it makes better sense to keep the three elements in separate fields for simplified sorting, the above formula can be split into three separate formulas, one for each column.
=MID(A3,SEARCH("-",A3)+1,4)
=value(MID(A3,SEARCH("/",A3)+1,SEARCH("-",A3)-SEARCH("/",A3)-1),2))
=LEFT(A3,SEARCH("/",A3)-1)
This corrects sorting problems given the three elements are variable length.

The initial specification for callsign format is inaccurate, since they can begin with numbers or letters and a logical sort would be by ITU assigned prefix. A function would need a table lookup for country after it determined if the string after the forward slash was a valid country designation. This is actually a pretty complicated problem.

Related

Excel 2016 - Sort value in cell in ascending order

I currently have a cell with some values, separated by commas. I wish to sort them in ascending order.
Sample Input (Value in a single cell):
Sample Output (Value in a single cell):
I have seen many answers when it comes to sorting rows and columns, but I can't seem to sort the values in a single cell in ascending order. Is it possible to sort the values in a single cell in ascending order? Or is there a workaround for this?
Some explanation/documentation would be appreciated as I'm a beginner at VBA. Thank you for your help.
Please try to split data in a single cell by comma, then sorting it and combine all of them together?
The comments provide you with numerous ways to sort an array. Rightly or wrongly, I've provided my own flavour on an old VBA topic along with the extended piece of outputting the string in a delimited format. Take it or leave it ...!
You should be able to refer to the below function directly from any given cell like you would any built-in function in Excel.
Public Function SplitAndSortAscending(ByVal strText As String, ByVal strDelimiter As String) As String
Dim arrData() As String, arrNewData() As String, i As Long, x As Long, y As Long
arrData = Split(strText, strDelimiter)
ReDim arrNewData(UBound(arrData))
For i = 0 To UBound(arrData)
For x = 0 To UBound(arrNewData)
If arrData(i) < arrNewData(x) Or arrNewData(x) = "" Then
For y = UBound(arrNewData) To x + 1 Step -1
arrNewData(y) = arrNewData(y - 1)
Next
arrNewData(x) = arrData(i)
Exit For
End If
Next
Next
For i = 0 To UBound(arrNewData)
SplitAndSortAscending = SplitAndSortAscending & strDelimiter & arrNewData(i)
Next
SplitAndSortAscending = Mid(SplitAndSortAscending, Len(strDelimiter) + 1)
End Function
If you have O365, you can use something like the below to achieve the same sort of thing. Take note, my implementation will take 1.0 and format it as a whole number, i.e. it will come out as 1.
=TEXTJOIN(",",TRUE,SORT(FILTERXML("<r><v>" & SUBSTITUTE(A1,",","</v><v>") & "</v></r>","//v")))
The assumption is that the example you provided is in cell A1.

Summing the digits in Excel cells (long and short strings)

I'm working on a research related to frequencies.
I want to sum all the numbers in each cell and reduce them to single number only.
some cells have 2 numbers, others have 13 numbers. like these..
24.0542653897891
25.4846064424057
27
28.6055035477009
I tried several formulas to do that. the best ones have me 2 digits number, that I couldn't sum it again to get a single result.
like these Formulas:
=SUMPRODUCT(MID(SUBSTITUTE(B5,".",""),ROW(INDIRECT("1:"&LEN(B5)-1)),1)+0)
=SUMPRODUCT(1*MID(C5,ROW(INDIRECT("1:"&LEN(C5))),1))
any suggestion?
Thank you in advance.
EDIT
Based on your explanation your comments, it seems that what you want is what is called the digital root of the all the digits (excluding the decimal point). In other words, repeatedly summing the digits until you get to a single digit.
That can be calculated by a simpler formula than adding up the digits.
=1+(SUBSTITUTE(B5,".","")-1)-(INT((SUBSTITUTE(B5,".","")-1)/9)*9)
For long numbers, we can split the number in half and process each half. eg:
=1+MOD(1+MOD(LEFT(SUBSTITUTE(B5,".",""),INT(LEN(SUBSTITUTE(B5,".",""))/2))-1,9)+1+MOD(RIGHT(SUBSTITUTE(B5,".",""),LEN(SUBSTITUTE(B5,".",""))-INT(LEN(SUBSTITUTE(B5,".",""))/2))-1,9)-1,9)
However, the numbers should be stored as TEXT. When numbers are stored as numbers, what we see may not necessarily be what is stored there, and what the formula (as well as the UDF) will process.
The long formula version will correct all the errors on your worksheet EXCEPT for B104. B104 appears to have the value 5226.9332653096000 but Excel is really storing the value 5226.9333265309688. Because of Excel's precision limitations, this will get processed as 5226.93332653097. Hence there will be a disagreement.
Another method that should work would be to round all of the results in your column B to 15 digits (eg: . Combining that with using the long formula version should result in agreement for all the values you show.
Explanation
if a number is divisible by 9, its digital root will be 9, otherwise, the digital root will be n MOD 9
The general formula would be: =1+Mod(n-1,9)
In your case, since we are dealing with numbers larger than can be calculated using the MOD function, we need to both remove the dot, and also use the equivalent of mod which is n-(int(n/9)*9)
Notes:
this will work best with numbers stored as text. Since Excel may display and/or convert large numbers, or numbers with many decimal places, differently than expected, working with text strings of digits is the most stable method.
this method will not work reliably with numbers > 15 digits.
If you have numbers > 15 digits, then I suggest a VBA User Defined Function:
Option Explicit
Function digitalRoot(num As String) As Long
Dim S As String, Sum As Long, I As Long
S = num
Do While Len(S) > 1
Sum = 0
For I = 1 To Len(S)
Sum = Sum + Val(Mid(S, I, 1))
Next I
S = Trim(Str(Sum))
Loop
digitalRoot = CLng(S)
End Function
You could use a formula like:
=SUMPRODUCT(FILTERXML("<t><s>"&SUBSTITUTE(A1," ","</s><s>")&"</s></t>","//s"))
You might need an extra SUBSTITUTE for changing . to , if that's your decimal delimiter:
=SUMPRODUCT(FILTERXML("<t><s>"&SUBSTITUTE(SUBSTITUTE(A1,".",",")," ","</s><s>")&"</s></t>","//s"))
However, maybe a UDF as others proposed is also a possibility for you. Though, something tells me I might have misinterpreted your question...
I hope you are looking for something like following UDF.
Function SumToOneDigit(myNumber)
Dim temp: temp = 0
CalcLoop:
For i = 1 To Len(myNumber)
If IsNumeric(Mid(myNumber, i, 1)) Then temp = temp + Mid(myNumber, i, 1)
Next
If Len(temp) > 1 Then
myNumber = temp
temp = 0
GoTo CalcLoop
End If
SumToOneDigit = temp
End Function
UDF (User Defined Functions) are codes in VBA (visual basic for applications).
When you can not make calculations with Given Excel functions like ones in your question, you can UDFs in VBA module in Excel. See this link for UDF .. If you dont have developer tab see this link ,, Add a module in VBA in by right clicking on the workbook and paste the above code in that module. Remember, this code remains in this workbook only. So, if you want to use this UDF in some other file your will have to add module in that file and paste the code in there as well. If you are frequently using such an UDF, better to make add-in out of it like this link
In addition to using "Text to Columns" as a one-off conversion, this is relatively easy to do in VBA, by creating a user function that accepts the data as a string, splits it into an array separated by spaces, and then loops the elements to add them up.
Add the following VBA code to a new module:
Function fSumData(strData As String) As Double
On Error GoTo E_Handle
Dim aData() As String
Dim lngLoop1 As Long
aData = Split(strData, " ")
For lngLoop1 = LBound(aData) To UBound(aData)
fSumData = fSumData + CDbl(aData(lngLoop1))
Next lngLoop1
fExit:
On Error Resume Next
Exit Function
E_Handle:
MsgBox Err.Description & vbCrLf & vbCrLf & "fSumData", vbOKOnly + vbCritical, "Error: " & Err.Number
Resume fExit
End Function
Then enter this into a cell in the Excel worksheet:
=fSumData(A1)
Regards,
The UDF below will return the sum of all numbers in a cell passed to it as an argument.
Function SumCell(Cell As Range) As Double
Dim Fun As Double ' function return value
Dim Sp() As String ' helper array
Dim i As Integer ' index to helper array
Sp = Split(Cell.Cells(1).Value)
For i = 0 To UBound(Sp)
Fun = Fun + Val(Sp(i))
Next i
SumCell = Fun
End Function
Install the function in a standard code module, created with a name like Module1. Call it from the worksheet with syntax like =SumCell(A2) where A2 is the cell that contains the numbers to be summed up. Copy down as you would a built-in function.

How to make a mask in Excel?

I have a text type column in excel with these values
2/02/1472
22/88/1234
1/8/1234
22/88/12
01/01/222
88/2222
I want to set a mask that my values do look like this
02/02/1472
22/88/1234
01/08/1234
22/88/1200
01/01/2220
00/88/2222
My mask is 00/00/0000 (if a part does not exist, fill with zero)
I use this "=text(A1,"00/00/0000")" but have error
Since you also mention vba in your tags, here is a User Defined Function:
Option Explicit
Function FormatMask(S As String) As String
Dim V
Dim I As Long
V = Split(S, "/")
V(UBound(V)) = Format(V(UBound(V)), "0000")
For I = UBound(V) - 1 To 0 Step -1
V(I) = Format(V(I), "00")
Next I
FormatMask = Right("00/00/" & Join(V, "/"), 10)
End Function
EDIT
#pnuts pointed out that your examples show that the first two groups are left-padded with 0's, but the third group is right-padded with zero's.
The following modification accomplishes that:
Option Explicit
Function FormatMask(S As String) As String
Dim V
Dim I As Long
V = Split(S, "/")
'This pads with 0's on the left
'V(UBound(V)) = Format(V(UBound(V)), "0000")
'For padding on right as you show for the last group only:
V(UBound(V)) = Left(V(UBound(V)) & "0000", 4)
For I = UBound(V) - 1 To 0 Step -1
V(I) = Format(V(I), "00")
Next I
FormatMask = Right("00/00/" & Join(V, "/"), 10)
End Function
Another example why spreadsheet software is not well suited to text processing, but Excel can manage with a (horrible) formula:
=IF(LEN(IF(LEN(A1)-LEN(SUBSTITUTE(A1,"/",""))=1,"00/"&A1,IF(LEFT(RIGHT(A1,3))="/",A1&"00",IF(LEFT(RIGHT(A1,4))="/",A1&"0",IF(MID(A1,2,1)="/","0"&A1,A1)))))=10,IF(LEN(A1)-LEN(SUBSTITUTE(A1,"/",""))=1,"00/"&A1,IF(LEFT(RIGHT(A1,3))="/",A1&"00",IF(LEFT(RIGHT(A1,4))="/",A1&"0",IF(MID(A1,2,1)="/","0"&A1,A1)))),SUBSTITUTE(IF(LEN(A1)-LEN(SUBSTITUTE(A1,"/",""))=1,"00/"&A1,IF(LEFT(RIGHT(A1,3))="/",A1&"00",IF(LEFT(RIGHT(A1,4))="/",A1&"0",IF(MID(A1,2,1)="/","0"&A1,A1)))),"/","/0",1))
Another horrible formula for you (I've broken it down so I'm using a few helper columns which you can hide so visually it looks the same). Part of the horribleness is also due to your source data not having a consistent format
In Column A I've got your original list which is stored as text (Excel won't recognise these as a date or number)
In Column B to get the first part I have the formula
=IF(LEN(A2)-LEN(SUBSTITUTE(A2,"/",""))=2, VALUE(LEFT(A2,FIND("/",A2)-1)),0)
In Column C to get the middle bit I have
=LEFT(RIGHT(A2,IF(LEN(A2)-LEN(SUBSTITUTE(A2,"/",""))=2,LEN(A2)-FIND("/",A2),LEN(A2))),FIND("/",RIGHT(A2,IF(LEN(A2)-LEN(SUBSTITUTE(A2,"/",""))=2,LEN(A2)-FIND("/",A2),LEN(A2))))-1)
and then in Column D I get the last bit using:
=RIGHT(A2,LEN(A2)-FIND("/",A2,IF(LEN(A2)-LEN(SUBSTITUTE(A2, "/", ""))=2,FIND("/",A2)+1,1)))
I then put it all together in Column E and format it using
=TEXT(B2,"00")&"/"&TEXT(C2, "00") &"/"&TEXT(D2,"0")&REPT(0,4-LEN(D2))
To get your output
You could of course combine this all into one formula, I've just broken it down for a little bit of clearness (although is still pretty bleak):
=TEXT(IF(LEN(A2)-LEN(SUBSTITUTE(A2,"/",""))=2, VALUE(LEFT(A2,FIND("/",A2)-1)),0),"00")&"/"&TEXT(LEFT(RIGHT(A2,IF(LEN(A2)-LEN(SUBSTITUTE(A2,"/",""))=2,LEN(A2)-FIND("/",A2),LEN(A2))),FIND("/",RIGHT(A2,IF(LEN(A2)-LEN(SUBSTITUTE(A2,"/",""))=2,LEN(A2)-FIND("/",A2),LEN(A2))))-1), "00") &"/"&TEXT(RIGHT(A2,LEN(A2)-FIND("/",A2,IF(LEN(A2)-LEN(SUBSTITUTE(A2, "/", ""))=2,FIND("/",A2)+1,1))),"0")&REPT(0,4-LEN(RIGHT(A2,LEN(A2)-FIND("/",A2,IF(LEN(A2)-LEN(SUBSTITUTE(A2, "/", ""))=2,FIND("/",A2)+1,1)))))
A slightly shorter version:
=IF(LEN(A2)-LEN(SUBSTITUTE(A2,"/",""))=1,"00",TEXT(LEFT(A2,FIND("/",A2)-1),"00"))&"/"&TEXT(IFERROR(MID(A2,FIND("/",A2)+1,LOOKUP(99^99,FIND("/",A2,ROW($1:$20)))-FIND("/",A2)-1),TEXT(LEFT(A2,FIND("/",A2)-1),"00")),"00")&"/"&LEFT(RIGHT(A2,LEN(A2)-LOOKUP(99^99,FIND("/",A2,ROW($1:$20))))*10000,4)
The only thing that you want to know is this:
LOOKUP(99^99,FIND("/",A2,ROW($1:$20)))
This is the function to find the last occurrence of /. I assume the maximum length of the string is 20 so you can replace that if needed.

Prevent Partial Duplicates in Excel

I have a worksheet with products where the people in my office can add new positions. The problem we're running into is that the products have specifications but not everybody puts them in (or inputs them wrong).
Example:
"cool product 14C"
Is there a way to convert Data Valuation option so that it warns me now in case I put "very cool product 14B" or anything that contains an already existing string of characters (say, longer than 4), like "cool produKt 14C" but also "good product 15" and so on?
I know that I can prevent 100% matches using COUNTIF and spot words that start/end in the same way using LEFT/RIGHT but I need to spot partial matches within the entries as well.
Thanks a lot!
If you want to cover typo's, word wraps, figure permutations etc. maybe a SOUNDEX algorithm would suit to your problem. Here's an implementation for Excel ...
So if you insert this as a user defined function, and create a column =SOUNDEX(A1) for each product row, upon entry of a new product name you can filter for all product rows with same SOUNDEX value. You can further automate this by letting user enter the new name into a dialog form first, do the validation, present them a Combo Box dropdown with possible duplicates, etc. etc. etc.
edit:
small function to find parts of strings terminated by blanks in a range (in answer to your comment)
Function FindSplit(Arg As Range, LookRange As Range) As String
Dim LookFor() As String, LookCell As Range
Dim Idx As Long
LookFor = Split(Arg)
FindSplit = ""
For Idx = 0 To UBound(LookFor)
For Each LookCell In LookRange.Cells
If InStr(1, LookCell, LookFor(Idx)) <> 0 Then
If FindSplit <> "" Then FindSplit = FindSplit & ", "
FindSplit = FindSplit & LookFor(Idx) & ":" & LookCell.Row
End If
Next LookCell
Next Idx
If FindSplit = "" Then FindSplit = "Cool entry!"
End Function
This is a bit crude ... but what it does is the following
split a single cell argument in pieces and put it into an array --> split()
process each piece --> For Idx = ...
search another range for strings that contain the piece --> For Each ...
add piece and row number of cell where it was found into a result string
You can enter/copy this as a formula next to each cell input and know immediately if you've done a cool input or not.
Value of cell D8 is [asd:3, wer:4]
Note the use of absolute addressing in the start of lookup range; this way you can copy the formula well down.
edit 17-Mar-2015
further to comment Joanna 17-Mar-2015, if the search argument is part of the range you're scanning, e.g. =FINDSPLIT(C5; C1:C12) you want to make sure that the If Instr(...) doesn't hit if LookCell and LookFor(Idx) are really the same cell as this would create a false positive. So you would rewrite the statement to
...
...
If InStr(1, LookCell, LookFor(Idx)) <> 0 And _
Not (LookCell.Row = Arg.Row And LookCell.Column = Arg.Column) _
Then
hint
Do not use a complete column (e.g. $C:$C) as the second argument as the function tends to become very slow without further precautions

How to add leading zeros in Excel (timestamp with ms has no leading zeroes)

I’m still having a grave problem with some files. It’s a rather stupid problem, but I’ve been working at it for quite some time and can’t find a solution.
I need leading zeroes in the time stamps, at least on the ms level.
The timestamps that my software makes always look like this: (example)
9:55:1:19 (that is 9h, 55min, 1sec, 19 ms)
while what I need would look like
09:55:01:019
It’s no problem to make a conversion in Excel. I use
=VALUE(SUBSTITUTE(G2;":";",";3))
but I always get
09:55:01:190 (190ms!!)
Thus the milliseconds are always read like comma values, which is understandable from the software’s point of view.
I'd like a solution that either appends the correct values to the end of each row in a new column or directly changes the values in the original column (D) to the correct values. (Appending is OK because my other scripts work that way already!)
Can you help out really quickly?
https://www.dropbox.com/sh/3ch6ikddplnyjgg/vUfnVgbbzH here's an example file
With a value in A1 like:
0.413206238
the formula:
=SUBSTITUTE(TEXT(A1,"hh:mm:ss.000"),".",":")
will display:
09:55:01:019
EDIT#1:
Or if you want to convert the values in column D, in place. Select the cells and run this small macro:
Sub FFormat()
Dim r As Range, L As Long, U As Long
For Each r In Selection
ary = Split(r.Text, ":")
U = UBound(ary)
L = LBound(ary)
For i = L To U
If Len(ary(i)) = 1 Then
ary(i) = "0" & ary(i)
End If
Next i
If Len(ary(U)) = 2 Then
ary(U) = "0" & ary(U)
End If
r.Value = Join(ary, ":")
Next r
End Sub
If the original in R12 (my example) is text, you can enter this big formula: :)
=TEXT(LEFT(SUBSTITUTE(R12;":";REPT(" ";20));4);"00") & ":"&TEXT(MID(SUBSTITUTE(R12;":";REPT(" ";20));14;20);"00") & ":" & TEXT(MID(SUBSTITUTE(R12;":";REPT(" ";20));34;20);"00") & ":" & TEXT(MID(SUBSTITUTE(R12;":";REPT(" ";20));54;20);"000")
Depending on your regional settings you may need to replace field separator "; " by ","
Your data in column G has a leading space - this formula in a new column should convert to a valid time value whether you have leading spaces or not
=LEFT(TRIM(G2);FIND("^";SUBSTITUTE(TRIM(G2);":";"^";3))-1)+LOOKUP(1000;RIGHT(G2;{1;2;3})+0)/86400000
format as [h]:mm:ss.000
This will cope with single or double digit milliseconds, assumes that if there are no milliseconds you will still have third : followed by a zero. Also copes with single digit hours, minutes or seconds

Resources