Checking a for progression in a list of variables - php

Let's say I want to check for simple mathematical progression. I understand I can do it like this:
if ($a<$b and $b<$c and $c<$d and $d<$e and $e<$f) { echo OK; }
Is there a way to do it in a more convenient way? Like so
if ($a..$f isprog(<)) { echo OK; }

I don 't know if I get your problem right. But propably the solution for your progression could be the SplHeap object of the SPL delivered with php.
$stack = new SplMaxHeap();
$stack->insert(1);
$stack->insert(3);
$stack->insert(2);
$stack->insert(4);
$stack->insert(5);
foreach ($stack as $value) {
echo $value . "\n";
}
// output will be: 5, 4, 3, 2, 1

I havent heard of something like this, but how about using simple function:
function checkProgress($vars){ //to make it easie i assume that vars can be given in an array
$result = true;
for ($i=0; $i<= count($vars); $i++){
if ($i>0 && $vars[$i] > $vars[$i-1]) continue;
$result = false;
}
return $result;
}

Solved it quick and dirty:
function ispositiveprogression($vars) {
$num=count($vars)-1;
while ($num) {
$result = true;
if ($vars[$num] > $vars[$num-1]) {
$num--;
}
else { $result = false; break; }
}
return $result;
}

Create an array of values, iterate over them and maintaining a flag that checks if the current element value is greater than / less than that of the next value. Unlike some of the solutions in this thread, this doesn't loop through the whole array. It stops looping when it discovers the first value that's not a progression. This will be a lot more faster if the operation involves a lot of numbers.
function checkIfProg($arr, $compare) {
$flag = true;
for ($i = 0, $c = count($arr); $i < $c; $i++) {
if ($compare == '<') {
if (isset($arr[$i + 1]) && $arr[$i] > $arr[$i + 1]) {
$flag = false;
break;
}
} elseif ($compare == '>') {
if (isset($arr[$i + 1]) && $arr[$i] < $arr[$i + 1]) {
$flag = false;
break;
}
}
}
return $flag;
}
Usage:
$a = 2;
$b = 3;
$c = 4;
$d = 5;
$e = 9;
$f = 22;
$arr = array($a, $b, $c, $d, $e, $f);
var_dump(checkIfProg($arr, '<')); // => bool(true)
If you want the array to be created dynamically, you could use some variable variable magic to achieve this:
$arr = array();
foreach (range('a','f') as $v) {
$arr[] = $$v;
}
This will create an array containing all the values of variables from $a ... $f.

Related

Find first duplicate in an array

Given an array a that contains only numbers in the range from 1 to a.length, find the first duplicate number for which the second occurrence has the minimal index. In other words, if there are more than 1 duplicated numbers, return the number for which the second occurrence has a smaller index than the second occurrence of the other number does. If there are no such elements, return -1.
My code:
function firstDuplicate($a) {
$unique = array_unique($a);
foreach ($a as $key => $val) {
if ($unique[$key] !== $val){
return $key;
}else{
return -1;
}
}
}
The code above will be OK when the input is [2, 4, 3, 5, 1] but if the input is [2, 1, 3, 5, 3, 2] the output is wrong. The second duplicate occurrence has a smaller index. The expected output should be 3.
How can I fix my code to output the correct result?
$arr = array(2,1,3,5,3,2);
function firstDuplicate($a) {
$res = -1;
for ($i = count($a); $i >= 1; --$i) {
for ($j = 0; $j < $i; ++$j) {
if ($a[$j] === $a[$i]) {
$res = $a[$j];
}
}
}
return $res;
}
var_dump(firstDuplicate($arr));
By traversing the array backwards, you will overwrite any previous duplicates with the new, lower-indexed one.
Note: this returns the value (not index), unless no duplicate is found. In that case, it returns -1.
// Return index of first found duplicate value in array
function firstDuplicate($a) {
$c_array = array_count_values($a);
foreach($c_array as $value=>$times)
{
if($times>1)
{
return array_search($value, $array);
}
}
return -1;
}
array_count_values() will count the duplicate values in the array for you then you just iterate over that until you find the first result with more then 1 and search for the first key in the original array matching that value.
Python3 Solution:
def firstDuplicate(a):
mySet = set()
for i in range(len(a)):
if a[i] in mySet:
return a[i]
else:
mySet.add(a[i])
return -1
function firstDuplicate($a) {
foreach($a as $index => $value) {
$detector[] = $value;
$counter = 0;
foreach($detector as $item) {
if($item == $value) {
$counter++;
if($counter >= 2) {
return $value;
break;
}
}
}
}
return -1;
}
It's easy to just get the first number that will be checked as duplicated, but unfortunately, this function exceeded 4 seconds with a large array of data, so please using it with a small scale of array data.
EDIT
I have new own code fixes execution time for large array data
function firstDuplicate($a) {
$b = [];
$counts = array_count_values($a);
foreach($counts as $num => $duplication) {
if($duplication > 1) {
$b[] = $num;
}
}
foreach($a as $value) {
if(in_array($value, $b)) {
$detector[] = $value;
$counter = 0;
foreach($detector as $item) {
if($item == $value) {
$counter++;
if($counter >= 2) {
return $value;
break;
}
}
}
}
}
return -1;
}
The new code target the current numbers having a reputation only by using array_count_values()
function firstDuplicate($a) {
$indexNumber = -1;
for($i = count($a); $i >= 1 ; --$i){
for($k = 0; $k < $i; $k++){
if(isset($a[$i]) && ($a[$i] === $a[$k]) ){
$indexNumber = $a[$k];
}
}
}
return $indexNumber;
}
Remove error from undefined index array.

