Pivot array based on row count - php

I have the following:
$array = array(1,2,3,4,5,6);
I need to "pivot" it to get :
Array ( [0] => 1 [1] => 4 [2] => 2 [3] => 5 [4] => 3 [5] => 6 )
by "pivot" I mean, let's imagine that the array is stored a 2 x 3 matrix (2 rows, and 3 columns). My goal is to pivot it, so that the matrix is now a 3 x 2 matrix (3 rows, 2 columns)
for that of course I need an extra argument, let's say "number of rows" (in this case this is like 2 rows)
I did the following:
function pivotArray($array, $nbrRows)
{
$countTotal = count($array);
$countCols = $countTotal / $nbrRows;
$chunk = array_chunk($array,$countCols);
$out = array();
for ($row=0;$row<$nbrRows;$row++) {
for ($col=0;$col<$countCols;$col++) {
$out[$col][$row] = $chunk[$row][$col];
}
}
$arraySingle = call_user_func_array('array_merge', $out);
return $arraySingle;
}
it works as designed but I wonder whether there is a better way to do that ? for instance avoiding the 2 for loops ? and also avoid the array_merge ?

Instead of reporcessing the array a few times, this code builds an intermediate array and spreads the elements based on $position % $countCols. I've also introduced ceil() to the count of columns in case there is an odd number of elements...
function pivotArray($array, $nbrRows)
{
$countTotal = count($array);
$countCols = ceil($countTotal / $nbrRows);
$arraySingle = [];
foreach ( $array as $position => $value ) {
$arraySingle[$position % $countCols][] = $value;
}
return array_merge(...$arraySingle);
}

Related

Check received index succession in php

I'am searching the best way to do that test in php :
So i have a list which contains numbers and I have to check the succesion of these numbers so if there is no succession it is necessary to announce an alert and to recover the missing index or the list of the missing indexes
So for example my array is like that:
Array ( [0] => 1 [1] => 2 [2] => 3 [3] => 4 [4] => 6 [5] => 9 )
Here my algorithm must returns missing indexes are : [5,7,8]
If you make a range of the min and max value then use array_diff that should give you the result you want.
$arr = []; // your array
$range = range(min($arr), max($arr));
var_export(array_diff($range, $arr));
// [5,7,8]
https://3v4l.org/0pE3o
If you sort the numbers
sort($numbers);
You can use a nested loop.
foreach ($numbers as $k => $x) {
for ($n = $x + 1, $y = $numbers[$k + 1] ?? $x; $n < $y; $n++) {
$missing[] = $n;
}
}
The outer loop iterates the set of numbers, and the inner loop counts up from the current number to the next number.

Order Multiple Arrays (Including Each Other)

Let's say we have arrays like below.
$arr00 = [0,1,2,...,9]; // It includes 9 arrays. So the score should be 9.
$arr01 = [0,1,...,8]; // score = 8
...
$arr09 = [0]; // score = 0
ArrScore (definition): If an array include an array with all elements it
gets one point. So in this case $arr00's total score is 9. Because it
includes all other 9 arrays. And $arr09's score will be 0.
Actual Conditions
Our array elements could be random numbers. (not sequent orders ascending +1)
There could be thousands of arrays.
Our arrays are always flat. (no duplicated element in an array)
We are using php (any theoretical approach is also ok)
Think that you have a standard PC and you will order these arrays everyday once. (No need for the result of "which arr eats which ones". Just ArrScores.)
Goal is to order arrays by ArrScore. And we need ArrScores. What should be the approach? (Theoretical or practical)
If I understood right, this might help:
function compare($a,$b) {
if(count(array_intersect($a, $b)) == count($a)) return -1;
else return 1;
}
$arr0 = [0,2,4,7];
$arr1 = [7,0,2,9,4];
$arr2 = [4,2];
$arr = [$arr0,$arr1,$arr2];
usort($arr,"compare");
foreach($arr as $a) {
print_r($a);
}
prints:
Array ( [0] => 4 [1] => 2 ) Array ( [0] => 0 [1] => 2 [2] => 4 [3] => 7 ) Array ( [0] => 7 [1] => 0 [2] => 2 [3] => 9 [4] => 4 )
EDIT:
Compute the ArrayScore for each array:
$arr0 = [0,2,4,7];
$arr1 = [7,0,2,9,4];
$arr2 = [4,2];
$arr = [$arr0,$arr1,$arr2];
$arrayScores = [];
//initialize the Scores with 0
foreach($arr as $a){
$arrayScores[] = 0;
}
//run through all arrays
for($i=0;$i<count($arr);$i++){
//with $j=$i+1, every combination is only checked once
for($j=$i+1; $j<count($arr);$j++){
if(count(array_intersect($arr[$j], $arr[$i])) == count($arr[$j])) {
$arrayScores[$i]++;
}
if(count(array_intersect($arr[$i], $arr[$j])) == count($arr[$i])){
$arrayScores[$j]++;
}
}
}

