Nested loops and array formation - php

Suppose that I start with an array that looks like:
$array_1 = array(array(1,2,3), array(2,4,5), array(3,6,7));
For simplicity, assume that I have a rule that says: delete the first subarray and then delete the first elements of the remaining subarrays. This would yield the result:
$new_array = array(array(4,5), array(6,7))
Then assume I expand the problem to larger arrays like:
$array_2 = array(array(1,2,3,4), array(2,3,4,5), array(3,4,5,6), array(4,5,6,7));
I have the same rule here - delete first subarray and then delete first elements of the remaining subarrays. BUT this rule must be continued until the smallest subarray contains only two elements (as in the first example). So that in stage one of the process, my new array would look like:
$new_array_s1 = array(array(3,4,5), array(4,5,6), array(5,6,7));
But in the final stage, the completed array would look like:
$new_array_s2 = array(array(5,6), array(6,7));
For context, here is my code for the $array_1 example:
<?php
$array_1 = array(array(1,2,3), array(2,4,5), array(3,6,7));
$array_shell = $array_1;
unset($array_shell[0]);
$array_size = count($array_shell);
$i = 0;
$cofactor = array();
while($i < $array_size) {
$el_part_[$i] = $array_1[$i];
unset($el_part_[$i][0]);
$el_part_[$i] = array_values($el_part_[$i]);
array_push($cofactor, $el_part_[$i]);
++$i;
}
echo '<pre>',print_r($cofactor,1),'</pre>';
?>
My Question: How can I generalise this code to work for N sized arrays?

You don't need a complicated code .. Just loop and use array_shift
Example:
print_r(cleanUp($array_1));
Function
function cleanUp($array) {
array_shift($array);
foreach($array as $k => $var) {
is_array($var) && array_shift($array[$k]);
}
return $array;
}
See Live DEMO

$num = count($array_1);
for($i=0;$i<=$num;$i++)
{
if($i==0)
unset($array_1[$i]);
else unset($array_1[$i][0]);
}

Building off of Baba's answer, to work with N element arrays (assuming each array contains the same number of elements):
<?php
$array_1 = array(array(1,2,3,4), array(2,4,5,6), array(3,6,7,8));
$array = $array_1;
while(count($array[0]) > 2)
$array = cleanUp($array);
print_r($array);
function cleanUp($array) {
array_shift($array);
foreach($array as $k => $var) {
is_array($var) && array_shift($array[$k]);
}
return $array;
}
This will keep reducing until the sub-arrays have only 2 elements.
-Ken

Related

Right rotation on array in php

