Searching Algorithms
20 min In ProgressLearning Objectives
- Choose between linear and binary search based on whether the data is sorted
- Implement LinearSearcher and BinarySearcher as reusable OOP classes
- Apply both to real security examples: port lists, IP blacklists
The Big Picture
Searching means finding whether something is in a collection and where. In security scripting this comes up constantly: is port 4444 in the list of open ports on this host? Is this IP in the known-bad blacklist? Is this CVE ID in the patched list? Two algorithms cover 95% of cases: Linear (works on anything, O(n)) and Binary (lightning fast on sorted data, O(log n)).
Linear Search — Check Every Item
Walk through the list from the beginning, compare each item to the target, return the position if found. Simple. Works on any list, sorted or not. The downside: worst case you look at every element — O(n). Fine for small or unsorted lists; slow for large sorted ones.
class linear_searcher:
def __init__(self, items):
self.items = items
def find(self, target):
for index, item in enumerate(self.items):
if item == target:
return index
return -1
def contains(self, target):
return self.find(target) != -1
# Security example: is a suspicious port in the open-port scan list?
open_ports = [80, 443, 22, 8080, 3389, 4444, 5900]
SUSPICIOUS = {4444, 1337, 31337, 5900}
scanner = linear_searcher(open_ports)
print('Open port audit:')
for port in open_ports:
flag = ' <- SUSPICIOUS' if port in SUSPICIOUS else ''
idx = scanner.find(port)
print(f' index {idx}: port {port}{flag}')
print(f'\nPort 4444 found: {scanner.contains(4444)}')
print(f'Port 53 found: {scanner.contains(53)}')
Binary Search — Halve the Space
Binary search requires a sorted list. It works by looking at the middle element: if the target is smaller, search the left half; if larger, search the right half. Each comparison cuts the remaining search space in half — O(log n). A million-item sorted blacklist takes at most 20 comparisons.
class binary_searcher:
def __init__(self, items):
self.items = sorted(items)
def find(self, target):
low, high = 0, len(self.items) - 1
while low <= high:
mid = (low + high) // 2
if self.items[mid] == target:
return mid
elif target < self.items[mid]:
high = mid - 1
else:
low = mid + 1
return -1
def contains(self, target):
return self.find(target) != -1
# Security example: is this IP in the known-bad blacklist?
blacklist = [
'10.0.0.5', '10.0.0.12', '10.0.0.20', '10.0.0.33',
'10.0.0.50', '10.0.0.99', '192.168.50.1',
]
checker = binary_searcher(blacklist)
test_ips = ['10.0.0.20', '10.0.0.21', '192.168.50.1', '8.8.8.8']
print('IP blacklist check:')
for ip in test_ips:
found = checker.contains(ip)
status = 'BLOCKED' if found else 'ALLOWED'
print(f' {ip:18} -> {status}')
print(f'\nList is pre-sorted internally: {checker.items[:4]} ...')
📝 Exercise: CVE Lookup — Linear vs Binary
You have a list of CVE IDs (already sorted). Implement both a linear_searcher and binary_searcher version of a function that checks whether a given CVE is in the patched list. Then count how many comparisons each approach would theoretically need to find the last item in the list (linear = n, binary = log2(n) rounded up).