PHP: How to get combination with the most matches of the sub-arrays?

I have an $myArray with sub-arrays which always contain 5 numbers - the numbers are sorted by size and can not be repeated in the sub-array, but there can be more "identical" sub-arrays (sub-arrays with the same numbers) in $myArray.
$myArray = array(
array(1,2,3,4,5),
array(5,6,10,18,20),
array(1,2,3,4,5),
array(2,3,4,5,9),
array(1,2,3,7,9),
array(1,3,4,5,7),
array(2,3,4,7,9),
array(2,4,5,10,29),
array(1,8,10,11,15) // etc.
);
How can I get an combination (array) of $n numbers where this combination (or rather the 5-number combinations generated from this $n-number combination) will match the most of the sub-arrays of $myArray?
Example: the desired result for $n=7 for the $myArray would be array(1,2,3,4,5,7,9) because there are twenty one 5-number combinations in total derived from this result:
1,2,3,4,5
1,2,3,4,7
1,2,3,4,9
//... and so on
and these combinations would match almost all sub-arrays (only second and last two sub-arrays are out of range).
I have tried counting with array_count_values() but simple frequency of all numbers doesn't work in this case...
class Combinations implements Iterator
{
protected $c = null;
protected $s = null;
protected $n = 0;
protected $k = 0;
protected $pos = 0;
function __construct($s, $k) {
if(is_array($s)) {
$this->s = array_values($s);
$this->n = count($this->s);
} else {
$this->s = (string) $s;
$this->n = strlen($this->s);
}
$this->k = $k;
$this->rewind();
}
function key() {
return $this->pos;
}
function current() {
$r = array();
for($i = 0; $i < $this->k; $i++)
$r[] = $this->s[$this->c[$i]];
return is_array($this->s) ? $r : implode('', $r);
}
function next() {
if($this->_next())
$this->pos++;
else
$this->pos = -1;
}
function rewind() {
$this->c = range(0, $this->k);
$this->pos = 0;
}
function valid() {
return $this->pos >= 0;
}
//
protected function _next() {
$i = $this->k - 1;
while ($i >= 0 && $this->c[$i] == $this->n - $this->k + $i)
$i--;
if($i < 0)
return false;
$this->c[$i]++;
while($i++ < $this->k - 1)
$this->c[$i] = $this->c[$i - 1] + 1;
return true;
}
}
$tickets = array(
array(1,2,3,4,5),
array(5,6,10,18,20),
array(1,2,3,4,5),
array(2,3,4,5,9),
array(1,2,3,7,9),
array(1,3,4,5,7),
array(2,3,4,7,9),
array(2,4,5,10,29),
array(1,8,10,11,15) // etc.
);
// first we need to find all numbers that are actually in one of the arrays.
foreach($tickets as $anArray) {
foreach($anArray as $aNumberUsed){
$numbersUsed[$aNumberUsed] = $aNumberUsed;
}
}
// next we assign the number of integers in the set we are looking for.
$r = 7;
// next we run the above class on our array (which gets us all of the possible combinations of these numbers).
foreach(new Combinations($numbersUsed, 7) as $comboKey => $substring){
$comboList[$comboKey] = $substring;
$countWins = 0;
// here we loop through all of the 5 number arrays, and flag any array who has all the variables in this iteration of the possible numbers. There are cleaner ways to do this, but this is easy to understand.
foreach($tickets as $valueList) {
$countNumbersFound = 0;
foreach($valueList as $value) {
if(in_array($value, $substring)) {
$countNumbersFound++;
}
}
if($countNumbersFound == 5) {
$countWins++;
}
}
$foundCount[$comboKey] = $countWins;
}
$bigly = max($foundCount);
$key = array_search($bigly, $foundCount);
foreach($comboList[$key] as $wellDone) {
echo "$wellDone ,";
}
The class is blatantly stolen from here: http://www.developerfiles.com/combinations-in-php/
Everything after the class is original. I don't believe in reinventing the wheel.

