Бывает нужно найти необходимое значение не в длинном списке возможных, а в большом списке диапазонов, в один из которых искомое значение входит. В таком случае не помогает промтой перебор всех вариантов, необходимо применять условия сравнения, что значительно замедляет быстродействие.
Наиболее оптимальным решением здесь является метод бинарного поиска в отсортированном массиве, любезно предложенный Microsoft MVP (Excel) Владимиром Захаровым (ZVI).
Ниже следует описание решения одной из задач - поиска страны по ее IP-адресу.
Диапазоны здесь представляют собой набор начальных и конечных ip-адресов каждой подсети. Каждая подсеть принадлежит определенной стране. Смысл - узнать страну по ip-адресу, если он находится в пределах какого-то диапазона. Для этого вычисляются контрольные числа по определенной формуле, которые легче сравнивать.
ipnum = 16777216*w + 65536*x + 256*y + z
где IP Address = w.x.y.z
Сложность задачи в том, что приходится сравнивать число с двумя контрольными числами сразу (начало и конец диапазона). Кроме того, в таблице отсутствуют некоторые диапазоны, так как они никому пока не принадлежат (или в таблицу еще не введены).
Полный список диапазонов в формате csv можно взять с maxmind.com. Следует учесть, что их количество - более 120 тысяч, а это значит, что для расположения на одном листе необходимо использовать Excel 2007 или 2010.
Поиск в полной базе диапазонов 50 тысяч различных ip-адресов составляет прмерно 1 с. Что является отличным результатом.
Код:
Option Explicit
' ZVI:2010-07-24 Find the countries by sorted numeric IPs
Sub Ip2Country()
Dim Rng As Range, a, b, c, d$(), i&, r&, Ub&, v#, x
' Screen&Events off
With Application
.ScreenUpdating = False
.EnableEvents = False
End With
' Copy Beginning & Ending IP Number into a(), Country - into b()
With Sheets("Data")
If .FilterMode Then .ShowAllData
Set Rng = .Range(.Cells(Rows.Count, "C").End(xlUp), "D2")
' Sort by Beginning IP Number
Rng.CurrentRegion.Sort .Cells(1, "C"), 1, Header:=xlYes
a = Rng.Value
Ub = UBound(a)
b = Rng.Columns(4).Value
End With
' Copy IP into c(), create resulting d()
With Sheets("History")
If .FilterMode Then .ShowAllData
Set Rng = .Range("F2", .Cells(Rows.Count, "F").End(xlUp))
c = Rng.Value
ReDim d(1 To UBound©, 1 To 1)
End With
' Main
For Each x In c
r = r + 1
v = Ip2Num(x)
i = BinSearch(v, a, 1, Ub, 1)
If v >= a(i, 1) And v <= a(i, 2) Then d(r, 1) = b(i, 1)
Next
' Copy the result from d()
Rng.EntireRow.Columns("J").Value = d()
' Screen&Events on
With Application
.EnableEvents = True
.ScreenUpdating = True
End With
End Sub
' ZVI:2010-07-24 Fast binary Search of x in sorted array a()
Function BinSearch(x, a, Lb1&, Ub1&, Ub2&) As Long
Dim i&, Lb&, Ub&, b&
Lb = Lb1: Ub = Ub1
Do
i = (Lb + Ub) \ 2
If a(i, Ub2) = x Then
BinSearch = i
Exit Function
ElseIf a(i, Ub2) > x Then
b = 0: Ub = i - 1
Else
b = 1: Lb = i + 1
End If
Loop Until Lb > Ub
BinSearch = i + b - 1
End Function
' ZVI:2010-07-24 Fast converting of string IP to the numeric one
Function Ip2Num(Ip) As Double
Dim s$, i&, ii&, iii&
s = Ip
i = InStr(1, s, ".")
Ip2Num = 16777216 * CDbl(Mid$(s, 1, i - 1))
ii = InStr(i + 1, s, ".")
Ip2Num = Ip2Num + 65536 * CDbl(Mid$(s, i + 1, ii - i - 1))
iii = InStr(ii + 1, s, ".")
Ip2Num = Ip2Num + 256 * CDbl(Mid$(s, ii + 1, iii - ii - 1)) + CDbl(Mid$(s, iii + 1))
End Function
Наиболее оптимальным решением здесь является метод бинарного поиска в отсортированном массиве, любезно предложенный Microsoft MVP (Excel) Владимиром Захаровым (ZVI).
Ниже следует описание решения одной из задач - поиска страны по ее IP-адресу.
Диапазоны здесь представляют собой набор начальных и конечных ip-адресов каждой подсети. Каждая подсеть принадлежит определенной стране. Смысл - узнать страну по ip-адресу, если он находится в пределах какого-то диапазона. Для этого вычисляются контрольные числа по определенной формуле, которые легче сравнивать.
ipnum = 16777216*w + 65536*x + 256*y + z
где IP Address = w.x.y.z
Сложность задачи в том, что приходится сравнивать число с двумя контрольными числами сразу (начало и конец диапазона). Кроме того, в таблице отсутствуют некоторые диапазоны, так как они никому пока не принадлежат (или в таблицу еще не введены).
Полный список диапазонов в формате csv можно взять с maxmind.com. Следует учесть, что их количество - более 120 тысяч, а это значит, что для расположения на одном листе необходимо использовать Excel 2007 или 2010.
Поиск в полной базе диапазонов 50 тысяч различных ip-адресов составляет прмерно 1 с. Что является отличным результатом.
Код:
Option Explicit
' ZVI:2010-07-24 Find the countries by sorted numeric IPs
Sub Ip2Country()
Dim Rng As Range, a, b, c, d$(), i&, r&, Ub&, v#, x
' Screen&Events off
With Application
.ScreenUpdating = False
.EnableEvents = False
End With
' Copy Beginning & Ending IP Number into a(), Country - into b()
With Sheets("Data")
If .FilterMode Then .ShowAllData
Set Rng = .Range(.Cells(Rows.Count, "C").End(xlUp), "D2")
' Sort by Beginning IP Number
Rng.CurrentRegion.Sort .Cells(1, "C"), 1, Header:=xlYes
a = Rng.Value
Ub = UBound(a)
b = Rng.Columns(4).Value
End With
' Copy IP into c(), create resulting d()
With Sheets("History")
If .FilterMode Then .ShowAllData
Set Rng = .Range("F2", .Cells(Rows.Count, "F").End(xlUp))
c = Rng.Value
ReDim d(1 To UBound©, 1 To 1)
End With
' Main
For Each x In c
r = r + 1
v = Ip2Num(x)
i = BinSearch(v, a, 1, Ub, 1)
If v >= a(i, 1) And v <= a(i, 2) Then d(r, 1) = b(i, 1)
Next
' Copy the result from d()
Rng.EntireRow.Columns("J").Value = d()
' Screen&Events on
With Application
.EnableEvents = True
.ScreenUpdating = True
End With
End Sub
' ZVI:2010-07-24 Fast binary Search of x in sorted array a()
Function BinSearch(x, a, Lb1&, Ub1&, Ub2&) As Long
Dim i&, Lb&, Ub&, b&
Lb = Lb1: Ub = Ub1
Do
i = (Lb + Ub) \ 2
If a(i, Ub2) = x Then
BinSearch = i
Exit Function
ElseIf a(i, Ub2) > x Then
b = 0: Ub = i - 1
Else
b = 1: Lb = i + 1
End If
Loop Until Lb > Ub
BinSearch = i + b - 1
End Function
' ZVI:2010-07-24 Fast converting of string IP to the numeric one
Function Ip2Num(Ip) As Double
Dim s$, i&, ii&, iii&
s = Ip
i = InStr(1, s, ".")
Ip2Num = 16777216 * CDbl(Mid$(s, 1, i - 1))
ii = InStr(i + 1, s, ".")
Ip2Num = Ip2Num + 65536 * CDbl(Mid$(s, i + 1, ii - i - 1))
iii = InStr(ii + 1, s, ".")
Ip2Num = Ip2Num + 256 * CDbl(Mid$(s, ii + 1, iii - ii - 1)) + CDbl(Mid$(s, iii + 1))
End Function