Getting available CIDR netblocks in PHP - php

Given a parent subnet and a number of "used" subnets, what would be the best possible method/algorithm to determine "unused" subnets?
For example, given:
$parent = '10.0.0.0/24';
$used = [
'10.0.0.0/29',
'10.0.0.8/29',
'10.0.0.144/29'
];
The program would need to come up with the smallest array of unused subnets:
$unused = [
'10.0.0.16/28',
'10.0.0.32/27',
'10.0.0.64/26',
'10.0.0.128/28',
'10.0.0.152/29',
'10.0.0.160/27',
'10.0.0.192/26'
];
I was planning on using the following pseudocode to achieve this:
create array $unused
add $parent to $unused
iterate through used subnets:
if $used in $unused (exact match):
remove from $unused
else
find subnet in $unused that contains $used
split into smaller subnets
However, I'm unable to figure out exactly how to do the split into smaller subnets part. If anyone could advise the best possible way to do this, or a better/more efficient method, it would be greatly appreciated!

One possible approach would be a form of recursive binary search.
A subnet is a range of IP addresses, so a first step would be developing functions to convert subnets to ranges and back again (in your example you've only shown IPv4 addresses, so I'm going to use integers to represent the ranges):
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;
}
Now that we're working with ranges represented by a lower and upper bound, the process of splitting a range involves creating two smaller ranges, one that covers the lower half of the original range, and the other that covers the upper half, e.g: if the original range was 8 to 15, the two new ranges would be 8 to 11, and 12 to 15.
Given this, we can implement a form of recursive binary search, by starting with the parent range, and then recursively splitting it into two halves if it overlaps with any of the used ranges, until we find either a completely unused range, or a completely used range:
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
}
Combining these four functions will allow you to find unused subnets:
$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));
Gives output (tested with 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"
}

Related

PHP create function Palindrome

I really need your help. I have to create a function that takes 2 positive integers as its arguments and returns the numerical palindromes as an array of n numerical palindromes that come after the num, including num. Also, single-digit numbers are not considered numerical palindromes. So the outcome must be like this --> function(4,6) // returns [11,22,33,44]. Function(4, 6) it's an array that will take only 4 elements and the numerical palindromes must be greater than 6. Other examples are function (1, 75) // returns [77] and function (3, 100) // returns [101, 111, 121]
My code so far:
<?php
function createPalindrome($input)
{
$m = $input;
$palin = $input;
while ($m > 1) {
$d = intval($m % 10);
$palin = $palin * 10 + $d;
$m = intval($m / 10);
}
return $palin;
}
function generatePalindromes($x, $n)
{
$arr = [];
$i = 1;
while (($number = createPalindrome($i)) <= $n) {
$arr[] = $number;
$i++;
}
for($j = 0; $j < $x; $j++)
var_dump($arr[$j]);
}
generatePalindromes(4, 77);
The outcome is:
int(1)
int(22)
int(33)
int(44)
Had to modify this answer a fair bit once Giann49 expounded on his question in a comment reply.
This is not the cleanest or most precise way to do this for sure but it will principally function and hopefully help point you in the right direction logically.
function findExceedingPalindromes($palindromeLimit,$startingPoint){
$palindromesFound = 0; //Set an initial counter for number of palindromes found so far.
$palindromeSet = []; //Create an array to contain all the palindromes.
if($palindromeLimit <= 0 || $startingPoint <= 0){ //Both integers need to be positive as stated.
return false; //If they aren't return false. You can return whatever you want to halt execution of the function. This is just an easy example.
}
if($startingPoint < 10){
$startingPoint = 10; //Since single digits aren't valid if the starting number if less than 10 kick it up to 10.
}
while($palindromesFound <= $palindromeLimit){
$startingPoint++; //Since the first palindrome must exceed the starting point increment it up once at the top of the loop.
$reverseNumber = strrev($startingPoint); //reverse the current number.
if($startingPoint === $reverseNumber){
array_push($palindromeSet,$startingPoint);
$palindomresFound++; //If we find a palindome move the number found 1 higher.
}
}
return $palindromeSet;
}
As an explanation.
The first argument is the number of palindromes to generate. The second argument is the number we want to start palindrome generation at then work up from there.
We create two variables. One is to track how many palindromes have been found. The other is an empty array to insert found palindromes into.
You say the two numbers must be positive integers so if they are anything less than 1 we'll want to exit the function. (optional)
You say single digits don't count so if the starting point is less than 10 we'll just move it up to 10 for starters. (optional)
Now we'll start a while loop. While the number of palindromes is less than the number we want to find the loop will keep running.
We add 1 to the starting point right out of the gate because we want to first palindrome to be higher than the starting point if it already is one. As in if 11 is the number set as the point to start searching we want to look at 11 + 1 for starters. (optional)
To check if a number of a palindrome we want to simply reverse it's string. If the strings are the same forward and back obviously it matches the definition of a palindrome. So we'll add that number into the set of found palindromes and move the number found 1 digit higher.
Once the requested number of palindromes are found we'll break the while loop and return the array of what was found.