Issue with custom script to sort Arrays ascending and descending order

I have an issue to deal with here (a logical error in my code 99%). I just can't seem to find the way to fix it, but I bet one of you will find the problem in no time!
I have to create a function which sorts array passed to it in asc or desc order, but can't use any array sorting functions !
I've been struggling with loops until now and I finally want to ask help from other devs ( you ).
Currently only code for ascending is worked on, descending will be no problem I assume once I do this one. It kinda of does sort values up to some point, but then stops ( it stops if the next smallest value is at the end of the passed array ). What could I do to prevent this and make it sort the whole array and it's elements?
Here is the code so far.
<?php
function order_array($array,$mode = 'ascending') {
$length = count($array);
if($mode == 'descending') {
return $array;
} else {
$sorted_array = array();
$used_indexes = array();
for($i = 0; $i < $length; $i++) {
$smallest = true;
echo $array[$i] . '<br/>';
for($y = 0; $y < $length; $y++) {
//echo $array[$i] . ' > ' . $array[$y] . '<br/>';
// if at ANY time during checking element vs other ones in his array, he is BIGGER than that element
// set smallest to false
if(!in_array($y,$used_indexes)) {
if($array[$i] > $array[$y]) {
$smallest = false;
break;
}
}
}
if($smallest) {
$sorted_array[] = $array[$i];
$used_indexes[] = $i;
}
}
return $sorted_array;
}
}
$array_to_sort = array(1, 3, 100, 99, 33, 20);
$sorted_array = order_array($array_to_sort);
print_r($sorted_array);
?>
I've solved the issue myself by doing it completely different. Now it sorts correctly all the elements of the passed in array. The logical issue I had was of using for() loop. The for() loop ran only a set ( length of passed array ) number of times, while we need it to loop more than that, because we will need to loop all the way untill we have a new sorted array in ascending order. Here is the code that will work
function order_array($array,$mode = 'ascending') {
if($mode == 'descending') {
// for() wont work here, since it will only loop an array length of times, when we would need it
// to loop more than that.
while(count($array)){
$value = MAX($array);
$key = array_search($value, $array);
if ($key !== false) {
unset($array[$key]);
}
$sorted[] = $value;
}
return $sorted;
} else {
// for() wont work here, since it will only loop an array length of times, when we would need it
// to loop more than that.
while(count($array)){
$value = MIN($array);
$key = array_search($value, $array);
if ($key !== false) {
unset($array[$key]);
}
$sorted[] = $value;
}
return $sorted;
}
}
function order_array($array,$mode = 'ascending') {
$length = count($array);
$sorted_array = array();
$used_indexes = array();
for($i = 0; $i < $length; $i++) {
$smallest = true;
echo $array[$i] . '<br/>';
for($y = 0; $y < $length; $y++) {
//echo $array[$i] . ' > ' . $array[$y] . '<br/>';
// if at ANY time during checking element vs other ones in his array, he is BIGGER than that element
// set smallest to false
if(!in_array($y,$used_indexes)) {
if($array[$i] > $array[$y]) {
$smallest = false;
break;
}
}
}
if($smallest) {
$sorted_array[] = $array[$i];
$used_indexes[] = $i;
}
if($mode == 'descending') {
return array_reverse($sorted_array);
}
return $sorted_array;
}
}

