How to reverse PHP array in partitions - php

I have a simple array that looks like this:
Array (
[0] => Array (
[id] => 8692
[name] => d
)
[1] => Array (
[id] => 8691
[name] => c
)
[2] => Array (
[id] => 8690
[name] => b
)
[3] => Array (
[id] => 8689
[name] => a
)
[4] => Array (
[id] => 8500
[name] => d
)
[5] => Array (
[id] => 8499
[name] => c
)
[6] => Array (
[id] => 8498
[name] => b
)
[7] => Array (
[id] => 8497
[name] => a
)
)
This array is quite long so I only included the first 4 items to give you an idea.
My problem is that I need the array to be in a format of
a,b,c,d,a,b,c,d
At the moment the format is like:
d,c,b,a,d,c,b,a
By this I mean the ['name'] value which is either a,b,c or d.
So I every 4 items in the array need to be reversed.
I have tried to achieve this but fail every time ending up with lots of for & while loops.

You can do it using array_chunk, array_merge and array_reverse:
$finalArray = array();
$arrays = array_chunk($myArray, 4);
foreach ($arrays as $array) {
$finalArray = array_merge($finalArray, array_reverse($array));
}

All the answers here, while perfectly valid, are pretty much on the order of O(n^2). So I figured I'd give you an O(n / 2), time complexity, solution as an alternative just in case you care about performance. The solution also uses only O(n + n + k) space complexity (in place swap).
Since the requirement is to reverse order of values, I'm ignoring keys and basing the solution on the constraint that the array is always 0-indexed.
To solve this problem, we can generalize the solution as a simple array reverse, which requires a simple O(n/2) operation with in-place swap. We can achieve this simply with two counters, $i starting from the beginning of the array, and $j starting at the end of the array. Thus, we can swap the values at $arr[$i] with that at $arr[$j] and then increment $i and decrement $j, at each step.
function reverseArray(Array $arr) {
for($i = 0, $j = count($arr); $i < $j; $i++, $j--) {
$tmp = $arr[$j];
$arr[$j] = $arr[$i];
$arr[$i] = $tmp;
}
return $arr;
}
Now, to apply the more specific solution of only reverse every group of 4 elements in the array, we just break up the array in partitions of 4 values, and only reverse each of those partitions at a time. Which just expands on the example above of reverseArray() by altering the starting and ending positions of the $i and $j counter to only reverse within each partition.
Thus we arrive the O(n / 2) solution here by just adding another loop for the partition size, and keep the inner loop from the earlier example.
function reverseArrayPartition(Array $arr, $partitionSize = 4) {
$end = count($arr);
// reverse only one partition at a time
for($start = 0; $start < $end; $start += $partitionSize ) {
$from = $start;
$to = $start + $partitionSize - 1;
for($i = $from, $j = $to; $i < $j; $i++, $j--) {
// swap the transposing values
$tmp = $arr[$j];
$arr[$j] = $arr[$i];
$arr[$i] = $tmp;
}
}
return $arr;
}
$arr = [4,3,2,1,4,3,2,1];
var_dump(reverseArrayPartition($arr)); // expected [1,2,3,4,1,2,3,4]
This will work with any array size at any $partitionSize so it's efficient if you're trying to do this on very large arrays.

You can iterate the array with a for and increment with 4 each time and keep the current offset in other variable and use array_slice to get the current slice of array and reverse order using array_reverse
I am thinking of something like this:
$step = 4;
$offset = 0;
$new_arr = []; //Here we create the new array.
for($i=0; $i<count($arr); $i+=$step) {
$part_of_array = array_slice($arr, $offset, $step);
$part_reverse = array_reverse($part_of_array);
$new_arr = array_merge($new_arr, $part_reverse);
$offset += $step;
}
print_r($new_arr); //Here will be the array as you expected.
All the content in the for can be simplify to:
$new_arr = array_merge($new_arr, array_reverse(array_slice($arr, $offset, $step)));
$offset += $step;

Not harcoding every 4, reverse based on char code of name value
<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);
/**
*/
$in = [
['name'=>'d','id'=>1]
, ['name'=>'c','id'=>12]
, ['name'=>'b','id'=>13]
, ['name'=>'a','id'=>14]
, ['name'=>'d','id'=>15]
, ['name'=>'c','id'=>16]
, ['name'=>'b','id'=>17]
, ['name'=>'a','id'=>18]
];
$last = PHP_INT_MAX;
$toReverse = [];
$out = [];
foreach ($in as $value) {
$p = ord($value['name']);
if ( $p < $last ) {
//echo 'ToReverse',var_export($value,true),"\n";
$toReverse[] = $value;
}
else {
$out = array_merge($out,array_reverse($toReverse));
//echo 'Join',var_export($out,true),"\n";
$toReverse = [$value];
}
$last = $p;
}
$out = array_merge($out,array_reverse($toReverse));
print_r($out);

