php calculator class - php

I am stuck in resolving a PHP script.
I want to calculate a result from a set of instructions. Instructions comprise of a keyword and a number that are separated by a space per line. Instructions are loaded from file and results are output to the screen. Any number of Instructions can be specified. Instructions are operators (add, divide, subtract, multiply). The instructions will ignore mathematical precedence. The last instruction should be “apply” and a number (e.g., “apply 3”). The calculator is then initialised with that number and the previous instructions are applied to that number.
[Input]
add 2
multiply 3
apply 3
[Output]
15
this is what i have tried but i cant get the logic to complete the methods
class Calculator {
public $result = 0;
public $queue = Array();
public parseString($text) {
// parse input string
$cmds = explode(" ", $text);
foreach($cmds as $cmd) {
$cmd = trim($cmd);
if(!$cmd) continue; // blank or space, ignoring
$this->queue[] = $cmd;
}
// lets process commands
$command = false;
foreach($this->queue as $index => $cmd) {
if(is_number($cmd)) {
// if it's number fire previous command if exists
if(!$command || !method_exists($this, $command)) {
throw new Exception("Unknown command $command");
}
$this->$command($index, $cmd);
}else{
$command = $cmd;
}
}
}
public function apply($index, $number) {
// manipulate $result, $queue, $number
}
public function add($index, $number) {
// manipulate $result, $queue, $number
}
public function substract($index, $number) {
// manipulate $result, $queue, $number
}
}
$calculator = new Calculator();
$calculator->parseString('...');
how can i call or switch the add,divide,multiply,substract and how to distinguish and trigger apply word
any kind of help will be appreciated.

You should process the apply first and then cut it out of your queue array. Before you start looping through with the commands, simply test for the apply command and run it first. This simplifies the whole process.
After many minutes of experimentation and chatting, has been resolved.
<?php
error_reporting(E_ALL);
class Calculator {
public $result = 0;
public $queue = array();
public function parseString($text) {
// parse input string
$split = explode(" ", $text); //This gets your input into new lines
for ($i = 0; $i < count($split); $i += 2) $pairs[] = array($split[$i], $split[$i+1]);
foreach ($pairs as $bits) {
if ($bits[0] == "apply") {
$this->apply($bits[1]); //Set result equal to apply.
$this->queue[] = "output";
} else {
$this->queue[] = $bits; //Set the queue item as an array of (command, value).
}
}
//var_dump($this->queue);
//die;
// lets process commands
foreach ($this->queue as $index => $cmd) {
if ($cmd == "output") {
echo "Answer: " .$this->result;
return;
} else {
switch($cmd[0]) {
case "add":
$this->add($cmd[1]);
break;
case "subtract":
$this->subtract($cmd[1]);
break;
case "multiply":
$this->multiply($cmd[1]);
break;
case "divide":
$this->divide($cmd[1]);
break;
default:
echo "Unknown command!";
break;
}
}
}
}
public function apply($number) {
// manipulate $result, $queue, $number
$this->result = $number;
}
public function add($number) {
// manipulate $result, $queue, $number
$this->result += $number;
}
public function subtract($number) {
// manipulate $result, $queue, $number
$this->result -= $number;
}
public function multiply($number) {
// manipulate $result, $queue, $number
$this->result *= $number;
}
public function divide($number) {
// manipulate $result, $queue, $number
$this->result /= $number;
}
}
?>

Try using the array_shift and array_pop functions:
//pop the apply num off end off the queue
$result= array_pop($this->queue);
//now pop the apply operator off end of the queue
$operator = array_pop($this->queue);
//now get the first operator and numbers using array_shift
$operator = array_shift($this->queue); //get first operator
$num = array_shift($this->queue); //get first number
//loop perform operation on result using number till done.
while($num !== null)
{
$result = $operator($result, $num);
$operator = array_shift($this->queue);
$num = array_shift($this->queue);
}

Related

How to get the largest palindromic product PHP

First than nothing I'm not a native guy so sorry for all those mistakes.
I'm trying to get the largest palindromic product between two integers, for example; 3 and 11, the largest will be 11*11=121.
I tried with this.
function Pallendrome($str) : bool {
return (strval($str) == strrev(strval($str))) ? true : false;
}
function largestPalindromicProduct($lower, $upper) {
$array = array();
for($it=$upper; $it >= $lower; $it--){
for($it_=$upper; $it_ >= $lower; $it_--){
$num = $it*$it_;
if(Pallendrome($num)) { array_push($array, $num); }
}
}
if(empty($array)) { return NAN; }
else{ ksort($array); return $array[0];}
}
But, I'd need to get a way to optimize it 'cause it's taking a long time due to the numbers introduced in, are kind of big.
Do you guys have some idea for it? Thank ya!.
I'm sure the folks over at math.stackexchange.com could help you with a better algorithm, but for the purposes of optimizing what you've got thus far, here's a summation of all my comments:
function largestPalindromicProduct($lower, $upper)
{
$largest = 0;
for ($it = $upper; $it >= $lower; $it--) {
for ($it_ = $upper; $it_ >= $lower; $it_--) {
$num = $it * $it_;
// If we're on the first iteration and we have a product lower than the
// largest we've found so far, then we know we can never get a larger
// number (because we're counting down) and we can abort immediately.
if ($num <= $largest) {
if ($it_ == $upper) {
break 2;
}
// Otherwise, we can at least abort the rest of this subloop.
break;
}
// Only check if it's a palindrome once we've passed all the other checks.
if ($num == strrev($num)) {
$largest = $num;
}
}
}
return $largest;
}