N random numbers that can duplicate after 2 elements

I want to generate 10 numbers with each ranging from (1 to 5) but can only duplicate after 2 elements
for example 5 3 1 4 2 5(can be duplicated here) 2 (cannot be duplicate here since it occur before 1 element) ...etc.
I have this code in php working but its performance is awful since it sometimes exceeds the maximum 30 seconds execution time.
<?php
function contain($prevItems, $number) {
if (count($prevItems) == 3)
{
array_shift($prevItems);
}
for ($k=0; $k<count($prevItems); $k++) {
if ($prevItems[$k] == $number)
return true;
}
return false;
}
$num[0] = rand(1,5);
$prevItems[0] = $num[0];
for ($i=1; $i<=10; $i++) {
$num[$i] = rand(1,5);
while (contain($prevItems, $num[$i])) {
$num[$i] = rand (1,5);
}
$prevItems[$i] = $num[$i]; //append into the array
}
print_r($num);
?>
Edit:
I have also tried this method, its performance is good but it duplicates elements
<?php
$evalAccurance = array();
$count = 0;
while ( $count < 11)
{
$random = rand(1, 5);
if (in_array($random, $evalAccurance))
{
$p = $random;
for ($k = $p ; $k <5; $k++)
{
$random = $random++;
if (in_array($random, $evalAccurance))
continue 1;
else break 1;
}
if (in_array($random, $evalAccurance))
{
for ($k = $p ; $k >0; $k--)
{
$random = $random--;
if (in_array($random, $evalAccurance))
continue 1;
else break 1;
}
}
}
$evalAccurance[] = $random;
if (count ($evalAccurance) == 4)
array_shift($evalAccurance);
print_r ($evalAccurance);
$count++;
}
?>
One way you could do this:
// pass to function current array of numbers
function randomNumber($ar){
// create a random number from 1 to 5
$num = rand(1,5);
// check backwards 3 elements for same number, if none found return it
if(!in_array($num, array_slice($ar, -3, 3, true))){
return $num;
} else {
// else recall function with same array of numbers
return randomNumber($ar);
}
}
$array = array();
// loop 10 numbers and call randomNumber with current set of results.
for($i=1; $i<=10; $i++){
$array[] = randomNumber($array);
}
print_r($array);
Using PHP SPLQueue:
$queue = new SplQueue();
$values = array(1, 2, 3, 4, 5);
$container = array();
for ($i = 0; $i < 10; $i++) {
$value = give_random($values, $queue);
$container[] = $value;
if ($queue->offsetExists(1) AND $queue->offsetExists(0)) {
$queue->dequeue();
}
$queue->enqueue($value);
}
function give_random(&$values, &$queue) {
$picked_value = $values[array_rand($values)];
if ($queue->offsetExists(1)) {
if ($picked_value == $queue->offsetGet(1)) {
$picked_value = give_random($values, $queue);
}
}
if ($queue->offsetExists(0)) {
if ($picked_value == $queue->offsetGet(0)) {
$picked_value = give_random($values, $queue);
}
}
return $picked_value;
}
print_r($container);
It could be neater, but you can figure what's going on.
Cheers.

Writing merge sort in PHP