Related

How To Reduce and Average Total PHP Array Elements To New Size

Whats the best solution to take an array with 100 elements (all numbers) and reduce the array size to a smaller number of elements averaging the in between/combined numbers into new. I do NOT mean slice or crop.
Example:
$array = [10,20,30,40,50,60];
$final_count = 3;
$new_array = array_slimmer($array, $final_count);
Output:
[15,35,55]
Here's a solution based on an existing answer about breaking an array into a set number of chunks:
$array = [11, 3, 45, 6, 61, 89, 22];
function array_slimmer(array $array, int $finalCount): array
{
// no work to be done if we're asking for equal or more than what the array holds
// same goes if we're asking for just one array or (nonsensical) less
if ($finalCount >= count($array) || $finalCount < 2) {
return $array;
}
return array_map(function (array $chunk) {
// rounded to two decimals, but you can modify to accommodate your needs
return round(array_sum($chunk) / count($chunk), 2);
}, custom_chunk($array, $finalCount));
}
// this function is from the linked answer
function custom_chunk($array, $maxrows) {
$size = sizeof($array);
$columns = ceil($size / $maxrows);
$fullrows = $size - ($columns - 1) * $maxrows;
for ($i = 0; $i < $maxrows; ++$i) {
$result[] = array_splice($array, 0, ($i < $fullrows ? $columns : $columns - 1));
}
return $result;
}
print_r(array_slimmer($array, 2));
print_r(array_slimmer($array, 3));
print_r(array_slimmer($array, 4));
This outputs:
Array ( [0] => 16.25 [1] => 57.33 )
Array ( [0] => 19.67 [1] => 33.5 [2] => 55.5 )
Array ( [0] => 7 [1] => 25.5 [2] => 75 [3] => 22 )
Demo
You could use array_chunk to split your array into pieces, the size being count($array) / $final_count, then use array_map to take the average value from each of those chunks (array_sum($chunk) / count($chunk)):
$array = [10,20,30,40,50,60];
$final_count = 3;
$new_array = array_map(function ($a) {
return array_sum($a) / count($a);
}, array_chunk($array, (int)(count($array) / $final_count)));
print_r($new_array);
Output
Array
(
[0] => 15
[1] => 35
[2] => 55
)
Demo on 3v4l.org
Note
This will give extra values on the end if the array length is not evenly divisible by $final_count (demo). You will need to pad the array out in that case to a multiple of that length using code such as this, which replicates the last value in the array:
while (count($array) % $final_count != 0) {
$array[] = end($array);
}
Demo on 3v4l.org

Sorting an array with bubble sort [duplicate]

