1

For instance, I would like these 6 numbers.

Currently when I use the sort method it puts 6.6.1.1.13 first and 6.6.1.1.2 later.

Before Sort

  6.6.1.1
  6.6.1.1.1
  6.6.1.1.13
  6.6.11.14
► 6.6.1.1.2

What I Want It To Look Like After Sort

  6.6.1.1
  6.6.1.1.1
► 6.6.1.1.2
  6.6.1.1.13
  6.6.11.14
T.M.
  • 9,436
  • 3
  • 33
  • 57
Punar
  • 21
  • 4
  • 1
    Those are not numbers. They are text. You should proceed on that basis. – freeflow Jan 03 '22 at 13:01
  • 1
    Actually these are *outline numbers* or *path-indexes* (obviously they have to be stored as text in Excel). – RBarryYoung Jan 03 '22 at 13:27
  • Which "sort function" do you mean? – RBarryYoung Jan 03 '22 at 13:30
  • 1
    I would assume you need to pad the numbers out in a helper column so 6.6.1.1.13 => 06.06.01.01.13, which can then be sorted. A UDF to split, test length of each element and adjust if necessary then join should work. – Tragamor Jan 03 '22 at 14:11
  • 1
    Vote to reopen as this sort of hierarchical enumerations (known e.g. from **chapter** enumerations) is quite common in scientific publications, reports etc and thus the sorting method should be widely understood, if not self explaining. – T.M. Jan 03 '22 at 19:36
  • 2
    @T.M. I agree, the vote to close this was wrong in the first place, it was always sufficiently focused to be answerable, as evidenced by the fact that there already was multiple answer at the time. – RBarryYoung Jan 04 '22 at 13:50
  • 1
    @Punar Posted a late answer to your recently re-opened question demonstrating two approaches **without** the need of a help column. - *Btw allow me a hint as new user: you got several (valid) answers. It's good use to accept your *preferred* one by ticking the green checkmark and to consider upvoting any helpful answer(s) thus giving some orientation to other users, too.* – T.M. Jan 09 '22 at 19:52

5 Answers5

4

Unfortunately the only fully general way to sort outline numbers (or more formally, "path-indexes") is with a custom comparison function. Unfortunately, the Excel sorting operations and functions do not support such a feature (not even from VBA). The Excel preferred way is with custom lists, but these are not workable for path-indexes.

This leaves two choices:

  1. Do your sorting entirely in VBA: This works (I've done it) but is pretty involved and messy. Or,

  2. Use a Helper Column with a VBA function: This works but is not fully general, because you have to know ahead of time what the maximum index values will be.

of these, #2 above is by far the simpler option, but it does have limitations (explained below).

Basically what we want is a VBA function that can take a string like "6.6.11.14" and make it always sortable in path index order. The problem with this string is that in text order two digit indexes like ".11" and ".14" come before ".2" rather than after it.

The obvious way to fix this is to fix this is to convert all indexes into 2-digit numbers with leading zeroes. So, 6.6.11.14 would become 06.06.11.14 and crucially 6.6.2.1 would become 06.06.02.01. Now these two path-index values will sort correctly use text sorting.

The catch, however, is that this is only true if each individual index number is never greater than two digits (99). Thus, 06.07.99 sorts correctly, but 06.07.110 does not under this scheme. This is easily fixable by simply raising it from two digits to three digits, but again, the catch is that you have to know this ahead of time.

So assuming that we do know ahead of time what the maximum size/(number of digits) will be for any single index number, we can use the following VBA function to reformat your outline numbers for a helper column:

Public Function OutlineSortingFormat(OutlineNumber As String, Digits As Integer) As String
    Dim PathIndexes() As String
    Dim Zeroes As String
    Dim i As Integer
    
    Zeroes = "0000000000"
    
    PathIndexes = Split(OutlineNumber, ".")
    
    For i = 0 To UBound(PathIndexes)
        PathIndexes(i) = Right(Zeroes & PathIndexes(i), Digits)
    Next i
    
    OutlineSortingFormat = Join(PathIndexes, ".")
End Function

This just splits the outline number into individual numeric strings, prefixes the correct amount of zeroes and then concatenates them back into a sortable outline number.

You then apply this by making a helper column and then using the function like so:

=OutlineSortingFormat(M3,2)

Where M is the column that has your unformatted outline indexes and the second parameter (, 2)) indicates that your want all index numbers filled (and truncated) to 2 digits. Then instead of sorting on your original outline numbers, your sort on the "helper column" containing the reformatted values.

