例如,我想要这 6 个数字。
目前,当我使用排序方法时,它会将 6.6.1.1.13 放在首位,然后将 6.6.1.1.2 放在后面。
排序前
6.6.1.1
6.6.1.1.1
6.6.1.1.13
6.6.11.14
► 6.6.1.1.2
我希望它在排序后看起来像什么
6.6.1.1
6.6.1.1.1
► 6.6.1.1.2
6.6.1.1.13
6.6.11.14
例如,我想要这 6 个数字。
目前,当我使用排序方法时,它会将 6.6.1.1.13 放在首位,然后将 6.6.1.1.2 放在后面。
排序前
6.6.1.1
6.6.1.1.1
6.6.1.1.13
6.6.11.14
► 6.6.1.1.2
我希望它在排序后看起来像什么
6.6.1.1
6.6.1.1.1
► 6.6.1.1.2
6.6.1.1.13
6.6.11.14
不幸的是,对大纲数字(或更正式地说,“路径索引”)进行排序的唯一完全通用的方法是使用自定义比较功能。不幸的是,Excel 排序操作和函数不支持这样的功能(甚至不支持 VBA)。Excel 首选方式是使用自定义列表,但这些不适用于路径索引。
这留下了两个选择:
完全在 VBA 中进行排序:这行得通(我已经完成了),但是非常复杂且混乱。或者,
使用带有 VBA 函数的辅助列:这可行,但并不完全通用,因为您必须提前知道最大索引值是多少。
其中,上面的#2 是迄今为止更简单的选项,但它确实有局限性(如下所述)。
基本上我们想要的是一个 VBA 函数,它可以采用像“6.6.11.14”这样的字符串并使其始终按路径索引顺序排序。这个字符串的问题是,在文本顺序中,两位数的索引像".11"
和".14"
在它之前 ".2"
而不是在它之后。
解决此问题的明显方法是将所有索引转换为带有前导零的 2 位数字。因此,6.6.11.14
将成为06.06.11.14
,并且至关重要地6.6.2.1
将成为06.06.02.01
。现在这两个路径索引值将使用文本排序正确排序。
然而,问题在于,只有当每个单独的索引号从不大于两位数( 99
) 时,这才是正确的。因此,06.07.99
排序正确,但不在06.07.110
此方案下。这很容易解决,只需将其从两位数提高到三位数,但同样,问题是您必须提前知道这一点。
因此,假设我们提前知道任何单个索引号的最大大小/(位数),我们可以使用以下 VBA 函数重新格式化帮助列的大纲数字:
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
这只是将大纲编号拆分为单独的数字字符串,为正确数量的零添加前缀,然后将它们连接回可排序的大纲编号。
然后,您可以通过创建一个帮助列然后使用如下函数来应用它:
=OutlineSortingFormat(M3,2)
M
具有未格式化大纲索引的列在哪里,第二个参数 ( , 2)
) 表示您希望将所有索引号填充(并截断)为 2 位数字。然后,不是对原始大纲数字进行排序,而是对包含重新格式化的值的“帮助列”进行排序。
这是一些用于多种目的的代码。
第一个函数是一个 UDF,如果需要,可以从工作表中调用它以用作辅助函数。因此,如果需要排序,可以很容易地更改焊盘长度。
第二个代码涉及更多一点,但在表格旁边插入一列,添加辅助函数,排序然后删除辅助列以保持工作表结构与以前一样。
SortColumn 应定义为索引所在的列索引。即如果在指定表的第一列中,那么它将被设置为'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
A) 无需帮助栏的用户定义功能
为了能够对大纲数字进行排序,您必须将各个数值转换为明确定义的统一数字格式 (例如"00"
,如果数字不超过 99 作为假定的默认值;请注意节中的灵活String()
功能b)
)。
这种动态数组方法允许任何 范围定义(加上可选的 数字最大值)的参数输入,例如
=Outline(A5:A10)
对一列进行排序(默认最大值为 2 位)甚至=Outline(A2:E4, 3)
在多列范围内(明确的最多 3 位)注意: 使用 Office 2019+/MS365 的较新动态功能进行测试;为了向后兼容,您必须更改TextJoin()
函数并可能使用 CSE ( + +Enter)=Outline(...)
作为数组公式输入。CtrlShift
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
帮助程序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)只是为了好玩:替代的单一公式方法 (有限的数字范围)
我没有扩展数字格式,而是尝试通过执行临时十六进制替换来限制数字显示。
请注意,这种基于单一公式评估的方法仅允许在1 到 15
的数字范围内的轮廓子编号(因为数字 10 到 15 被字符 A 到 F 替换),但对于低层次深度可能就足够了!此外,它还包括一个Sort()
仅在 Excel 版本 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