This question already has answers here:
How can I sort arrays and data in PHP?
(14 answers)
Closed 3 years ago.
I am trying to create an algorithm that shows each step of bubble sort, sorting one number at a time. I was was able to sort the number at the first index, but I need to figure out how to sort all the numbers.
$x = array (9,7,5,3,0);
$count = count($x);
for($i = 0; $i < $count-1; $i++ ) {
$temp = $x[$i+1];
$x[$i+1] = $x[$i];
$x[$i] = $temp;
echo '<pre>';
print_r($x);
}
My current output is:
Array
(
[0] => 7
[1] => 9
[2] => 5
[3] => 3
[4] => 0
)
Array
(
[0] => 7
[1] => 5
[2] => 9
[3] => 3
[4] => 0
)
Array
(
[0] => 7
[1] => 5
[2] => 3
[3] => 9
[4] => 0
)
Array
(
[0] => 7
[1] => 5
[2] => 3
[3] => 0
[4] => 9
)
From here I need to continue sorting the remaining numbers. For 7, the output should be
57390
53790
53970
53907
and then for 3
35079
30579
30759
30795
and then for 0, it should be same for all 4 lines like
03570
03579
03579
03579
[The assigned problem.][1]
Two issues:
You should only swap values when they are not in the right order
You need an outer loop to repeat this inner loop, similar to a proper bubble sort algorithm.
Your code can be modified like below so it generates the required output:
$x = array (9,7,5,3,0);
$count = count($x) - 1;
for($times = 0; $times < $count; $times++) {
for($i = 0; $i < $count; $i++ ) {
$temp = $x[$i+1];
if ($temp < $x[$i]) {
$x[$i+1] = $x[$i];
$x[$i] = $temp;
}
echo implode(" ", $x) . "\n";
}
echo "\n";
}
Note that a proper bubble sort algorithm will perform fewer iterations.
Bubble sort is basically simplified into a min() + shift (or sometimes swap) operation that occurs N times. So it's an O(n^2) algorithm.
Since this is for learning purposes I'm going to try to be as verbose as possible.
function getMin(Array &$array): Int {
$min = reset($array);
$minKey = null;
// Find the minimum value
foreach($array as $k => $n) {
if ($n < $min) {
$min = $n;
$minKey = $k;
}
}
// remove the minimum value from the array
$array[$k] = null;
$array = array_filter($array, function ($v) { return $v !== null; });
return $min;
}
$array = [9,7,5,3,0];
foreach ($array as $n => $value) {
// Find the min value in the array from Nth index onward
$min = getMin($array); // get the smallest value in the array and remove it.
$sorted[] = $min; // push it on to the new array
}
var_dump($sorted);
This gives you the expect result:
array(5) {
[0]=>
int(0)
[1]=>
int(3)
[2]=>
int(5)
[3]=>
int(7)
[4]=>
int(9)
}
Of course, this is not the way you would want to implement Bubble Sort, because it's a bit circuitous. Again, it's for educational purposes. You can inspect the $array as it's being modified at each step and the new $sorted as it's being built. Typically you would just swap the min/max values in place and use the same array (keeping track of the keys to rescan the array).
Like this...
// Unsorted $array
$array = [9,7,5,3,0];
// Sort the array
for ($lastKey = $i = 0, $len = count($array); $i < $len; $i++) {
// Scan for minimum value
for ($minKey = $j = $lastKey, $min = $array[$minKey]; $j < $len; $j++) {
if ($array[$j] < $min) {
$minKey = $j;
$min = $array[$j];
}
}
// Swap the values
$swap = $array[$lastKey];
$array[$lastKey] = $min;
$array[$minKey] = $swap;
// Update the scan position
$lastKey++;
}
var_dump($array); // Gives you [0,3,5,7,9]
You are performing unconditional position swapping, but you actually need to check if movement is required.
There is no shortage of tutorials that demonstrate a bubble sort in php. A quick good lead me to this one: https://www.w3resource.com/php-exercises/searching-and-sorting-algorithm/searching-and-sorting-algorithm-exercise-6.php which can be modified for your purposes.
PHP now offers "array destructuring" which means you no longer need to use a temporary holding variable while swapping.
Code: (Demo)
$x = [9,7,5,3,0];
$count = count($x) - 1;
for ($pass = 0; $pass < $count; ++$pass) {
for ($i = 0; $i < $count; ++$i) {
if ($x[$i] > $x[$i + 1]) {
[$x[$i + 1], $x[$i]] = [$x[$i], $x[$i + 1]];
}
$results[] = implode($x);
}
$results[] = "\n";
}
echo implode("\n", $results);

Make binary Pairing array from dynamic associative array on the basis of Keys, One Element from each Key

