# A Practical Example For HoledRange

Someone asked me a non mathematical example of using the Domain thing, so here goes.

Let's imagine I am writing a network mapper or a load balancer of some description. I have a range of IPs that are available and I want to pick a few from them, or check that an IP is in a white or black list that's handled by ranges.

First I have to define what an IP address is (I went IPV4 because it's shorter): a group of 4 numbers from 0 to 255 (0 and 255 are "special", but it only affects a tiny portion of the code). I want to build them from that representation, strings (because we love strings) and a 32 bits number because that's what it ultimately is:

struct IPAddress {
var group1: UInt8
var group2: UInt8
var group3: UInt8
var group4: UInt8

init(_ c1: UInt8, _ c2: UInt8, _ c3: UInt8, _ c4: UInt8) {
group1 = c1
group2 = c2
group3 = c3
group4 = c4
}

init?(_ s: String) {
let comps = s.components(separatedBy: ".")
if comps.count != 4 { return nil }
if let c1 = UInt8(comps[0]), let c2 = UInt8(comps[1]), let c3 = UInt8(comps[2]), let c4 = UInt8(comps[3]) {
group1 = c1
group2 = c2
group3 = c3
group4 = c4
} else {
return nil
}
}

init(_ ip: UInt32) {
let c1 = ip >> 24
let c2 = (ip >> 16) & 0x00ff
let c3 = (ip >> 8) & 0x0000ff
let c4 = ip & 0x000000ff
group1 = UInt8(c1)
group2 = UInt8(c2)
group3 = UInt8(c3)
group4 = UInt8(c4)
}

var asString : String {
return "\(group1).\(group2).\(group3).\(group4)"
}

var asUInt32 : UInt32 {
let c1 = UInt32(group1) << 24
let c2 = UInt32(group2) << 16
let c3 = UInt32(group3) << 8
let c4 = UInt32(group4)

return c1 | c2 | c3 | c4
}
}

I also included to string and to 32 bits because I could, and because it's generally good practice to be able to output what you accept as input.

If I want to make a domain out of that, they need to be Hashable, and Comparable:

extension IPAddress : Equatable, Comparable, Hashable {
static func == (lhs : Self, rhs : Self) -> Bool {
return lhs.group1 == rhs.group1 && lhs.group2 == rhs.group2 && lhs.group2 == rhs.group3 && lhs.group4 == rhs.group4
}
static func != (lhs : Self, rhs : Self) -> Bool {
return !(lhs == rhs)
}
static func < (lhs : Self, rhs : Self) -> Bool {
if lhs.group1 < rhs.group1 { return true }
else if lhs.group1 == rhs.group1 && lhs.group2 < rhs.group2 { return true }
else if lhs.group1 == rhs.group1 && lhs.group2 == rhs.group2 && lhs.group3 < rhs.group3 { return true }
else if lhs.group1 == rhs.group1 && lhs.group2 == rhs.group2 && lhs.group3 == rhs.group3 && lhs.group4 < rhs.group4 { return true }
else { return false }
}
static func <= (lhs : Self, rhs : Self) -> Bool {
return lhs == rhs || lhs < rhs
}
static func > (lhs : Self, rhs : Self) -> Bool {
if lhs.group1 > rhs.group1 { return true }
else if lhs.group1 == rhs.group1 && lhs.group2 > rhs.group2 { return true }
else if lhs.group1 == rhs.group1 && lhs.group2 == rhs.group2 && lhs.group3 > rhs.group3 { return true }
else if lhs.group1 == rhs.group1 && lhs.group2 == rhs.group2 && lhs.group3 == rhs.group3 && lhs.group4 > rhs.group4 { return true }
else { return false }

}
static func >= (lhs : Self, rhs : Self) -> Bool {
return lhs == rhs || lhs > rhs
}

func hash(into: inout Hasher) {
into.combine(group1)
into.combine(group2)
into.combine(group3)
into.combine(group4)
}

var hashValue: Int {
return Int(self.asUInt32)
}
}

This stuff isn't quite boilerplate, but not very far from it. Comparing two IP addresses is about comparing the 4 groups of numbers, in order of importance.

Finally, because I want to sample my ranges, I'd like a Randomizable implementation as well, which is more interesting than it seemed at first glance:

extension IPAddress : Randomizable {
static func randomElement() -> IPAddress? {

let c1 = UInt8.random(in: 1...254)
let c2 = UInt8.random(in: 1...254)
let c3 = UInt8.random(in: 1...254)
let c4 = UInt8.random(in: 1...254)

return IPAddress(c1,c2,c3,c4)

}
static func randomElement(in r: ClosedRange<IPAddress>) -> IPAddress? {
let lb = r.lowerBound
let ub = r.upperBound

if lb == ub { return lb }
if ub < lb { return nil }

let c1 = UInt8.random(in: lb.group1...ub.group1)
let c2: UInt8
let c3 : UInt8
let c4 : UInt8

if c1 == lb.group1 || c1 == ub.group1 { // special case , because we have to check the more minor parts of the group as well
if c1 == lb.group1 && c1 == ub.group1 { // same group 1 for upper and lower bounds
c2 = UInt8.random(in: lb.group2...ub.group2)
} else if c1 == lb.group1 {
c2 = UInt8.random(in: lb.group2...254)
} else { //  if c1 == ub.group1
c2 = UInt8.random(in: 1...ub.group2)
}
// same problem, again
if c2 == lb.group2 || c2 == ub.group2 {
if c2 == lb.group2 && c2 == ub.group2 {
c3 = UInt8.random(in: lb.group3...ub.group3)
} else if c2 == lb.group2 {
c3 = UInt8.random(in: lb.group3...254)
} else {
c3 = UInt8.random(in: 1...ub.group3)
}
// and finally
if c3 == lb.group3 || c3 == ub.group3 {
if c3 == lb.group3 && c3 == ub.group3 {
c4 = UInt8.random(in: lb.group4...ub.group4)
} else if c3 == lb.group3 {
c4 = UInt8.random(in: lb.group4...254)
} else {
c4 = UInt8.random(in: 1...ub.group4)
}
} else {
c4 = UInt8.random(in: 1...254)
}
} else {
c3 = UInt8.random(in: 1...254)
c4 = UInt8.random(in: 1...254)
}
} else {
c2 = UInt8.random(in: 1...254)
c3 = UInt8.random(in: 1...254)
c4 = UInt8.random(in: 1...254)

}

return IPAddress(c1, c2, c3, c4)
}
}

As for why random IPs don't contain 0 or 255, it's because they are special in some cases and I wanted to spare myself the headache. Note the hierarchy of groups as well for randomization...

Finally a test program:

       if let ip = IPAddress.randomElement(), let ip2 = IPAddress.randomElement() {
let mi = min(ip,ip2)
let ma = max(ip,ip2)
let d = Domain(mi...ma)
if let ip3 = d.randomSample(5) {
print("["+ip.asString+";"+ip2.asString+"] => \(ip3.map({ $0.asString }))") } } let d = Domain(IPAddress(192,168,125,1)...IPAddress(192,168,125,10)) if let ip3 = d.randomSample(5) { print("["+d.lowerBound!.asString+";"+d.upperBound!.asString+"] => \(ip3.map({$0.asString }))")
}
[58.47.159.20;110.200.52.86] => ["92.196.40.9", "89.184.245.202", "69.19.204.59", "101.41.128.20", "74.231.21.246"]
[192.168.125.1;192.168.125.10] => ["192.168.125.4", "192.168.125.1", "192.168.125.9", "192.168.125.3", "192.168.125.10"]

Seems legit.