I have tried to write a basic merge sort in PHP involving a small array, yet the problem is it takes about a minute or so to execute, and returns:
Fatal error: Allowed memory size of 536870912 bytes exhausted (tried
to allocate 35 bytes) in /Users/web/www/merge.php on line 39
Does anyone have an idea where the code might be going wrong (if at all)? I've been staring at this for a good hour now.
<?php
$array = array(8,1,2,5,6,7);
print_array($array);
merge_sort($array);
print_array($array);
function merge_sort(&$list){
if( count($list) <= 1 ){
return $list;
}
$left = array();
$right = array();
$middle = (int) ( count($list)/2 );
// Make left
for( $i=0; $i < $middle; $i++ ){
$left[] = $list[$i];
}
// Make right
for( $i = $middle; $i < count($list); $i++ ){
$right[] = $list[$i];
}
// Merge sort left & right
merge_sort($left);
merge_sort($right);
// Merge left & right
return merge($left, $right);
}
function merge(&$left, &$right){
$result = array();
while(count($left) > 0 || count(right) > 0){
if(count($left) > 0 && count(right) > 0){
if($left[0] <= $right[0]){
$result[] = array_shift($left);
} else {
$result[] = array_shift($right);
}
} elseif (count($left) > 0){
$result[] = array_shift($left);
} elseif (count($right) > 0){
$result[] = array_shift($right);
}
}
print_array($result);exit;
return $result;
}
function print_array($array){
echo "<pre>";
print_r($array);
echo "<br/>";
echo "</pre>";
}
?>
In your merge function, you call count on right instead of $right. PHP assumes this is a string constant (at least in 5.3.9) and when casted into an array that always has one element. So count(right) is always one, and you never exit the first merge.
Try this approach. Instead of shifting it, slice.
Also, for in while loop for the merge function, you need to do an and && comparison instead
of ||
function mergeSort($array)
{
if(count($array) == 1 )
{
return $array;
}
$mid = count($array) / 2;
$left = array_slice($array, 0, $mid);
$right = array_slice($array, $mid);
$left = mergeSort($left);
$right = mergeSort($right);
return merge($left, $right);
}
function merge($left, $right)
{
$res = array();
while (count($left) > 0 && count($right) > 0)
{
if($left[0] > $right[0])
{
$res[] = $right[0];
$right = array_slice($right , 1);
}
else
{
$res[] = $left[0];
$left = array_slice($left, 1);
}
}
while (count($left) > 0)
{
$res[] = $left[0];
$left = array_slice($left, 1);
}
while (count($right) > 0)
{
$res[] = $right[0];
$right = array_slice($right, 1);
}
return $res;
}
Have a look at this, the algorithm is already implemented, using array_push and array splice instead of just array_shift.
http://www.codecodex.com/wiki/Merge_sort#PHP
I implement merge sort this way
function mergeSort($Array)
{
$len = count($Array);
if($len==1){
return $Array;
}
$mid = (int)$len / 2;
$left = mergeSort(array_slice($Array, 0, $mid));
$right = mergeSort(array_slice($Array, $mid));
return merge($left, $right);
}
function merge($left, $right)
{
$combined = [];
$totalLeft = count($left);
$totalRight = count($right);
$rightIndex = $leftIndex=0;
while ($leftIndex < $totalLeft && $rightIndex < $totalRight) {
if ($left[$leftIndex] > $right[$rightIndex]) {
$combined[]=$right[$rightIndex];
$rightIndex++;
}else {
$combined[] =$left[$leftIndex];
$leftIndex++;
}
}
while($leftIndex<$totalLeft){
$combined[]=$left[$leftIndex];
$leftIndex++;
}
while ($rightIndex<$totalRight){
$combined[] =$right[$rightIndex];
$rightIndex++;
}
return $combined;
}
Here is the class in PHP to implement the Merge Sort -
<?php
class mergeSort{
public $arr;
public function __construct($arr){
$this->arr = $arr;
}
public function mSort($l,$r){
if($l===null || $r===null){
return false;
}
if ($l < $r)
{
// Same as ($l+$r)/2, but avoids overflow for large $l and $r
$m = $l+floor(($r-$l)/2);
// Sort first and second halves
$this->mSort($l, $m);
$this->mSort($m+1, $r);
$this->merge($l, $m, $r);
}
}
// Merges two subarrays of $this->arr[]. First subarray is $this->arr[$l..$m]. Second subarray is $this->arr[$m+1..$r]
public function merge($l, $m, $r)
{
if($l===null || $m===null || $r===null){
return false;
}
$n1 = $m - $l + 1;
$n2 = $r - $m;
/* create temp arrays */
$L=array();
$R=array();
/* Copy data to temp arrays $L[] and $R[] */
for ($i = 0; $i < $n1; $i++)
$L[$i] = $this->arr[$l + $i];
for ($j = 0; $j < $n2; $j++)
$R[$j] = $this->arr[$m + 1+ $j];
/* Merge the temp arrays back into $this->arr[$l..$r]*/
$i = 0; // Initial index of first subarray
$j = 0; // Initial index of second subarray
$k = $l; // Initial index of merged subarray
while ($i < $n1 && $j < $n2)
{
if($L[$i] <= $R[$j])
{
$this->arr[$k] = $L[$i];
$i++;
}
else
{
$this->arr[$k] = $R[$j];
$j++;
}
$k++;
}
/* Copy the remaining elements of $L[], if there are any */
while($i < $n1)
{
$this->arr[$k] = $L[$i];
$i++;
$k++;
}
/* Copy the remaining elements of $R[], if there are any */
while($j < $n2)
{
$this->arr[$k] = $R[$j];
$j++;
$k++;
}
}
}
$arr = array(38, 27, 43, 5, 9, 91, 12);
$obj = new mergeSort($arr);
$obj->mSort(0,6);
print_r($obj->arr);
?>
I was looking for a optimized Mergesort algorithm in PHP. There are 5 algorithms in the answers, so I tested those, and mine too. Using PHP 7.2.7, these are the times:
Sorting 1000 random numbers:
Avanche 1 0.0396 seconds
Avanche 2 0.0347 seconds
Kartik 0.0291 seconds
Kripa 0.0282 seconds
Samuel 0.0247 seconds
Mine 0.0144 seconds
Sorting 10 random numbers:
Avanche 1 0.000222 seconds
Kartik 0.000216 seconds
Kripa 0.000159 seconds
Avanche 2 0.000144 seconds
Samuel 0.000128 seconds
Mine 0.000098 seconds
So, although I encourage to whom read it to make it faster (that was I was looking for, and I believe it can be done), I let you my implementation too, cause seems to be faster than the other answers:
//This function needs start and end limits
function mergeSortRec(&$a,$start,$end){
if($start<$end){
$center=($start+$end)>>1; //Binary right shift is like divide by 2
mergeSortRec($a, $start, $center);
mergeSortRec($a, $center+1, $end);
//Mixing the 2 halfs
$aux=array();
$left=$start; $right=$center;
//Main loop
while($left<$center && $right<=$end){
if($a[$left]<$a[$right]){
$aux[]=$a[$left++];
}else{
$aux[]=$a[$right++];
}
}
//Copy the rest of the first half
while($left<$center) $aux[]=$a[$left++];
//Copy the rest of the second half
while($right<=$end) $aux[]=$a[$right++];
//Copy the aux array to the main array
foreach($aux as $v) $a[$start++]=$v;
}
}
//This is the function easier to call
function mergeSort(&$a) {
mergeSortRec($a,0,count($a)-1);
}
If you post a new answer, let me a comment to test it and add it.
Edit: I did some new optimizations, for those looking for a better implementation.
Your merge sort accepts a list by reference
function merge_sort(&$list)
So you need to assign it the new merged and sorted list. So instead of
return merge($left, $right);
do
$list = $this->merge($left, $right);
That should do it, just remove the exit and fix the count variable
MergeSort in PHP
<?php
class Solution
{
function mergeSort(&$arr)
{
if(count($arr) > 1) {
$mid = floor(count($arr)/2);
$left = array_slice($arr, 0, $mid);
$right = array_slice($arr, $mid);
$this->mergeSort($left);
$this->mergeSort($right);
// Merge the results.
$i = $j = $k = 0;
while(($i < count($left)) && ($j < count($right))) {
if($left[$i] < $right[$j]) {
$arr[$k] = $left[$i];
$i++;
} else {
$arr[$k] = $right[$j];
$j++;
}
$k++;
}
while($i < count($left)) {
$arr[$k] = $left[$i];
$i++;
$k++;
}
while($j < count($right)) {
$arr[$k] = $right[$j];
$j++;
$k++;
}
}
}
}
$s = new Solution();
$tmp = [12, 7, 11, 13, 5, 6, 7];
$s->mergeSort($tmp);
print_r($tmp);

Categories