I'm Basically Creating Pair from a dynamic associative array in such a manner that I took 1 element from one key array and other element from other key array on which these two elements combine make pair 1. Problem is this a dynamic associative array it can have 4 Keys having arrays every key can have maximum two elements and minimum 1 elememt in its array and to make pair we should have two keys having 1 element on both side of these two keys array.
For Example
[parent_id] => Array
(
[9] => Array
(
[0] => 11
[1] => 12
)
[10] => Array
(
[0] => 13
[1] => 14
)
[20] => Array
(
[0] => 21
[1] => 22
)
[21] => Array
(
[0] => 23
)
)
This is an associative array which gives 3 pairs right now.
For Example
pair 1 contain: 11 , 13
pair 2 contain: 12 , 14
pair 3 contain: 21 , 23
Now the Issue is by using what methodology I can get my desire result.
Any Suggestions !!!!
Update 3
The last update didn't find all the possible pairs for one case. This version loops through differently, taking at most one element from each array before moving to the next one and pairing from there. It loops until there are insufficient values left to pair.
function find_pairs($array) {
// re-index to 0
$array = array_values($array['parent_id']);
// sort so the longest arrays are first
usort($array, function ($a, $b) { return count($b) - count($a); });
// output array
$pairs = array();
$c = count($array);
$i = 0;
// loop while there are enough values to pair (2 or more)
while (array_reduce($array, function ($c, $v) { return $c + count($v); }, 0) > 1) {
// make sure there are some elements in this array
while (!count($array[$i])) $i = ($i + 1) % $c;
// find the next array with a value
$j = ($i + 1) % $c;
while (!count($array[$j])) $j = ($j + 1) % $c;
// have we come full circle?
if ($j == $i) break;
// save the pair
$pairs[] = array(array_shift($array[$i]), array_shift($array[$j]));
// move on to the next array
$i = ($i + 1) % $c;
}
return $pairs;
}
Demo (includes all possible test cases) on 3v4l.org
Original answer
Here's one way to do this. Re-index the parent_id array to start at 0, and then loop through the array 2 elements at a time, combining all the values from each element. We use min to make sure we only pair as many values as there are in the smallest value array.
// re-index to 0
$array = array_values($array['parent_id']);
// output array
$pairs = array();
for ($i = 0; $i < count($array) - 1; $i += 2) {
for ($j = 0; $j < min(count($array[$i]), count($array[$i+1])); $j++) {
$pairs[] = array($array[$i][$j], $array[$i+1][$j]);
}
}
print_r($pairs);
Output:
Array (
[0] => Array ( [0] => 11 [1] => 13 )
[1] => Array ( [0] => 12 [1] => 14 )
[2] => Array ( [0] => 21 [1] => 23 )
)
Demo on 3v4l.org
Update
If you want to guarantee getting the maximum number of pairs from the array, sort to make the longest arrays come first:
$array = array_values($array['parent_id']);
// sort so the longest arrays are first
usort($array, function ($a, $b) { return count($b) - count($a); });
// output array
$pairs = array();
for ($i = 0; $i < count($array) - 1; $i += 2) {
for ($j = 0; $j < min(count($array[$i]), count($array[$i+1])); $j++) {
$pairs[] = array($array[$i][$j], $array[$i+1][$j]);
}
}
print_r($pairs);
Demo on 3v4l.org
Update 2
Based on further comments, it seems the only requirement for pairing is that the key of the array from which the elements from is not the same. This makes things a bit more complicated, but this function should do what you want:
function find_pairs($array) {
// re-index to 0
$array = array_values($array['parent_id']);
// sort so the longest arrays are first
usort($array, function ($a, $b) { return count($b) - count($a); });
// output array
$pairs = array();
for ($i = 0, $j = 1; $i < count($array) - 1; $i++) {
if (!count($array[$i])) continue;
while ($j <= $i || $j < count($array) && !count($array[$j])) $j++;
while (count($array[$i]) && isset($array[$j]) && count($array[$j])) {
$pairs[] = array(array_shift($array[$i]), array_shift($array[$j]));
// exhausted other arrays elements?
while ($j < count($array) && !count($array[$j])) $j++;
}
}
return $pairs;
}
Demo on 3v4l.org

How to get the factorial value of each number in an array?