enter image description here

RBarryYoung
  • 55,398
  • 14
  • 96
  • 137
  • Thank you for #2 Helper column method. Is it possible to help us in #1 Sorting entirely in VBA code ? – Punar Jan 03 '22 at 17:18
  • @Punar Well it is a lot of work, is there some reason that the Helper Column is not sufficient? – RBarryYoung Jan 03 '22 at 18:35
  • Thanks, you are right, I changed some names when I was posting it and didn't get everything. I have changed the last `NumberText` to `OutlineNumber` and corrected the function's call name in the excel formula. I have left `OutlineNumber as String` because that was intentional and works as is. – RBarryYoung Jan 04 '22 at 13:44
  • @Punar Please note that I have corrected some mistakes in my code and in the Excel Formula. – RBarryYoung Jan 04 '22 at 13:46
  • @Prema Right, sorry, fixed now. – RBarryYoung Jan 04 '22 at 17:19
  • As a general comment; there is no reason you couldn't just programmatically insert a helper function column, sort then delete it to leave the table with the previous structure – Tragamor Jan 06 '22 at 20:12
2

Manual method

Use the Text to Column function and separate out your headers using "." as a delimiter.

enter image description here

When you are done select all the data as follows:

enter image description here

Perform a sort on the selected data.

enter image description here

Note: My data has headers has been selected and column 6 and 7 come up a A to Z as they are currently empty and it defaults to alphabetical sort as a result. The alphabetical sort can be added by adding a dummy row of data at the start or end of your data to be sorted. This is done by either adding all 0's or a number larger than any number in your list to all columns.

After selecting ok your "Combined" data will be sorted numerically based on the outline numbers to the right.

enter image description here

Forward Ed
  • 9,484
  • 3
  • 22
  • 52
1

Here is some code for multiple purposes.

The first function is a UDF which if wanted could be called from the worksheet to be used as a helper function. Therefore it's easy enough to alter the pad length if required for sorting.

The second code is a little more involved but inserts a column next to the table, adds the helper function, sorts then deletes the helper column to leave the sheet structure as before.

SortColumn should be defined as the column index where the indices are. i.e. if in the first column of the designated table then it would be set to '1'

Public Function PadIndices(Cell As Range, PadLength As Long, Optional Delimiter As String) As String
    If Cell.Count > 1 Then Exit Function
    If Delimiter = "" Then Delimiter = "."
    
    Dim Arr As Variant: Arr = Split(Cell.Value, Delimiter)
    Dim i As Long: For i = LBound(Arr) To UBound(Arr)
        If Len(Arr(i)) < PadLength Then Arr(i) = WorksheetFunction.Rept("0", PadLength - Len(Arr(i))) & Arr(i)
    Next i
    PadIndices = Join(Arr, Delimiter)
End Function

Sub SortByIndices()
    Dim ws As Worksheet: Set ws = ThisWorkbook.Sheets("Sheet1")
    
    Dim Table As Range: Set Table = ws.Range("H7:I11")
    Dim PadLength As Long: PadLength = 2
    Dim SortColumn As Long: SortColumn = 1
    
    Table.Columns(1).Insert Shift:=xlToRight, CopyOrigin:=xlFormatFromLeftOrAbove
    
    Dim SortRange As Range: Set SortRange = Table.Columns(1).Offset(0, -1)
    SortRange.Formula2R1C1 = "=PadIndices(RC[" & SortColumn & "], " & PadLength & ")"
    
    With ws.Sort.SortFields
        .Clear
        .Add2 Key:=SortRange, SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:=xlSortNormal
    End With
    
    With ws.Sort
        .SetRange Application.Union(Table, SortRange)
        .Header = xlGuess
        .MatchCase = False
        .Orientation = xlTopToBottom
        .SortMethod = xlPinYin
        .Apply
    End With
    
    SortRange.Delete Shift:=xlToLeft
    
End Sub
Tragamor
  • 3,594
  • 3
  • 15
  • 32
0