in_array always returning false when searching for strings

I'm currently writing a simple Battleships game in PHP. At the start of the game, I generate three ship positions on a 5 x 5 board, with each ship occupying one square:
function generate_three_battleships($ships){
for ($n=0; $n<3; $n++){
// Generate new position, returned from function as string
$ship_position = generate_new_position();
// Check to ensure that the position is not already occupied - if so, recast
if (in_array($ship_position, $ships)){
$ship_position = generate_new_position();
}//if
// Assign position to array
array_push($ships, $ship_position);
}//for
}//generate_three_battleships
Each position is represented as a two-digit string, which represent cartesian coordinates (so for example, "32" represents y = 3, x = 2). This task is handled by the generate_new_position function:
When the game starts, the user will enter in their guesses for rows and columns:
function generate_new_position(){
// Generate x and y coordinates - cast to string for storage
$ship_row = (string)random_pos();
$ship_col = (string)random_pos();
$ship_position = $ship_row.$ship_col;
return $ship_position;
}//generate_new_position
The user then enters their guesses for rows and columns, and the game will check to see if there is a ship there:
// Generate battleships
generate_three_battleships($ships);
for($turn=1; $turn <= GUESSES; $turn++){
// First check to see if all ships have been sunk. If not, proceed with the game
if ($ships_sunk < 3){
$guess_row = (string)readline("Guess a row: ");
$guess_col = (string)readline("Guess a column: ");
$guess = $guess_row.$guess_col; // format guesses as strings
if(($guess_row=="") || ($guess_col=="") || ($guess_row < 0) || ($guess_col < 0) || ($guess_row >= BOARDSIZE) || ($guess_col >= BOARDSIZE)){
print("Oops, that's not even in the ocean. \n");
}
else if(array_search($guess, $ships) != false){
print("Congratulations! You sunk one of my battleships!\n");
$board[$guess_row][$guess_col] = "X";
$ships_sunk++;
}
}
However, the in_array function is consistently returning false for every guess, even if that guess is actually in the $ships array. I can't see where I am going wrong, as I have explicitly cast everything to string. Am I missing something obvious?
As some people have asked, the output of var_dump on $ships after generate_three_battleships has executed is as follows:
array(3) {
[0]=>
string(2) "12"
[1]=>
string(2) "30"
[2]=>
string(2) "03"
}
Unfortunately I don't have a complete answer because I am missing some information to understand what the problem is.
You can debug what's going on by printing the contents of the array using var_dump to see the actual contents of $ships and maybe forcing generate_new_position to always return the same value.
If you can't solve this problem yourself, could you post the contents of $ships (using var_dump) before and after the for loop?

PHP Reliable Calculating of Cube Root

It seems like a no brainer but I am having trouble to get this solved:
I would like to calculate a level based on given experience points (exp). Therefore I use the cube root formula and round down to the next whole number. The next level is reached when exp exactly reaches level^3. The number of levels is unlimited, so I would avoid having a pre-calculated lookup table.
When I use the standard php math
floor( pow( 10648, 1/3))
It returns 21 instead of 22. This is wrong since 21^3 gives 92161. The reason is that due to limited floating point precision pow(10648, 1/3) returns not exactly 22, instead it returns 21.9993112732.
You can check it out with the following snippet:
$lvl = pow( 10647, (float) 1 / 3);
print number_format( $lvl, 10);
This is my workaround. But I am not sure if this is bulletproof:
public static function getLevel($exp) {
$lvl = floor(pow($exp, (float) 1 / 3)); // calculate the level
if (pow($lvl + 1, 3) == $exp) { // make check
$lvl++; // correct
}
return $lvl;
}
Also it looks a bit fragile when it comes to the check. So the question remains:
Is there a reliable, efficient and bulletproof way of calculating cube root (of positive numbers).
Thanks.
I think this is the only modification your code needs:
public static function getLevel($exp) {
$lvl = floor(pow($exp, (float) 1 / 3));
if (pow($lvl + 1, 3) <= $exp) { // compare with <= instead of ==
$lvl++;
}
return $lvl;
}
If you need 100% reliable results, you should probably use the GMP library for arbitrary precision calculations.
The gmp_root function should do what you need. You'll need PHP version 5.6 or newer with the GMP extension enabled.
$num = gmp_init(10648);
$third_root = gmp_root($num, 3);
var_dump(gmp_strval($third_root)); // string(2) "22"
If the GMP library is inconvenient for you and you're guaranteed that your number has an integer root then you can try the following:
function getLevel($base, $root = 3.0) {
$exact = pow($base, 1.0 / $root);
$ceil = ceil($exact);
$floor = floor($exact);
if (pow($exact, $root) == $base) { return $exact; }
if (pow($ceil, $root) == $base) { return $ceil; }
if (pow($floor, $root) == $base) { return $floor; }
// Default: no integer root
return FALSE;
}
It checks the exact, floor, and ceil values of the result to find which is the correct answer. If it's not one of the three then the number has no integer root and defaults to FALSE.
Here's an example of it in action:
var_dump(getLevel(10648, 3)); // 22^3 => float(22)
var_dump(getLevel(16807, 5)); // 7^5 => float(7)
var_dump(getLevel(1, 3)); // Should always return 1 => float(1)
var_dump(getLevel(1, 99)); // Should always return 1 => float(1)
var_dump(getLevel(7)); // Has no integer 3rd root => bool(false)
Of course, you can make the function return $floor; or return $ceil; as the default case, but that's up to you.

rounding a number, NOT necessarily decimel PHP

I have a question.
I am using php to generate a number based on operations that a user has specified
This variable is called
$new
$new is an integer, I want to be able to round $new to a 12 digit number, regardless of the answer
I was thinking I could use
round() or ceil()
but I believe these are used for rounding decimel places
So, I have an integer stored in $new, when $new is echoed out I want for it to print 12 digits. Whether the number is 60 billion or 0.00000000006
If i understand correctly
function showNumber($input) {
$show = 12;
$input = number_format(min($input,str_repeat('9', $show)), ($show-1) - strlen(number_format($input,0,'.','')),'.','');
return $input;
}
var_dump(showNumber(1));
var_dump(showNumber(0.00000000006));
var_dump(showNumber(100000000000000000000000));
gives
string(12) "1.0000000000"
string(12) "0.0000000001"
string(12) "999999999999"

Printing all permutations of strings that can be formed from phone number

I'm trying to print the possible words that can be formed from a phone number in php. My general strategy is to map each digit to an array of possible characters. I then iterate through each number, recursively calling the function to iterate over each possible character.
Here's what my code looks like so far, but it's not working out just yet. Any syntax corrections I can make to get it to work?
$pad = array(
array('0'), array('1'), array('abc'), array('def'), array('ghi'),
array('jkl'), array('mno'), array('pqr'), array('stuv'), array('wxyz')
);
function convertNumberToAlpha($number, $next, $alpha){
global $pad;
for($i =0; $i<count($pad[$number[$next]][0]); $i++){
$alpha[$next] = $pad[$next][0][$i];
if($i<strlen($number) -1){
convertNumberToAlpha($number, $next++, $alpha);
}else{
print_r($alpha);
}
}
}
$alpha = array();
convertNumberToAlpha('22', 0, $alpha);
How is this going to be used? This is not a job for a simple recursive algorithm such as what you have suggested, nor even an iterative approach. An average 10-digit number will yield 59,049 (3^10) possibilities, each of which will have to be evaluated against a dictionary if you want to determine actual words.
Many times, the best approach to this is to pre-compile a dictionary which maps 10-digit numbers to various words. Then, your look-up is a constant O(1) algorithm, just selecting by a 10 digit number which is mapped to an array of possible words.
In fact, pre-compiled dictionaries were the way that T9 worked, mapping dictionaries to trees with logarithmic look-up functions.
The following code should do it. Fairly straight forward: it uses recursion, each level processes one character of input, a copy of current combination is built/passed at each recursive call, recursion stops at the level where last character of input is processed.
function alphaGenerator($input, &$output, $current = "") {
static $lookup = array(
1 => "1", 2 => "abc", 3 => "def",
4 => "ghi", 5 => "jkl", 6 => "mno",
7 => "pqrs", 8 => "tuv", 9 => "wxyz",
0 => "0"
);
$digit = substr($input, 0, 1); // e.g. "4"
$other = substr($input, 1); // e.g. "3556"
$chars = str_split($lookup[$digit], 1); // e.g. "ghi"
foreach ($chars as $char) { // e.g. g, h, i
if ($other === false) { // base case
$output[] = $current . $char;
} else { // recursive case
alphaGenerator($other, $output, $current . $char);
}
}
}
$output = array();
alphaGenerator("43556", $output);
var_dump($output);
Output:
array(243) {
[0]=>string(5) "gdjjm"
[1]=>string(5) "gdjjn"
...
[133]=>string(5) "helln"
[134]=>string(5) "hello"
[135]=>string(5) "hfjjm"
...
[241]=>string(5) "iflln"
[242]=>string(5) "ifllo"
}
You should read Norvigs article on writing a spellchecker in Python http://norvig.com/spell-correct.html . Although its a spellchecker and in python not php, it is the same concept around finding words with possible variations, might give u some good ideas.

Categories