PHP loop isn't working, what am I doing wrong?

I am building a function that would receive 2 params, a string and a number.
it would basically print the first letters $n of $s.
I need to run a loop, please don't advise other non looping methods.
And yes, I need to keep the loop true throughout the function, it's suppose to close when the if is met.
For some reason the loop isn't closing, even though there's a return in the if condition that is being met when $stringCounter equals $n (=10 in this example.)
function printCounter($s, $n)
{
$stringCaller = '';
$stringCounter = strlen($stringCaller);
while (1) {
$stringCaller .= $s;
if ($stringCounter == $n) {
return $stringCaller;
}
}
}
printCounter('aba', '10');
You should imove the calc of $stringCounter inside the loop otherwise this never change
$stringCaller = '';
while (1) {
$stringCounter = strlen($stringCaller);
$stringCaller .= $s;
if ($stringCounter >= $n) {
return $stringCaller;
}
}
On my oppinion, TS is searching next approach:
function printCounter($s, $n)
{
$result = '';
$str_lenght = strlen($s);
if(!$str_lenght) {
return $result;
}
while (true) {
$result .= $s;
$result_lenght = strlen($result);
if($result_lenght/$str_lenght >= $n) {
return $result_lenght;
}
}
}
echo printCounter('aba', '10');
exit;
You can fix part of the issue by updating $stringCounter inside the loop.
function printCounter($s, $n)
{
$stringCaller = '';
while (1) {
$stringCaller .= $s;
$stringCounter = strlen($stringCaller); // check strlen after each addition of $s
if ($stringCounter == $n) {
return $stringCaller;
}
}
}
However, even after you fix that issue, there will still be cases where your loop will never exit (such as the example in your question) because $stringCounter can only equal $n if $n is a multiple of strlen($s).
For the example printCounter('aba', '10');
Iteration $stringCounter ($stringCounter == $n)
1 3 false
2 6 false
3 9 false
4 12 false
Obviously $stringCounter will only get farther away from $n from that point.
So to ensure that the function will exit, check for inequality instead with:
if ($stringCounter >= $n) {
If you need the function to return exactly $n characters, the function will need to be a little more complex and take a substring of $s to make up the remaining characters when $stringCounter + strlen($s) will be greater than $n, or return an $n character substring of your result like this:
function printCounter($s, $n)
{
$result = '';
while (1) {
$result .= $s;
if (strlen($result) >= $n) {
return substr($result, 0, $n);
}
}
}

Find the longest word given a dictionary

I'm reading overflow for years but never had to post anything (thanks to great answers) until now because i can't rly find solution for my problem.
I'm kinda new to PHP.
So I'm creating game where you have to find a longest word with 12 random generated letters. I actually did this successfully in C# and Java, but now I'm porting some of code to PHP because i'm working on multiplayer version and some stuff will be on server.
So i did all this using this great thread (Answer by Thomas Jungblut):
Find the longest word given a collection
Now i tried to do same in PHP but, it's weird for me. I get some crazy result's and i dont know how to replicate this java method in php:
arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
I'm not getting any error, but obvisuly thing is not working, is there anyone that can maybe help me to work this out?
UPDATE: BTW, i know post might be confusing im new to posting here...so forgive me ^^
I "fixed" code, it will now find me longest word. But there is bug somewhere. Bug is allowing algortithm to use one character more than once, which should not be possible.
I think problem is here:
$newDict[$index] = array_splice($allowedCharacters, $index +1, count($allowedCharacters) - ($index +1));
This is my Current Code:
parse_dictionary.php
<?php
$ouput = array();
$mysqli = new mysqli('localhost','root','','multiquiz_db');
$mysqli->set_charset('utf8');
if ($result = $mysqli->query("SELECT word FROM words_live")) {
while($row = $result->fetch_array(MYSQL_ASSOC)) {
//echo(mb_convert_encoding($row['word'], 'HTML-ENTITIES', 'utf-8'));
array_push($ouput, $row['word']);
}
//echo utf8_decode(json_encode($ouput));
}
$result->close();
$mysqli->close();
?>
Trie.php
<?php
class Trie
{
public $children = array();
public $value = null;
public $word = null;
public function __construct($value = null)
{
$this->value = $value;
}
public function adda($array)
{
$this->addb($array, 0);
}
public function addb($array, $offset)
{
foreach ($this->children as $child)
{
if($child->value == $array[$offset])
{
$child->addb($array, $offset + 1);
return;
}
}
$trieNode = new Trie($array[$offset]);
array_push($this->children, $trieNode);
if($offset < count($array) - 1)
{
$trieNode->addb($array, $offset+1);
}
else
{
$trieNode->word = implode(" ", $array);
}
}
}
?>
Index.php
<?php
include 'Trie.php';
include 'parse_dictionary.php';
ini_set('memory_limit', '1024M'); // or you could use 1G
header('Content-Type: text/html; charset=utf-8');
mb_internal_encoding("UTF-8");
class LongestWord
{
public $root = null;
public function __construct($ouput)
{
$this->root = new Trie();
foreach ($ouput as $word)
{
//echo($word);
//echo(str_split_unicode($word)[0]);
$this->root->adda(str_split_unicode($word));
}
}
public function search($cs)
{
return $this->visit($this->root, $cs);
}
function visit($n, $allowedCharacters)
{
$bestMatch = null;
if(count($n->children) == 0)
{
$bestMatch = $n->word;
}
foreach($n->children as $child)
{
if($this->contains($allowedCharacters, $child->value))
{
$result = $this->visit($child, $this->remove($allowedCharacters, $child->value));
if($bestMatch == null || $result != null && strlen($bestMatch) < strlen($result))
{
$bestMatch = $result;
}
}
}
return $bestMatch;
}
function remove($allowedCharacters, $value)
{
$newDict = $allowedCharacters;
if(($key = array_search($value, $newDict)))
{
unset($newDict[$key]);
}
return $newDict;
}
function contains($allowedCharacters, $value)
{
foreach($allowedCharacters as $x)
{
if($value == $x)
{
// echo $value . "=====". $x. "|||||||";
return true;
}
else
{
//echo $value . "!!===". $x. "|||||||";
}
}
return false;
}
}
function str_split_unicode($str, $l = 0) {
if ($l > 0) {
$ret = array();
$len = mb_strlen($str, "UTF-8");
for ($i = 0; $i < $len; $i += $l) {
$ret[] = mb_substr($str, $i, $l, "UTF-8");
}
return $ret;
}
return preg_split("//u", $str, -1, PREG_SPLIT_NO_EMPTY);
}
$chars = 'IIOIOFNČGDĆJ';
$testCharacters = str_split_unicode($chars);
$lw = new LongestWord($ouput);
echo($lw->search($testCharacters));
?>
As you're using MySQL, here's an approach that will let the DB server do the work.
It's a bit dirty, because you have to add several WHERE conditions with regex matching, which will have a rather poor performance. (Unfortunately, I could not come up with a regex that would require all of the letters in one expression, but I'd be happy to be corrected.)
However, I have tested it on a database table of >200000 entries; it delivers results within less than 0.3 sec.
SELECT word
FROM words_live
WHERE
word REGEXP "a" AND
word REGEXP "b" AND
word REGEXP "c" AND
word REGEXP "d"
ORDER BY LENGTH(word) DESC
LIMIT 1;
Obviously, you must generate one word REGEXP "a" condition per letter in your PHP code when constructing the query.
The query should then give you exactly one result, namely the longest word in the database containing all of the characters.
I solved problem with this function, full working code updated in question post
function remove($allowedCharacters, $value)
{
$newDict = $allowedCharacters;
if(($key = array_search($value, $newDict)))
{
unset($newDict[$key]);
}
return $newDict;
}
removed old one:
function remove($allowedCharacters, $value)
{
$newDict = [count($allowedCharacters) - 1];
$index = 0;
foreach($allowedCharacters as $x)
{
if($x != $value)
{
$newDict[$index++] = $x;
}
else
{
//we removed the first hit, now copy the rest
break;
}
}
//System.arraycopy(allowedCharacters, index + 1, newDict, index, allowedCharacters.length - (index + 1)); JAVA arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
//$newDict[$index] = array_splice($allowedCharacters, $index +1, count($allowedCharacters) - ($index +1));
//$newDict = $allowedCharacters;
return $newDict;
}