Sorting an array based on a condition

I have the following array
$records = array(
array("postId"=>"1","grid"=>"6"),
array("postId"=>"2","grid"=>"3"),
array("postId"=>"3","grid"=>"6"),
array("postId"=>"4","grid"=>"3"),
array("postId"=>"5","grid"=>"3"),
array("postId"=>"6","grid"=>"12"),
array("postId"=>"7","grid"=>"3"),
);
I want to sort this array in a way that the sum of any number of back to back "grids" is equals to 12.
Example: The values of the "grids" in the above array are : 6,3,6,3,3,12,3
(6+6=12), (3+3+3+3=12),(12=12) so the new order should be 6,6,3,3,3,3,12 or 3,3,3,3,12,6,6 or 6,3,3,6,3,3,12
So after sorting the array the new array should look like following:
$records=array(
array("postId"=>"1","grid"=>"6"),
array("postId"=>"3","grid"=>"6"),
array("postId"=>"2","grid"=>"3"),
array("postId"=>"4","grid"=>"3"),
array("postId"=>"5","grid"=>"3"),
array("postId"=>"7","grid"=>"3"),
array("postId"=>"6","grid"=>"12"),
);
I searched in php manual and found these functions: sort,uasort, uksort, usort but I couldn't figure out how to use them.
Could you please tell me how to achieve this using PHP ?
Update
The value of grid will always be 3 or 6 or 12 (these three numbers only )
Problem
$records = array(
array("postId"=>"1","grid"=>"3"),
array("postId"=>"2","grid"=>"6"),
array("postId"=>"3","grid"=>"3"),
array("postId"=>"4","grid"=>"3"),
array("postId"=>"5","grid"=>"6"),
array("postId"=>"6","grid"=>"6"),
array("postId"=>"7","grid"=>"3"),
array("postId"=>"8","grid"=>"6"),
);
So you are not really sorting, but reordering to create sequence. I imagine that you are trying to do some layout of bricks with fixed height, and you need to have it reordered to fill each row and leave the rest at the end. With given fixed variants of 12,6,3 it can be done by sorting it in descending order - with odd number of sixes it will be filled with smaller threes. However such order will produce boring layout - to have it more interesting you only need to reorder some posts. For this you will need to create temporary container and merge it when sum of its grids is equal 12. If you are left with some temporary containers, merge them into one and sort descending before merging with previously grouped.
Code illustrating my concept:
//auxiliary function to calculate sum of grids in given temporary container
function reduc($a) {
return array_reduce($a, function ($result, $item) {
return $result . $item['grid'] . ',';
}, '');
}
function regroup($records, $group_sum = 12) {
$temp = array();
$grouped = array();
foreach ($records as $r) {
if ($r['grid'] == $group_sum) {
$grouped[] = $r;
} else {
if (!$temp) {
$temp[] = array($r);
} else {
$was_grouped = false;
foreach ($temp as $idx => $container) {
$current_sum = sum_collection($container);
if ($current_sum + $r['grid'] <= $group_sum) {
$temp[$idx][] = $r;
if ($current_sum + $r['grid'] == $group_sum) {
$grouped = array_merge($grouped, $temp[$idx]);
unset($temp[$idx]);
}
$was_grouped = true;
break;
}
}
if (!$was_grouped) {
$temp[] = array($r);
}
}
}
}
if ($temp) {
//Sort descending, so biggest ones will be filled first with smalller
$rest = call_user_func_array('array_merge', $temp);
usort($rest, function($a, $b) {
return $b['grid'] - $a['grid'];
});
$grouped = array_merge($grouped, $rest);
}
return $grouped;
}
The question is:
I want to sort this array in a way that the sum of any number of back
to back "grids" is equals to 12.
You may try this (using usort)
$records = array(
array("postId"=>"1","grid"=>"6"),
array("postId"=>"2","grid"=>"3"),
array("postId"=>"3","grid"=>"6"),
array("postId"=>"4","grid"=>"3"),
array("postId"=>"5","grid"=>"3"),
array("postId"=>"6","grid"=>"12"),
array("postId"=>"7","grid"=>"3"),
);
You have number 34 times, nimber 6 2 times and number 12 once.
// Sort (ASC)
usort($records, function($a, $b) {
return $a['grid'] - $b['grid'];
});
DEMO-1 (ASC) (3+3+3+3=12, 6+6=12, 12=12).
// Sort (DESC)
usort($records, function($a, $b) {
return $b['grid'] - $a['grid'];
});
DEMO-2 (DESC) (12=12, 6+6=12, 3+3+3+3=12).
Output after sort using (ASC) :
Array (
[0] => Array
(
[postId] => 7
[grid] => 3
)
[1] => Array
(
[postId] => 5
[grid] => 3
)
[2] => Array
(
[postId] => 4
[grid] => 3
)
[3] => Array
(
[postId] => 2
[grid] => 3
)
[4] => Array
(
[postId] => 3
[grid] => 6
)
[5] => Array
(
[postId] => 1
[grid] => 6
)
[6] => Array
(
[postId] => 6
[grid] => 12
)
)
This solution first sorts by grid size descending and then brute forces it's way down by testing each remaining element against the sum so far for each row:
$sum=0;
$grouped=array();
usort($records, function($a, $b) { return $a['grid']<$b['grid']; });
while ($records)
{
$next=reset($records);
if ($sum) foreach ($records as $next) if ($sum+$next['grid']<=12) break;
$grouped[]=$next;
$sum+=$next['grid'];
unset($records[array_search($next, $records)]);
if ($sum>=12) $sum=0;
}
Update
Turns out that sorting in descending order is enough to solve the requirement using only 3, 6 and 12 elements. A 12 as well as a 6 followed by a 6 stand alone, and all other combinations are filled up with remaining threes. (For some reason I thought the algorithm would have to be able to deal with nines as well.) So this is all you need:
usort($records, function($a, $b) { return $a['grid']<$b['grid']; });
Granted this makes for a very boring grid.
In PHP >= 5.3.0 you can do this with usort() and closures (or globals as a hack). Given $records:
$running_length = 0;
usort( $records, function( $a, $b ) use( $running_length ) {
$running_length += $a["grid"];
if( $running_length >= 12 ) return( true );
return( false );
});
If you visualize "grid" parameter as a string length, the end result, $records, becomes ordered like:
... 3
... 3
...... 6
... 3
...... 6
... 3
............ 12
Given randomness of available chunks, you may want to sort this array first from smallest-to largest and then see whether it gets arranged better for you. This method obviously doesn't detect fragmentations and blocks that don't fit --- or can't resolve to fit.

