I'm trying to loop through a set of records, all of which have a "number" property. I am trying to check if there are 3 consecutive records, e.g 6, 7 and 8.
I think i'm almost there with the code below, have hit the wall though at the last stage - any help would be great!
$nums = array();
while (count($nums <= 3))
{
//run through entries (already in descending order by 'number'
foreach ($entries as $e)
{
//ignore if the number is already in the array, as duplicate numbers may exist
if (in_array($e->number, $num))
continue;
else
{
//store this number in the array
$num[] = $e->number;
}
//here i need to somehow check that the numbers stored are consecutive
}
}
function isConsecutive($array) {
return ((int)max($array)-(int)min($array) == (count($array)-1));
}
You can achieve the same result without looping, too.
If they just have to be consecutive, store a $last, and check to make sure $current == $last + 1.
If you're looking for n numbers that are consecutive, use the same, except also keep a counter of how many ones fulfilled that requirement.
$arr = Array(1,2,3,4,5,6,7,343,6543,234,23432,100,101,102,103,200,201,202,203,204);
for($i=0;$i<sizeof($arr);$i++)
{
if(isset($arr[$i+1]))
if($arr[$i]+1==$arr[$i+1])
{
if(isset($arr[$i+2]))
if($arr[$i]+2==$arr[$i+2])
{
if(isset($arr[$i+3]))
if($arr[$i]+3==$arr[$i+3])
{
echo 'I found it:',$arr[$i],'|',$arr[$i+1],'|',$arr[$i+2],'|',$arr[$i+3],'<br>';
}//if3
}//if 2
}//if 1
}
I haven't investigated it thoroughly, maybe can be improved to work faster!
This will confirm if all items of an array are consecutive either up or down.
You could update to return an array of [$up, $down] or another value instead if you need direction.
function areAllConsecutive($sequence)
{
$up = true;
$down = true;
foreach($sequence as $key => $item)
{
if($key > 0){
if(($item-1) != $prev) $up = false;
if(($item+1) != $prev) $down = false;
}
$prev = $item;
}
return $up || $down;
}
// areAllConsecutive([3,4,5,6]); // true
// areAllConsecutive([3,5,6,7]); // false
// areAllConsecutive([12,11,10,9]); // true
Here's an example that can check this requirement for a list of any size:
class MockNumber
{
public $number;
public function __construct($number)
{
$this->number = $number;
}
static public function IsListConsecutive(array $list)
{
$result = true;
foreach($list as $n)
{
if (isset($n_minus_one) && $n->number !== $n_minus_one->number + 1)
{
$result = false;
break;
}
$n_minus_one = $n;
}
return $result;
}
}
$list_consecutive = array(
new MockNumber(0)
,new MockNumber(1)
,new MockNumber(2)
,new MockNumber(3)
);
$list_not_consecutive = array(
new MockNumber(5)
,new MockNumber(1)
,new MockNumber(3)
,new MockNumber(2)
);
printf("list_consecutive %s consecutive\n", MockNumber::IsListConsecutive($list_consecutive) ? 'is' : 'is not');
// output: list_consecutive is consecutive
printf("list_not_consecutive %s consecutive\n", MockNumber::IsListConsecutive($list_not_consecutive) ? 'is' : 'is not');
// output: list_not_consecutive is not consecutive
If u don't wanna mess with any sorting, picking any of three numbers that are consecutive should give you:
- it either is adjacent to both the other numbers (diff1 = 1, diff2 = -1)
- the only number that is adjacent (diff = +-1) should comply the previous statement.
Test for the first condition. If it fails, test for the second one and under success, you've got your secuence; else the set doesn't comply.
Seems right to me. Hope it helps.
I think you need something like the following function (no need of arrays to store data)
<?php
function seqOfthree($entries) {
// entries has to be sorted descending on $e->number
$sequence = 0;
$lastNumber = 0;
foreach($entries as $e) {
if ($sequence==0 or ($e->number==$lastNumber-1)) {
$sequence--;
} else {
$sequence=1;
}
$lastNumber = $e->number;
if ($sequence ==3) {
// if you need the array of sequence you can obtain it easy
// return $records = range($lastNumber,$lastNumber+2);
return true;
}
}
// there isn't a sequence
return false;
}
function isConsecutive($array, $total_consecutive = 3, $consecutive_count = 1, $offset = 0) {
// if you run out of space, e.g. not enough array values left to full fill the required # of consecutive count
if ( $offset + ($total_consecutive - $consecutive_count ) > count($array) ) {
return false;
}
if ( $array[$offset] + 1 == $array[$offset + 1]) {
$consecutive_count+=1;
if ( $consecutive_count == $total_consecutive ) {
return true;
}
return isConsecutive($array, $total_consecutive, $consecutive_count, $offset+=1 );
} else {
return isConsecutive($array, $total_consecutive, 1, $offset+=1 );
}
}
The following function will return the index of the first of the consecutive elements, and false if none exist:
function findConsecutive(array $numbers)
{
for ($i = 0, $max = count($numbers) - 2; $i < $max; ++$i)
if ($numbers[$i] == $numbers[$i + 1] - 1 && $numbers[$i] == $numbers[$i + 2] - 2)
return $i;
return false;
}
Edit: This seemed to cause some confusion. Like strpos(), this function returns the position of the elements if any such exists. The position may be 0, which can evaluate to false. If you just need to see if they exist, then you can replace return $i; with return true;. You can also easily make it return the actual elements if you need to.
Edit 2: Fixed to actually find consecutive numbers.
Related
I have a function which gives me all combination of values, in an array with fixed length a fixed sum :
// $n_valeurs is the length of the array
// $x_entrees is the sum
function distributions_possibles($n_valeurs, $x_entrees, $combi_presences = array()) {
if ($n_valeurs == 1) {
$combi_presences[] = $x_entrees;
return array($combi_presences);
}
$combinaisons = array();
// on fait appel à une fonction récursive pour générer les distributions
for ($tiroir = 0; $tiroir <= $x_entrees; $tiroir++) {
$combinaisons = array_merge($combinaisons, distributions_possibles(
$n_valeurs - 1,
$x_entrees - $tiroir,
array_merge($combi_presences, array($tiroir))));
}
return $combinaisons;
}
distributions_possibles(4,2);
// output :
[0,0,0,2]
[0,0,1,1]
[0,0,2,0]
[0,1,0,1]
[0,1,1,0]
[0,2,0,0]
[1,0,0,1]
[1,0,1,0]
[1,1,0,0]
[2,0,0,0]
I need to generate all possible combinations adding another parameter : a reference array $ref whose values are considered as limits.
All combination $combi generated must respect the rule : $combi[x] <= $ref[x]
For example with [2,1,1,0] we can't have [0,0,2,0], [0,2,0,0].
I created the following function to add the new parameter :
// $distribution is the array reference
// $similitude is the sum of values
function SETpossibilites1distri($distribution, $similitude){
$possibilites = [];
$all_distri = distributions_possibles(count($distribution), $similitude);
foreach($all_distri as $distri){
$verif = true;
$distri_possi = [];
for($x = 0; $x < count($distri); $x++){
if($distri[$x] > $distribution[$x]){
$verif = false;
break;
}
if($distribution[$x] == 0){
$distri_possi[$x] = null;
}
elseif($distribution[$x] > $distri[$x] && $distri[$x] != 0){
// si c'est une valeur fixée qui informe sur la distri_cach
if($this->distri_cach[$x] == $distri[$x]){
$distri_possi[$x] = $distri[$x]+.1;
}
else{
$distri_possi[$x] = $distri[$x]+.2;
}
}
else{
$distri_possi[$x] = $distri[$x];
}
}
if($verif){
$possibilites[] = $distri_possi;
}
}
return $possibilites;
}
This function makes me generate and filter a big list of combinations with the new parameter.
I need to have a function which generates only the combinations I want.
Do you have ideas ?
Honestly, the simplest solution would be to generate the full set of possibilities and then filter the unsuitable results afterwards. Trying to apply a mask over a recursive function like this is going to be a giant pile of work, which will likely only complicate and bog down the process.
That said, there are a couple ways in which I think you could optimize your generation.
Caching
Write a simple cache layer so that you're not constantly re-computing smaller sub-lists, eg:
function cached_distributions_possibles($n_valeurs, $x_entrees, $combi_presences = array()) {
$key = "$n_valeurs:$x_entrees";
if( ! key_exists($key, $this->cache) ) {
$this->cache[$key] = distributions_possibles($n_valeurs, $x_entrees, $combi_presences);
}
return $this->cache[$key];
}
You might want to set a lower limit on the size of a list that will be cached so you can balance between memory usage and CPU time.
Generators: https://www.php.net/manual/en/language.generators.overview.php
As it stands the function is basically building out many redundant subtrees of combinations in-memory, and you're likely to run into memory usage concerns depending on how broad the sets of possibilities become.
Rather than something like:
function foo() {
$result = [];
for(...) {
result[] = foo(...);
}
return $result;
}
Something like:
function foo() {
for(...) {
yield foo(...);
}
}
Now you're essentially only ever holding in memory a single copy of the sublist segments you're currently interested in, and a handful of coroutines, rather than the whole subtree.
I have found a solution, here it is :
function sous($dist, $d){
$l = count($dist);
$L = [[]];
foreach(range(0,$l - 1) as $i){
$K = [];
$s = array_sum(array_slice($dist, $i+1));
foreach($L as $p){
$q = array_sum($p);
$m = max($d-$q-$s, 0);
$M = min($dist[$i], $d-$q);
foreach(range($m, $M) as $j){
$p_copy = $p;
$p_copy[] = $j;
$K[] = $p_copy;
}
}
$L = $K;
}
return $L;
}
I've written this function that returns a time(number )that must not exist in the same day in the database which means that the same time must not be repeated but when I execute it the time(number) is repeated
can you help me sort this one out?
screen shots of the tables
https://files.fm/u/9aw9yc8x
note: the table is empty and it will be filled when the entire code is executed I have just posted the function that must not repeat the time.
function checkTime($className,$day,$conn){
global $numberLimet,$Daily,$UserLimt,$exist;
$sqlHoure = "SELECT * from schedule WHERE classname='".$className."'";
$hourQuery = mysqli_query($conn,$sqlHoure);
$h = array();
while ($hour = mysqli_fetch_assoc($hourQuery)){
$h[$hour['hour']] = $hour['day1'];
// array_search();
}
$number = rand(1,7);
if(array_search($day,$h) && array_key_exists($number,$h)){
return 'error';
}else{
if($hour['hour'] != $number && $hour['day1'] != $day){
$numberLimet++;
if($numberLimet == $Daily){
$UserLimt = 1;
$FristAdd = false;
echo("UserLimt" . $UserLimt);
return 'errorLimit';
}else{
return $number;
}
}else{
}
}
return $h;
}
Let me change my question completely to explain myself better;
I have an order;
An order have multiple order rows. Each order row has two fields; Quantity ordered, and quantity delivered.
If all order rows' quantities delivered are the same as the quantity ordered, the entire order should get a status of '100% delivered'.
If multiple or even one order row's quantities delivered does not match the quantities ordered the entire order should get a status of 'partly delivered'.
If no order row have any deliveries (if all deliveries stands on 0) the status should be '0% delivered'.
What I have so far looks only at the last order row of the entire order because all the previous rows gets overridden by the latest check. This is my code;
public function deliveryAction(Request $request, $id) {
$em = $this->getDoctrine()->getManager();
$order = $em->getRepository('QiBssBaseBundle:PmodOrder')->find($id);
$orderRowsDelivered = $request->request->all();
$delivered = "0%";
foreach ($orderRowsDelivered['order_row_id'] as $orderRowId => $quantityDelivered) {
if($quantityDelivered != '' || $quantityDelivered != null) {
$orderRow = $em->getRepository("QiBssBaseBundle:PmodOrderRow")->find($orderRowId);
$orderDelivered = new PmodDelivery();
$orderDelivered->setOrderRow($orderRow);
$orderDelivered->setQuantity($quantityDelivered);
$orderDelivered->setTimeArrived(new \DateTime());
$em->persist($orderDelivered);
$em->flush();
if($orderRow->getQuantityDelivered() > 0 && $orderRow->getQuantityDelivered() < $orderRow->getQuantity()) {
$delivered = "partly";
} elseif ($orderRow->getQuantityDelivered() == $orderRow->getQuantity()) {
$delivered = "100%";
}
}
}
var_dump($delivered);exit;
return new RedirectResponse ... ;
}
Because as of this moment he looks at the last one with 10 and 8 in the example image, and give a status of 'partly', as soon as the 'quantity delivered' amounts is entered. But he should take all rows together.
I hope this makes more sense.
Based on what Cerad in the comments of his own answer said, this is my answer; (I'll make use of OP's scenario where he uses order rows per order.)
I've added an extra property to my OrderRow entity called $rowStatus.
After that I created a getter function for the $rowStatus called getRowStatus() that gives each row a status individually;
public function getRowStatus()
{
if ($this->getQuantityDelivered() == $this->getQuantity()) {
return $this->rowStatus = 100;
} elseif ($this->getQuantityDelivered() == 0) {
return $this->rowStatus = 0;
} else {
return $this->rowStatus = 50;
}
}
After that in my Order entity I've added a $deliveryStatus property, with a corresponding getter function called getDeliveryStatus() that looks like this;
public function getDeliveryStatus()
{
if (count($this->getOrderRows()) > 0) { //this check is to make sure there are orderRows, because you can't devide by zero if it might happen that there are no order rows. If not the delivery status will just be set on 0.
$sum = 0;
foreach ($this->getOrderRows() as $row) {
$sum += $row->getRowStatus();
}
$average = $sum / count($this->getOrderRows());
if ($average == 100) {
return $this->deliveryStatus = 100;
} elseif ($average == 0) {
return $this->deliveryStatus = 0;
} else {
return $this->deliveryStatus = 50;
}
} else {
return $this->deliveryStatus = 0;
}
}
That's it! After this I just use an enum function to display the 100 as "100% delivered", the 50 as "partly delivered", and the 0 as "0% delivered". I know this isn't really necessary, and you can instead change the status number directly to a string or whatever you want to display.
Just off the top of my head I might do:
$deliveredNone = true;
$deliveredAll = true;
$deliveredSome = false;
foreach ($orderRowsDelivered['order_row_id'] as $orderRowId => $quantityDelivered) {
if ($quantityDelivered) {
$deliveredNone = false; // Know that something has been delivered
}
...
if ($orderRow->getQuantityDelivered() != $orderRow->getQuantity()) {
$deliveredSome = true;
$deliveredAll = false;
}
}
$delivered = null;
if ($deliveredNone) $delivered = '0%';
if ($deliveredAll) $delivered = '100%';
if ($deliveredSome) $delivered = 'partly';
Though I would probably just update the order with the quantities delivered then use a different function to calculate the percentage delivered. As you can see, mixing the two processes can result in confusion.
Here is what I have tried but it is giving me wrong output. Can anyone point out what is the mistake?
function superPower($n) {
$response = false;
$n = abs($n);
if ($n < 2) {
$response = true;
}
for ($i=2;$i<$n;$i++) {
for ($j=2;$j<$n;$j++) {
if (pow($i,$j) == $n) {
$response = true;
}
}
}
return $response;
}
For example if I give it number 25, it gives 1 as output. //Correct
But if I give it 26 it still gives me 1 which is wrong.
By using superPower, you are essentially trying to put a certain defence to the power of an attack to see if it holds up. This can be done much more effectively than through the brute-force method you have now.
function superPower( $hp) { // Niet used Superpower!
if( $hp <= 1) return true;
for( $def = floor(sqrt($hp)); $def > 1; $def--) { // Niet's Defence fell
for( $atk = ceil(log($hp)/log($def)); $atk > 1; $atk--) { // Niet's Attack fell
if( pow($def,$atk) == $hp) return true;
break;
// you don't need the $atk loop, but I wanted to make a Pokémon joke. Sorry.
}
// in fact, all you really need here is:
// $atk = log($hp)/log($def);
// if( $atk-floor($atk) == 0) return true;
}
return false;
}
The maths on the accepted answer is absolutely brilliant, however there are a couple of issues with the solution:
the function erroneously returns true for all of the following inputs: monkey, -3 and 0. (Technically 0 is unsigned, so there is no way of getting it by taking a positive integer to the power of another positive integer. The same goes for any negative input.)
the function compares floating numbers with integers (floor() and ceil() return float), which should be avoided like the plague. To see why, try running php -r '$n = (-(4.42-5))/0.29; echo "n == {$n}\n".($n == 2 ? "OK" : "Surprise")."\n";'
The following solution improves on the idea by fixing all of the above issues:
function superPower($value)
{
// Fail if supplied value is not numeric
if (!is_numeric($value)) {
// throw new InvalidArgumentException("Value is not numeric: $value");
return false;
}
// Normalise numeric input
$number = abs($value);
// Fail if supplied number is not an integer
if (!is_int($number)) {
// throw new InvalidArgumentException("Number is not an integer: $number");
return false;
}
// Exit early if possible
if ($number == 1) {
// 1 to the power of any positive integer is one
return true;
} elseif ($number < 1) {
// X to the power of Y is never less then 1, if X & Y are greater then 0
return false;
}
// Determine the highest logarithm base and work backwards from it
for ($base = (int) sqrt($number); $base > 1; $base--) {
$coefficient = log($number)/log($base);
// Check that the result of division is a whole number
if (ctype_digit((string) $coefficient)) {
return true;
}
}
return false;
}
Although the order of matrices should be fine, the following code throws back the exception. It might be a tiny thing I'm not being able to notice, but can't figure it out.
<?php
$mat1 = array(5,1);
$mat2 = array(1,5);
function matrixmult($m1,$m2){
$r=count($m1);
$c=count($m2[0]);
$p=count($m2);
if(count($m1[0])!=$p){throw new Exception('Incompatible matrixes');}
$m3=array();
for ($i=0;$i< $r;$i++){
for($j=0;$j<$c;$j++){
$m3[$i][$j]=0;
for($k=0;$k<$p;$k++){
$m3[$i][$j]+=$m1[$i][$k]*$m2[$k][$j];
}
}
}
}
return($m3);
}
matrixmult($mat1,$mat2);
?>
You're specifying your test matrices wrong, in two ways:
The arrays aren't two-dimensional (that is, arrays of arrays of numbers).
Even if you wrapped another array( ) around them, the condition that the width of the first matrix be equal to the height of the second matrix doesn't hold with [5 1] and [1 5], which are both 2 wide and 1 high.
What you need is something like
$mat1 = array(array(5,1));
$mat2 = array(array(1),array(5));
Just to round this out. Here is a working solution:
function M_mult($_A,$_B) {
// AxB outcome is C with A's rows and B'c cols
$r = count($_A);
$c = count($_B[0]);
$in= count($_B); // or $_A[0]. $in is 'inner' count
if ( $in != count($_A[0]) ) {
print("ERROR: need to have inner size of matrices match.\n");
print(" : trying to multiply a ".count($_A)."x".count($_A[0])." by a ".count($_B)."x".count($_B[0])." matrix.\n");
print("\n");
exit(1);
}
// allocate retval
$retval = array();
for($i=0;$i< $r; $i++) { $retval[$i] = array(); }
// multiplication here
for($ri=0;$ri<$r;$ri++) {
for($ci=0;$ci<$c;$ci++) {
$retval[$ri][$ci] = 0.0;
for($j=0;$j<$in;$j++) {
$retval[$ri][$ci] += $_A[$ri][$j] * $_B[$j][$ci];
}
}
}
return $retval;
}
}
You must delete the curly braces before return.
<?php
$mat1 = array(5,1);
$mat2 = array(1,5);
function matrixmult($m1,$m2){
$r=count($m1);
$c=count($m2[0]);
$p=count($m2);
if(count($m1[0])!=$p){throw new Exception('Incompatible matrixes');}
$m3=array();
for ($i=0;$i< $r;$i++){
for($j=0;$j<$c;$j++){
$m3[$i][$j]=0;
for($k=0;$k<$p;$k++){
$m3[$i][$j]+=$m1[$i][$k]*$m2[$k][$j];
}
}
}
return($m3);
}
matrixmult($mat1,$mat2);
?>