一种可能的方法是递归二分搜索。
子网是一个 IP 地址范围,因此第一步是开发将子网转换为范围并再次返回的函数(在您的示例中,您只显示了 IPv4 地址,所以我将使用整数来表示范围):
function subnetToRange($subnet)
{
list($ip_address, $mask_length) = explode('/', $subnet);
$ip = ip2long($ip_address);
$mask_right = pow(2, 32 - $mask_length) - 1;
$mask_left = 0xffffffff ^ $mask_right;
$ip_low = $ip & $mask_left;
$ip_high = $ip_low | $mask_right;
$range = [$ip_low, $ip_high];
return $range;
}
function rangeToSubnet(array $range)
{
$subnet = sprintf(
'%s/%d',
long2ip($range[0]),
32 - log($range[1] - $range[0] + 1, 2)
);
return $subnet;
}
现在我们正在处理由下限和上限表示的范围,拆分范围的过程涉及创建两个较小的范围,一个覆盖原始范围的下半部分,另一个覆盖上半部分,例如:如果原始范围是 8 到 15,则两个新范围将是 8 到 11 和 12 到 15。
鉴于此,我们可以实现一种递归二分搜索形式,从父范围开始,然后如果它与任何使用的范围重叠,则递归地将其分成两半,直到找到完全未使用的范围,或完全使用的范围:
function findUnusedSubnets($parent, array $used)
{
$parent = subnetToRange($parent);
$used = array_map('subnetToRange', $used);
$unused = array();
findUnusedRanges($parent, $used, $unused);
$unused = array_map('rangeToSubnet', $unused);
return $unused;
}
function findUnusedRanges($subnet, $used, &$unused)
{
foreach ($used as $range) {
if ($subnet[0] > $range[1] || $subnet[1] < $range[0]) {
continue; // subnet does not overlap used range
}
if ($subnet[0] >= $range[0] && $subnet[1] <= $range[1]) {
return; // subnet completely within used range
}
// split subnet into two smaller subnets and check again
$split = ($subnet[1] - $subnet[0] - 1) >> 1;
findUnusedRanges([$subnet[0], $subnet[0] + $split], $used, $unused);
findUnusedRanges([$subnet[1] - $split, $subnet[1]], $used, $unused);
return;
}
$unused[] = $subnet; // subnet does not overlap any used range
}
结合这四个功能将允许您找到未使用的子网:
$parent = '10.0.0.0/24';
$used = [
'10.0.0.0/29',
'10.0.0.8/29',
'10.0.0.144/29'
];
var_dump(findUnusedSubnets($parent, $used));
给出输出(用 php 5.6 测试):
array(7) {
[0]=>
string(12) "10.0.0.16/28"
[1]=>
string(12) "10.0.0.32/27"
[2]=>
string(12) "10.0.0.64/26"
[3]=>
string(13) "10.0.0.128/28"
[4]=>
string(13) "10.0.0.152/29"
[5]=>
string(13) "10.0.0.160/27"
[6]=>
string(13) "10.0.0.192/26"
}