How to compact/rearrange a multi-dimensional array by removing null values - php

I have an array called $rows. The first row is the header.
The first column is X (input signal), the other columns are Y1,Y2,Y3, etc. (output signals).
For any X value some of Y values may be NULL.
I print it with this code:
$first_key = key($rows);
foreach ($rows as $key => $row) {
if($key == $first_key) { // print header
fputcsv($out, array_keys($row));
}
fputcsv($out, $row);
}
The output of this code look like this (sorted by X column):
X | Y1 | Y2 | Y3 |
--------------------------
0.1 | | 10 | |
0.5 | 90 | | 7 |
0.7 | 15 | 40 | |
1.2 | | 12 | |
Goal: reorganize output to have columns X1,Y1,X2,Y2, etc such that in every pair (Xi,Yi) no NULL(empty) values are in between the row data and the header:
X1 | Y1 | X2 | Y2 | X3 | Y3 |
------------------------------------------
0.5 | 90 | 0.1 | 10 | 0.5| 7 |
0.7 | 15 | 0.7 | 40 | | |
| | 1.2 | 12 | | |
My attempt:
$current_header = array_keys($rows[0]);
$size = count($current_header);
$new_header=array(); // the new header: X1, Y1, X2, Y2,...
$arr_X=array();
$arr_Y=array();
$x_column=$current_header[0];
for ($i=1; $i<$size; $i++) {
$y_column=$current_header[$i];
$new_header[2*$i] = $x_column;
$new_header[2*$i+1] = $y_column;
$arr_Y[$y_column] = array_column($rows, $y_column);
$arr_X[$y_column] = array_column($rows, $x_column);
}
Next step: join $arr_X[$y_column] and $arr_Y[$y_column] into arr_XY. I think in this array the key should be the index (row#); also arr_XY should not include the points where $arr_Y[$y_column] is NULL: I do not know how to do it
$arr_XY=array();
for ($i=1; $i<$size; $i++) {
$y_column=$current_header[$i];
// here should be the code to join arrays and eliminate NULL arr_Y points
$arr_XY[$y_column] = ($arr_X[$y_column], $arr_Y[$y_column]);
}
The final step is where I need help: build and print the output rows by combining all arr_XY[$y_column] by row index.

Is this what you are after?
Input:
$rows=[
['X','Y1','Y2','Y3'],
[.1,null,10,null],
[.5,90,null,7],
[.7,15,40,null],
[1.2,null,12,null]
];
Method:
foreach($rows as $i=>$row){
if(!isset($result)){ // prepare result keys in order
foreach(array_slice($row,1) as $col){ // don't iterate X column of header
$no=substr($col,1); // get number following Y
$result["X$no"]=[]; // declare X column with column integer
$result["Y$no"]=[]; // declare Y column with column integer
}
}else{
foreach(array_slice($row,1,null,true) as $i=>$col){ // ignore X column
if(!is_null($col)){ // only store non-null values
$result["X$i"][]=$row[0]; // assign X value
$result["Y$i"][]=$col; // assign Y value
}
}
}
}
var_export($result);
Alternative Method:
foreach($rows as $i=>$row){
foreach(array_slice($row,1,null,true) as $c=>$col){
if($i==0){
$result["X$c"]=[];
$result["Y$c"]=[];
}elseif(!is_null($col)){
$result["X$c"][]=$row[0];
$result["Y$c"][]=$col;
}
}
}
Output:
array (
'X1' => [0.5, 0.7],
'Y1' => [90, 15],
'X2' => [0.1, 0.7, 1.2],
'Y2' => [10, 40, 12],
'X3' => [0.5],
'Y3' => [7]
];

Related

Problem with foreach loop with group by one column but have multiple values another column in php

db_table => commitment
ref_no
comm_date
1
2022-10-05
2
2022-10-05
3
2022-10-06
4
2022-10-07
5
2022-10-07
6
2022-10-08
db_table => collection
ref_no
amount
trnx_date
1
500
2022-10-05
2
100
2022-10-05
1
700
2022-10-06
3
400
2022-10-07
3
600
2022-10-08
5
800
2022-10-08
1
700
2022-10-08
I want to achieve something like this in datatable:
ref_no
comm_date
collection summary
1
2022-10-05
500 (2022-10-05) + 700 (2022-10-06) + 700 (2022-10-08) = 1900
2
2022-10-05
100 (2022-10-05) = 100
3
2022-10-06
400 (2022-10-07) + 600 (2022-10-08) = 1000
4
2022-10-07
0
5
2022-10-07
800 (2022-10-08) = 800
6
2022-10-08
0
How can I achieve this with php and mysql and show it to datatable. Thanks in advance!
What I have tried in sql in codeigniter model:
SELECT c.*, t.*
FROM commitment c
LEFT JOIN collection t ON c.ref_no = t.ref_no
WHERE c.ref_no IN (SELECT ref_no FROM collection)
GROUP BY c.ref_no
In controller:
public function collection_statement_list() {
// Datatables Variables
$draw = intval($this->input->get("draw"));
$start = intval($this->input->get("start"));
$length = intval($this->input->get("length"));
$fetch = $this->Project_model->get_collection_statement();
$data = array();
foreach($fetch->result() as $r) {
$ref_no = $r->ref_no;
$comm_date = $this->Project_model->set_date_format($r->comm_date);
$coll_date = $this->Project_model->set_date_format($r->trnx_date);
$coll_summary = $r->amount.'<span class="text-primary"><small>('.$coll_date.')</small></span>';
$data[] = array(
$ref_no,
$comm_date,
$coll_summary,
);
}
$output = array(
"draw" => $draw,
"recordsTotal" => $fetch->num_rows(),
"recordsFiltered" => $fetch->num_rows(),
"data" => $data
);
echo json_encode($output);
exit();
}
And the output in datatable is:
| ref_no | comm_date | collection summary |
| ------ | ---------- | ------------------ |
| 1 | 2022-10-05 | 500 (2022-10-05) |
| 2 | 2022-10-05 | 100 (2022-10-05) |
| 3 | 2022-10-06 | 400 (2022-10-07) |
| 4 | 2022-10-07 | 0 |
| 5 | 2022-10-07 | 800 (2022-10-08) |
| 6 | 2022-10-08 | 0 |
And so in SQL only this query corresponds to your solution with group_concat...? I'm trying to answer to help those who would be looking for the solution in SQL only.
select COM.ref_no,
if(COL.ref_no is not null,group_concat(COL.trnx_date,' (',COL.amount,')' separator '+'),'') as 'collection summary details',
if(COL.ref_no is not null,sum(COL.amount),0) as 'collection summary'
from commitment as COM
left join collection as COL on COM.ref_no=COL.ref_no
group by COM.ref_no
I think such way. Imagine you have a table about amounts. It is will be solution by sql:
Select ref_no, comm_date, sub(summary) as collect_summary from amount;
However you may use alternative way to be group with php such:
<?php
$amount = [
['ref_no'=> 1, 'amount'=>500 , 'date'=>'2022-10-05'],
['ref_no'=> 2, 'amount'=>100 , 'date'=>'2022-10-05'],
['ref_no'=> 1, 'amount'=>700 , 'date'=>'2022-10-05'],
['ref_no'=> 3, 'amount'=>400 , 'date'=>'2022-10-05'],
['ref_no'=> 3, 'amount'=>600 , 'date'=>'2022-10-05'],
['ref_no'=> 5, 'amount'=>800 , 'date'=>'2022-10-05'],
['ref_no'=> 1, 'amount'=>700 , 'date'=>'2022-10-05'],
];
$result = [];
foreach($amount as $item) {
$ref = $item['ref_no'];
if(isset($result[$ref])) {
$result[$ref]['collect_amount'] = $result[$ref]['collect_amount'] + $item['amount'];
}else{
$result[$ref] = [
'ref_no' => $ref,
'date' => $item['date'],
'collect_amount' =>$item['amount']
];
}
}
echo '<pre>';
print_r($result);

How to grouping data from MySQL and make it multi-dimensional array PHP

I have some data in the database that records the stocks of items and I want to group that data based on month-year for every item.
It looks like this for items :
item_id | item |
1 | item_1 |
2 | item_2 |
3 | item_3 |
4 | item_4 |
And for stocks :
stock_id | item_id | date | amount
1 | 1 | 2022-03-01 | 100
2 | 2 | 2022-03-01 | 120
3 | 3 | 2022-03-01 | 100
4 | 4 | 2022-03-01 | 400
5 | 1 | 2022-04-01 | 100
6 | 2 | 2022-04-01 | 120
7 | 3 | 2022-04-01 | 100
8 | 4 | 2022-04-01 | 400
...
What I need to achieve is something like this :
$data = [
[
'y' => '03',
'1' => 100,
'2' => 120,
'3' => 100,
'4' => 400
],
[
'y' => '04',
'1' => 100,
'2' => 120,
'3' => 100,
'4' => 400
],
];
...
A simple query that I've tried so far :
SELECT MONTH(date) as Month, SUM(amount) as Amount, item
FROM stocks as s
LEFT JOIN items as i
ON s.item_id = i.item_id
GROUP BY s.id_item, MONTH(date)
ORDER BY MONTH(date) ASC
DB Fiddle
How to grouping that data by month and sum the amount of every item in every month?
UPDATED PHP SNIPPET
$StockData = [];
foreach ( $StockModel->stockMonthly()->getResult() as $stock )
{
$StockData[] = [
'y' => $stock->Month
];
for ( $i = 0; $i < count($StockData); $i++ )
{
if ( $StockData[$i]['y'] === $stock->Month)
{
array_push($StockData[$i], [$stock->item => $stock->Amount]);
}
}
}
I don't know how to grouping the data into the same month
Nice to see a reproducible example. For this, you can collect all the item IDs on the month key in an associative array and later add that single entry of month at the top using array_merge like below:
<?php
$StockData = [];
foreach ( $StockModel->stockMonthly()->getResult() as $stock ){
$StockData[ $stock->Month ] = $StockData[ $stock->Month ] ?? [];
$StockData[ $stock->Month ][ $stock->item ] = $stock->Amount;
}
$result = [];
foreach($StockData as $month => $monthlyData){
$result[] = array_merge(['y' => $month], $monthlyData);
}
print_r($result);
You can use the query:
select right(concat('0',month(`date`)),2) as "y",
sum(if (item_id=1,amount, null)) as "1",
sum(if (item_id=2,amount, null)) as "2",
sum(if (item_id=3,amount, null)) as "3",
sum(if (item_id=4,amount, null)) as "4"
from stocks
group by right(concat('0',month(`date`)),2)
In case you have a variable number of item_id's, you are better off by building the array in PHP from a direct data query.

converting a related database table object into a nested array - PHP

i have some dummy related data in my table "you can take a look at it bellow". i want to nest the data according to their relationships into one array.
Table Data:
+-----+------+--------+
| uid | name | parent |
+-----+------+--------+
| 1 | A | 0 |
| 2 | B | 1 |
| 3 | C | 1 |
| 4 | D | 2 |
| 5 | E | 3 |
| 7 | G | 3 |
| 9 | H | 4 |
| 10 | I | 4 |
| 11 | J | 7 |
+-----+------+--------+
the array is going to be like array('A' =>array('B'=>'D','C'=>array(...)).
am currently using codeigniter and here is what i've done
CODE
public function nestDataIntoArray($id)
{
$this->db->where('uid', $id);
$query = $this->db->get('binary_tbl');
$result = [];
if ($query->num_rows() > 0) {
foreach ($query->result() as $k) {
// secondLevel deep
$secondLevel = $this->getSubLevel($k->uid);
$secondLevelRowCount = $secondLevel->num_rows();
if ($secondLevelRowCount > 0 ) {
if ($secondLevelRowCount > 1) {
foreach ($secondLevel->result() as $key) {
// create second level deep array
$result[$k->name][$key->name][] = $this->getSubLevel($key->uid)->row('name');
// thirdLevel deep
$thirdLevel = $this->getSubLevel($key->uid);
$thirdLevelRowCount = $thirdLevel->num_rows();
if ($thirdLevelRowCount > 0) {
if($thirdLevelRowCount > 1){
foreach ($thirdLevel->result() as $tKey) {
// create third level deep array
$result[$k->name][$key->name][$tKey->name] = $this->getSubLevel($tKey->uid)->row('name');
}
}else{
foreach ($thirdLevel->result() as $tKey) {
$result[$k->name][$key->name][$tKey->name] = $this->getSubLevel($tKey->uid)->row('name');
}
}
} else {
$result[$k->name][$key->name] = $this->getSubLevel($key->uid)->result_array();
} //end thirdLevel deep
}
}
} else {
$result[$k->name] = $this->getSubLevel($key->uid)->row('name');
} // end second level deep
}
} else {
$result = NULL;
}
return $result;
}
private function getSubLevel($id)
{
return $this->db->select('*')->from('binary_tbl')->where('supermember', $id)->get();
}
upon invoking the nestDataIntoArray(1) method i got the following output
OUTPUT
array (size=1)
'A' =>
array (size=2)
'B' =>
array (size=2)
0 => string 'D' (length=1)
'D' => string 'H' (length=1)
'C' =>
array (size=3)
0 => string 'E' (length=1)
'E' => null
'G' => string 'J' (length=1)
this output seems to be fine but i really dont want that Zero index and some of the data still has one or two data related to them which i still have to loop through to get and that to me just seems to be alot of unnecessary coding. So the question is: which other better way can i achieve this and how do i get rid of that zero index?

PHP get distinct ids count from an array by date

Looking for the simplest way to achieve the following in php:
I have this array with dates and ids
$data = array(
array("date" => "2016-01", "ids" => array(1,2,3,4,5,6,7,8)),
array("date" => "2016-02", "ids" => array(1,2,9,10,11,12)),
array("date" => "2016-03", "ids" => array(3,16,17,18,19,20,21)),
array("date" => "2016-04", "ids" => array(1,2,3,19,20,22,23))
);
The idea is to count ids by date but also return count(existing ids) since the start of every month separately. (I hope this is understandable)
The returned array should look like this:
$data = array(
array("date" => "2016-01", "counter" => array(8)),
array("date" => "2016-02", "counter" => array(2,4)), /* 2 existing ids from 2016-01 and 4 new in 2016-02) */
array("date" => "2016-03", "counter" => array(1,0,6)), /* 1 existing from 2016-01 and 0 exiting from 2016-02 and 6 new */
array("date" => "2016-04", "counter" => array(3,0,2,2)) /* etc. */
);
| 2016-01 | 2016-02 | 2016-03 | 2016-04
------ | ------- | ------- | ------- | -------
2016-01 | 8 | | |
------ | ------- | ------- | ------- | -------
2016-02 | 2 | 4 | |
------ | ------- | ------- | ------- | -------
2016-03 | 1 | 0 | 6 |
------ | ------- | ------- | ------- | -------
2016-04 | 3 | 0 | 2 | 2
Of course if there is a way to do that directly in sql i'll take it :)
Here is one way to do it. (I'm not sure if it's the simplest). Looping over the set of data is the easiest part.
Within that, consider each id from the current day with while ($id = array_pop($day['ids'])). For each of those ids, start at the first day, and look for the id in that days set of ids. If it's found, increment the count for that day and continue with the next id (continue 2 continues the while loop.) If it's not found, move on to the next day. If it's not found when you get to the current day (indicated by $i < $key in the for loop) then increment the count for the current day and move on to the next id.
foreach ($data as $key => $day) {
$counts = array_fill(0, $key + 1, 0);
while ($id = array_pop($day['ids'])) {
for ($i=0; $i < $key; $i++) {
$past_day = $data[$i];
if (in_array($id, $past_day['ids'])) {
$counts[$i]++;
continue 2;
}
}
$counts[$key]++;
}
$new_data[] = ['date' => $day['date'], 'counter' => $counts];
}
p.s. I have no idea how to do this in SQL.
You can do something like this to achieve the desired result,
$newArray = array();
$count = count($data);
for($i = 0; $i < $count; ++$i){
$newArray[$i] = array('date' => $data[$i]['date'], 'counter' => array());
for($j = 0; $j < $i; ++$j){
$counter = 0;
foreach($data[$i]['ids'] as $key => $id){
if(in_array($id, $data[$j]['ids'])){
++$counter;
unset($data[$i]['ids'][$key]);
}
}
$newArray[$i]['counter'][] = $counter;
}
$newArray[$i]['counter'][] = count($data[$i]['ids']);
}
// display $newArray array
var_dump($newArray);
Here's the live demo

How to find the best subset from an array whose sum is equal to X number in php

I have an array as
$array = array(3,5,6,10,15,30);
$sum = 69;
then there are 3 different sums that equal 69:
3+5+6+10+15+30,
3+6+10+20+30,
3+6+30+30,
The best one is 3+6+30+30. Because it contains higher number from array to complete the sum that reduce the number count.
(A number can be used within a sum as many times as it appears in the list, and a single number counts as a sum.)
Here is the code that I am implementing
$sum = 18;
$list = array();
$this->sumUpRecursive(array(3,5,6,10,15,30), $list); //function call
$list = array_filter($list,function($var) use ($sum) { return(array_sum($var) == $sum);});
var_dump($list);
function sumUpRecursive($array, &$list, $temp = array()) {
if (count($temp) > 0 && !in_array($temp, $list))
$list[] = $temp;
for ($i = 0; $i < sizeof($array); $i++) {
$copy = $array;
$elem = array_splice($copy, $i, 1);
if (sizeof($copy) > 0) {
$add = array_merge($temp, array($elem[0]));
sort($add);
$this->sumUpRecursive($copy, $list, $add);
}
else {
$add = array_merge($temp, array($elem[0]));
sort($add);
if (!in_array($temp, $list)) {
$list[] = $add;
}
}
}
}
Result:
Array
(
[9] => Array
(
[0] => 3
[1] => 5
[2] => 10
)
[28] => Array
(
[0] => 3
[1] => 15
)
)
I hope this might be bit complex . Its taking a number from array once. But how to figure out for 69...
Thanks
This should work for you:
What does this code do (the function getKnapsackSum())?
1. prepare data
In the first few lines of the function I prepare the data, that means I sort the array DESC with rsort(). I get the last element of the list with end(), reset the array pointer with rest() and assign it to a temp variable.
2. input check
Before I begin to calculate the sum I check that the input isn't 0.
3. calculate the sum
After this the real stuff happens (IT voodoo, not really)! I start with a loop which is false as long as I didn't got the sum. The next 2 lines in the while loop are there to check, that first if the sum can't be calculated, that it returns an array with a 0 and second that when we got a "offset" of the array pointer, that I set it back the last array element.
Then the first if clause is there to check if it made a mistake and it has to try it with lower values. So as an example:
list = [3, 5, 6]
sum = 8
//entire sum
| 0
|
check 6: |
|
6 + sum -> 8 | 6 | fits
6 + sum -> 8 | | does not fit
|
check 5: |
|
5 + sum -> 8 | | does not fit
|
check 3: |
|
3 + sum -> 8 | | does not fit
--------------------------------------------------------
= sum = [6] != 8
As you can see right here with this example the algorithm made a mistake, because it started to build the sum with 6, but after this it couldn't build it until the end, now here is where the first if clause checks, if the current element of the array pointer is the end of the array and this value + the current sum is higher than the goal.
If this is the case it deletes the last value from the sum array and pops off the highest element of the list. So now it tries it again with the following data:
list = [3, 5]
//^ As you can see it removed the highest value of the list
sum = 8
//entire sum
| 0 <- it removed the last value of the sum array, here 6
|
check 5: |
|
5 + sum -> 8 | 5 | fits
5 + sum -> 8 | | does not fit
|
check 3: |
|
3 + sum -> 8 | 3 | fits
3 + sum -> 8 | | does not fit
--------------------------------------------------------
= sum = [5, 3] == 8
So as you can see now the first if clause made it possible to build the sum with the list. So the if statement makes sure that it corrects errors of level 1.
Now the second if statement in my while loop corrects errors of level 2. So what are errors of level 2? We also look again at an example:
list = [3, 5, 6]
sum = 22
//entire sum
| 0
|
check 6: |
|
6 + sum -> 22 | 6 | fits
6 + sum -> 22 | 6 | fits
6 + sum -> 22 | 6 | fits
6 + sum -> 22 | | does not fit
|
check 5: |
|
5 + sum -> 22 | | does not fit
|
check 3: |
|
3 + sum -> 22 | 3 | fits
3 + sum -> 22 | | does not fit
--------------------------------------------------------
= sum = [6, 6, 6, 3] != 22
So as you can see it couldn't build the sum entirely. But this time it isn't an error of level 1, because if we take the last element away from the current sum and go through the new list where we removed the highest element it still can't build the sum as you can see here:
correction of errors level 1:
list = [3, 5]
//^ As you can see it removed the highest value of the list
sum = 22
//entire sum
| [6, 6, 6] <- it removed the last value of the sum array, here 3
|
check 5: |
|
5 + sum -> 22 | | does not fit
|
check 3: |
|
3 + sum -> 22 | 3 | fits
3 + sum -> 22 | | does not fit
|
--------------------------------------------------------
= sum = [6, 6, 6, 3] != 22
So as you can see it is stuck now, it can't correct the mistake only with the first if clause. And here is where the second if statement checks if the list is empty, if this is the case that means that you tried every value, but the mistake is higher up in the sum array.
So now it goes back 1 array element of the sum array and deletes it. It also takes the mistake as offset to get a list without the number which was the mistake. So here: [6, 6, 6, 3] the 3 gets removed from the first if clause so now the array looks like this: [6, 6, 6]. Now the second if statement see's that the mistake was 6. It removes it and also corrects the list where it chooses the numbers from.
So now the result array looks like: [6, 6] and the list to choose numbers from like this: [5, 3]. And now that it made a correction of level 2 it can build the sum as you can see here:
list = [3, 5]
//^ As you can see it removed the highest value of the list
sum = 22
//entire sum
| [6, 6] <- it removed the last value of the sum array, here 3 and 6
|
check 5: |
|
5 + sum -> 22 | 5 | fits
5 + sum -> 22 | 5 | fits
5 + sum -> 22 | | does not fit
|
check 3: |
|
3 + sum -> 22 | | does not fit
|
|
--------------------------------------------------------
= sum = [6, 6, 5, 5] == 22
So after this the if else statement is there just to check if the current value of the list fits into the sum array and isn't bigger than the goal. If it fits it adds it to the array otherwise it goes to the next list element.
Now as you can see my code corrects a few errors, but I'm not 100% sure that it works for everything and I'm also not sure if you can produce errors of level 3 where you have to go back in the sum array an correct 3 values, and if there is one I'm also not sure if my code corrects it or maybe even fall into a endless loop.
But after all this talking here is the code which does all the magic:
<?php
//data
$arr = [3,5,6,10,15,30];
$sum = 69;
//get sum
function getKnapsackSum($arr, $sum) {
//prepare data
rsort($arr);
$end = end($arr);
reset($arr);
$tmp = $arr;
$result = [];
//check if it is not a empty input
if($sum == 0 || empty($tmp) || array_sum($tmp) == 0) return [0];
//calculate the sum
while(array_sum($result) != $sum) {
if(empty($tmp) && empty($result)) return [0];
if(current($tmp) === FALSE) end($tmp);
//correction for errors level 1
if(current($tmp) == $end && array_sum($result) + current($tmp) > $sum) {
array_pop($result);
array_shift($tmp);
reset($tmp);
}
//correction for errors level 2
if(empty($tmp)) {
$mistake = array_pop($result);
if($mistake == NULL) return [0];
$tmp = array_slice($arr, array_search($mistake, $arr)+1);
reset($tmp);
}
if(array_sum($result) + current($tmp) <= $sum)
$result[] = current($tmp);
else
next($tmp);
}
return $result;
}
$result = getKnapsackSum($arr, $sum);
print_r($result);
?>
Output:
Array ( [0] => 30 [1] => 30 [2] => 6 [3] => 3 )
Also here a few examples of inputs and there outputs from my code:
input | output
------------------------------------
list: [] |
goal: 0 | sum: [0]
------------------------------------
list: [] |
goal: 1 | sum: [0]
------------------------------------
list: [0] |
goal: 1 | sum: [0]
------------------------------------
list: [0] |
goal: 0 | sum: [0]
------------------------------------
list: [3, 5, 6] | //error level 1
goal: 8 | sum: [5, 3]
------------------------------------
list: [3, 5, 6] | //error level 2
goal: 22 | sum: [6, 6, 5, 5]
------------------------------------
list: [5, 7, 9] | //can't build the sum
goal: 56 | sum: [0]
------------------------------------
list: [5, 7, 9] |
goal: 34 | sum: [9, 9, 9, 7]
------------------------------------

Categories