I have an array for example
$a = [1,2,3,4,5];
From this $a, how to take last one and set it first like [5,1,2,3,4]
And how do I take last two arrays to make it like [4,5,1,2,3]
You can combine using array_pop(), which pops the last element of the array out, and array_unshift() to push it to the front of the array. You can create a simple function for this,
function array_pop_unshift($array) {
array_unshift($array, array_pop($array));
return $array;
}
Then use it as
$a = [1,2,3,4,5];
$new = array_pop_unshift($a);
print_r($new); // [5,1,2,3,4]
To continue shifting it, just call the function again until you're done, for instance through a for loop,
$a = [1,2,3,4,5];
for ($i = 0; $i < 2; $i++) {
$new = array_pop_unshift($a);
}
print_r($new); // [4,5,1,2,3]
Live demo at https://3v4l.org/CoJZZ
If you want to avoid the cost of several array_unshift and array_pop, you can build a generator that plays with the array internal pointer.
If you really need a result array, use iterator_to_array() to create it:
$a = range(1,5);
function rotate(&$array, $step = 1) {
$length = count($array);
end($array);
while ($step--)
prev($array);
while ($length--) {
next($array);
if (key($array) === null)
reset($array);
yield current($array);
}
}
print_r(iterator_to_array(rotate($a, 2))); // [4,5,1,2,3]
demo
Note that the rotate() generator uses a reference to avoid the array copy but doesn't modify the orginal array: it only moves the array pointer n times (where n is the array length) from the choosen position. When the array pointer is out of the array (key() returns null) the array pointer is reseted. In other words it stays efficient even with a large array and many rotations (what I have called "step" in the code).
You are actually doing right rotation, not left. Anyway, here are functions for doing both of them. They are probably not the most efficient but they are short in code and pretty self-explanatory:
<?php
function rotateLeft($array, $times) {
for($i=0; $i<$times; $i++){
$array[] = array_shift($array);
}
return $array;
}
function rotateRight($array, $times) {
for($i=0; $i<$times; $i++){
array_unshift($array, array_pop($array));
}
return $array;
}
$a = [1,2,3,4,5];
$a = rotateRight($a, 1);
print_r($a);
?>
to take last one and set it first This is known as right rotation.
And
$k is the number of units the shifting should be. $a is the array.
for($x=0; $x < $k; $x++){
//remove last element
$last = array_pop($a);
//push last element to the beginning
array_unshift($a, $last);
}
array_pop() pops and returns the value of the last element of array, shortening the array by one element.
https://www.php.net/manual/en/function.array-pop.php
array_unshift() prepends passed elements to the front of the array
https://www.php.net/manual/en/function.array-unshift.php
You could create a function that takes two arguments $k(number of rotations), $a (the array) and returns the array after performing right rotation $k times.
function rotateRight($a, $k){
for($x=0; $x < $k; $x++){
//remove last element
$last = array_pop($a);
//push last element to the beginning
array_unshift($a, $last);
}
return $a;
}
And then call it accordingly.
Example:
$a = [1,2,3,4,5];
$a_one_shift = rotateRight($a, 1);
// [5,1,2,3,4];
$a_two_shift = rotateRight($a_one_shift, 1);
// [4,5,1,2,3];
Or you could pass 2 to directly get the array after two right rotations.
$a_new = rotateRight($a, 2);
// [4,5,1,2,3];
Rather than making iterated calls of array_pop() and array_unshift(), use an efficient, elegant approach that makes fewer function calls and has the lowest possible time complexity. Using early returns prevents making needless function calls for the same result.
Code: (Demo)
function popUnshift(array $indexedArray, int $popShiftsCount): array
{
$count = count($indexedArray);
if ($count < 2) {
return $indexedArray; // array cannot be rotated
}
$remainder = $popShiftsCount % $count;
if (!$remainder) {
return $indexedArray; // sought rotation is the original order
}
return array_merge(
array_splice($indexedArray, -$remainder),
$indexedArray
);
}
Disclosure: This answer was built on the CodeReview page (Codility cyclic rotation solution in PHP) where I offered this snippet in my review.
//$A input array, $K rotation times
function solution($A, $K) {
$new = array();
for($j=1;$j<=$K;$j++)
{
if(count($new)>0)
$A = $new;
for($i=0;$i<count($A);$i++)
{
if($i==0)
$new[$i] = $A[count($A)-1];
else
$new[$i] = $A[$i-1];
}
}
return $new;}

Replace array value with more than one values