Testing if a network in cidr notation overlaps another network

I'm searching for a php algorithm that efficiently test if one cidr notated network overlaps another.
Basically I have the following situation:
Array of cidr adresses:
$cidrNetworks = array(
'192.168.10.0/24',
'10.10.0.30/20',
etc.
);
I have a method that adds networks to the array, but this method should throw an exception when a network is added that overlaps with a network allready in the array.
So ie. if 192.168.10.0/25 is added an exception should be thrown.
Does anyone have/know/"can think of" an method to test this efficiently?
Here is an updated version of the class previously discussed in chat. It can do what you require, as well as many other useful things.
<?php
class IPv4Subnet implements ArrayAccess, Iterator {
/*
* Address format constants
*/
const ADDRESS_BINARY = 0x01;
const ADDRESS_INT = 0x02;
const ADDRESS_DOTDEC = 0x04;
const ADDRESS_SUBNET = 0x08;
/*
* Constants to control whether getHosts() returns the network/broadcast addresses
*/
const HOSTS_WITH_NETWORK = 0x10;
const HOSTS_WITH_BROADCAST = 0x20;
const HOSTS_ALL = 0x30;
/*
* Properties to store base address and subnet mask as binary strings
*/
protected $address;
protected $mask;
/*
* Counter to track the current iteration offset
*/
private $iteratorOffset = 0;
/*
* Array to hold values retrieved via ArrayAccess
*/
private $arrayAccessObjects = array();
/*
* Helper methods
*/
private function longToBinary ($long) {
return pack('N', $long);
}
private function longToDottedDecimal ($long) {
return ($long >> 24 & 0xFF).'.'.($long >> 16 & 0xFF).'.'.($long >> 8 & 0xFF).'.'.($long & 0xFF);
}
private function longToByteArray ($long) {
return array(
$long >> 24 & 0xFF,
$long >> 16 & 0xFF,
$long >> 8 & 0xFF,
$long & 0xFF
);
}
private function longToSubnet ($long) {
if (!isset($this->arrayAccessObjects[$long])) {
$this->arrayAccessObjects[$long] = new self($long);
}
return $this->arrayAccessObjects[$long];
}
private function binaryToLong ($binary) {
return current(unpack('N', $binary));
}
private function binaryToDottedDecimal ($binary) {
return implode('.', unpack('C*', $binary));
}
private function binaryToX ($binary, $mode) {
if ($mode & self::ADDRESS_BINARY) {
$result = $binary;
} else if ($mode & self::ADDRESS_INT) {
$result = $this->binaryToLong($binary);
} else if ($mode & self::ADDRESS_DOTDEC) {
$result = $this->binaryToDottedDecimal($binary);
} else {
$result = $this->longToSubnet($this->binaryToLong($binary));
}
return $result;
}
private function byteArrayToLong($bytes) {
return ($bytes[0] << 24) | ($bytes[1] << 16) | ($bytes[2] << 8) | $bytes[3];
}
private function byteArrayToBinary($bytes) {
return pack('C*', $bytes[0], $bytes[1], $bytes[2], $bytes[3]);
}
private function normaliseComparisonSubject (&$subject) {
if (!is_object($subject)) {
$subject = new self($subject);
}
if (!($subject instanceof self)) {
throw new InvalidArgumentException('Subject must be an instance of IPv4Subnet');
}
}
private function validateOctetArray (&$octets) {
foreach ($octets as &$octet) {
$octet = (int) $octet;
if ($octet < 0 || $octet > 255) {
return FALSE;
}
}
return TRUE;
}
/*
* Constructor
*/
public function __construct ($address = NULL, $mask = NULL) {
if ($address === NULL || (is_string($address) && trim($address) === '')) {
$address = array(0, 0, 0, 0);
} else if (is_int($address)) {
$address = $this->longToByteArray($address);
} else if (is_string($address)) {
$parts = preg_split('#\s*/\s*#', trim($address), -1, PREG_SPLIT_NO_EMPTY);
if (count($parts) > 2) {
throw new InvalidArgumentException('No usable IP address supplied: Syntax error');
} else if ($parts[0] === '') {
throw new InvalidArgumentException('No usable IP address supplied: IP address empty');
}
if (!empty($parts[1]) && !isset($mask)) {
$mask = $parts[1];
}
$address = preg_split('#\s*\.\s*#', $parts[0], -1, PREG_SPLIT_NO_EMPTY);
} else if (is_array($address)) {
$address = array_values($address);
} else {
throw new InvalidArgumentException('No usable IP address supplied: Value must be a string or an integer');
}
$suppliedAddressOctets = count($address);
$address += array(0, 0, 0, 0);
if ($suppliedAddressOctets > 4) {
throw new InvalidArgumentException('No usable IP address supplied: IP address has more than 4 octets');
} else if (!$this->validateOctetArray($address)) {
throw new InvalidArgumentException('No usable IP address supplied: At least one octet value outside acceptable range 0 - 255');
}
if ($mask === NULL) {
$mask = array_pad(array(), $suppliedAddressOctets, 255) + array(0, 0, 0, 0);
} else if (is_int($mask)) {
$mask = $this->longToByteArray($mask);
} else if (is_string($mask)) {
$mask = preg_split('#\s*\.\s*#', trim($mask), -1, PREG_SPLIT_NO_EMPTY);
switch (count($mask)) {
case 1: // CIDR
$cidr = (int) $mask[0];
if ($cidr === 0) {
// Shifting 32 bits on a 32 bit system doesn't work, so treat this as a special case
$mask = array(0, 0, 0, 0);
} else if ($cidr <= 32) {
// This looks odd, but it's the nicest way I have found to get the 32 least significant bits set in a
// way that works on both 32 and 64 bit platforms
$base = ~((~0 << 16) << 16);
$mask = $this->longToByteArray($base << (32 - $cidr));
} else {
throw new InvalidArgumentException('Supplied mask invalid: CIDR outside acceptable range 0 - 32');
}
break;
case 4: break; // Dotted decimal
default: throw new InvalidArgumentException('Supplied mask invalid: Must be either a full dotted-decimal or a CIDR');
}
} else if (is_array($mask)) {
$mask = array_values($mask);
} else {
throw new InvalidArgumentException('Supplied mask invalid: Type invalid');
}
if (!$this->validateOctetArray($mask)) {
throw new InvalidArgumentException('Supplied mask invalid: At least one octet value outside acceptable range 0 - 255');
}
// Check bits are contiguous from left
// TODO: Improve this mechanism
$asciiBits = sprintf('%032b', $this->byteArrayToLong($mask));
if (strpos(rtrim($asciiBits, '0'), '0') !== FALSE) {
throw new InvalidArgumentException('Supplied mask invalid: Set bits are not contiguous from the most significant bit');
}
$this->mask = $this->byteArrayToBinary($mask);
$this->address = $this->byteArrayToBinary($address) & $this->mask;
}
/*
* ArrayAccess interface methods (read only)
*/
public function offsetExists ($offset) {
if ($offset === 'network' || $offset === 'broadcast') {
return TRUE;
}
$offset = filter_var($offset, FILTER_VALIDATE_INT);
if ($offset === FALSE || $offset < 0) {
return FALSE;
}
return $offset < $this->getHostsCount();
}
public function offsetGet ($offset) {
if (!$this->offsetExists($offset)) {
return NULL;
}
if ($offset === 'network') {
$address = $this->getNetworkAddress(self::ADDRESS_INT);
} else if ($offset === 'broadcast') {
$address = $this->getBroadcastAddress(self::ADDRESS_INT);
} else {
// How much the address needs to be adjusted by to account for network address
$adjustment = (int) ($this->getHostsCount() > 2);
$address = $this->binaryToLong($this->address) + $offset + $adjustment;
}
return $this->longToSubnet($address);
}
public function offsetSet ($offset, $value) {}
public function offsetUnset ($offset) {}
/*
* Iterator interface methods
*/
public function current () {
return $this->offsetGet($this->iteratorOffset);
}
public function key () {
return $this->iteratorOffset;
}
public function next () {
$this->iteratorOffset++;
}
public function rewind () {
$this->iteratorOffset = 0;
}
public function valid () {
return $this->iteratorOffset < $this->getHostsCount();
}
/*
* Data access methods
*/
public function getHosts ($mode = self::ADDRESS_SUBNET) {
// Parse flags and initialise vars
$bin = (bool) ($mode & self::ADDRESS_BINARY);
$int = (bool) ($mode & self::ADDRESS_INT);
$dd = (bool) ($mode & self::ADDRESS_DOTDEC);
$base = $this->binaryToLong($this->address);
$mask = $this->binaryToLong($this->mask);
$hasNwBc = !($mask & 0x03);
$result = array();
// Get network address if requested
if (($mode & self::HOSTS_WITH_NETWORK) && $hasNwBc) {
$result[] = $base;
}
// Get hosts
for ($current = $hasNwBc ? $base + 1 : $base; ($current & $mask) === $base; $current++) {
$result[] = $current;
}
// Remove broadcast address if present and not requested
if ($hasNwBc && !($mode & self::HOSTS_WITH_BROADCAST)) {
array_pop($result);
}
// Convert to the correct type
if ($bin) {
$result = array_map(array($this, 'longToBinary'), $result);
} else if ($dd) {
$result = array_map(array($this, 'longToDottedDecimal'), $result);
} else if (!$int) {
$result = array_map(array($this, 'longToSubnet'), $result);
}
return $result;
}
public function getHostsCount () {
$count = $this->getBroadcastAddress(self::ADDRESS_INT) - $this->getNetworkAddress(self::ADDRESS_INT);
return $count > 2 ? $count - 1 : $count + 1; // Adjust return value to exclude network/broadcast addresses
}
public function getNetworkAddress ($mode = self::ADDRESS_SUBNET) {
return $this->binaryToX($this->address, $mode);
}
public function getBroadcastAddress ($mode = self::ADDRESS_SUBNET) {
return $this->binaryToX($this->address | ~$this->mask, $mode);
}
public function getMask ($mode = self::ADDRESS_DOTDEC) {
return $this->binaryToX($this->mask, $mode);
}
/*
* Stringify methods
*/
public function __toString () {
if ($this->getHostsCount() === 1) {
$result = $this->toDottedDecimal();
} else {
$result = $this->toCIDR();
}
return $result;
}
public function toDottedDecimal () {
$result = $this->getNetworkAddress(self::ADDRESS_DOTDEC);
if ($this->mask !== "\xFF\xFF\xFF\xFF") {
$result .= '/'.$this->getMask(self::ADDRESS_DOTDEC);
}
return $result;
}
public function toCIDR () {
$address = $this->getNetworkAddress(self::ADDRESS_DOTDEC);
$cidr = strlen(trim(sprintf('%b', $this->getMask(self::ADDRESS_INT)), '0')); // TODO: Improve this mechanism
return $address.'/'.$cidr;
}
/*
* Comparison methods
*/
public function contains ($subject) {
$this->normaliseComparisonSubject($subject);
$subjectAddress = $subject->getNetworkAddress(self::ADDRESS_BINARY);
$subjectMask = $subject->getMask(self::ADDRESS_BINARY);
return $this->mask !== $subjectMask && ($this->mask | ($this->mask ^ $subjectMask)) !== $this->mask && ($subjectAddress & $this->mask) === $this->address;
}
public function within ($subject) {
$this->normaliseComparisonSubject($subject);
$subjectAddress = $subject->getNetworkAddress(self::ADDRESS_BINARY);
$subjectMask = $subject->getMask(self::ADDRESS_BINARY);
return $this->mask !== $subjectMask && ($this->mask | ($this->mask ^ $subjectMask)) === $this->mask && ($this->address & $subjectMask) === $subjectAddress;
}
public function equalTo ($subject) {
$this->normaliseComparisonSubject($subject);
return $this->address === $subject->getNetworkAddress(self::ADDRESS_BINARY) && $this->mask === $subject->getMask(self::ADDRESS_BINARY);
}
public function intersect ($subject) {
$this->normaliseComparisonSubject($subject);
return $this->equalTo($subject) || $this->contains($subject) || $this->within($subject);
}
}
In order to do what you desire, the class provides 4 methods:
contains()
within()
equalTo()
intersect()
Example usage of these:
// Also accepts dotted decimal mask. The mask may also be passed to the second
// argument. Any valid combination of dotted decimal, CIDR and integers will be
// accepted
$subnet = new IPv4Subnet('192.168.0.0/24');
// These methods will accept a string or another instance
var_dump($subnet->contains('192.168.0.1')); //TRUE
var_dump($subnet->contains('192.168.1.1')); //FALSE
var_dump($subnet->contains('192.168.0.0/16')); //FALSE
var_dump($subnet->within('192.168.0.0/16')); //TRUE
// ...hopefully you get the picture. intersect() returns TRUE if any of the
// other three match.
The class also implements the Iterator interface, allowing you to iterate over all the addresses in a subnet. The iterator excludes the network and broadcast addresses, which can be retrieved separately.
Example:
$subnet = new IPv4Subnet('192.168.0.0/28');
echo "Network: ", $subnet->getNetworkAddress(),
"; Broadcast: ", $subnet->getBroadcastAddress(),
"\nHosts:\n";
foreach ($subnet as $host) {
echo $host, "\n";
}
The class also implements ArrayAccess, allowing you to treat it as an array:
$subnet = new IPv4Subnet('192.168.0.0/28');
echo $subnet['network'], "\n"; // 192.168.0.0
echo $subnet[0], "\n"; // 192.168.0.1
// ...
echo $subnet[13], "\n"; // 192.168.0.14
echo $subnet['broadcast'], "\n"; // 192.168.0.15
NB: The iterator/array methods of accessing the subnet's host addresses will return another IPv4Subnet object. The class implements __toString(), which will return the IP address as a dotted decimal if it represents a single address, or the CIDR if it represents more than one. The data can be accessed directly as a string or an integer by calling the relevant get*() method and passing the desired flag(s) (see constants defined at the top of the class).
All operations are 32- and 64-bit safe. Compatibility should be (although not thoroughly tested) 5.2+
See it working
For completeness, I imagine your use case would be implemented something along these lines:
public function addSubnet ($newSubnet) {
$newSubnet = new IPv4Subnet($newSubnet);
foreach ($this->subnets as &$existingSubnet) {
if ($existingSubnet->contains($newSubnet)) {
throw new Exception('Subnet already added');
} else if ($existingSubnet->within($newSubnet)) {
$existingSubnet = $newSubnet;
return;
}
}
$this->subnets[] = $newSubnet;
}
See it working
As discussed briefly in PHP chat, here's how I would implement it, to compare any two addresses.
Convert the IP addresses to their binary form
Extract the masks from the CIDR format
Take the minimum mask of the two (least specific =
contains more addresses)
Use the mask on both binary representations.
Compare the two.
If there is a match, then one is contained within the other.
Here's some example code, it's not very pretty and you'll want to adapt it to cater for your array.
function bin_pad($num)
{
return str_pad(decbin($num), 8, '0', STR_PAD_LEFT);
}
$ip1 = '192.168.0.0/23';
$ip2 = '192.168.1.0/24';
$regex = '~(\d+)\.(\d+)\.(\d+)\.(\d+)/(\d+)~';
preg_match($regex, $ip1, $ip1);
preg_match($regex, $ip2, $ip2);
$mask = min($ip1[5], $ip2[5]);
$ip1 = substr(
bin_pad($ip1[1]) . bin_pad($ip1[2]) .
bin_pad($ip1[3]) . bin_pad($ip1[4]),
0, $mask
);
$ip2 = substr(
bin_pad($ip2[1]) . bin_pad($ip2[2]) .
bin_pad($ip2[3]) . bin_pad($ip2[4]),
0, $mask
);
var_dump($ip1, $ip2, $ip1 === $ip2);
I had trouble making it 32 bit compatible, which is why I eventually opted for converting each octet of the IP address into binary individually, and then using substr.
I started off using pack('C4', $ip[1] .. $ip[4]) but when it came to using a full 32 bit mask I ran into problems converting it into binary (since PHP integers are signed). Thought for a future implementation though!
Intuitively I would suggest you'd want to do something like:
Let the new entry be X
Convert X to single integer form, let that integer be Y
Let mask length of any entry A be mask(A)
Compare any existing entries where mask(entry) = mask(Y)
Mask off existing entries where mask(entry) > mask(Y) and compare with Y
Mask off Y for each existing entry where mask(entry) < mask(X), such that mask(Y) = mask(entry) and compare
Provided you encounter no collisions, all is well.
Of course this does not check if the proposed subnet is valid.
My proposition of correctness here is that I can't think of a counter-example, but there may well be one so I offer this as a basis for further thought - hope this helps.
<?php
function checkOverlap ($net1, $net2) {
$mask1 = explode("/", $net1)[1];
$net1 = explode("/", $net1)[0];
$netArr1 = explode(".",$net1);
$mask2 = explode("/", $net2)[1];
$net2 = explode("/", $net2)[0];
$netArr2 = explode(".",$net2);
$newnet1 = $newnet2 = "";
foreach($netArr1 as $num) {
$binnum = decbin($num);
$length = strlen($binnum);
for ($i = 0; $i < 8-$length; $i++) {
$binnum = '0'.$binnum;
}
$newnet1 .= $binnum;
}
foreach($netArr2 as $num) {
$binnum = decbin($num);
$length = strlen($binnum);
for ($i = 0; $i < 8-$length; $i++) {
$binnum = '0'.$binnum;
}
$newnet2 .= $binnum;
}
$length = min($mask1, $mask2);
$newnet1 = substr($newnet1,0,$length);
$newnet2 = substr($newnet2,0,$length);
$overlap = 0;
if ($newnet1 == $newnet2) $overlap = 1;
return $overlap;
}
function networksOverlap ($networks, $newnet) {
$overlap = false;
foreach ($networks as $network) {
$overlap = checkOverlap($network, $newnet);
if ($overlap) return 1;
}
return $overlap;
}
$cidrNetworks = array(
'192.168.10.0/24',
'10.10.0.30/20'
);
$newnet = "192.168.10.0/25";
$overlap = networksOverlap($cidrNetworks, $newnet);
?>
Not sure if this is 100% correct but try it out see if it works.