I am trying to get an factorial value of each item in array by using this method but this outputs only one value
can any body help me finding where i am doing wrong?
function mathh($arr, $fn){
for($i = 1; $i < sizeof($arr); $i++){
$arr2 = [];
$arr2[$i] = $fn($arr[$i]);
}
return $arr2;
}
$userDefined = function($value){
$x = 1;
return $x = $value * $x;
};
$arr = [1,2,3,4,5];
$newArray = mathh($arr, $userDefined);
print_r($newArray);
You're going to need a little recursion so in order to do that you need to pass the lambda function into itself by reference:
function mathh($arr, $fn){
$arr2 = []; // moved the array formation out of the for loop so it doesn't get overwritten
for($i = 0; $i < sizeof($arr); $i++){ // starting $i at 0
$arr2[$i] = $fn($arr[$i]);
}
return $arr2;
}
$userDefined = function($value) use (&$userDefined){ // note the reference to the lambda function $userDefined
if(1 == $value) {
return 1;
} else {
return $value * $userDefined($value - 1); // here is the recursion which performs the factorial math
}
};
$arr = [1,2,3,4,5];
$newArray = mathh($arr, $userDefined);
print_r($newArray);
The output:
Array
(
[0] => 1
[1] => 2
[2] => 6
[3] => 24
[4] => 120
)
I wanted to expand on this some since you're essentially (in this case) creating an array map. This could be handy if you're doing additional calculations in your function mathh() but if all you want to do is use the lambda function to create a new array with a range you could do this (utilizing the same lambda we've already created):
$mapped_to_lambda = array_map($userDefined, range(1, 5));
print_r($mapped_to_lambda);
You will get the same output, because the range (1,5) of the mapped array is the same as your original array:
Array
(
[0] => 1
[1] => 2
[2] => 6
[3] => 24
[4] => 120
)

simple way in PHP to split path string into breadth-first array

Using PHP 5.2, I'm trying to parse an arbitrary number of path/directory strings into an array, such that they can be processed breadth-first. This is to allow me to script a sparse checkout from a Subversion repository, telescoping the indicated paths. All the paths on the same level have to be specified in the same svn update --depth empty statement.
I get the desired output, but I wonder if there's a cleaner way to do this. (And, yes, I know there are changes needed for efficiency.)
EDIT I modified the original post to handle cases of multiple children in the same parent. My revised code is
$main = array(
'a/b/c1/',
'a/b/c2/d/e1',
'a/b/c2/d/e2',
'A/B/',
'alpha/beta/gamma/delta/epsilon'
);
$splits = array();
$max = 0;
for ($i=0; $i<count($main); $i++) {
$splits[$i] = explode(DIRECTORY_SEPARATOR, trim($main[$i], DIRECTORY_SEPARATOR));
if (count($splits[$i]) > $max) {
$max = count($splits[$i]);
}
}
for ($i=0; $i<$max; $i++) {
$levels[$i] = array();
for ($path=0; $path<count($splits); $path++) {
if (array_key_exists($i, $splits[$path])) {
$levels[$i][] = implode(DIRECTORY_SEPARATOR, array_slice($splits[$path], 0, $i+1));
}
}
$levels[$i] = array_unique($levels[$i]);
sort($levels[$i]); // just to reset indices
}
This changes my output structure to the following, which both provides unique directories at each level and retains sibling nodes.
Array
(
[0] => Array
(
[0] => A
[1] => a
[2] => alpha
)
[1] => Array
(
[0] => A/B
[1] => a/b
[2] => alpha/beta
)
[2] => Array
(
[0] => a/b/c1
[1] => a/b/c2
[2] => alpha/beta/gamma
)
[3] => Array
(
[0] => a/b/c2/d
[1] => alpha/beta/gamma/delta
)
[4] => Array
(
[0] => a/b/c2/d/e1
[1] => a/b/c2/d/e2
[2] => alpha/beta/gamma/delta/epsilon
)
)
In my code, I then iterate over the final $levels array. Unfortunately, this still requires two iterations: one for depth empty and one for depth infinity, but I'm sure that could be worked out.
$count = count($levels);
for ($i=0; $i<$count; $i++) {
echo '<p>', 'svn update --set-depth empty ', implode(' ', $levels[$i]), "</p>\n";
}
$count = count($main);
for ($i=0; $i<$count; $i++) {
echo '<p>', 'svn update --set-depth infinity ', $main[$i], "</p>\n";
}
$levels=array();
$depth=0;
$i=0;
foreach ($main as $m) {
$m=explode('/',$m);
while (sizeof($m)<$depth) $m[]=null;
$d=0;
foreach ($m as $mm) {
if ($d>$depth) {
if (!$mm) break;
$depth=$d;
$levels[$d]=array();
for ($j=0;$j<=$i;$j++) $levels[$d][$j]=null;
}
$levels[$d][$i]=$mm;
$d++;
}
$i++;
}
looks like a good alternative with only one traversion of the array. In short you don't use one pass to decide on the depth, but if you encounter a deeper entry, you just fill the relevant places in the array retroactively with nulls.
$depth has the depth-1 after the loop.
Edit:
This does yet handle a case of multiple children in the same parent, but I am unsure if it does so the way you want
Here's my implementation (it got easier with your latest request):
$levels = array();
for ($i=0; $i<count($main); $i++) {
$splits = explode(DIRECTORY_SEPARATOR, trim($main[$i], DIRECTORY_SEPARATOR));
$current = array();
/* Load every subpath in an array*/
for($j=0; $j<count($splits); $j++) {
$current[$j . "hack"] = implode("/", array_slice($splits, 0, $j+1));
}
$levels = array_merge_recursive($levels, $current);
}
/* Removes duplicates and resets indices */
array_walk($levels, function(&$l, $i) { $l = array_unique($l); sort($l); });
What makes this implementation simple is that I handle each path separately. I only have one loop, and join the results with array_merge_recursive. For example, with "a/b" and "a/c", my code :
creates array(0 => array("a"), 1 => array("a/b")) and array(0 => array("a"), 1 => array("a/c"))
joins them with array_merge_recursive which gives array(0 => array("a", "a"), 1 => array("a/b", "a/c"))
removes unique values with array_unique
resets the indices with sort
Note: I need to use $j + "hack", otherwise array_merge_recursive won't merge the values as expected (try it for yourself).

Categories