PHP check if an array contains a specific value every tot elements

I have a long array like the following.
What I need to achieve is to check if every 6 elements in this array is found the value 1. If it is not found must be added to one of the 6 elements randomly ( by replacing any of the original values ). Also if the value is found more than 2 times every 6 elements, one of them needs to be replaced with another random number.
I have already created the code that is able to generate the following array randomly.
But I do not know how to check if every 6 elements the array contains the value 1, if not how to add it randomly, if found more than 1 time how to replace one of the values with another random number.
I know it s pretty hard to understand. I hope someone will be able to help.
In few words the final result should be:
Every 6 elements this array has to contain at least the value 1 for one time and no more than one time, located every 6 elements in a random position.
This is what I have done so far, this code generates the array below:
/*
We select the elements to add randomly in the final array from a DB table
*/
$elements = parent::$db->select('_matrix_elements', 'element_id', NULL, 'ORDER BY RAND()');
$count = count($elements) - 1; //count the total number of the elements -1 to start from zero
$columns = 6;
$lines = 8;
//generating the final array
for ($a = 1; $a <= $lines; $a++) {
for ($b = 1; $b <= $columns; $b++) {
$rand = rand(1,$count);
$line['position_'.$a.'_' . $b] = $elements[$rand]['element_id'];
}
}
Array
(
[position_1_1] => 14
[position_1_2] => 6
[position_1_3] => 5
[position_1_4] => 6
[position_1_5] => 9
[position_1_6] => 8
[position_2_1] => 11
[position_2_2] => 7
[position_2_3] => 6
[position_2_4] => 1
[position_2_5] => 7
[position_2_6] => 5
[position_3_1] => 14
[position_3_2] => 5
[position_3_3] => 4
[position_3_4] => 7
[position_3_5] => 4
[position_3_6] => 10
[position_4_1] => 6
[position_4_2] => 2
[position_4_3] => 2
[position_4_4] => 1
[position_4_5] => 7
[position_4_6] => 6
[position_5_1] => 3
[position_5_2] => 7
[position_5_3] => 8
[position_5_4] => 10
[position_5_5] => 3
[position_5_6] => 2
[position_6_1] => 8
[position_6_2] => 2
[position_6_3] => 10
[position_6_4] => 2
[position_6_5] => 10
[position_6_6] => 9
[position_7_1] => 6
[position_7_2] => 10
[position_7_3] => 4
[position_7_4] => 8
[position_7_5] => 1
[position_7_6] => 5
[position_8_1] => 2
[position_8_2] => 7
[position_8_3] => 4
[position_8_4] => 7
[position_8_5] => 9
[position_8_6] => 13
)
Split the list up into a two dimensional array with 6 elements in each 1D array
Count the number of times each element appears in the 6 element sub array
If there are no ones in the array, make a random element 1
If there are 2 or more ones in the array
Replace with a random number (other than one) until there is only one one
Add the final sub array to a 1 dimensional array
Repeat for all the sub arrays in the 2D array
$list_2d = array_chunk($list, 6);
$final_list = array();
foreach ($list_2d as $array) {
$count = array_count_values($array);
if (!array_key_exists(1, $count))
$array[mt_rand(0, 5)] = 1;
else if ($count[1] > 1)
for ($i = 1; $i <= $count[1] - 1; $i++)
$array[array_search(1, $array)] = mt_rand(2, 15);
//Use whatever random number you want here, as long as it's not 1
$final_list = array_merge($final_list, $array);
}
Better to get it right straight away, than to make another solution for fixing it. Try this two functions with your code. Also I tested it so check it out here http://phpfiddle.org/lite/code/i9e-gb3. Hope this is what you need.
function randomizer($columns, $rows, $elements)
{
$line = array();
for ($row = 1; $row <= $rows; $row++)
{
$one_count = array();
for ($col = 1; $col <= $columns; $col++)
{
$line['position_' . $row . '_' . $col] = randomID($elements);
if ($line['position_' . $row . '_' . $col] == 1)
{
//we have 1 - store key value
$one_count[] = 'position_' . $row . '_' . $col;
}
}
if (empty($one_count))
{
//no 1 in last row - we will replace one random
$rand_col = rand(1, $columns);
$line['position_' . $row . '_' . $rand_col] = 1;
}
elseif (count($one_count) > 1)
{
//more than one 1 in last row, we will leave only one
//first - pick one random to keep
$keep_key = array_rand($one_count);
unset($one_count[$keep_key]);
// second - replace others with non 1 values
foreach ($one_count as $repl_key)
{
//this time we won't take ID=1
$line[$repl_key] = randomID($elements, true);
}
}
}
return $line;
}
function randomID($elements, $not1=false)
{
$count = count($elements) - 1;
$rand = rand(0, $count);
$el_id = $elements[$rand]['element_id'];
if ($not1 === true && $el_id == 1)
{
return randomID($elements, true);
}
return $el_id;
}
Almost forgot, about this $rand = rand(0, $count); < this is the correct way. You have done $count = count($elements) - 1; and yet started rand from 1 :)