Longest Common Substring with wrong character tolerance

I have a script I found on here that works well when looking for the Lowest Common Substring.
However, I need it to tolerate some incorrect/missing characters. I would like be able to either input a percentage of similarity required, or perhaps specify the number of missing/wrong characters allowable.
For example, I want to find this string:
big yellow school bus
inside of this string:
they rode the bigyellow schook bus that afternoon
This is the code i'm currently using:
function longest_common_substring($words) {
$words = array_map('strtolower', array_map('trim', $words));
$sort_by_strlen = create_function('$a, $b', 'if (strlen($a) == strlen($b)) { return strcmp($a, $b); } return (strlen($a) < strlen($b)) ? -1 : 1;');
usort($words, $sort_by_strlen);
// We have to assume that each string has something in common with the first
// string (post sort), we just need to figure out what the longest common
// string is. If any string DOES NOT have something in common with the first
// string, return false.
$longest_common_substring = array();
$shortest_string = str_split(array_shift($words));
while (sizeof($shortest_string)) {
array_unshift($longest_common_substring, '');
foreach ($shortest_string as $ci => $char) {
foreach ($words as $wi => $word) {
if (!strstr($word, $longest_common_substring[0] . $char)) {
// No match
break 2;
}
}
// we found the current char in each word, so add it to the first longest_common_substring element,
// then start checking again using the next char as well
$longest_common_substring[0].= $char;
}
// We've finished looping through the entire shortest_string.
// Remove the first char and start all over. Do this until there are no more
// chars to search on.
array_shift($shortest_string);
}
// If we made it here then we've run through everything
usort($longest_common_substring, $sort_by_strlen);
return array_pop($longest_common_substring);
}
Any help is much appreciated.
UPDATE
The PHP levenshtein function is limited to 255 characters, and some of the haystacks i'm searching are 1000+ characters.
Writing this as a second answer because it's not based on my previous (bad) one at all.
This code is based on http://en.wikipedia.org/wiki/Wagner%E2%80%93Fischer_algorithm and http://en.wikipedia.org/wiki/Approximate_string_matching#Problem_formulation_and_algorithms
It returns one (of potentially several) minimum-levenshtein substrings of $haystack, given $needle. Now, levenshtein distance is just one measure of edit distance and it may not actually suit your needs. 'hte' is closer on this metric to 'he' than it is to 'the'. Some of the examples I put in show the limitations of this technique. I believe this to be considerably more reliable than the previous answer I gave, but let me know how it works for you.
// utility function - returns the key of the array minimum
function array_min_key($arr)
{
$min_key = null;
$min = PHP_INT_MAX;
foreach($arr as $k => $v) {
if ($v < $min) {
$min = $v;
$min_key = $k;
}
}
return $min_key;
}
// Calculate the edit distance between two strings
function edit_distance($string1, $string2)
{
$m = strlen($string1);
$n = strlen($string2);
$d = array();
// the distance from '' to substr(string,$i)
for($i=0;$i<=$m;$i++) $d[$i][0] = $i;
for($i=0;$i<=$n;$i++) $d[0][$i] = $i;
// fill-in the edit distance matrix
for($j=1; $j<=$n; $j++)
{
for($i=1; $i<=$m; $i++)
{
// Using, for example, the levenshtein distance as edit distance
list($p_i,$p_j,$cost) = levenshtein_weighting($i,$j,$d,$string1,$string2);
$d[$i][$j] = $d[$p_i][$p_j]+$cost;
}
}
return $d[$m][$n];
}
// Helper function for edit_distance()
function levenshtein_weighting($i,$j,$d,$string1,$string2)
{
// if the two letters are equal, cost is 0
if($string1[$i-1] === $string2[$j-1]) {
return array($i-1,$j-1,0);
}
// cost we assign each operation
$cost['delete'] = 1;
$cost['insert'] = 1;
$cost['substitute'] = 1;
// cost of operation + cost to get to the substring we perform it on
$total_cost['delete'] = $d[$i-1][$j] + $cost['delete'];
$total_cost['insert'] = $d[$i][$j-1] + $cost['insert'];
$total_cost['substitute'] = $d[$i-1][$j-1] + $cost['substitute'];
// return the parent array keys of $d and the operation's cost
$min_key = array_min_key($total_cost);
if ($min_key == 'delete') {
return array($i-1,$j,$cost['delete']);
} elseif($min_key == 'insert') {
return array($i,$j-1,$cost['insert']);
} else {
return array($i-1,$j-1,$cost['substitute']);
}
}
// attempt to find the substring of $haystack most closely matching $needle
function shortest_edit_substring($needle, $haystack)
{
// initialize edit distance matrix
$m = strlen($needle);
$n = strlen($haystack);
$d = array();
for($i=0;$i<=$m;$i++) {
$d[$i][0] = $i;
$backtrace[$i][0] = null;
}
// instead of strlen, we initialize the top row to all 0's
for($i=0;$i<=$n;$i++) {
$d[0][$i] = 0;
$backtrace[0][$i] = null;
}
// same as the edit_distance calculation, but keep track of how we got there
for($j=1; $j<=$n; $j++)
{
for($i=1; $i<=$m; $i++)
{
list($p_i,$p_j,$cost) = levenshtein_weighting($i,$j,$d,$needle,$haystack);
$d[$i][$j] = $d[$p_i][$p_j]+$cost;
$backtrace[$i][$j] = array($p_i,$p_j);
}
}
// now find the minimum at the bottom row
$min_key = array_min_key($d[$m]);
$current = array($m,$min_key);
$parent = $backtrace[$m][$min_key];
// trace up path to the top row
while(! is_null($parent)) {
$current = $parent;
$parent = $backtrace[$current[0]][$current[1]];
}
// and take a substring based on those results
$start = $current[1];
$end = $min_key;
return substr($haystack,$start,$end-$start);
}
// some testing
$data = array( array('foo',' foo'), array('fat','far'), array('dat burn','rugburn'));
$data[] = array('big yellow school bus','they rode the bigyellow schook bus that afternoon');
$data[] = array('bus','they rode the bigyellow schook bus that afternoon');
$data[] = array('big','they rode the bigyellow schook bus that afternoon');
$data[] = array('nook','they rode the bigyellow schook bus that afternoon');
$data[] = array('they','console, controller and games are all in very good condition, only played occasionally. includes power cable, controller charge cable and audio cable. smoke free house. pes 2011 super street fighter');
$data[] = array('controker','console, controller and games are all in very good condition, only played occasionally. includes power cable, controller charge cable and audio cable. smoke free house. pes 2011 super street fighter');
foreach($data as $dat) {
$substring = shortest_edit_substring($dat[0],$dat[1]);
$dist = edit_distance($dat[0],$substring);
printf("Found |%s| in |%s|, matching |%s| with edit distance %d\n",$substring,$dat[1],$dat[0],$dist);
}

Categories