#vba #ms-access
#vba #ms-access
Вопрос:
Вот мой код. Я беру следующие обозначения и сокращаю их до AR2-AR4, AR15, AT3-AT4, C68, C76, C316, C319, FL14-FL18, J1-J6, L2-5 и т.д. Все это работает хорошо, за исключением случаев, когда фильтр применяет «L», в котором он возвращает FL14, FL15, FL16, FL17, FL8, L2, L3, L4, L5. Мне нужен способ сделать точное совпадение символов или что-то в этом роде.
Sub FormatAsRanges()
Dim Lne As String, arr, s
Dim n As Long, v As Long, prev As Long, inRange As Boolean
Dim test As String
Dim x As Variant
Dim filterarray As Variant
inRange = False
Lne = "AR15,AR2,AR3,AR4,AT3,AT4,C316,C319,C68,C76,FL14,FL15,FL16,FL17,FL18,FL6,J1,J2,J3,J4,J5,J6,L2,L3,L4,L5,T4,T5,T6,U38"
arr = Split(Lne, ",") 'Break apart references into array items
x = Prefix(arr) 'Get the Prefix's (AR,AT,C,FL,J,L,T,U)
x = Split(x, ",") ' Split them in an array
For j = 0 To UBound(x)
inRange = False 'Initialize to False
arr = Split(Lne, ",") ' Redifine arr since it is being filtered and use in the j loop for each prefix
filterarray = Filter(arr, x(j)) ' Apply filter
For i = 0 To UBound(filterarray)
filterarray(i) = Replace(filterarray(i), x(j), "")
Next i
arr = ArraySort(filterarray)
prev = -999 'dummy value
For n = LBound(filterarray) To UBound(filterarray)
v = CLng(filterarray(n))
If v - prev = 1 Then 'starting or continuing a range?
inRange = True 'wait until range ends before adding anything
Else
If inRange Then 'ending a range ?
s = s amp; "-" amp; x(j) amp; prev 'close out current range with previous item
inRange = False
End If
s = s amp; IIf(Len(s) > 0, ",", "") amp; x(j) amp; v 'add the current item
End If
prev = v
Next n
If inRange Then s = s amp; "-" amp; x(j) amp; prev 'close out last item if in a range
Debug.Print s
s = Empty
filterarray = Empty
Next j
End Sub
Function ArraySort(MyArray As Variant)
Dim First As Long, last As Long
Dim i As Long, j As Long, Temp
First = LBound(MyArray)
last = UBound(MyArray)
For i = First To last - 1
For j = i 1 To last
If CLng(MyArray(i)) > CLng(MyArray(j)) Then
Temp = MyArray(j)
MyArray(j) = MyArray(i)
MyArray(i) = Temp
End If
Next j
Next i
ArraySort = MyArray
End Function
'get the character prefix (up to the first digit)
Public Function Prefix(a As Variant)
Dim rv As String, c As String, i As Long, j As Long, k As Integer, Prf As String
Dim flt(10) As String
Prf = "*" 'Initialize string
k = 0 'initialize
For j = 0 To UBound(a)
If InStr(a(j), Prf) Then
'Debug.Print "Yes"
Else
Prf = Empty
For i = 0 To Len(a(j))
c = Mid(a(j), i 1, 1)
If c Like "#" Then
Exit For
Else
rv = rv amp; c
End If
Next i
Prf = rv
flt(k) = Prf
k = k 1
rv = Empty
End If
Next j
For l = 0 To UBound(flt) 'Output as string so to define an array that is the correct size in the main program
If flt(l) Like "?" Then
rtn = rtn flt(l) ","
ElseIf flt(l) Like "??" Then
rtn = rtn flt(l) ","
ElseIf flt(l) Like "???" Then
rtn = rtn flt(l) ","
End If
Next l
rtn = Left(rtn, Len(rtn) - 1)
Prefix = rtn
End Function
Комментарии:
1. «для точного сопоставления символов или чего-то еще» — это что-то совершенно непонятное. По крайней мере, для меня… Вы ожидаете, что мы выведем из вашего кода, который «сталкивается с проблемой», то, что вы действительно хотите выполнить? Никому не нравится тратить свое время на такую деятельность… Я бы посоветовал вам отредактировать вопрос и попытаться точно объяснить, что вы пытаетесь сделать, в сравнении с тем, что вы получаете.
2. Вы пытаетесь перегруппировать смежные диапазоны в одном и том же диапазоне? Вы можете сделать это с помощью объединения:
Set r = Range("AR15,AR2,AR3,AR4,AT3,AT4,C316,C319,C68,C76,FL14,FL15,FL16,FL17,FL18,FL6,J1,J2,J3,J4,J5,J6,L2,L3,L4,L5,T4,T5,T6,U38"): Set r = Union(r, r): Debug.Print r.Address(False, False)
вернетAR15,AR2:AR4,AT3:AT4,C316,C319,C68,C76,FL14:FL18,FL6,J1:J6,L2:L5,T4:T6,U38
3. Код работает нормально, удаляя префикс с оставшимися числами. Например, когда применяется фильтр «AR», он найдет только ссылки с AR, в данном случае AR15, AR2, AR3, AR4. Затем он удаляет AR и возвращает числа, которые попадают в пузырьковую сортировку, так что из пузырьковой сортировки получается 2,3,4,15. Затем он возвращается к основной программе для диапазонов, в данном случае 2,3,4, и снова включает префикс AR и показывает AR2-AR4, а затем, поскольку 15 само по себе является AR15. Таким образом, общая возвращаемая строка равна AR2-AR4, AR5. Все в порядке с фильтром до «L». Поскольку «L» находится в «FL», он возвращается…
4. это тоже, и мне нужен способ, чтобы он различал префикс L от префикса FL.
5.
Filter
редко бывает настолько полезным, потому что он всегда фильтрует по подстрокам. Вы должны написать функцию, которая будет фильтровать по префиксу и возвращать массив только из этих типов совпадений.
Ответ №1:
Вы можете переместить больше кода в отдельные методы:
Sub Tester()
Dim Lne As String, arr, allPrefixes, arrFilt, arrSorted, s, prefix
Lne = "AR15,AR2,AR3,AR4,AT3,AT4,C316,C319,C68,C76,FL14,FL15," amp; _
"FL16,FL17,FL18,FL6,J1,J2,J3,J4,J5,J6,L2,L3,L4,L5,T4,T5,T6,U38"
arr = Split(Lne, ",") 'split to an array
allPrefixes = UniquePrefixes(arr) 'All unique character prefixes
Debug.Print "All prefixes: " amp; Join(allPrefixes, ",")
'process each prefix in turn
For Each prefix In allPrefixes
arrFilt = FilterPrefixNumbers(arr, prefix) 'items for this prefix (numbers only)
Debug.Print , "'" amp; prefix amp; "' items:", Join(arrFilt, ",")
arrSorted = ArraySort(arrFilt) 'numeric parts, sorted ascending
Debug.Print , "Sorted:", Join(arrSorted, ",")
s = s amp; iif(s<>"", ",", "") amp; FormatAsRanges(arrSorted, prefix)
'Debug.Print FormatAsRanges(arrSorted, prefix)
Next prefix
Debug.Print s 'the whole thing
End Sub
Function FormatAsRanges(arr, prefix) As String
Dim s As String, n As Long, v As Long, prev As Long, inRange As Boolean
prev = -999 'dummy value
For n = LBound(arr) To UBound(arr)
v = CLng(arr(n))
If v - prev = 1 Then 'starting or continuing a range?
inRange = True 'wait until range ends before adding anything
Else
If inRange Then 'ending a range ?
s = s amp; "-" amp; prefix amp; prev 'close out current range with previous item
inRange = False
End If
s = s amp; IIf(Len(s) > 0, ",", "") amp; prefix amp; v 'add the current item
End If
prev = v
Next n
If inRange Then s = s amp; "-" amp; prefix amp; prev 'close out last item if in a range
FormatAsRanges = s
End Function
Function ArraySort(MyArray As Variant)
Dim First As Long, last As Long
Dim i As Long, j As Long, Temp
First = LBound(MyArray)
last = UBound(MyArray)
For i = First To last - 1
For j = i 1 To last
If CLng(MyArray(i)) > CLng(MyArray(j)) Then
Temp = MyArray(j)
MyArray(j) = MyArray(i)
MyArray(i) = Temp
End If
Next j
Next i
ArraySort = MyArray
End Function
'return an array *of numbers* from all items in "arr" with the given prefix
Function FilterPrefixNumbers(arr, prefix)
Dim rv(), e, n As Long
ReDim rv(LBound(arr) To UBound(arr))
n = LBound(arr)
For Each e In arr
If GetPrefix(CStr(e)) = prefix Then
rv(n) = Replace(e, prefix, "") 'return just the numeric parts...
n = n 1
End If
Next e
ReDim Preserve rv(LBound(arr) To n - 1) 'shrink to remove any empty slots
FilterPrefixNumbers = rv
End Function
'all unique character prefixes
Function UniquePrefixes(arr)
Dim dict, e
Set dict = CreateObject("scripting.dictionary")
For Each e In arr
dict(GetPrefix(CStr(e))) = True
Next e
UniquePrefixes = dict.keys
End Function
'get the character prefix (all non-digit characters preceding the first digit)
Function GetPrefix(v As String) As String
Dim rv As String, c As String, i As Long
For i = 1 To Len(v)
c = Mid(v, i, 1)
If c Like "#" Then
Exit For
Else
rv = rv amp; c
End If
Next i
GetPrefix = rv
End Function
Комментарии:
1. Тим, это отлично работает. Единственное, что мне нужно, это чтобы все это возвращалось в одну строку. Я пытался, но это выглядит не так хорошо, как те, у которых один общий префикс.
2. См. Мою правку выше — собрать все это вместе не так уж и сложно.
3. Спасибо за помощь. возможно, это не скачок для вас, но это для меня. Я изучаю то, что вы сделали, поэтому в следующий раз мне не нужно задавать так много вопросов.
4. Тим, не заданный вопрос, могут ли ключи словаря выводиться в запрос в access, например, по 1 строке на ключ. Не могу ничего найти на нем.
5. Не уверен, что именно вы спрашиваете — вы говорите о вставке данных или запросе?
Ответ №2:
Устраните функции Filter() и Replace() . Учитывая, что входные данные уже отсортированы в алфавитном порядке по префиксу, работает следующая пересмотренная процедура:
Sub FormatAsRanges()
Dim Lne As String, arr, s
Dim n As Long, v As Long, prev As Long
Dim inRange As Boolean
Dim j As Integer, i As Integer
Dim x As Variant
Dim filterarray As Variant
Lne = "AR15,AR2,AR3,AR4,AT3,AT4,C316,C319,C68,C76,FL14,FL15,FL16,FL17,FL18,FL6,J1,J2,J3,J4,J5,J6,L2,L3,L4,L5,T4,T5,T6,U38"
arr = Split(Lne, ",") 'Break apart references into array items
x = Split(Prefix(arr), ",") 'Get the Prefix's (AR,AT,C,FL,J,L,T,U)
For j = 0 To UBound(x)
inRange = False 'Initialize to False
Do While arr(i) Like x(j) amp; "*" And i <= UBound(arr)
If arr(i) Like x(j) amp; "*" Then
s = s amp; Mid(arr(i), Len(x(j)) 1) amp; ","
If i = UBound(arr) Then
Exit Do
Else
i = i 1
End If
End If
Loop
If Right(s, 1) = "," Then s = Left(s, Len(s) - 1)
filterarray = ArraySort(Split(s, ","))
prev = -999 'dummy value
s = ""
For n = LBound(filterarray) To UBound(filterarray)
v = CLng(filterarray(n))
If v - prev = 1 Then 'starting or continuing a range?
inRange = True 'wait until range ends before adding anything
Else
If inRange Then 'ending a range ?
s = s amp; "-" amp; x(j) amp; prev 'close out current range with previous item
inRange = False
End If
s = s amp; IIf(Len(s) > 0, ",", "") amp; x(j) amp; v 'add the current item
End If
prev = v
Next n
If inRange Then s = s amp; "-" amp; x(j) amp; prev 'close out last item if in a range
Debug.Print s
s = Empty
filterarray = Empty
Next j
End Sub