You could make a helper column in which you remove the points and sort that helper column.

=NUMBERVALUE(SUBSTITUTE(E4;".";))

enter image description here

WeAreOne
  • 1,310
  • 1
  • 2
  • 13
0

A) User defined function without the need of a help column

In order to be able to sort outline numbers, you have to bring the individual numerical values to a well defined uniform number format (like e.g. "00" if numbers don't exceed 99 as assumed default; note the flexible String() function in section b)).

This dynamic array approach allows argument inputs of any range definitions (plus optional digit maxima) like

  • =Outline(A5:A10) to sort one column (with a 2-digits default maximum) or even
  • =Outline(A2:E4, 3) over a multicolumn range (with an explicit 3-digits maximum)

Note: tested with the newer dynamic features of Office 2019+/MS365; for backward compatibility you would have to change the TextJoin() function and possibly enter =Outline(...) as array formula using CSE (Ctrl+Shift+Enter).

Function Outline(rng As Range, Optional ByVal digits As Long = 2)
'Date: 2022-01-09
'Auth: https://stackoverflow.com/users/6460297/t-m
'a) create unordered 1-dim array from any contiguous range
    Dim myFormula As String
    myFormula = "TextJoin("","",True," & rng.Address(False, False) & ")"
    Dim codes
    codes = Split(rng.Parent.Evaluate(myFormula), ",")
'b) add leading zeros via number format
    Dim i As Long
    For i = LBound(codes) To UBound(codes)
        Dim tmp: tmp = Split(codes(i), ".")
        Dim ii As Long
        For ii = LBound(tmp) To UBound(tmp)
            tmp(ii) = Format(CInt(tmp(ii)), String(digits, "0"))
        Next ii
        codes(i) = Join(tmp, ".")   ' join to entire string element
        Debug.Print i, codes(i)
    Next i
'c) sort
    BubbleSort codes                ' << help proc BubbleSort
'd) remove leading zeros again
    For i = LBound(codes) To UBound(codes)
        For ii = 1 To digits - 1    ' repeat (digits - 1) times
            codes(i) = Replace(codes(i), ".0", ".")
            If Left(codes(i), 1) = "0" Then codes(i) = Mid(codes(i), 2)
        Next
    Next
'e) return function result
    Outline = Application.Transpose(codes)
End Function

Help procedure BubbleSort

Sub BubbleSort(arr)
'Date: 2022-01-09
'Auth: https://stackoverflow.com/users/6460297/t-m
    Dim cnt As Long, nxt As Long, temp
    For cnt = LBound(arr) To UBound(arr) - 1
        For nxt = cnt + 1 To UBound(arr)
            If arr(cnt) > arr(nxt) Then
                temp = arr(cnt)
                arr(cnt) = arr(nxt)
                arr(nxt) = temp
            End If
        Next nxt
    Next cnt
End Sub


B) Just for fun: alternative single-formula approach (with restricted number range)

Instead of extending the digit formats, I played with the idea to restrict the numeric display by executing a temporary hexadecimal replacement.

Note that this approach based on a single-formula evaluation allows outline sub-numbers only within a numeric range from 1 to 15 (as numbers 10 to 15 get replaced by characters A to F), but might be sufficient for low hierarchy depths! Furthermore it includes a tabular Sort() function available only in Excel version MS365!

Function Outline(rng As Range)
'Site: https://stackoverflow.com/questions/70565436/how-to-sort-outline-numbers-in-numerical-order
'Date: 2022-01-09
'Auth: https://stackoverflow.com/users/6460297/t-m
'Meth: hex replacements + sort; assuming chapters from (0)1 to 15 (10=A,11=B..15=F)
'Note: allows outline sub-numbers only up to 15! Needs Excel version MS365.
    Dim pattern
    pattern = String(6, "X") & "Sort(" & String(6, "X") & "$,15,""F""),14,""E""),13,""D""),12,""C""),11,""B""),10,""A"")),""A"",10),""B"",11),""C"",12),""D"",13),""E"",14),""F"",15)"
    pattern = Replace(Replace(pattern, "$", rng.Address(False, False)), "X", "Substitute(")
    Outline = rng.Parent.Evaluate(pattern)
End Function

T.M.
  • 9,436
  • 3
  • 33
  • 57