I have an array like this,
$array = array(
1,2,3,'4>12','13.1','13.2','14>30'
);
I want to find any value with an ">" and replace it with a range().
The result I want is,
array(
1,2,3,4,5,6,7,8,9,10,11,12, '13.1', '13.2', 14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30
);
My understanding:
if any element of $array has '>' in it,
$separate = explode(">", $that_element);
$range_array = range($separate[0], $separate[1]); //makes an array of 4 to 12.
Now somehow replace '4>12' of with $range_array and get a result like above example.
May be I can find which element has '>' in it using foreach() and rebuild $array again using array_push() and multi level foreach. Looking for a more elegant solution.
You can even do it in a one-liner like this:
$array = array(1,2,3,'4>12','13.1','13.2','14>30');
print_r(array_reduce(
$array,
function($a,$c){return array_merge($a,#range(...array_slice(explode(">","$c>$c"),0,2)));},
[]
));
I avoid any if clause by using range() on the array_slice() array I get from exploding "$c>$c" (this will always at least give me a two-element array).
You can find a little demo here: https://rextester.com/DXPTD44420
Edit:
OK, if the array can also contain non-numeric values the strategy needs to be modified: Now I will check for the existence of the separator sign > and will then either merge some cells created by a range() call or simply put the non-numeric element into an array and merge that with the original array:
$array = array(1,2,3,'4>12','13.1','64+2','14>30');
print_r(array_reduce(
$array,
function($a,$c){return array_merge($a,strpos($c,'>')>0?range(...explode(">",$c)):[$c]);},
[]
));
See the updated demo here: https://rextester.com/BWBYF59990
It's easy to create an empty array and fill it while loop a source
$array = array(
1,2,3,'4>12','13.1','13.2','14>30'
);
$res = [];
foreach($array as $x) {
$separate = explode(">", $x);
if(count($separate) !== 2) {
// No char '<' in the string or more than 1
$res[] = $x;
}
else {
$res = array_merge($res, range($separate[0], $separate[1]));
}
}
print_r($res);
range function will help you with this:
$array = array(
1,2,3,'4>12','13.1','13.2','14>30'
);
$newArray = [];
foreach ($array as $item) {
if (strpos($item, '>') !== false) {
$newArray = array_merge($newArray, range(...explode('>', $item)));
} else {
$newArray[] = $item;
}
}
print_r($newArray);

Sort a flat array in recurring ascending sequences

I am trying to sort it in a repeating, sequential pattern of numerical order with the largest sets first.
Sample array:
$array = [1,1,1,2,3,2,3,4,5,4,4,4,5,1,2,2,3];
In the above array, I have the highest value of 5 which appears twice so the first two sets would 1,2,3,4,5 then it would revert to the second, highest value set etc.
Desired result:
[1,2,3,4,5,1,2,3,4,5,1,2,3,4,1,2,4]
I am pretty sure I can split the array into chunks of the integer values then cherrypick an item from each subarray sequentially until there are no remaining items, but I just feel that this is going to be poor for performance and I don't want to miss a simple trick that PHP can already handle.
Here's my attempt at a very manual loop using process, the idea is to simply sort the numbers into containers for array_unshifting. I'm sure this is terrible and I'd love someone to do this in five lines or less :)
$array = array(1,1,1,2,3,2,3,4,5,4,4,4,5,1,2,2,3);
sort($array);
// Build the container array
$numbers = array_fill_keys(array_unique($array),array());
// Assignment
foreach( $array as $number )
{
$numbers[ $number ][] = $number;
}
// Worker Loop
$output = array();
while( empty( $numbers ) === false )
{
foreach( $numbers as $outer => $inner )
{
$output[] = array_shift( $numbers[ $outer ] );
if( empty( $numbers[ $outer ] ) )
{
unset( $numbers[ $outer ] );
}
}
}
var_dump( $output );
I think I'd look at this not as a sorting problem, but alternating values from multiple lists, so rather than coming up with sets of distinct numbers I'd make sets of the same number.
Since there's no difference between one 1 and another, all you actually need is to count the number of times each appears. It turns out PHP can do this for you with aaray_count_values.
$sets = array_count_values ($input);
Then we can make sure the sets are in order by sorting by key:
ksort($sets);
Now, we iterate round our sets, counting down how many times we've output each number. Once we've "drained" a set, we remove it from the list, and once we have no sets left, we're all done:
$output = [];
while ( count($sets) > 0 ) {
foreach ( $sets as $number => $count ) {
$output[] = $number;
if ( --$sets[$number] == 0 ) {
unset($sets[$number]);
}
}
}
This algorithm could be adapted for cases where the values are actually distinct but can be put into sets, by having the value of each set be a list rather than a count. Instead of -- you'd use array_shift, and then check if the length of the set was zero.
You can use only linear logic to sort using php functions. Here is optimized way to fill data structures. It can be used for streams, generators or anything else you can iterate and compare.
$array = array(1,1,1,2,3,2,3,4,5,4,4,4,5,1,2,2,3);
sort($array);
$chunks = [];
$index = [];
foreach($array as $i){
if(!isset($index[$i])){
$index[$i]=0;
}
if(!isset($chunks[$index[$i]])){
$chunks[$index[$i]]=[$i];
} else {
$chunks[$index[$i]][] = $i;
}
$index[$i]++;
}
$result = call_user_func_array('array_merge', $chunks);
print_r($result);
<?php
$array = array(1,1,1,2,3,2,3,4,5,4,4,4,5,1,2,2,3);
sort($array);
while($array) {
$n = 0;
foreach($array as $k => $v) {
if($v>$n) {
$result[] = $n = $v;
unset($array[$k]);
}
}
}
echo implode(',', $result);
Output:
1,2,3,4,5,1,2,3,4,5,1,2,3,4,1,2,4
New, more elegant, more performant, more concise answer:
Create a sorting array where each number gets its own independent counter to increment. Then use array_multisort() to sort by this grouping array, then sort by values ascending.
Code: (Demo)
$encounters = [];
foreach ($array as $v) {
$encounters[] = $e[$v] = ($e[$v] ?? 0) + 1;
}
array_multisort($encounters, $array);
var_export($array);
Or with a functional style with no global variable declarations: (Demo)
array_multisort(
array_map(
function($v) {
static $e;
return $e[$v] = ($e[$v] ?? 0) + 1;
},
$array
),
$array
);
var_export($array);
Old answer:
My advice is functionally identical to #El''s snippet, but is implemented in a more concise/modern/attractive fashion.
After ensuring that the input array is sorted, make only one pass over the array and push each re-encountered value into its next row of values. The $counter variable indicates which row (in $grouped) the current value should be pushed into. When finished looping and grouping, $grouped will have unique values in each row. The final step is to merge/flatten the rows (preserving their order).
Code: (Demo)
$grouped = [];
$counter = [];
sort($array);
foreach ($array as $v) {
$counter[$v] = ($counter[$v] ?? -1) + 1;
$grouped[$counter[$v]][] = $v;
}
var_export(array_merge(...$grouped));

Get values greater than x and lower than x in separate arrays PHP

I have an array in PHP with some variables for example (1,2,3,4,5,6,7) I want to get two separate arrays one with all the variables lower than $idusuario (which might be 2, for example), for example the array 1 would have only one value "1" and array 2 would have "3,4,5,6,7".
PS: My php variables are:
$arrayfinal[] --> the array I want to divide
$idusuario --> the variable which separates both arrays
Easily:
$lessThan = array();
$greaterThan = array();
foreach($arrayFinal as $element){ // loop initial array
if($element < $idusuario){ // if element < idusuario, add to first array
$lessThan[] = $element;
}else{
$greaterThan[] = $element; // add to second array
Exclude and remove $idusuario
$lessThan = array();
$greaterThan = array();
foreach($arrayFinal as $element){ // loop initial array
if($element < $idusuario){ // if element < idusuario, add to first array
$lessThan[] = $element;
}elseif($element > $idusuario){
$greaterThan[] = $element; // add to second array
}
}
Another approach could be using array_filter instead of foreach:
$smaller = array_filter($array, function($value) use ($separator) {
return $value < $separator;
});
$bigger = array_filter($array, function($value) use ($separator) {
return $value > $separator;
});
But I'd guess the foreach approach is faster.

PHP create 1 array of 4 arrays ordered by index

I have 4 arrays, each one holds another column of a table, I would like to create one array with the data ordered per array[$i]. All arrays have the same number of values: $namesArr, $folderArr, $updatedAt, $valuesArr .
I would like my new array to be contain:
$namesArr[0], $folderArr[0], $updatedAt[0], $valuesArr[0],
$namesArr[1], $folderArr[1], $updatedAt[1], $valuesArr[1],
$namesArr[2], $folderArr[2], $updatedAt[2], $valuesArr[2],
...
I guess the solution is pretty simple, but I got stuck :(
Can anyone help?
I would do something like:
$arr = array_map(function () { return func_get_args(); },$namesArr, $folderArr, $updatedAt, $valuesArr);
You can use foreach loop to merge 4 arrays:
foreach ($namesArr as $key => $value) {
$arr[$key][] = $value;
$arr[$key][] = $folderArr[$key];
$arr[$key][] = $updatedAt[$key];
$arr[$key][] = $valuesArr[$key];
}
Thus $arr will be the merged array
<?php
$newArr = array();
for ($i = 0; $i < count($namesArr); $i++)
{
$newArr[$i][0] = $namesArr[$i];
$newArr[$i][1] = $folderArr[$i];
$newArr[$i][2] = $updatedAt[$i];
$newArr[$i][3] = $valuesArr[$i];
}
?>
Explanation
What this will do is iterate depending on how many elements there are in $namesArr.
I utilised a multidimensional array here so that the first set of square brackets is effectively the "row" in a table, and the second set of square brackets are the "column" of a table.
do the following way:
while($db->query($sql)){
$namesArr[] =$db->f('names');
$folderArr[]=$db->f('folder');
$updatedAt[]=$db->f('updated');
$valuesArr[]=$db->f('values');
}

Categories