Dividing numbers from file over array columns

This question is similar to a previous question of mine, but I formulated it wrong. I'm very sorry; here's the actual problem.
I have a file with thousands of numbers underneath each other. Let's simplify by using this:
4
7
1
9
3
3
8
6
2
6
5
1
What I need is to output a matrix (in the form of an array) with a variable number of matrix-rows. The numbers from the file have to be devided over the rows with the first number going to the first row, the second number to the second row, etc. Or, if you like, the 1st number, and every fourth number after that, go to column 1. The second number, and every fourth number after that, to column 2, etc. In the example below, the number of rows is 3:
array (
[0] => 4,9,8,6
[1] => 7,3,6,5
[2] => 1,3,2,1
)
And in this example, the number of rows is 4:
array (
[0] => 4,3,2
[1] => 7,3,6
[2] => 1,8,5
[3] => 9,6,1
)
The number of rows is variable.
Currently, with help of Oscar Jara, I now have this:
$path = "data.csv";
$array = explode("\n", file_get_contents($path));
$numbers = array();
foreach(array_chunk($array, 3) as $number){
$numbers[] = implode(",", $number);
}
But this outputs the numbers from the file over rows instead of columns:
array (
[0] => 4,7,1
[1] => 9,3,3
[2] => 8,6,2
[3] => 6,5,1
)
I get confused when transforming this code into dividing into columns. If you don't, then any help is appreciated.
Try this:
$path = "data.csv";
$data = file($path);
$numbers = Array();
$rowcount = 4;
foreach($data as $i=>$n) {
$numbers[$i % $rowcount][] = $n;
}
// OPTIONAL: Join rows together into comma-separated string
$numbers = array_map(function($a) {return implode(",",$a);},$numbers);
$verticallyChunked = array();
$numColumns = 4;
$i = 0;
// optionally pad the array to next largest multiple of the chunksize
// important if you output an html table and like valid well formed html
$totalRows = ceil(count($array) / $numColumns);
$array = array_pad($array, $totalRows * $numColumns, '');
foreach ($array as $val) {
$verticallyChunked[$i++ % $numColumns][] = $val;
}

Categories